From 34a1a9517c1c8206a149050342145f7e8637f08c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 30 Jul 2015 22:44:10 +0200 Subject: [PATCH 001/874] New export profile entity --- .../Customers/SystemCustomerRoleNames.cs | 1 - .../Domain/Export/ExportProfile.cs | 73 ++++++++++ .../SmartStore.Core/SmartStore.Core.csproj | 2 + .../Mapping/Export/ExportProfileMap.cs | 27 ++++ ...01507301550466_ExportFramework.Designer.cs | 29 ++++ .../201507301550466_ExportFramework.cs | 105 +++++++++++++++ .../201507301550466_ExportFramework.resx | 126 ++++++++++++++++++ .../Setup/Builder/PermissionMigrator.cs | 55 ++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 9 ++ .../ExportImport/ExportService.cs | 79 +++++++++++ .../ExportImport/IExportService.cs | 33 +++++ .../Security/StandardPermissionProvider.cs | 3 + .../SmartStore.Services.csproj | 2 + .../DependencyRegistrar.cs | 2 + .../Controllers/ExportController.cs | 73 ++++++++++ .../Models/Export/ExportProfileModel.cs | 31 +++++ .../Administration/SmartStore.Admin.csproj | 3 + .../Administration/Views/Export/List.cshtml | 59 ++++++++ .../Administration/sitemap.config | 2 + 19 files changed, 713 insertions(+), 1 deletion(-) create mode 100644 src/Libraries/SmartStore.Core/Domain/Export/ExportProfile.cs create mode 100644 src/Libraries/SmartStore.Data/Mapping/Export/ExportProfileMap.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201507301550466_ExportFramework.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201507301550466_ExportFramework.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201507301550466_ExportFramework.resx create mode 100644 src/Libraries/SmartStore.Data/Setup/Builder/PermissionMigrator.cs create mode 100644 src/Libraries/SmartStore.Services/ExportImport/ExportService.cs create mode 100644 src/Libraries/SmartStore.Services/ExportImport/IExportService.cs create mode 100644 src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs create mode 100644 src/Presentation/SmartStore.Web/Administration/Models/Export/ExportProfileModel.cs create mode 100644 src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerRoleNames.cs b/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerRoleNames.cs index 760d14d9be..214fd18191 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerRoleNames.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/SystemCustomerRoleNames.cs @@ -3,7 +3,6 @@ namespace SmartStore.Core.Domain.Customers { public static partial class SystemCustomerRoleNames { - /// codehint: sm-add public static string SuperAdministrators { get { return "SuperAdmins"; } } public static string Administrators { get { return "Administrators"; } } diff --git a/src/Libraries/SmartStore.Core/Domain/Export/ExportProfile.cs b/src/Libraries/SmartStore.Core/Domain/Export/ExportProfile.cs new file mode 100644 index 0000000000..bca569da98 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Export/ExportProfile.cs @@ -0,0 +1,73 @@ +using System; +using SmartStore.Core.Domain.Tasks; + +namespace SmartStore.Core.Domain +{ + public class ExportProfile : BaseEntity + { + /// + /// The name of the profile + /// + public string Name { get; set; } + + /// + /// Whether the export profile is enabled + /// + public bool Enabled { get; set; } + + /// + /// The system name of the export provider + /// + public string ProviderSystemName { get; set; } + + /// + /// The entity type to be exported + /// + public string EntityType { get; set; } + + /// + /// Number of hours after the profile is automatically executed by scheduling + /// + public int SchedulingHours { get; set; } + + /// + /// The scheduling task identifier + /// + public int SchedulingTaskId { get; set; } + + /// + /// The profile GUID is the folder name in the file system + /// + public Guid ProfileGuid { get; set; } + + /// + /// XML with partioning information + /// + public string Partitioning { get; set; } + + /// + /// XML with filtering information + /// + public string Filtering { get; set; } + + /// + /// Last start time the profile has been executed + /// + public DateTime? LastExecutionStartUtc { get; set; } + + /// + /// /// Last end time the profile has been executed + /// + public DateTime? LastExecutionEndUtc { get; set; } + + /// + /// Message of the last execution + /// + public string LastExecutionMessage { get; set; } + + /// + /// The scheduling task + /// + public virtual ScheduleTask ScheduleTask { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index f1c102b6d5..e4f7dc5af8 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -159,6 +159,7 @@ + @@ -595,6 +596,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/Setup/Builder/PermissionMigrator.cs b/src/Libraries/SmartStore.Data/Setup/Builder/PermissionMigrator.cs new file mode 100644 index 0000000000..fe509e1cdf --- /dev/null +++ b/src/Libraries/SmartStore.Data/Setup/Builder/PermissionMigrator.cs @@ -0,0 +1,55 @@ +using System.Data.Entity; +using System.Linq; +using SmartStore.Core.Data; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Security; + +namespace SmartStore.Data.Setup +{ + internal class PermissionMigrator + { + private readonly SmartObjectContext _ctx; + private readonly DbSet _permissionRecords; + private readonly IQueryable _customerRoles; + + public PermissionMigrator(SmartObjectContext ctx) + { + Guard.ArgumentNotNull(() => ctx); + + _ctx = ctx; + _permissionRecords = _ctx.Set(); + _customerRoles = _ctx.Set().Expand(x => x.PermissionRecords); + } + + public void AddPermission(PermissionRecord permission, string[] rolesToMap) + { + Guard.ArgumentNotNull(() => permission); + Guard.ArgumentNotNull(() => rolesToMap); + + if (permission.SystemName.IsEmpty()) + return; + + var permissionRecord = _permissionRecords.FirstOrDefault(x => x.SystemName == permission.SystemName); + + if (permissionRecord == null) + { + _permissionRecords.Add(permission); + + _ctx.SaveChanges(); + } + + permissionRecord = _permissionRecords.FirstOrDefault(x => x.SystemName == permission.SystemName); + + foreach (var roleName in rolesToMap) + { + var role = _customerRoles.FirstOrDefault(x => x.SystemName == roleName); + if (role != null && !role.PermissionRecords.Any(x => x.SystemName == permission.SystemName)) + { + role.PermissionRecords.Add(permissionRecord); + } + } + + _ctx.SaveChanges(); + } + } +} diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index a6fce9f98b..9bde1ba879 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -121,6 +121,7 @@ + @@ -331,6 +332,11 @@ 201507250039446_Merge2.cs + + + 201507301550466_ExportFramework.cs + + @@ -627,6 +633,9 @@ 201507250039446_Merge2.cs + + 201507301550466_ExportFramework.cs + diff --git a/src/Libraries/SmartStore.Services/ExportImport/ExportService.cs b/src/Libraries/SmartStore.Services/ExportImport/ExportService.cs new file mode 100644 index 0000000000..c19a63129a --- /dev/null +++ b/src/Libraries/SmartStore.Services/ExportImport/ExportService.cs @@ -0,0 +1,79 @@ +using System; +using System.Linq; +using SmartStore.Core.Data; +using SmartStore.Core.Domain; +using SmartStore.Core.Events; +using SmartStore.Services.Tasks; + +namespace SmartStore.Services.ExportImport +{ + public partial class ExportService : IExportService + { + private readonly IRepository _exportProfileRepository; + private readonly IEventPublisher _eventPublisher; + private readonly Lazy _scheduleTaskService; + + public ExportService( + IRepository exportProfileRepository, + IEventPublisher eventPublisher, + Lazy scheduleTaskService) + { + _exportProfileRepository = exportProfileRepository; + _eventPublisher = eventPublisher; + _scheduleTaskService = scheduleTaskService; + } + + #region Export profiles + + public virtual void InsertExportProfile(ExportProfile profile) + { + if (profile == null) + throw new ArgumentNullException("profile"); + + _exportProfileRepository.Insert(profile); + + _eventPublisher.EntityInserted(profile); + } + + public virtual void UpdateExportProfile(ExportProfile profile) + { + if (profile == null) + throw new ArgumentNullException("profile"); + + _exportProfileRepository.Update(profile); + + _eventPublisher.EntityUpdated(profile); + } + + public virtual void DeleteExportProfile(ExportProfile profile) + { + if (profile == null) + throw new ArgumentNullException("profile"); + + if (profile.ScheduleTask != null) + { + _scheduleTaskService.Value.DeleteTask(profile.ScheduleTask); + } + + _exportProfileRepository.Delete(profile); + + _eventPublisher.EntityDeleted(profile); + } + + public virtual IQueryable GetExportProfiles(bool? enabled = null) + { + var query = + from x in _exportProfileRepository.Table.Expand(x => x.ScheduleTask) + select x; + + if (enabled.HasValue) + { + query = query.Where(x => x.Enabled == enabled.Value); + } + + return query.OrderBy(x => x.Name); + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Services/ExportImport/IExportService.cs b/src/Libraries/SmartStore.Services/ExportImport/IExportService.cs new file mode 100644 index 0000000000..0e8389011b --- /dev/null +++ b/src/Libraries/SmartStore.Services/ExportImport/IExportService.cs @@ -0,0 +1,33 @@ +using System.Linq; +using SmartStore.Core.Domain; + +namespace SmartStore.Services.ExportImport +{ + public interface IExportService + { + /// + /// Inserts an export profile + /// + /// Export profile + void InsertExportProfile(ExportProfile profile); + + /// + /// Updates an export profile + /// + /// Export profile + void UpdateExportProfile(ExportProfile profile); + + /// + /// Deletes an export profile + /// + /// Export profile + void DeleteExportProfile(ExportProfile profile); + + /// + /// Get queryable export profiles + /// + /// Whether to filter enabled or disabled profiles + /// + IQueryable GetExportProfiles(bool? enabled = null); + } +} diff --git a/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs b/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs index 01b83e6a38..7018c51146 100644 --- a/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs +++ b/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs @@ -49,6 +49,7 @@ public partial class StandardPermissionProvider : IPermissionProvider public static readonly PermissionRecord ManageMaintenance = new PermissionRecord { Name = "Admin area. Manage Maintenance", SystemName = "ManageMaintenance", Category = "Configuration" }; public static readonly PermissionRecord UploadPictures = new PermissionRecord { Name = "Admin area. Upload Pictures", SystemName = "UploadPictures", Category = "Configuration" }; public static readonly PermissionRecord ManageScheduleTasks = new PermissionRecord { Name = "Admin area. Manage Schedule Tasks", SystemName = "ManageScheduleTasks", Category = "Configuration" }; + public static readonly PermissionRecord ManageExports = new PermissionRecord { Name = "Admin area. Manage Exports", SystemName = "ManageExports", Category = "Configuration" }; //public store permissions @@ -103,6 +104,7 @@ public virtual IEnumerable GetPermissions() ManageMaintenance, UploadPictures, ManageScheduleTasks, + ManageExports, DisplayPrices, EnableShoppingCart, EnableWishlist, @@ -160,6 +162,7 @@ public virtual IEnumerable GetDefaultPermissions() ManageMaintenance, UploadPictures, ManageScheduleTasks, + ManageExports, DisplayPrices, EnableShoppingCart, EnableWishlist, diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 49e1c86d78..33484e0895 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -162,6 +162,8 @@ + + diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index 99d42a6b9c..29357d57db 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -223,6 +223,8 @@ public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, b .InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs new file mode 100644 index 0000000000..839265dfef --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -0,0 +1,73 @@ +using System.Linq; +using System.Web.Mvc; +using SmartStore.Admin.Models.Export; +using SmartStore.Core; +using SmartStore.Core.Domain; +using SmartStore.Services; +using SmartStore.Services.ExportImport; +using SmartStore.Services.Security; +using SmartStore.Web.Framework.Controllers; +using Telerik.Web.Mvc; + +namespace SmartStore.Admin.Controllers +{ + [AdminAuthorize] + public class ExportController : AdminControllerBase + { + private readonly ICommonServices _services; + private readonly IExportService _exportService; + + public ExportController( + ICommonServices services, + IExportService exportService) + { + _services = services; + _exportService = exportService; + } + + public ActionResult Index() + { + return RedirectToAction("List"); + } + + public ActionResult List() + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + return View(); + } + + [HttpPost, GridAction(EnableCustomBinding = true)] + public ActionResult List(GridCommand command) + { + var model = new GridModel(); + + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + { + var query = _exportService.GetExportProfiles(); + var profiles = query.ToList(); + + model.Total = profiles.Count; + + model.Data = profiles.Select(x => + { + var profileModel = new ExportProfileModel + { + Name = x.Name, + ProviderSystemName = x.ProviderSystemName, + EntityType = x.EntityType, + Enabled = x.Enabled, + FileType = "", // TODO + SchedulingHours = x.SchedulingHours, + LastExecution = "" // TODO + }; + + return profileModel; + }); + } + + return new JsonResult { Data = model }; + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Export/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Export/ExportProfileModel.cs new file mode 100644 index 0000000000..c95bd05837 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/Export/ExportProfileModel.cs @@ -0,0 +1,31 @@ +using System.Web.Mvc; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Mvc; + +namespace SmartStore.Admin.Models.Export +{ + public partial class ExportProfileModel : EntityModelBase + { + [SmartResourceDisplayName("Admin.Configuration.Export.Name")] + public string Name { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.ProviderSystemName")] + public string ProviderSystemName { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.EntityType")] + public string EntityType { get; set; } + + [SmartResourceDisplayName("Common.Enabled")] + public bool Enabled { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.FileType")] + public string FileType { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.SchedulingHours")] + public int SchedulingHours { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.LastExecution")] + [AllowHtml] + public string LastExecution { get; set; } + } +} \ 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 209e12c89f..0f7013c4e4 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -175,6 +175,7 @@ + @@ -247,6 +248,7 @@ + @@ -498,6 +500,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml new file mode 100644 index 0000000000..17122852e5 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -0,0 +1,59 @@ +@using SmartStore.Admin.Models.Export +@using Telerik.Web.Mvc.UI +@{ + ViewBag.Title = T("Common.Export").Text; +} + +
+
+ + @T("Common.Export") +
+ +
+ + + +
+ + + + +
+ @(Html.Telerik().Grid() + .Name("export-profile-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("Id"); + }) + .Columns(columns => + { + columns.Bound(x => x.Name) + .Width(400) + .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) + .ClientTemplate("\"><#= Name #>"); + columns.Bound(x => x.ProviderSystemName); + columns.Bound(x => x.EntityType); + columns.Bound(x => x.Enabled) + .Width(100) + .Template(item => @Html.SymbolForBool(item.Enabled)) + .ClientTemplate(@Html.SymbolForBool("Enabled")) + .Centered(); + columns.Bound(x => x.FileType); + columns.Bound(x => x.SchedulingHours); + columns.Bound(x => x.LastExecution) + .ReadOnly(); + }) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Export")) + .ClientEvents(events => events.OnRowDataBound("onRowDataBound_ExportProfiles")) + .EnableCustomBinding(true)) +
+
diff --git a/src/Presentation/SmartStore.Web/Administration/sitemap.config b/src/Presentation/SmartStore.Web/Administration/sitemap.config index 33a9fd264f..0ba3a9156e 100644 --- a/src/Presentation/SmartStore.Web/Administration/sitemap.config +++ b/src/Presentation/SmartStore.Web/Administration/sitemap.config @@ -127,6 +127,8 @@ + + From 14827cd5b33cbb2cc61d12dbe8704cce4d802532 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 3 Aug 2015 10:06:00 +0200 Subject: [PATCH 002/874] Editing an export profile --- .../Domain/Export/ExportCore.cs | 54 +++++ .../Domain/Export/ExportProfile.cs | 27 ++- .../SmartStore.Core/SmartStore.Core.csproj | 1 + src/Libraries/SmartStore.Core/XmlHelper.cs | 41 ++++ .../Mapping/Export/ExportProfileMap.cs | 4 +- .../201507301550466_ExportFramework.cs | 105 --------- .../201507301550466_ExportFramework.resx | 126 ----------- ...1508011828241_ExportFramework.Designer.cs} | 2 +- .../201508011828241_ExportFramework.cs | 157 ++++++++++++++ .../201508011828241_ExportFramework.resx | 126 +++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 10 +- .../Export/ExportExtensions.cs | 16 ++ .../Export/ExportService.cs | 146 +++++++++++++ .../Export/IExportProvider.cs | 16 ++ .../Export/IExportService.cs | 58 +++++ .../ExportImport/ExportService.cs | 79 ------- .../ExportImport/IExportService.cs | 33 --- .../SmartStore.Services.csproj | 6 +- .../DependencyRegistrar.cs | 6 + .../Administration/Content/admin.less | 1 - .../Controllers/ExportController.cs | 205 ++++++++++++++++-- .../Models/Export/ExportProfileModel.cs | 45 +++- .../Administration/SmartStore.Admin.csproj | 2 + .../Administration/Views/Export/Create.cshtml | 27 +++ .../Administration/Views/Export/Edit.cshtml | 135 ++++++++++++ .../Administration/Views/Export/List.cshtml | 115 +++++++++- .../Administration/sitemap.config | 2 +- 27 files changed, 1154 insertions(+), 391 deletions(-) create mode 100644 src/Libraries/SmartStore.Core/Domain/Export/ExportCore.cs delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201507301550466_ExportFramework.cs delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201507301550466_ExportFramework.resx rename src/Libraries/SmartStore.Data/Migrations/{201507301550466_ExportFramework.Designer.cs => 201508011828241_ExportFramework.Designer.cs} (92%) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.resx create mode 100644 src/Libraries/SmartStore.Services/Export/ExportExtensions.cs create mode 100644 src/Libraries/SmartStore.Services/Export/ExportService.cs create mode 100644 src/Libraries/SmartStore.Services/Export/IExportProvider.cs create mode 100644 src/Libraries/SmartStore.Services/Export/IExportService.cs delete mode 100644 src/Libraries/SmartStore.Services/ExportImport/ExportService.cs delete mode 100644 src/Libraries/SmartStore.Services/ExportImport/IExportService.cs create mode 100644 src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml create mode 100644 src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml diff --git a/src/Libraries/SmartStore.Core/Domain/Export/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/Export/ExportCore.cs new file mode 100644 index 0000000000..b5cca2b555 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Export/ExportCore.cs @@ -0,0 +1,54 @@ +using System; + +namespace SmartStore.Core.Domain.Export +{ + public enum ExportFileType + { + Xml = 0, + Xls, + Csv, + Txt, + Pdf + } + + + public enum ExportEntityType + { + Product = 0, + Category, + Manufacturer, + Customer, + Order + } + + + [Serializable] + public class ExportPartition + { + public ExportPartition() + { + Limit = 100; + BatchSize = 1000; + } + + /// + /// The number of records to be skipped + /// + public int Offset { get; set; } + + /// + /// How many records to be loaded per database round-trip + /// + public int Limit { get; set; } + + /// + /// The maximum number of records of one processed batch + /// + public int BatchSize { get; set; } + + /// + /// Whether to start a separate run-through for each store + /// + public bool PerStore { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Export/ExportProfile.cs b/src/Libraries/SmartStore.Core/Domain/Export/ExportProfile.cs index bca569da98..ed71e475a0 100644 --- a/src/Libraries/SmartStore.Core/Domain/Export/ExportProfile.cs +++ b/src/Libraries/SmartStore.Core/Domain/Export/ExportProfile.cs @@ -1,4 +1,5 @@ using System; +using SmartStore.Core.Domain.Export; using SmartStore.Core.Domain.Tasks; namespace SmartStore.Core.Domain @@ -11,24 +12,34 @@ public class ExportProfile : BaseEntity public string Name { get; set; } /// - /// Whether the export profile is enabled + /// The system name of the export provider /// - public bool Enabled { get; set; } + public string ProviderSystemName { get; set; } /// - /// The system name of the export provider + /// Whether the export profile is enabled /// - public string ProviderSystemName { get; set; } + public bool Enabled { get; set; } /// - /// The entity type to be exported + /// The export file type identifier /// - public string EntityType { get; set; } + public int FileTypeId { get; set; } /// - /// Number of hours after the profile is automatically executed by scheduling + /// The export file type /// - public int SchedulingHours { get; set; } + public ExportFileType FileType + { + get + { + return (ExportFileType)FileTypeId; + } + set + { + FileTypeId = (int)value; + } + } /// /// The scheduling task identifier diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index e4f7dc5af8..21718e5f5a 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -159,6 +159,7 @@ + diff --git a/src/Libraries/SmartStore.Core/XmlHelper.cs b/src/Libraries/SmartStore.Core/XmlHelper.cs index d8fd64a6ce..7f546eba8a 100644 --- a/src/Libraries/SmartStore.Core/XmlHelper.cs +++ b/src/Libraries/SmartStore.Core/XmlHelper.cs @@ -115,6 +115,47 @@ public static DateTime DeserializeDateTime(string dateTime) } } + /// + /// Serializes an object instance to a XML formatted string + /// + /// Object type + /// Object instance + /// XML + public static string Serialize(T instance) + { + if (instance != null) + { + using (var writer = new StringWriter()) + { + var xmlSerializer = new XmlSerializer(typeof(T)); + + xmlSerializer.Serialize(writer, instance); + return writer.ToString(); + } + } + return null; + } + + /// + /// Deserializes a XML formatted string to an object instance + /// + /// Object type + /// XML + /// Object instance + public static T Deserialize(string xml) + { + if (xml.HasValue()) + { + using (var reader = new StringReader(xml)) + { + var serializer = new XmlSerializer(typeof(T)); + + return (T)serializer.Deserialize(reader); + } + } + return (T)Activator.CreateInstance(typeof(T)); + } + #endregion } } diff --git a/src/Libraries/SmartStore.Data/Mapping/Export/ExportProfileMap.cs b/src/Libraries/SmartStore.Data/Mapping/Export/ExportProfileMap.cs index 96538c9c74..a6e66ad885 100644 --- a/src/Libraries/SmartStore.Data/Mapping/Export/ExportProfileMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/Export/ExportProfileMap.cs @@ -12,12 +12,12 @@ public ExportProfileMap() this.Property(x => x.Name).IsRequired().HasMaxLength(100); this.Property(x => x.ProviderSystemName).IsRequired().HasMaxLength(4000); - this.Property(x => x.EntityType).IsRequired().HasMaxLength(100); - this.Property(x => x.EntityType).IsRequired().HasMaxLength(100); this.Property(x => x.Partitioning).IsMaxLength(); this.Property(x => x.Filtering).IsMaxLength(); this.Property(x => x.LastExecutionMessage).HasMaxLength(4000); + this.Ignore(x => x.FileType); + this.HasRequired(x => x.ScheduleTask) .WithMany() .HasForeignKey(x => x.SchedulingTaskId) diff --git a/src/Libraries/SmartStore.Data/Migrations/201507301550466_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201507301550466_ExportFramework.cs deleted file mode 100644 index f4c69b6734..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201507301550466_ExportFramework.cs +++ /dev/null @@ -1,105 +0,0 @@ -namespace SmartStore.Data.Migrations -{ - using System.Data.Entity.Migrations; - using SmartStore.Core.Domain.Customers; - using SmartStore.Core.Domain.Security; - using SmartStore.Data.Setup; - - public partial class ExportFramework : DbMigration, ILocaleResourcesProvider, IDataSeeder - { - public override void Up() - { - CreateTable( - "dbo.ExportProfile", - c => new - { - Id = c.Int(nullable: false, identity: true), - Name = c.String(nullable: false, maxLength: 100), - Enabled = c.Boolean(nullable: false), - ProviderSystemName = c.String(nullable: false, maxLength: 4000), - EntityType = c.String(nullable: false, maxLength: 100), - SchedulingHours = c.Int(nullable: false), - SchedulingTaskId = c.Int(nullable: false), - ProfileGuid = c.Guid(nullable: false), - Partitioning = c.String(), - Filtering = c.String(), - LastExecutionStartUtc = c.DateTime(), - LastExecutionEndUtc = c.DateTime(), - LastExecutionMessage = c.String(maxLength: 4000), - }) - .PrimaryKey(t => t.Id) - .ForeignKey("dbo.ScheduleTask", t => t.SchedulingTaskId) - .Index(t => t.SchedulingTaskId); - - } - - public override void Down() - { - DropForeignKey("dbo.ExportProfile", "SchedulingTaskId", "dbo.ScheduleTask"); - DropIndex("dbo.ExportProfile", new[] { "SchedulingTaskId" }); - DropTable("dbo.ExportProfile"); - } - - public bool RollbackOnFailure - { - get { return false; } - } - - public void Seed(SmartObjectContext context) - { - context.MigrateLocaleResources(MigrateLocaleResources); - - var permissionMigrator = new PermissionMigrator(context); - - permissionMigrator.AddPermission(new PermissionRecord - { - Name = "Admin area. Manage Exports", - SystemName = "ManageExports", - Category = "Configuration" - }, new string[] { SystemCustomerRoleNames.Administrators }); - } - - public void MigrateLocaleResources(LocaleResourcesBuilder builder) - { - builder.AddOrUpdate("Common.Enabled", - "Enabled", - "Aktiviert"); - - builder.AddOrUpdate("Admin.Configuration.Export.Name", - "Profile name", - "Profilname", - "Specifies the name of the export profile.", - "Legt den Namen des Exportprofils fest."); - - builder.AddOrUpdate("Admin.Configuration.Export.ProviderSystemName", - "Provider name", - "Provider-Name", - "Specifies the name of the export provider.", - "Legt den Namen des Export-Providers fest."); - - builder.AddOrUpdate("Admin.Configuration.Export.EntityType", - "Entity type", - "Entittstyp", - "Specifies the type of the entity to be exported.", - "Legt den Typ der zu exportierenden Entitt fest."); - - builder.AddOrUpdate("Admin.Configuration.Export.FileType", - "File type", - "Dateityp", - "The file type of the exported data.", - "Der Dateityp der exportierten Daten."); - - builder.AddOrUpdate("Admin.Configuration.Export.SchedulingHours", - "Hours (interval)", - "Stunden (Intervall)", - "Specifies the interval in hours to which the export should execute automatically.", - "Legt das Intervall in Stunden fest, zu dem der Export automatisch erfolgen soll."); - - builder.AddOrUpdate("Admin.Configuration.Export.LastExecution", - "Last execution", - "Letzte Ausfhrung", - "Information about the last execution of the export.", - "Informationen zur letzten Ausfhrung des Exports."); - } - } -} diff --git a/src/Libraries/SmartStore.Data/Migrations/201507301550466_ExportFramework.resx b/src/Libraries/SmartStore.Data/Migrations/201507301550466_ExportFramework.resx deleted file mode 100644 index a08ee1d639..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201507301550466_ExportFramework.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/201507301550466_ExportFramework.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.Designer.cs similarity index 92% rename from src/Libraries/SmartStore.Data/Migrations/201507301550466_ExportFramework.Designer.cs rename to src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.Designer.cs index 4e2831a004..0e3246e714 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201507301550466_ExportFramework.Designer.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.Designer.cs @@ -13,7 +13,7 @@ public sealed partial class ExportFramework : IMigrationMetadata string IMigrationMetadata.Id { - get { return "201507301550466_ExportFramework"; } + get { return "201508011828241_ExportFramework"; } } string IMigrationMetadata.Source diff --git a/src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.cs new file mode 100644 index 0000000000..86c6f7c509 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.cs @@ -0,0 +1,157 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Core.Domain.Customers; + using SmartStore.Core.Domain.Security; + using SmartStore.Data.Setup; + + public partial class ExportFramework : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + CreateTable( + "dbo.ExportProfile", + c => new + { + Id = c.Int(nullable: false, identity: true), + Name = c.String(nullable: false, maxLength: 100), + ProviderSystemName = c.String(nullable: false, maxLength: 4000), + Enabled = c.Boolean(nullable: false), + FileTypeId = c.Int(nullable: false), + SchedulingTaskId = c.Int(nullable: false), + ProfileGuid = c.Guid(nullable: false), + Partitioning = c.String(), + Filtering = c.String(), + LastExecutionStartUtc = c.DateTime(), + LastExecutionEndUtc = c.DateTime(), + LastExecutionMessage = c.String(maxLength: 4000), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.ScheduleTask", t => t.SchedulingTaskId) + .Index(t => t.SchedulingTaskId); + + } + + public override void Down() + { + DropForeignKey("dbo.ExportProfile", "SchedulingTaskId", "dbo.ScheduleTask"); + DropIndex("dbo.ExportProfile", new[] { "SchedulingTaskId" }); + DropTable("dbo.ExportProfile"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + var permissionMigrator = new PermissionMigrator(context); + + permissionMigrator.AddPermission(new PermissionRecord + { + Name = "Admin area. Manage Exports", + SystemName = "ManageExports", + Category = "Configuration" + }, new string[] { SystemCustomerRoleNames.Administrators }); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Common.Enabled", "Enabled", "Aktiviert"); + builder.AddOrUpdate("Common.Provider", "Provider", "Provider"); + builder.AddOrUpdate("Common.Profile", "Profile", "Profil"); + builder.AddOrUpdate("Common.Partition", "Partition", "Aufteilung"); + builder.AddOrUpdate("Common.Image", "Image", "Bild"); + + + builder.AddOrUpdate("Admin.Configuration.Export.ProviderSystemName.Validate", + "There were no export provider found for system name \"{0}\". A provider is mandatory for an export profile.", + "Es wurde kein Export-Provider mit dem Systemnamen \"{0}\" gefunden. Ein Provider ist fr ein Exportprofil zwingend erforderlich."); + + builder.AddOrUpdate("Admin.Configuration.Export.NoProfiles", + "There were no export profiles found.", + "Es wurden keine Exportprofile gefunden."); + + + builder.AddOrUpdate("Admin.Configuration.Export.ProviderSystemName", + "Provider", + "Provider", + "Specifies the export provider. It is responsible for the individual formatting of the export data.", + "Legt den Export-Provider fest. Er ist fr die individuelle Formatierung der zu exportierenden Daten zustndig."); + + builder.AddOrUpdate("Admin.Configuration.Export.EntityType", + "Entity", + "Entitt", + "The entity type the provider processes.", + "Der Entittstyp, den der Provider verarbeitet."); + + + builder.AddOrUpdate("Admin.Configuration.Export.Name", + "Name of profile", + "Name des Profils", + "Specifies the name of the export profile.", + "Legt den Namen des Exportprofils fest."); + + builder.AddOrUpdate("Admin.Configuration.Export.FileType", + "File type", + "Dateityp", + "The file type of the exported data.", + "Der Dateityp der exportierten Daten."); + + builder.AddOrUpdate("Admin.Configuration.Export.SchedulingHours", + "Hours (interval)", + "Stunden (Intervall)", + "Specifies the interval in hours to which the export should execute automatically.", + "Legt das Intervall in Stunden fest, zu dem der Export automatisch erfolgen soll."); + + builder.AddOrUpdate("Admin.Configuration.Export.LastExecution", + "Last execution", + "Letzte Ausfhrung", + "Information about the last execution of the export.", + "Informationen zur letzten Ausfhrung des Exports."); + + + builder.AddOrUpdate("Admin.Configuration.Export.Segmentation.Offset", + "Offset", + "Abstand", + "Specifies the number of records to be skipped.", + "Legt die Anzahl der zu berspringenden Datenstze fest."); + + builder.AddOrUpdate("Admin.Configuration.Export.Segmentation.Limit", + "Limit", + "Begrenzung", + "Specifies how many records to be loaded per database round-trip.", + "Legt die Anzahl der Datenstze fest, die pro Datenbankaufruf geladen werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Export.Segmentation.BatchSize", + "Batch size", + "Stapelgre", + "Specifies the maximum number of records of one processed batch.", + "Legt die maximale Anzahl der Datenstze eines Vearbeitungsstapels fest."); + + builder.AddOrUpdate("Admin.Configuration.Export.Segmentation.PerStore", + "Per store", + "Per Shop", + "Specifies whether to start a separate run-through for each store.", + "Legt fest, ob fr jeden Shop ein separater Verarbeitungsdurchlauf erfolgen soll."); + + + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportFileType.Xml", "XML (Extensible Markup Language)", "XML (Extensible Markup Language)"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportFileType.Xls", "XLS (Microsoft Excel)", "XLS (Microsoft Excel)"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportFileType.Csv", "CSV (Delimiter Separated Values)", "CSV (Trennzeichen getrennt)"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportFileType.Txt", "TXT (Plain text)", "TXT (Einfacher Text)"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportFileType.Pdf", "PDF (Portable Document Format)", "PDF (Portables Dokumentenformat)"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportEntityType.Product", "Product", "Produkt"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportEntityType.Category", "Category", "Warengruppe"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportEntityType.Manufacturer", "Manufacturer", "Hersteller"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportEntityType.Customer", "Customer", "Kunde"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportEntityType.Order", "Order", "Auftrag"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.resx b/src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.resx new file mode 100644 index 0000000000..ce78c71be0 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.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 + + + H4sIAAAAAAAEAOy923IcObIg+L5m+w8yPc2s9UglVZdZn7KqHSMpUko7EsVmUtLpfqEFI8HMaEZGZMWFlx6bL9uH/aT9hQUQl8T9HhGZWfkiJQMOB+BwONwdDsf/9//8v7/9z+d1+uoRFGWSZ7+/fvfmp9evQBbniyRb/v66ru7/x99e/8//+//8P347X6yfX33v4H5GcLBmVv7+elVVm1/fvi3jFVhH5Zt1Ehd5md9Xb+J8/TZa5G/f//TTf7x99+4tgCheQ1yvXv12XWdVsgb4D/jnWZ7FYFPVUfolX4C0bL/DkjnG+uoyWoNyE8Xg99fzdVRU8yovwJsPURW9fnWSJhHsxhyk969fRVmWV1EFO/nrtxLMqyLPlvMN/BClNy8bAOHuo7QEbed/3YKbjuOn92gcb7cVO1RxXVb52hLhu59bwrxlqzuR93VPOEi6c0ji6gWNGpPv99d/ryP86VuWVK9fsQ3+epYWCJii8BkmMwRLsjcfkgLE8OvLGxLRX14x4H/pueT9m/dvfnrz019endVpVRfg9wzUVRGlf3l1Vd+lSfyf4OUmfwDZ71mdpmTXYedhGfUBfroq8g0oqpdrcN8OaLZ4/eotXe8tW7GvRtRphjnLqp/fv351CRuP7lLQcwZBEjyqjyADRVSBxVVUVaDIEA6Ax8+1zrSF/u1ag6wIl9TrV1+i588gW1ar31//AtfQRfIMFt2HtgOQrHABwjpVUQNBB9WNfgBlXCSbhod829Y0lZSbNHr5nMdRGmCgRo19LRag0E2hGtOs/ADuI8iTHZrTPE9BlGkQ/fZ2u6KU6+z8eZMXFWz0PkFksV5oVP3j+nJeX+9+GmSBwT8fE8iE85eyAmtNH/760zCdOM8Q8MKOg3k0F5DDEAb9rKnxoG16Uadw9DdR+eCLrWX9j3XSI2p+2+KBCydBjIZnRTpH8GcA+QRJCbl3jJY+R2V1/gziGg1tXsFBfqvirlWoE4GbZG2H5Dxb+KL4AsoyWgZZCkyrl9FjssTyQsx0ALHc61fXIMVA5SrZNNogLUhvafCLIl9f5ykrrimo23leFzEaU64FvYmKJagcdwy6a9YbBqpWviGRHHcNeVtarWwQgY2tlADNauQwiPNsUfqJX1RX0dO/7fSOBplj8zU7L4q88EV1CZ4raKg6SkYvwewsj+d1HENJ7NouSTaxRhWCS2flp2QBV6vvBME/lwUc7RWAUjqrGK43q6vft5wGbSz6/16DGizOoRxPT6oqildrPBLrPaAdR/lGiPC4H8jbIgjmrQbDnsBJQAYxZX9jZYSeEQ7UWretVsOvVGQfcETR2OnYKdcu7CSLihczFdhr034/zJ70BcpNzW4YqmWplnuBnQgC7Va40G8b8K12K4fitFsFqEi7VXWaQGXRd6qWbgjEV8ORkDW81PUP+VOW5tHCSUwvkuhNh+AolhVypKWRtxfgWwk6XN+K1FfnEKAazNDv2urEaGihepZnFZwMjYQL5h4BmVqIh2no/BmOqVR7v8O0NCsvwZMvP83KmyKC/SX0V1dU3zYLtNK+ii2WML5tSkQHUVKPMlDe1lWR5AWG89FK0U46vsMDtRrI1aJxV+TjD+4mH2Vo12CTvgQZn1E7o4zp7GzwJk7jePA25vXdv0BcDW+CneaLl8G3srMCuG4dIick1CmKBHi6IREaSYc0GgDW++M4r7PK26IH2eJLlNVRmr5Y789S82hrlJRjmEedsWNuHnUGlemASJLrRnRLAwsHQsGozDsa0Muqo7vlrtuQeI7KjWaZqjwrv/wySHxME0eicygZNq5u61NeqnaIgUZ4lResL9zeZC40xtpQfY/K8ikvFuO33Jw/zUtvDwHyNzTxRXBXxWsB1hoo1OgqekGy+wuoVrmLO6qtX76hEB2llopBCUJNGwL0HKf1AizO8KSDAu2Ns8XwZ8x9u2iDK17UTb4PowR3bc6hRrGBbTTkH2O07ShxB9AKwD6zZ2+98kuSJet6jaMZT9aNytFquSBO1hGUQlcF/NWGZf/t9as5CrKE3KTFHD0PhLnBdg1KSOu4CkeLCwhuFrXqFEJkLkyLfFHH1WmdLeBKggsbKsdFcldXoIntcpCvZ1EVpfnyjQ71UeLK29oSzZfReqIHQ/Q9SmtvbLMSLkhoJYG4coh/kdpkW7oJLTIdS96S9bdGmkU1zm6zqetlynENhVy6x7Uqb6sllu+KaIgdCFl3W8UPCzQb45CbaYevjV0iQpCcw9W0amgIt+cKGpdmm3XIsOpeUFgaUgKTPFmAm1W9vssI74Mrsu9w4u9S75nDwqFc+Qc/hrsjE9Qh7HEwqfGikruH2JU6wjbHulddtki7Tb1twWzAt0wdxSBpUP3+zcDbRgnZDcNoAOZdV3XaVtHwVy+OSoVCPDYkQs153yyKCtjmxyKvN7AjYfSLdhOYZYvkMVm4nRQNs5XbnnWNvrcbG/5h2jtZrJPsLF+viWCbwRrruBas4Zbsb3LCyXn6mn3K1+AqgK7Y4QmnMHwBVQSFBvLdq5xyYbRQ1NiYjIPau0kq5SXnMCM7SdP8qffogscEPHmrvCebTZE/gsU1lMbZcl6v/Wb6Mq8CY+zQ3cANI2UG7d3HcEjbkI+b/CT2thw+J+ukQp3De6P3FM8f6uEXXZTV91GMVJAC3ay9hEbUVmoM1uxHyGKDNzIrPyb31VlUeJtgHZ4Q2so1+KNOCvC1WkGKN9uJN6e0OLfKj0paBwphOqmRblwlMdKNThYLpg/eY5qV24j8MNHdvjP3LUubBd4h9B4jnJVmwX2953A6erRaNOfPm6TAivqH6IXFaYbiJK6SR4wiBNt/isp5BLUmEGpWaWyW95lgb1A4xMmyAIBUHD3CA7bIbsDz8JrorLwGcV2QuQncpUeL6OwlTkHTKV8ZR2K8AkWSe6++Hife+zFiz7UyK9HJc6AryrPyAk5/d5Ttrd0tFjjHRZR2GM9WyKVh7qi2v7EQPZ8/Q7vGey3MyvPSm5ywN2fQwFrmKBbB04iJMmgUzbJHuDQhui7KwFNtrPL4IdghBDLXMMaTxyiBdZOUwOrpNxb21HnDSrKAI/+cPzWjRjsNxOY7KdBASO5fsEPgIi+6Pp4CaH15Hl1F8UOOzGmUyc77nBlZgwjjrKEltEB6o9dbo8BGfxuHEmaSGoxN/EkYjJgAYNEiI0LKh1NW4VpAHTytX07rqto6FzzWFoL+kZSrNCmrMEjbxZ8CyLxQrlP+G1ecZ1A7x+iS2P9Ei0QSfAf6mi6GbaA1TM6I8N3gbcw3EE+UWg7EHCfOP9KdsjncoiBxnWcLD0ydO+s8Q6d0QfirFVsUZjAwU7SCbdQ2ofFxk4BmTXoLfLiHgrLqDyPP8vVdkrXncgGZEPa5C24oTzabNPHXmH+AZLkabinSdkxw9D+SxYDYPw1Lm36n8ZUnPSI/YRLuxCJc1AVIgUvk3u6GXPADTB5B8YIqW3pPyFTBllVPoxK0u08Qs7vH9wVEZV2AJg2yVJ0MkrS2bzNsJHaPFv2gUZuRFodB4BMtjQcqjHu9DRMBBYq8COX4oJAiaoTGOV/ljT8lKuw9HfKopWZL7LdIYfRLV3rbQt/kW1/5NvxFDsXFJClAbUOQSGmgCt25pQG5qB2qXBawQwM5Bhhto34MY8TcQ6a0sWCSECvLEbUOL2wQywfUucUUoVMsiGwAHJxjx8lDRGXfSUBF/0VgsjEIYR3HcZVgPMohtDCK3jMQso6zYI597g/B5V1uQBQ9pgFkHWagHPuLLdD7pEmQR0YAy/svrqIYj7qCbHyaWo7jvYmWysHdUnC8PCWKpeKUhHGUpt+jIoFKnciOVHZfUU8xQQa1ZLNkUtVxqljUVgO3GK3tEJ3HRb8NouBBGpBnQrJcyoUUkC0bsmfDis6yoHx3aQhphxkw2y6TPhxBd/tiEWdwhRwv8BCi2TfPbtjfdHF4IqZVavtfx7hmeVuaKN+Bcpp2MxMiUuNbiWylQPeX5Deigju1uqZsrXLrk9/WZeaSxKjxkjnUbKOryrO83qA72gvvmeExeby2YjYzOD4SrzJfHt1iQsSzjQPRmfE3ucYU4w1vsoLKlt/CcduSEth2cxK4GIbwSOjHwDkvjD0SLaqW8+V5tfo2xTUE4xAByocihPa6YNTZ3R43jDoUx61Y3pZmKx7owg26/xKoZZ0Df7y7Eqd5Bfl01Gs9mqdVwuxM3TIKdaHneGEmyMiaG32hAgBbT5vlSRW6STVP/g382qau/pQ3eZMRhMXtHJHT4vlKB4952B0GEUDXUbZUxmwFS0oQ8m5c+PiFHb45tGun4AeZwoAjFU4P+D0BT1/URpWjNA59+OhgsugPIAX2jbEXthHTwt5vT6o6oG2P2TJOk+cA/LIDdD1wfCakrX9U3BXiqyHRUC9kGDwA9Ndh7uKD3ODFjJ8DXX48mHcsQp/c8iJEDKE9uWXkjZs4oXD65xw5ipex8pnJzIrxdCOnrdR5NUiO0WSLxnIFh4250PU1RJoggV8wyOkage+4ipXrJshjEAKyX9cpMMoCHcavMt+AwU+tTpMUPeTep3f2vP3YXUoNg67LPowNCCrxtZXHZouml/AqN0WYySMaJZOIj9lwNxt6L1Dollt/heU8nT9DQUW+KDqCjbyNfxjuBElqHSvPm9z2HioK12HTgWIFXYZ+QyI67jbytjQbgeG9Ausj+jQvPoFnnJB69NZbxRQ9Zqx0+Qe5U+GqBdvaWQGOXhlMx1UjbyuQpRXqIGhWXoAITUSnIEzo15buVFsONbgDsAWWXwLof0iMIh5wABPO5daCrr9BrLgr/hqBv2wgsR3lw+DygST3n0JG0NxqeN+GrqS+dEP9IVmH8goDyA+fm0Mm/Q8iSwIJkaP02OX4rlEjbghOOIYo7VaI0jGoaA+Dio5xNvsXZ+N0ykVv7vwZl6icUxSEQCGsjeYqrb+d0eAJpiO8Qpkn2vZx1E0rYvCTgFn1egxdXie9Q7mwEeFGSdTapFP3I8onkG7u6/QfoMTJT4Mgu8xdcPno8Lb30CV6u+S2umVHGywtLTJQlnCt6lIvcHVumfUsGZG0hvqqvbya19lBt6pdxE+nc/S/jvaJvK2ORh/rpG+1+e2QXVoTNBboFWDN6+1hWjF4DjxsQxd5sY78431abPMoVaeYCpJdf8xXbQImfz65v0/SJICZinMCe9sygfTqWdmEg5zE1HORzjcHTGJLwvDR56isZpuTxQJaOyNcpwppNaCuf86XSeZ4ZRrV7zJLS1G4XxVuSCpRGDrRf0uAERHrXCkfs86D2Co6bchPP/eqbrKwgr7SIPIOM3C2vWZtDlW3W5jbrUbD95uF4VQuKaDt/QUycklDbgZUQG0KQk5sGsyW1ufPSKGJ0pO6WiGVpkl9dA3ixjcoGIGqhnAajCpwc2JWy3aCoBpQr6/yUnJDpi8WDoQv5XotAHHq4k2+SWJFH3G5vJN0sbiXDIxtN7GDSdxDXCTsHF3C9Ysptu3SNYA2RoYirIBsgikQYRfFEFxXJWD2XX6KisVVnmRV+SkpK9mhvwBO0ns5nGAMCmDbkXQRiEZ7DQcsEH8MjFwAsoDWeaeIdKDyXJIslJD8UiCO9nJIL8O+J6i9XQ8NjTx70yI42vTyti6SoqxGiQpH6usoDemM/jCtQBbbRNnLCO2IQ9I1FhnkDpS+9THJYtszvTPiQZQBX7nFS/PdWA29H7yhfyYbpCtFqSYFVaCHe1d5BrSPWwZ6Abl71W94Zveyt+WWTLOGhFtht9H2MNsNkCniNm223HqvJhepsnMMJN9FCkDaURrKL+VTRy6PWP0Wx3F7lrel2TBNvVwu8Rhl6/jw9qBiZKHy6d885Z8BIuKszDWC9n0AgXSzKgAwbfDnAA1CQQuKJGYa83wK+nvk7eINFxkSLKTDN/ZFYceyN9H6O3BCW4qEud1WbsQbk45EDy2wrrRVrO1bciOQDIvaKwS7oxCA77wQyssspOnhsAN1CJipO+5DCrF08KGp4eTSbJlBWjfv23pH5J0/x2m9gGudukasOvkKc87WrlTcPFoC+DTh2f7M2UDIEnJyDPHKKscWEtlNXNG6u4e+TGE6Sist83o/vzuN8n13V4DHJNJIvTAH3Lug1jmZ7Y7qCbf4lUqMVzhaf+AbICqtxXVc84o135LK+76cQXBJmHsCM5S4E7LeQT3KqI0mCB8AITtLk0ZKOC3r0zRf9vFr1ksa1S7fEDh2I8a97cwoceNo8MhBHk6F7DAKWYqg9e0WcMtOonKOlYRA3mzUdMWDhxCC414gb+tzlC3raOkdsxnq7oZtbqt8EeIAzukSXsPs3mZrt7O1+M4Ej7/aKuN5FR5p80jdwIJvmgd2jtdid/GC5UAHnMQ+JfaiOO2GrC9XuWWadrWTzdJuYqxbKLqPVKFwu6YhvPbqbSes92qcZCr5NwZ+0+E5btnytqbxtXQz01JdafIN0T6s/UcN5mCMo8yLNFrO1nCwKGW39q5GiIui1eE+IzGAt6vJS9dMxjUosWgTC3MRpFBiKgE58a6G9jqyE6H2lqoCnEcJq5d1AZLbYlprRMhAD4N2retSSA5kJM1KtLqu0nqZZJwQ0V5KzOtYJHyCqE6BhAKrUpmJECehcCW5Gx4qjQSBcje8bVTvfJfhj6hsBxgu8OWKvv0/ZuIAZW4Efb4BJwa8BE+lu0sX1X5DoNgNHus8uqNkHRnTfYwoje6WhHMfdxiFjE5M7O0WcMvWonKOiYVA3izbdMWdX1H9o6Ikb2vPvcfzVV6EWI6WzV5g0TV2q8EsummctH9Gn/tOeVSPTvMgI3MzWfotUWSmcIXC3ZWGsL2AQ+zPYk+LkxbA+lWUqoKb5ZSn6fe8wiZZjBMPWWsDCEX5hkW0Gzos6tVJVj45BPPITZsep9iuYQhxS8ITxowcjLdgFLB+djPRNY95b1AcFUE1Hw4cQzqQWtRc1/x6D/lvu8Pu0HN+ORIRkmXYLpEGhl56RJFwuZHl1mkUmeUqSZzoLye4FIkGMsVZTnhJiKNskLcVykicRj40CdtaTXf4u+rBjLRV/vQ1+wQViSusLXraS8j4+ojT69zkSFLuztXGaazRwS5UKq0ALK1FFgBVIJT37pr/VsbK5bzhRiSS56KNyusyAUId5CYBQnSU6/K2dKeav/wyiPOsACDYDflQqU8holaiITzMFGhEa5Bkp12W0ob5PbcOkxSlprNrLGVAsU7KUpODkIWSZ1NUQ/KSSA3up1wyuF2uYYO4LiBWjgBH+eQsnwbVF6eRjdsnAQdv2SMVarBFzOk5ZmveaRGr0pJ6aRsqxMfVreD1QJcYx0mB181yM7r7ZIQEYV2TrWIyYstfES9j7hunqZM4BmU5ToNX6AI0NFqNxHzol8yVF0MHys/MClm7rM5OsrZPpuwgWHHdcpvQ+ShC5W3hTNDeDzQHksOBIqNs7aarvXwfYYqL59Y50lnJoUijbtrBbQJzTRdJQFEniUzo8m4SQP7yrO2Ml0DDOI4STd4WptLOSDQ0WwiBL6I2OaEqFsXwFpL92Wj7ZoJP978n4MkTBUqI7ZQQQYIn1BR3+Bq5a3eEcBgbgt2DFEJZK3m0wmpP0HWwBRL2Dv9Uda0B8N8CPKX/UfDL28IE+ljk9WbgU+aB7mqNGdx4uX1mxotQQTYHJEGD2CGHuEX8ud503q5huTS/JYEYaU6UiaU5CeAvzdtOeIl0jOMo1xVC5uCl8UGu8bGiZZq15C8uhA+lieSJk7ho59ZaUjRPrTX/HYWEvC1MoJGeXcFteb/qjAe8K34K+tVOb2cF/Sqb5YNKmLwo02wdrCNhsF1FL+hWSlBkTRrtIU+SJBxzVhcFyOKXUZ5f6hq7jrZhox9AnKyj9PWrqwL+wlLs9bu/vX41RxkUfn/9N+e1QAdi+c7T96gaU6zM67sKisR0lsXpDUJrRKy/Wg+LagylrR+jsRvUGJwb/Ir2WCOkGh1npK3UGWeEbWOjjgw2ZLGU7RujhCPcRRKkIkTpBQBD01Te8tAElrc8NLVb/MOn9sQ8NDiTdmt92FawWBmsCah419kCLE7Wg46EfAX4BygAZLsA6dVXIH7I6+qkgnx0V1dgTFOaa7z8r/UIr5u2qkeoa04n9/dJmsA1Gcw+2AxOA3w/CBk16GXjAkD5dQZ5i9adnFkKYiKj6oebSNTlUdRwjjbDJTKIygewkE3JoCM8e3wc/iVZ1ND58yYpsCPhS55Vq5Hb/AeIhqcnub6aR44+gLvE+9oKgeokxvvmpzxdjMAffMMjMSbR8GmUPYxiADNtjiJiyDZnZ2M2178vNF6Ts7toBOWi3U2xAtinwht63dfQICjaNK43RZSVUYx+blWD0ZseZcnIGr8GZZ0OT/azaIMc6eMSnG90pNHO67teRx93yFd1Ea+iEozpwL+KEtdL3+xrqkPPS9scss+hwNnUzWnJ6F7jDyAF1UG9vKWwhK8BOnoj7hYbHVt8ispL8NR6cS5zdM8mpl4l9D/qpE9phEeeeBndsoDbY09RORcpIQSyjclTxg02LYhCBukSSdecAwU739C3Elrpn5KyyiUPJooAb9tD3G1n5VDccbIC1PZNkI/JPTbktIMQAd5+K8HiR1KtuMHooblBGVSxHRyuhXK+Kfgbp4Tj+s8UcZ1ly516dtmkiJL1DBVLerYtEveMKLft2TVYALAGC1KInUvfAiWhtEyhBeYGo69h/fw67Ls86WBXypOdLhE+FE8Uu/RqYySNOUhW2jEAEqHHQnmFr4kFocNryw2aUigwjwEr8rb6Y1BPD28j1XzdxMO8s7U9iTHdYbc1NJts95FbK2poW32hlSlDKAhGHacll9NKF2sLroFoImzHZS5vq6OX7wqldtAQyKgXYYIfLA4kUDpqmmu72xoaRbf7yK1LNbStQGEUrTGUdqMBiXU6L4HjL2SOgkXeVuc3I6SC6LkPM+kUIlBu4FCFWdl1FmdLiwJ4ozqEZ3m9GcmpfQ2JsUmQg2gMr13f2jgJbuYgQ3bsGCNrmhpnWF+gsUXkTx2snVm55Q7sPpza3zqQA8p+SzZyO3EbuOloxJJUOZ5bWR1+RBJQ6a4sg/fakokeet1AOb6FZGKMQyJ53w4JoudfNY+/+aL5ex21o/cyOrKkuiqSePAQ3r6hwSN2xxjNKCMZOsK48200OuLQJKNbG5p2k4S5jhve+iF/ytI8WgR4zmpWdsiCqfGf4QLJStDhtTSBkLT+AZLlysJ00dylq7NFCqCaFQ0fyNAI+DOcJy8Uf0sVpZOyzGMUlbzolBXxqUdALUmm+em0Kn//rsVBo/C4RnAQaayQNrOq6VYPJepYW6joWgfhr1de4rcgvPRKhOKoV8rbCqINXhJvdoyXXbG7iplvgz120qbVSAKzg33hchMc/DstNsHBulfWaQG+4xpU8F2gq/YNwUPgOI3SKIuBp1lWDn7LbCwH2kCSQRnIJop2EYW1qeA4saEEDn/iFDggyGQ0AY+bujAelyc12oiaPoboKP/kbQXRQW6KKH6AFB8p0Btf0g1r3WGeAa7h4x9AmjyC4sWx+ui6j3FwHbvoJbF3tiF/8kBQEuJ2KwP4DlIA0iBAGsor1ROJMoRQOjrd9SsS0ylIJJ3oEH0cf7l2MYRcB7L1Kl4tjvYKZMfsGvyB3ox0dxBQaI7rQLEOQiQUC7YIQplLYQ6frkFU5tlFXjTcNL4bpOVfgN3eQQ4I/DqgvjxlNrXtDdjh88bMq+j+/jIfI0XNyWKdZPqrvX/9KciDIJRsC5NXboeu2Lla1ARNJLa0CEJgdwrBPI3NHCtnZ3AT8ounYDEddzbV+g+ws11FRavfWJ4QNud5DhXJKQ4RYRnMAxkmLGSaFEtwSYIC8hAKixjMYxlG6dgHUbxlbol1QUupWxKetDGkYAJLQw4b9I4x15BoL5EC6fvt7JFVne9yrQiOeWUw+i4HOfRtkcw3cK11d9x7WeByIhVVUZov3yjxHrfHweW5mPBfN3QiEMe9AiWvukhSOLAAb7ZDvn76mrXDviKOltyTYDkn4Hda5UpWFy15owr8Y8hGtax9swo+cRitGp0pCVRYLOmiROVnPSiH6iw4VWiPctNW4A38eNSwYTZDSDAxmSTvuk+91NnznQBSw08uirU8ZbsSPIQCaF+d1w0dcAwg/IKLvaPA+zOIJhX3Ss6Lx19y3Fmzx7L1sdRuoqW/WQaRHJeW89L6a6BHynR7tXJXvt1OZSnYbsli2T5KwYTgze9RkUTZNl37Wb6+S7LIUyPWIz/ysryt+UMdgJU1d++rRHUIGqaRL1FW30cxmsTiCs62NuosTLOWvmnN7SbckVDYfiSLcMg+hb02FZUAU842GtkQLfpBozY6xDkpy2SZwVWUYD6aLVRn0O/CHAt3UYLIt2955tSdXKBLsLY34Up8Jc/bmbaXx0tQZMYPYY59sK/za119vcdIm/Nea6LKk6oRzKHa7RWb3y2Ng9METKvKHGvG9UOenBi0LXesGtRyGayri5Vcxa6DpXFYjZis6jJsqn6Isya2sfCq4VEfHFygt2iCOVpvwHMFP603w6fHR6l5/qiTwv+yOrFesqrI0xBxIROdXrFLyEK82spUZ0HK8pzLeIT+INM6tiNUO1sNhsoixnkelW4BYY1bqeTV0kBZX+ZYsEMSwvMQcDM57iIGu8hJmkQa2ymApJ7IxT1Y6hdfTalN8xpaXcJoj9wub0tCN/84mj1aRJroyDzNi/kfdVSA8np5N/igGt/S4l9wJZDB/cFjR5vrpiM0NCshJriRgzhAEiUPHZLBhEVDCLX2c5I9oEDfKXPY2Wpbu6NnGeqdZsqa0450k4CidYU7bz89juNeo5JsYeI/Q9yy6IKiEdM5uq59vQxBLwZoQ8/RQIWrvmfdWxp0u67FENzKlYCF9J1umxDY8FyhoocqO908UggxosvNefSlbP47ygt5WyPFKjCtfitUpzgDNTov0/MMwXsrSHMQQ14JMwjNsWpeKu/UBjpk/Jwv8/5QczIHJDv2ap2e5ouXMd5azbMKrpfuPOoSVE958TBGcAAUTC94CZ/VRQGy+MXfs45xnj/HqyhbAvxYqCtqxf4hbUQccIdGeKusRYTX6YD5+FVtDfsNkp8Z/cgYcMmQKCj1WGhQr4102y2Hh93Q09p58fKmQ3LcUeVtaXbUXwbZ27qJ0Txc8ssg2STMs4X/zXU7+ZwjBP5kNTFYLvJiDSeduJs2VHufk3UCWewmb7RV72AbtMDK1S45gHbo2jM7RizbzrMFnNngGpadefMlwjdzPa2cFstRNMvbasjvq2E1WKYxnBz8MuY+uhVYA+z2u0tdTG5cv3xDoTlyo7wtTKjBbYxAfGrg51e08j6MmTok959G8cMsw1Ga8/qOOMJ19ldLMB6XRNAJHswFHirDzhSZqCWsJ8x6ooPlLFRthZDeaFljAt+0BtR4HEH81uR1kxuwhuq0V3CRCN1RkMjbmkY7+56AJ9jV1fgtu1ps5u4jOA3LvHgJwMssqiMfH/l4ND5uhXsANmYwHbn4yMWjcTGeiiT2ClvO1+s8e8NiOrKxvK0w7iM4oo9FXm/GZ0vY8gSrUOMaGCgKe0hXwUmcXoM4R69W23tsUegARPemR3JccUOvuCkdtsoYOOsg39liewjmf3tYG7bW86g0bE0MwZnaEjAvC/tb4bMK8zd9/eMCPOQFOE9r1TnuQK2Gyp/wOcqWdbQcbCe7v0/SxM0O6uuW25/HpSRv62SxKEDp/abDB5CCAHc8HNlT/h50MzrxNtKxx20PRewgbCG/eXAQfp5ZEJWQ/7qX4DyioShMR9aXt6WR/KZ3umwl/0tZgTWkwxNWEsZu/hqRbui4qOFcDC1zf0jWICvdDkK5ldIjOy6W42I5pMVykRf12jNsAOMo33CojmtFwbUEnT7WSd9y83uiA388g94JbfJNEu9j6AHHv8KgAzkUp/4pQD3TSSWPkBb9g+SuC5bGc1ytitUaIroH8VColXqTh8IE+fNfIFa+EjhM4D9KfDW+dx3lwIq8bdBZ2Rqzpy8ndbXKi4AIr6HmsEmIlAuuOAeSoSQbSxIFkGLlloYn8wFIwQTX/uWwtjFb26Vj0nsSWtr3LZCu5wSk1xaAnSDw0+fcJfge1lqit7kJLEfpL2+LIBNqcmfCM7Xvuw4kQwcSLAyZJec6PcwtB08e7kjBBCc8ctigL/mRDYmEiqhc2dvgoqQhYwhxguCPIkXe1sROC42n5v0wrTrlMLCI81xvomTp4sBod+fyTYfjyLrOvDNUVIzWTBmoYZTHYd+22JCXdS3c7ngRecSn9suQQXVcjc6rcSBJfhrH53DK0vZYE6iuIZt2wXf9B0ooE2i9jxNk0UxCHKP3QPwTFQa90j8H2QLd/YnS9MUX10lVRfEKGTvvLhLrjGzb2u+9av+srW0sKy/BU/kZoKXuefTSi0wxxqPklLclppj3WQxekipx+MsgqUzCyJOgaseQgcxnKxA/5LVX+vPmSZs3HKrjolEsmkkiIffyjYz5KtlsEHx73SoU3ll5Ez2fPwOCGq6oIKLuSmOwd65240WQUPqW3NPHSg3FCxVi2FuBDNs6A03rcC9RGFf0en1C3EpAIXxMza/bqlmKDfzi+0Ci/ZjPfiAJaC65xpdZ7KGGtbBzklnX6AIb4uur6AWxwKekRCGe7kJLgvAoteRtsSTzFVp4JnY02Iwdq3CdSXjolq+8XWemdbh1Zlwx6DoLt8COK0vBxC9xCuhnlh1XA0J0BYokX/hHqVVRivGVfojmFZx2tCRD+CVCOV8D3WeaZUmVROkuSzKyi0ZS7JauIRddFKBWXtHQtsEZ0v1/bLHMmmzW8txJLONExMm/8VtEmCxOkR4YBwZ+wyE8imd5W2GuI7tdqhXgwUmpp8vn0bc/VdNj5faQCqNuJoXSh1tZt1vwrbyRQ3GCVAHqqeqleDV0KQ2dU1LRiI6CRN7WVZcj9J2fCOjxvJ/GSWvuaizysoTLIvXnMhbVkc92lc8stBqfAPhj4Ltmr1x+Bo8g9b5jtMqLqr+uNnYs3QUED9e4xpjb9IkiBg6Ikt4i0CxHSAX1819hCAGZHBQFKMZoa8SnOnQ3/MAfdfNUkd/FDvym16eoVGeAdCGd29UF2ZUF5VWFcFcU5qB91MYhV2R2nyzrorFYWzxHkS9va5p40gNMtUjEVri8SBI9vyEwHDnWmWP3NVEuTqPgwjmoXtn8d2QbxdrHN7ACXWbRefjjtF6AWTZPYJNR729zjwC7isoS3RyDBVWQQIMO4eC0uEkq5Wt4YVoZJYj/C6ii9gafSt0P19gHQISKj9DeOHMV+N7BNcigSD4pfySL5pDFC1uD5Z95NjwdmqZ+FNGm1f+5vpvUh6b2EzVvfiM/zdEGF0C+FEleYKnsdZ6MBnYTqbKiusyFfE+u18SO3Fy26a+DIHaFxjRyrTcrclZepNGy7Ft0v8yhbinYrg5pAic3fYE0JGeFnoIvYH0Hik6upvnd61dYW//99U/cdFGw6LYM9gJAgxj0ld6pKzWvHrSwgts1zYSQH0/KMo8TTJhunp43eYHixe8TlNA5XoFFjdimfGCGdp4tXjWmqrzK1qptZg5ZwxQ05D5I62QDqQtZ6PfX/xc3QKNm+jPhbTNUz5lWeDK2pEbH41B+lHDWk6zi1bQki5NNlBp3iMFgqOqhyerbYks+gA2S0VllTHyTTrT1EC/DmuIu9S0zeqmOdr+9JbhMzXx/r0ENFswCvkVrQcp88ioi5hNC2zChojkBE37In7I0RxmT6BZ+evPmHTsvv33NmpiYVyjABkXSnkVlHC14uQ5X8iIA++qHMgL76qfPpBPd3cKpmfaWvFJqwrJUBQ3DOrIp3YSASak+c5Jyh7hUOJJxeVQ4XyZdYO8aT82pxGIjWcxO7JGEGU/Ykq0K2FmxYnaKm9Ujmkz0CubUpC9EtcnYuz0sv0rwe5m37f9SnhaDixiZhrThYEkbAraVIJ+eZdVDGIFP1fNk0oG2ys4wZhuKY8o07Du2QzAm8/Yt38buMyY9hAkYk54nI8bcPlk9CWN2J2RaWckCiphxe2BnzoYcXnPJiG2oIHa8rBMjsJCMrvsg1T4kZaMGn2zgpCAfdDuaBJRSRlJVEjFVB2/DVMo2BAxmyLhepGlXug1huirDkaVvwVzsB6BJ96NN6YC0TgOyiGrZUCbofmXaP5FLiIfTT6KTWDMi3QgyzohEJv3o6k+tx91CvkkeQfGCo9o0GhYFrNDhHJQ3GrWI18h+DrZ9qnoznhYmpLMRVxH1puasXh3otyQNC3AVFBzmop9Jm7HYRnfGUpCOYjwulc6XSRfIbEM7waWGhiwLPzCP7rEtKxvDBAy6h9YsSph5H2GjqNBatCJgEWOScDZcKcQ/hXWr6sgIfKWi8z5YuS1TU8OgeEIjlqQVFVLQlee0TQr4T9XWzshF7YjGE5Da+TTpCllvpzjbcEcX1RmJn/d4d1eNYyIG3sNdHmcGTbLlF1Ct8sXtNYDYEhTFfYZ8A0rno76qiInpWjb8a9CeyI7ChQG9kfMKKnVw2h4ho4DbDr200yJoIV1IQCuyCFswp8T0C1o5ghGWsnKOjEzJpspki7h70V3HjAyciA37K8PmDMhiNWe9cGq5pA8jMI+EpvvENrTo0U0zvVrCshCNWxSHrJKS4dlJ2J8RmUpIa5P2qYrTHYh3N663b7PIOIAHFR6K9/e+LQ7FecwCxjJjWpexnyYpigTvGtB2k4YPTgUGvTkp+NXlQI32OtH2Ar+uv2wFFT36u0r2ZOGaEW1iErJPr0DpRjGCxNLNl9F+SKQRmUReNfnYmptc0LzAPd+mO5PxkLKWiF9FFWyYVt2ggHO3Gdt2jXONhjIC+xrNoUk/yKyIk/DwaZovr/Ky0vMtByni1Q7Ihj95xHvFk9Luj8CH0jnZG95rX4K+7RlHxSUssIwD++el7ZiQQy/gQxmD7wYfykYwEivK5sek+a7O1B75a/CYgKdPIN3c12mGdF6qQOec11RX+Om5mg7Oel3rcr99O7qd42q7gY3nyDecZwufflNxMvZHbwrOKrDWKwIcpIipOyAbJuYR75UiIO3+CFwpnZO9UARQ77u9o2ccFZewwDIOdFAEhOgFfChj8N3gQ9kIRmJF2fyYNN/VmU4RyNNULwUpKOG2nqdWt8ZphHsl+YRdH2MvFs3BXkg81POTrHxCUQA5l6WOZgoCTsZnDYgtt5GIRZqhgIF3g9cEHR+J2wRzYaTiQfhJee17XmFXVZwXi1uCY1TsIakj40ES3JYTZU1JuFLM7rvBm5qhjMSnmrkz5dmm1nScC4p1UqL0YO1IOpc/Gqj8bExdTci/TA0r/lW3pjgZQgADnCCaEUkMHvz8cCJinD+jlKpRelJXK8SLTfo5ZoIUOdUMaovTq8kr2mVbM+nAXh06Wg1pBClpNccm/Zn8OPIiL+o1drtqGZwHFXFzD2XDugLUVnwaJCBH3okROEtO3P1hI5yD25CPaFgpI7VZwS05iUE+ESuJezEWL4kJvD/MdIv/bd7rU042AShlI2sOIpEK2Ifo287tmbL+j8V4gvkwaXpbaxeEWMM1BkKmGfIQ4qvBLGO+HeU7QdfHFXjUfBjz3Q6oXwS/6LUkYsDhVTACuYz7hHy9IywoGcOoOhw/PybN4wqTsWLzyLRhJLEIWMSK7Xsx5mwoRGweQBxIf1P1YgRGUlHXpHm65sQcpbUHaLCAXLSfPg9x30fjur20GLo8U9/QexLdc/J9XjEZlyhrqTKVkRVc8rmJG1SkHts9LjUayghMazSHJv2YPFWZcCSN5LPip2YZj8W9TWsC1hXK7B3lW2oQUzEtNW8mncAVpt3ccZiOmkcZOOn2bhv3x+LdHxaU9HysHZ6fi31iNl26Ew5yAIbbx4Qm0t6PynZ7mLzkY3JfnUUoIAJ2eRWVYPEjqVZbDpKxi6aeiC27KjZcqWtGJhZF3B/ubrthr0bgPcNpMOJEIYbJGZNSInoW0vGLsJaKKV21RnWDAvaUrYLpZajRUEbkaeUcmvSjq7NbPPyNXGJ2jExVHY2b6Vb3RxE1H8xUTC2cT5POUBWnVVsv8wqY2EhbOKnKikCsVVYC7/6wpqTnYymr/Fzsvo10DZ7g8rnKIYKyWz9a37uqkogNBfA2DKlsbq+89CYjGYFbTeZvLzz4ooGYKQLamsZiT2j/2DTktmC8kh6hhJX41pyaSDSYLGem7bVHBuv+bC/ijo+wXsXzsPubS9fvxmbuWEXHFxS0iulsfXHiBkS5/CRcvTssKBzCiJwonCOT9nsE04YXdBl7zSJWGOiAwQYsZp+kd15as6wnY+nOMhqb8tRmByJXrkFVFxl69QqY3IwQg4vVAQLSTnMWNrFnOrNqDKNoy6p52gs9edtpQ7knqxD80l5IAWilmeS42bOoaPay0zpbpEB5NqOoI9ZSaHA7TUXelPz8kBjDYJuFQc9GUUO0U2HSi22tCVVkZiTabUNaY3Am3M/9QzuMKfh1L3cRbhS6sApZhcE5dR9jLXSDmIJN9zDygs6Np+NQIbQ2c6JzusS9ZEzlCEbgSuUc7RFLzjcgTu7b2/8nVVUkd3UFTFlUXVvBsuKKDiys6cEesrTZiMZjcbM53geWF4/k6wb9eSvhSOnubo9LqGAo0FgpGw7dEfmYjZbl9EvFY7hjaCzuvGHkWRRi2NGNRMngbrJdTd7Jthxlt4zXmnjpT7/i/Mc++Y5lwjfu66/BM/Uq7Lblm2ipyA/HwyrWjcMKoTDLVTBYHC7jWYvze1QkUVb103KWr++SDAPewhWTPILi5SZZK/La2uFREE6BwoGmxh0S3b0kBz6YF9axo+PJBds5NekZWW/qxa8an6FBZ4BiJzh+j+07i2HtxtLYQ0vPYFR/r2EJ5IxvWeK1Kkg8O7E0qA4J1gc18Ck3A1FHd4PjRXNq0jOy3q7xvusO4CD2AzD0IQn43ZHqByTKu6HoPXWmCKZhcKU7jhvjvrH8FN432/m2WAQ742xjB/c9Sut+hBy3WrKmEpnFKsF4AiwVdX/ky0a3and29RgNeLqVZMQfFquKRTH14rIyDUz1fxcv2k5p8hOr6wehk9/Oo/UmBR/ypyzNI3lmAzF4YO5ikIvchl0/B+ctcV9G5C4xpY1OA6iak3HYTQIKOOoYUC+OSDlMDC7isB7Shsck6C2fP8F8NvUerR7KCCyqniqTDpD1doBBdY4IDnIAttxH94K096My4f66EIhw/OanoUdMVk+xH8svPRjszNL29olZDccy3havm0WTnlAVd4id7Rl5PBZ2Yd6QuqW8K5Pw3iGIz95Sv0jSCmV3119Ts8BhxJNMdS8WVXTF5VrbDspe/QinWAt6FjCXydNel6vyAilGyToqXs6f41WULcF1VIGzuoBNxC/yKGJdTWHMMKpkFRysbUVomLV9H0Y6G/dpBM40ngUzaS1FsxsMiv+w40yqSniWpNFPzIvCzozNhEKCW3AfVX8ytjuN4odZBvsSP8zruzIukibWVnubWFdRxIKSOjZMqW12r64Ym45mBN42nU8bZ9bOcbXOCNPUG5mn99GnYDiWCRl6D627kzgVvNsu5WIxuIh5e0gbdpWgtzw4mJ5V1eMYgUPV87Q3pwYn9/dJCr+AW11yIA5SyJMdkBVPcphHf8RP2oUxOElGWJO2p85+hl+vtFM+5VWkD5S6bs6KpiZ66HtqTVFP/L3QEeF4H+GK+QJZP1qC24siX2v5TlFH7BIlwe0coPKGxmc7g96M4pzUEt+kF2S9XWG+m9yW9bY1BmU8opnJ2Y7vy/hMx5PdpA/bWhOaEFXyCCfnc768JX6jyVTYEdI6YmOiB7EzJ+StiJQ4pvM7aFZoxzOKbaGdOyPdkK66E+yrFZUi4IEYdj/djqoRjMybe6k6nq1A/JDXbLg991meJdcQgTBrrrCuVQ5d09ZFTM2Ncee423Z4I3C87XwbrQK28oTpy+O6gKRYXkUvOK3/LEsQXt1DKopa4mTmdAW7fOaqxsxfEgmi1xp1ZgSuNJoBo34Q9XaGC7s3czi2MeURGQIT3nR6rMqweQG36pbG9ELZdnQTsL9uvk26xNadbDV8zuMoTf4NFl0fbz9H2bJG3gAZA8qriDieg7bhdUVTAu7ue75zXK0fxwh8rJ83k050dSbkWAPTTmfSWZpyribcTtx6msh62xOr7RzfKIV1KlgDFF2U3Toqqq93/wJxhYrAM5zvGHcsyrK8wlh+/VaCs7RAbFD+/roqal5kItRzUAmur5avXzXF0jvAHDPSyM6fN3lRQfLdJykQYaMAtOjm8Qos6hTcROWDCBtZrkX29xrUYHG+jpIUWh1RvEJbnHjEAkAt+u6Cpgjj9p6reR81PdNPBe5/HOe1eJRkuRZZqxB8AdUqF46QAtCj00RqC1vQRujbNmrUiilaBTI96ySldJq6Mi2Ss6gCy7xIhMuuLXzRjyaJq7oQ4miLTAmiwkRBGJMHvUWVFEC2bAVgetREckUhTiqvpdnAlRNBwxjMRwP/Jcrq+wiTS7U4SDAtah1OK2Td/ihkvl4P0SBpYzmEhOvDX3QdQSwgWwW4TE/07nUuubijIfQI4d6MbvU9Qi1F2DMKwJjYWAkQLwYGRIvyNM2XV3kpxNWVaZF0WrgIydYM0iFBBgGYV8gOvQYlVtGECAVwWuSX4KmUif6uTL8m8zQ9yconyVLsS40QyVAYMwHSa1Uc0IQv6noCinVSllB/bGL6hL1iYAyUQbgtZ1F6UlcrpAQ3+azlDajgtY3huB4ZB/eFZmhu8k0SS/HgUhNEkQyDWS8+Fnm9kfYCl2oRYaemCEfrITbceb+h8IHGwySWrAJAvZj9mNzDjbBYaLELAM2xqxCa0U8mMPpCMzSXeSUcXV+oRcO/jy6ml/AddYM9T6ZZbR+GNkQioxj9hLV2tMQDpOJhUu/DavtGv/Il7h/7GpuZeiZO2K9Q1GRPRejGoHgYQDge5QsQTo2ZN2NKPPySgJRSOIW/GSJFwl1FA8q8yW4NW7Rm2oQJbmukwrx7Fp1vz+41jfWZXUSYiSw7Wh06F1uT7dVQrbqE7ypKDIP++qdJJ75EWEhI+9KW6wmzgkYqpumdWIWjAPQqvPjKkFCjl137sjAZb8B6k0ZihhTBmTowXlSIWRhjGaPAyYDoFQuQAcixyhXJwuhN3+42jdD23V500qD5VijQ9IX63nQ3MoS92V5x0bELiErIAj9AslwJd3AKwBTdhwSqEKWEs1kYM1Vbt14EdzC0fEdG3orZjo5y1jJIH3YmZhEiGtAcFT4mUKNr4lS1a3e9iZKlkHhdmcH8YlIoBQsNYmTvfwYVtCt1UyyG1A+cjWMSUoAPO7NFK92aZQF1WuVaFD4gMyck4SGWbZjgNnMPEefF4i4Lzve1nUUMtVB482kI/RQWeVnCiqkCJQtjMHrh6jdZ9fBXJdNamiK9whI9K53b23I9vaU+FrF7hTgnlZw23tKHgUQF0dEjDc0e6G7P2OWV+gP8fmT88Sd3UmyEuDvKJxAzB6HskTxNHQPKCY84by/wmSxPOQW0fIDySiLKyQ5nFRRUNCCgIHE8G5J6t/QxqpJ2NKzRwKgqGroZU4tGKqAVc3Q8ELdRh9mmTEdWsmUNkgRDsiDZjoC6qilzIC59mHrbH6ryFJVAyocnriCiHXfmqyCaBKuAUjJ0AajU7bd6KomSrijHw2RbCUQlJpsKjzUElbrdW8VFHIx8DCyoiCakQqEgB4dqWHbpzjNuTzabNAGLm5zsJ08UJbx8VKpqImIRgSIKWimxCugmn4IglNvypwndemib8XWVQtKsxznskusbFke5KEgmrGAwQlG9AIQTohUpYsJxhhLw9BO8cvGueKpXJIbFL/IacIQemYhEdFRSKNr0MnRrpEnpw8Nqh8VVUdDJUOpLMQ8txdiG9foCB2o+Kr3G4EiucZQG0tmvUhyEcPLBiMBFJGLC2BTkEWIcRe2kWqbj6qQMJa+jZQBpVQWLmZNR24iApErsgeiqX6NCcLuB6teqHyHHWbJ0+OLtNSirAjI9WBDRkzwVDWrJB6uvLKIpF4mpIKdBC6JtYztif7KSUZy3fbSpgJJCQMXQRPBCejGBpipyCXEOTKEuwZuCNiyIfAQMpIgeRESxghQsopGIwAT9yklBA+rHQU+tN1lodCLvtJLrXPwQ3Q0toqMCTwQPpXAgcMBCb8Q2jF3ljOBxCagiJbIPQU6TNIUyro+GV1CFATUYDl0jAH0YhCMRqY15397HVFCJg9WPiq2iotM2Qt+AXBxikRySTYADvUQR9NuL1gKiqSvIB6isJyKf5A6AgobqJgSEJO4qeBOyuyChJB4PJB8NBysiEnFlQ0EYHtUIxDjL1/gkZHtzREwPDk49Dhbce+UJkQroIyW1u+lyDR4T8PQJpJv7Os2QdKQKFFaMrqbW4NAg8KaqYTMK6SZrxoHc3a0e5drkgeSj42BF9CLuGSkIxaMaeG2iBjt+3153EtODg1OPgwX35iIhUgF9pKR2WZt5mioZhQZQLAESTugsaK5+qVYRhWJgxtheWrttLp+Jh06CqHtOQMqG31+h0xCBRCVyi4go6UiC73mF9Qec1p68yCcmhwxcPR5JLX+xq8AtoZtkClyox1wOvGWuJwoIqK6hGKeyopDb+NuNKjqq8Su2rXas4aw9LRElkAammZZolqbe6ERS3RZVmn5mFeVDNqovjg5U3odVBguaNDmwudhfolVSVwAlHxcPLKIbebVXQSQBsjEoguNV9SRhwDTDoKGlROmCaHVUYdCNQZZb8payhCYkjGYEBKiEGpGeDCQSAQ2oW9chmaO99K3kjAbGaB6bsYThiQaXmBgcRb2EBnl3XiU2CDiTtU6MJYToINDJOERMYAfaNJlK9R5dIZx8MCJwEW263AAKughRDezFbdpUyVMGQtd9lRQ1psFYslOUSOF2mwNKHg4mriAflrKeKhyMzdZgEBsmbkMRGzYYJdu0F4ZkFKSA1o+PzgA9AAHprM9aZnZdgtjfIqUXC6JZP1tI6SrUu8tYTOOQQBFWwgOZdF4RQmJNiHHCRbp0KbdXsMerqASLH0m1ItKf8KTRVZEPTlNTRDYi1YuCajrEMnYK5VwUZbG53WaikdNQXEE/UGE9Ff0sJJO6DQEppXMUipLfyAk1Jiddy3K8VOUhCUs3NLjcQ8mINKKfANHIqS2kVOK1mZF0Eo/ANCQJBNmTlOqoEl4+JFU1EaUkyZ8URFO2MLAeK2pbu0L1lewG67hsLPG6zpVj3CY+EJOSkIGQj4sGlMVe6q9AMniGXJhk9rDbbU4yORFoQP0YKHgVSfQqmhilKHZORmVn07mLg9V6FFhAnQ3MwHvZ0yyugZ0KVKo4jTQXQqoEhKiCWCrQCe2UUkeIdGChve28noWksAZnRQaMZHn6NDI7sXkCb8l84SJ5JAdXiRBpLbFs4vIbKuWTHLncmKRyp4enompdyoEtBqlanZ70G2uNcg0r/BNSWItxKbwVnhQbx3dBx5zp7wgxgIowARF82Ni5cUkkzuJpQDJNRe1g1fUVV62kGUr1BNa0OfD1K0Va1ltZMlXB8nZAo1if9tiEQkGdolYlIBw6IFKozZgi+DpR59q1XTxKbK7crabwiMtM2RHjOZUwlUfygC4VKM5ILJ0yCkxLAhLaO3MAiUsuo5r+h6KKIkGyabYFYxRaCphiUlBanSZaPwnGXRgvs4OqS/r926S2F1X0O/mQczKqEqXqCP2SmtN0UCi8qEJimmhiqC4IE6VRL9MNNUUOK8R9WTisBSc6T8v1XetK9dW4rvPglYrqENRWKqYszID0bx5olxRazIUajzWZlOgs5qhLhmw/UeoeyCdNyyUeSqfhtmAp+00FvJneOZGovp1H600Kts9nyqnDQOqHRFfwphCDbuAMtP2zGdS9CxF9JJDyAYkriOhDPuyhoJAE4QhXRLYtK7Z4HshkKIpt3Joso27VhJ+/+anXfqRVtKtCVlOx2hRHDFYtjE1MKzK6EHBI0k1ENOYFX80pmk11i8HLsRiRmn+52IbyisZHO4VDTxZBDkjWUfFy/hyvomwJriNoqvWPIglcz9pKCj+vrq44sVWuy5isxyvccrbPQoUlJf7DmIY0tOEgqUohqEYjHJpckleolMfA2jrykeqqCnOsSN/UUmUV0TU08BGxrH3FFqWrYj9YxXYVlKjjbF39C1xafVsCKR+YuIIwaxvxRpgqbZsY4Qj6dv8y2K0icIgHUgyFhRWShXisTEUWDtfAQULco2H6S8K2Ik1eSXoZ0nzFKZAPLMDop9FuL4p8rSKdClyli0lriRU/5kE3pZonRz0u6W5yC8IRwMZj29YJTDQC8cAkI966u+XevRMJeDm4SihLa4lFPfnan1LYy/GK5Bv39F9Q8qmYTQhnNjAVizlRarSAWuHjfLeC9wAFAbamdeVDNkUhjFqTvUCoCsc1bU9EdsFDigHCvunXBW9nWVIlUaq4gaKqIB+5sp44Fpx7G1EZDq5CP+xlKOHbj7f8u416Ykrrmg9chsKExKaXpgxbFFBdO6muSVrJlyz1GVrF0PIhyytJc7OyD3AqCKrAPnBSO92OZLgT6XYg7c4zxI7z29sGAQoqjpIMFH3Zb2/RY5HrqP3w21sIEoNNVUfpl3wB0rIr6J4x39Zsv7yab6IYifP/MX/96nmdZuXvr1dVtfn17dsSoy7frJO4yMv8vnoT5+u30SJ/+/6nn/7j7bt3b9cNjrcxZdf9xvS2bwmuLTjbTClKt74AF0lRVh+iKrqLSkjxs8WaA5uvo6L6evcvEFc4uvqZkSW/9VTuGuweJCSPK/mZQ2VIP+pqod+tiw+1iN1kb1DX3qgOZbcUvYCDRAIBjxdIzk356hDBHC6eqOiWTtv92QKSI0/rdbb9m+U/eW30L12/+WKO4QMgHkgmEVEFFviScpNGL00OZwYjXWSNs93pBSjbEnOMs/IDuI/qtGKIv/3M4/rtLcMBLK+95ZiNkQAsBxvxN/MOrQ+DU6gcOFxTf1dZ/Aq9ZgAZZP5SVmDN4xOVm2M/z6K7FDBD7D+a40GP3aKpY6lFfjfH1j4yDGcTPTPM4uRLraiJpv9jnSw4Mm4LLPBBHk2QlMGvVlMIqRIrSlag4NARn81xfY7K6vwZ6oJ4265gj75VMY1XAuLYBlQ11C10AI74W5+MooEeYmdEIP1ito8EVL0UbiAA1dV3Vf6dpElU0ijaTxbyBMR5tmCw9B/N8TSeOBJJ82V8eQvndvM1Oy+KnNEpqAKLeYIa63WdcYuX/G63ZuXixlnKyISLk0yZ13EMZYW4h0SZZR/5GSE+26h4n5LFAmSshtd9tdrxlugk6QpAczGruF2PLrTHKxTJXOHOSGPJ2+p+lpcApZMJZoRnGEFNNM4iYoqshBSyqZHJxFtoXKGN0lWtWGULfbFTV0Wqqt340OwzFh3+Mub2+CVZA35b2n7dmYW3DTr2WWuysGqD5SWvOsyK6trjbQ26xBzjt7IP6/5WpDROtsy+nxxKT3ynSRahhxVFKLsyc6xtzgKe1akCOwmQcatv+9VCpXuG7ZecfCM+22z7l+jFJXrPx59scNwUEWyZ2+ypAguu2yyiCiy+8sohXbIzkobYr4Jt7H7b+Vgi56pI8gK2zypj3VeL5VHka2Zp4C92GPjtbfvVwurKGZsrt6vN96L7Zo7lGmzSF7Yj/UdrPHyXqAILsXjGSMMzm9qnMbOi8QcLBa/GJx6MYtd9tOhHvmB4tvliQYcCSIQUXWLjM4CbSnNIS3sN+s92uARdIz5bbDfYPohxNm5WWrBlVj1coOe/ozR94TpJlOyMkCeH6nmuQGByOVZQVh9GzjebCj/1DsdSvCSiCszxfcKva5KImi8Wu1desO4J/MVKQS54pW771cbELMunvGAPCfqvth6/ecnO1/aznQHQHPFBqbaA3JhEackNVgSyM+u2DUj5AqpV7mkLUqgcVq6m/kAqGtmo9ExPBmRjlcRpvQALMrJ+xrrApUAO7TQv1subIMrtsXdZNRuKSBsRgNlYmLiHGBey29o4Dna6FWA2XpssWddrfPJ/sm5eVKH9N3y5BfboWY1dUG5xIINrXIOyKpK4khNKBWdhRtRpKo3y4Ap3R8xpb2d6ST4NdhdhaI1yGPm47QCLhy6x4NduIByDkgUO+HBMsRRpX2rjubkqwByk0HpijwiZot3l88CMHYKTx/O+4IZZJMRn22UgwcgV2hzzNGF+7AlP99XKfoj5rWX71R5TewjJHSaKysc8YpmvoBki3YP4UttYH/KiAR/yo8oHJcf8KVmAm1W9vss4S5EpMsf5PSmTOzYssv9osU7quzQpV6yIIz5PGVwZ3pO0dw70q+66cgBR7i7ARxbbouhFpsgqLBCODL/giq8bCPcFCYz1gpxli+QxWQh8d0KAAxGdnnaBVLtbrBNoqazX3PEZXWLPX2AN5RGvhQqKrej79DX7BG33K27bZMtsXHlNHblwFUNYWKigiv4TvDw1KQQo25QqscMo5Qeu0A7vTVKx2x7x2SaUMc2fel8LTqzPRTaKICxa2GyK/BEsrqMKSth5zZzhCYotpEFeadCLIex7f5NXUSqmjxDCaQzyRqRA1gdjN/lJzGhfdIlFcGGyTirUoS6TDhViyBZa9PShZjqIPtj4mrL6PoqrugAFCnm/hIolKy1kMOatfITcRONsvtjY1tsHLmnDevvdoj9tHZHSwJbZHA7/UScF+FqtUEK+LmUMfUosgrBuYatuiPFT5Rbrt4ZyCy78GCkaJ4sFg41dy1pom9ndZuakZ3f73T6CiZ1Z8ruF+p+lzfIk0m5SRoCg3MrX2yynr/cS/GIIe2qcP2+SAsdqfoheSjFlWBj7VnByBIxBtLbkUBbaTVSyyVwp1YYvtpCmVE3u4hJXatVrdI55siwA4HVTvtTu0LSveINurnKnp0yxzbrsb6KzC5MosJFfbaWzlzgFn0G2ZCOBxRCuLVyBIsm5eZTBOLSCFQyMhpPEIggbyqPDMOF1E6bIBucF5IPukI1FSpfZWFwLfE8uSrvaZyt0NZ21vmRQVpGR0fP5M7S0+MjIbYENvvOSoy3+ZBGeFj2fQbNvmaOzUSZKjS6y0smgbTbLHuGKhZW701BOKRMDWcX3xw9ijzJTZO3Qw/VPHqMkje6SlEMvh3JrSTwIMYTVKa8CO19qYQ3kT83Y21RC7OSKyq2spOT+Bfs7LvKi698pgLYpZynJAS0OO6L4IUcuBJQWgjvsYAst7W15jkfO8DZPBylvE7tC2uN78byLIWxbaI7wVS2wEJZ0A4u2KhcCKSi2WnVo3zmtX07rquLSNnCl1ph/JOUqTcpKgZ4FsaBMI2tSANn9CtphvGdMDGFx/ACtIVw1idmLDlSJVVA4i8oax9d0IUCz/WrtDT3jogWpAos9CD3IFqWC3tElbhjxVdUPcBNGz2Tx8btKSLcWz7OFUXssnAWHte7F8wzFoYgYTQRgHdtEYQEi9lECWkc7GbSnArSyxMiHFRgzTPXmghwn3K9BKXybSjAOLbDVWLrT9fJks0kT1lYQApjj/wGS5YpZ6N03C+oI7Dx7y+5HsmCRtJ8s6CUYzyfr8fR7hFq8KMAc2lIJFinQlAfxQcMEQAq4mKr+434GByjG2r/syPnPmDL7wCGUH4y/IE6X2Wj+JWi3M4Gjgi91wPwFRGVdgOZNKyF2CsKhBVGILVfogBf9UOImAWxDy/DhJe/t4wqtQ9ZAgWLuxE4iCYhjG4gG6iZ6COdRbF+KV46EBNuZMJ5+4/a7cd9F9NkH8sirDhPJg/71i1fpeiw8e2DKrDzssthGpmhXgia72iLxw5bZ+AtbPYa3bKgSc4ytvsLhI79bn46WZ3m9QdcVFtwdXb7cQneQYHXD1s0CPvrHyr+MX2kIy4iD5oyteSOaizggC3dG6rUu88Qz22TneXcQe/Kquyr2ULwcj2X71Ub5DJuQ9TSvoLEuxSootnFz+meW6+ZaFt0nKj+GyalxNYGxslMxvtQCc4KDkDiU2882vVyCefJv7uZm99UxLLC8yZtrPmL8Olj7/n8VHYdwhZbu5mv04h2LkyyYPMR1YMfX/gQA7qKb5bBvdciph+/tf0/A0xdOS+QKd0bhaqWn522RBonLbRFZzWGUrbY5UXIzpsjmxMQ2h59U7IBcnNiMKrCJcDnmI+PL3a5ThVknFC73y1Vjr5qAV2NDKnHuG83EPr3WFeGfvFaA0MPTp8QyDGt1TcvcH27YiIFc1ymQJWoxALcKAxB73KgCC9s5SdEjCX3mFcZ05kptdPI2BFOMWlBs44RrEoYgbZbOS8M64+RwLq1dSa9EiCCcWqBTH8laYaBcWuomQGjbKcDcZgkLA9X09AAWjt3nqoj4/MrE590RycSZqqcsJjC5CGFl9V31PcK6efEJPON8KaxnnCqy3tZ35BmnadXOQP5wGpuLW1yLYfdVT5lP0s0bOSsvQIR01m5n4W5UsMWHz63kVdEgDEsidGdaNZbdZ1yy/4KLIFTZkYGdGDgc53qy7BS86q8GhD44JKkgO5yTwRwP6NS4jsdo+3eMdjw+OpTjo6niatrV0r63EkQ5azKruKtlsvq7r5AJRLG1GG5Gz8fwkt8tsOGMPQym9pvNBYl0c1+n/wAlvrfO3pRgCq3xXuZytH1Z+CWj4e5ZiX5/vf9vXBAZvWb+u0swGY1iLF7vmuUsXeK7haGwOVks0HuATJe2n22Mji45E2ttbL//OQW7pQw+TfNll87Oni+VtQfiyaY5XuBRBRbnI3AIV3nJp9clvk8+S9RO10q6DC0Zz01TiWrQ/bNpX7KLbgvN8f6IynY0NEry++TzeAmeSvfVpqw97GrjFRW6ZLr1i4giShRPfp983q/yNP2eIzXqGsTo6RiHdatFMdB6he2eZOUTrwXQJTtnonhG/rdYPJS1sbU0/olRusTqqlKgF5RCvFEV7hWmrs5FXqwjQSpmttQe8zxK2YwWVImFIyt4CuLQia9O7u+TNBF4VKkCC3woTRHromu/je8+mpVN/E73qBtDMabQImpGEkTkFi2EnpWX2FVM0ZTWEOrK53yZZMJrenypHeYuvZUUOQewM3tVOzuep/LdHNtvVdKaw+xUF0lRVjzrE5/tZp5Htf067h4FxfEmyl44jbb5aINHGEvnFp9XQX6H3x6TLOZkNFdo0Ucu1diZbWqxhu3esfta99Ua03shpvc2mP6ZbJC5H6X83VymyEIrWOUZEOXCpgos1k+XxJdZP9vPY0j5qfR6vAa8b/Q2K8lFrZfV3NXDdHy8WbYRvoKTz22RLU5xXg+2zOIc5Cn/DKoKGpJlzi8/vtQC86oAQIVbUG4xRzU0rpJYiJktc7g1+D0SP6rdlkx0PBv8WNX3UHoieUQ/M+onlWhcDsJJh2BXZVTogJ/w3DlbZpDUTZpp9uSKLrKJax/nWd4hHrOdarWR+qrnYiNRuaw1df2hHPHhDIMAqsXdXQEek4hft3TJvm1REzF3d9box9cdFsdzXHHVYbj5c5Qta5R4nsFCfh83jOY0XzBGbfPFNvKwccsKH/TqiyxwtsEMbd0z3hMphrCQBdtnruSNSIEs5ihaMkRpvli5M3Y6b9ifMwp4qIjSvXNPdKLLT4Z3WBxkuLzqrir/XY/P6hSnIRBuBX2hxfFllvxRgzkQWOVMkYXrLY2WszXsD0qgwQ9dUGwRdFkx7mf8Yfo1+KfWyZq7ofOqwBEXZV4XvnaHCKPLOjdCsw96W9d7fjnRJfYYBdeImSKbg+CLIl9fpfWSfX2SLrE6iM/rmFtXxOedWQVdFJUf53dYHGPexFX3gcNDWCb4fWxG4W0+WexedcpsMc2XafaCXdfkj5acqp3d0/j/nBbZVFf0+6hPz8tfPR7HYFhZ5WH2BdSiKAB2bJupe8T3e16xa48pOmDdHBHen/sc+W4/NRF/zmsCFFupKYpd7Ism0ioGSRGMN/yPNSgrqJ2jlSXQB+jiKQ8rd12z2tOjfvIINsy1AoTJ42qBuPqu+vnk70W7vhYtCWR3CmOHldrlxmffZcvGDmbv4szxfAsj0JuSocPPp9rnQbFOShSS0Nx58tzzGWwu+78Wxa6uwbDXDvr0bbSd1n/dGQ46f67QpaX0pK5WsNEkxoEJIbhJhdmBs+zQDcNlYa/7hwir76gyWyCa3CesniQqt8feinhdIwIw87a+oom9yR8A4zQgv1tiO4ljUJYynFSphUaO4pqg1imTF6LynVntF3lRr/0DaXo0DutYUXeYRXuTb5KYRdF/nGrx89e7be91z67E2UKuduJG294lbsJ8idkiwNrAeFwXh6TyMKsDt8ii6D9OtjoQCUSvS1IF1jcGhJcFLJ2LjfRk/YrtV3NM6IUaBk37ycYBVlaizCzkd3tsspkUldtjx1m3hXibkqPMspNZkb+wcpVTo4qoj0Veb4Ryqi/Z52sZl/3ew0qW7vMUQgotTaH6RBUcBZYhzxzzbQ6ktmEREEBtw3hcxaGk8q76u3b/YtkucfeBndfgufBbMM102q8VSb1hlgluTJQRgCqwxMenkSI+2xxFCt4DcngDKKzl1V55b30IXM5HrtTm+Lk5xpKgFhRbzgu6dlhzeJki+/6K0fKlFi7E5u0qMWKu0Bpvc8VZ6p+UAdlz3FldFCCLX/hbBGIImxaaetcRG09Al9j3mT6wlK0ZHsrCso8qkcQhPtvydX1XoZTNsyxOYcdE7M1COLaALjirW+gh7FvAaaf75wZVYxFDeraoHJsY0rbFViQoxsZCOLagGAsL4dgCrMuvPTGEo3yCcj5BOmaUXgAgJJkBeIi2hcQ0AA/RtpDMBuBWwRuoCne1tPtqyR9irnPhtG61CdBti2x7x2e9J7/bXJO5r7MFWIieGWXLbLA+RcXiKk+yqvwBCgDnlg3sk4BY7HMrED/kdXVSVUVyV1dAauepIT1aLP9rnWqaakDsd29ZyKeo3CIUKXCezl7z3kg08o11gCWyC6DQhYYvFAdnkEVEKoYa0iZ4pVjwoV7br3aYeBV0+9USk2DMbiP8EpUPYKGmpgzGrs9nj4/v+R43X+0wnT9vkgIHvXzJs2rFI+UAXPH/A0QCKrPlbhz8ISlAXH0Adwl7u0cGZOOC6qu1eWc/5anAHSWD8mlJxEFyKKeWTqPsgTe1hADO+PnFKgRwwz87k6NGZU5Yu6yDMsx9uRP22V3EukfZQvt9AeskwszVYgiLlVZDnbFI/o2X6U0RZWUUo5/cxqaA82+NZ1I1pH+L16Cs2bziOlgb6bhBGRYU9BRD+LQgGpEcyioSotfyFANSgNlcXiniVVQCqRdWCGBjVyXiuxdUgb3Hr80RKPT3dWX2WJHBhRIe11WzRyu9dMaVbA58wuR934djGdKMugbrKMm4mxUSEPM2PkXlJXhqjfXLHEW8xoJ8bwqwnTkO6gzubyW0nT4lZeX/ELoApUtOYTM0wxwe9R5Ids0Q3y0dFMLDCcutYt/yPn1M7rH9FJC5BChdmMsMzTDM1bXNYiG/W0jQEix+JNVKyGRcoR1eQaoY4vOfgHHD8KoHf452hb9VxwhuEbwbJoey537RGRxbZmEdCLy19l7aWdn1AF9HjDidSQhgP3ZomG5EtpKo3EbziZNNglQNTqdkihxwCm4ssWUWejHIkNbPq77Ed1tsgg5SBRYeQlCW3C35/qMNN23JjpU/npsEAAcsVXuJESD8yDFllqLugGFIqEFJ6NC2aHx98irgc91/ryNMZRrV9quF4pEl1VWRxOKTYb7UAbPw3JcvtaGkrL+ufZX307WPwgNnh2PlzgZqtlfhoCUgrm0IySABsVAZtMekvsejimNR5+PQD/lTlubRQpC/jCmy2ai6qlK1RwBgEykbg6wEHQ4uWJ8vtug7lJ8/QLJcsa8QEt8tQiDrbJECtMswwY/Ed2v5eoZuvIokbFOwW9vzJU6Q5b09IzSu27O47pDbs/9WesllP7p0S3h0k/dP5Ioi6cniA9YUSQ9tGL8Rj9HFbWSEZRhODRvr3QyBkUrtN1ssp7AEvfYiQNYX2fmdRNY8+X18W27vVhA6RWoSsfqsmg6L46tb4qq7Lclviih+gAMTHVqyZRZYURiiSE+hCixPFoH4CJQtszonTB5BIX4tmCv8E6wef2cFicljFY3psujbFJyDd98dHCDitWntP7Z3NEymw1R1kV2DP3AGTE/lhUDlpLYo6w/ERoEuuYVmoLCKVDi/1zWIyjy7yItmtlhfO1NogxdPO8C2O+vi4Aqd8cojOZSA9vMmTj7Pl9pwanR/f8knjSa/W3h9FuskE4b40SU2lCaWr/jqoARkP0N7Jtv0c/znGRS+ITZ+GpvT5q9DsduS+yoqWtWFv+lKltj650QY6RIbZWdLY9GRtKh8MqM94HnNEL7pblSQhUEBFvigQjxuGmKK3fYo7zq+mW9A3EcmbufeL52wCrVLbmE7fMOIxZDLTzySrxthQLYG1vI2zkWSwsXHBcWyZVYi9Olr1pJB9r4AVWztFd+f5zdUk+WpUCgwuygXVugGUjSEfTDj/9GTJh0GJw7Bg8G4b49SaO0bN7TyFz8sHWBnh3jct3Fh5alnetp5+R4VCVRi+5Vwlq/vkizy3zX0+N3n0QrpQDvIQ81sFuiDRURuxT5c2nyxOGGMsvo+itHtvAKatcLbwDIYy3AwQRyYVRgOyJbs5fHumzmWH8mCRdJ+srg/JTgI+2QfrBOVABNBdFzMFTrgRT+UuEkACy28LJNlBq3fBHPEjH2AUVRuf4aHUpRy15eYMnvDGwVJsljZMpvAM9FTQNuvh+MagRI0fhB7L5giS2vua119vcco2sSOvFHHg+z67jfIlhdun9tHh0NbSWpkicotwhvAcwU/8Y+OEd/tLk78USeF6L5E991hOZ/lWVXkqcjLK4M5fIMg7JoLsNhGX2UnaRJxYhN/GtWgdMzJvFuCG98MHUR6N3dOg4lwCbpB5TjbB4kIFoHtGzfDunkx/6OOClBeL++YAxa20NIMOln8qy4r/nidK7QwarDxIUPMl9rsZVcFmIMUxIJbFVTRGHuNDCNeDqKNkSqwufGRPaDjPaH+whUeYPDXTQItfcSPfsKwR+Mg+RR1d19dDZ0hHT0FKzuE78qmOIa28etMdY7QvGDgdWyAfrqcEojrTe0qlkYAFIy7AH+wYPoyPc+iu5TdKMjvFthAXBeA6xPx2cJhxz8q9Mn6RaF8mfcuLWZHoIum3Ak/Vev0NF+8sF0kv9voQVkFGbvzu12C6ikvHlh1SAxjJUTggnvBq6VLks9LYTGMdSvnz/EqypYApydTNyYD3RnR1nbK925ZNzanR+tlVXdVyKlefnB78YG/le5wJR0t9s85LORymFJFtprDRV6so6riomX4Uhvl2O/hIOkCre/SpFyxuwfxeUrBuksBd9JR5ygZ4Hm2gDPKzAtTtDMiDLNJ+ymAktaCuepq0urDSLOmeRbH9qstJl46kt+Hs16mMlJXYA2wuwnqlp6GKonKxVhV1x+GfXCjzIFF82nMDVWQZM86wd6eMNxpFD/MMnwUSWYZ9mM9CVIHJjTGNAw7Brt2EdAPE/YCw97dAyYDd27AGio+vgdlIowOrGqGZldtCPTc+VXEhhFtvx7wsesZnKhlXryE4SYWm4sFqkVx5KKd46IukDYIEzHIPOJ6jyy0Pyz0EWSgSOJA8R8sNpeUx1oUu25E/id4aV4CpzBtv1ph4pAcbQohH5/E6TWIc99c3T0aB85V1N11lg3r9xjmtHVWzhasW7f7tjNs+K0IwoY9Ggc2VNT9c7HhPK0Zd33zZYrAdNmzlernKqeSpd2jl56ytEPjIkvldYdhYsmb9E5v0Yd6Z0nEfHLWm8o3AqKyLkATHOfpFCFRuXhD1PV31fpoHvOCqt0TEtq0wKKLrA5Tk5w7TUWfDtiWaef/QwKbLL0dyiw2d4ZUoDjy5IHz5EVe1OtwpxwcOgeuNMAx0MkG0Sb/DgZfOtX5BCYQi6r/aHGwmG+SmMXTfzzg85KrAr8O0CZY9nVLkricvJJqBLt9indR5GsZd7NlNpwpw0mXWK3tf4G44pZ089HuCiF/edD22mAkuDIYWXobWoX99KV5RphFyBU74e5ffpKiJyAOWGJgswYWfM49w5cIRE4uPEXtgQzPbZPCK6l88WTH9sL3yu3zmO4zcyLMwRgUAfkxqRjDQDvaACaBv6EivCmhuCYx2Xn/ehMlS0/tv8PidL4vq7qzJmggjQJd16CRNF+mjFn2jf2ezMGCdehAIVA0Mif3igbDrrL2aRzj50hb7zLLAILifVw24U5PGmrEwrfY2bLpL2Cg92ZRYF6UpmyOH6rEQkesqiheoTXw7iLhz1QFxS6436txv/fB/bMa989y3BOJukvwVH4GVQWKcM47MU4HwWeKaCD5J2ydd+ip4CzXvmDJD3eQNp4asCexNWcrED/kdagkQRw6F3VWj2NXN/9dTpKF3wxDtlMbyinGLYey6fdN9Hz+DDgyUAUWVI2euxBmzqVJF1ltXzudMiyc4rUrgiVA4igxzhAiZtRkUVzrnA9PBDCmHDsmYfJbvZO9RxfXBXrb4Sp6Qa2GeldXhNXtbV1DTMOsO7Z5Fpuo3Bx7qNdJ986FzpItLLcFYLPR5PpLnAJRCm6qwBLfFSiSfMHtEHSRzelwFaW4NuMUoQpszBw4feI3aqmSKVxLoWI1Z1lSJVEqXOBs2QGvc5xgJfk3zuuHBuK7r7D4XHYUAxzDrPXzYIHxbuHjUmw4C474mhJb5oBVgtAel+D6ElWwM1x/DdD5Q5fL0ndvI3E57WxqBMPw+lWXVuAdawhsvztgey/B9v6gdfSzIi9LaI2kQTiKxeZiC2tRHC5XTbaTekZCuUVAjRj5BJv6DB5Bym1qxHcb12VeVF2QJ+OuJEssQi/rNBUipAosVMRNe6DLEG37earILvQcI5chtP9oYzzfg6IABYeLKpjyxCV8sriLAr22jXI3MrG5/WebCDucA/RTVLLGIVmwMyIKfkBJDz3zzTVIXFLNyWru6snPn+g6/vbMxdcCJE5vXBLKqWrvKp/snb6K77R4TjNC4TLB4nrDTG0T58pPMPndxmUUp/UCzLJ5AmtHjCHOl1oddERliYJxYUElPu7gAWz0haYyqzB0Xy1cjUnF5nRoP40ZqfYFVFEbvcwoZ3SJHUbpWz5coR1eAc2Iz9PHv12DDIqnk/JHsliCilUE6TKLY0Fc4595xgyd/G6L7UcRbVrVSoSUKrbFjd47F8wUV2iL9zRHwp9d0GyZ1TEu3KTZRyy2Xy3XMnrUl1/O+Ov429VJWeZxgl+i5fYsuJXimNHbk80mTRCnEwqLYqNS12O3pw5apoTAPXLB7/7SBm7neV0In4Ex2u161KJtDxGz74tnN2+iAi1vx252xLLs5m9vhfPtwxK9/82KIba1ZOwgcaMZkbnDvtO80HfSjxNaNGMzAg5wg3/CbXWVL26vQVkVCVKSztAodRLCpDZny1J1GkATYaFvy5NNmAYCMItBlz3FR0O9sZmmc73dEtc2VI+F8ND8qyANTOcf1NOWx+o5/T3CABMv6JzfRLd4Jpvo0yRN4efefWsw22wV2ZTbzDWN05OmDLKQ086g9mNM645BRX6RoPl8NSsv6zT9/fV9lJbAcvTezHMFinVSomRATZK+WzIhopKHdDW5vBcMPAluwF7q5jxnj0MegM80HfbcUwhck8kbY1aR1ZBJG0vWEKPfxZ3mUFjgGjxFxeIqT7KqbANib7+VYPEjqVatM1gVUaKtzMeQcFUM+ELbkOcM0LgC8Im+w7u4SenIEE7gdKq6jYbD1Qmh4jBIPfmIxRZS4rC4d5GB9OP3V3Naw739/yZaqnUbETifyAsXbmFMtBgesa/q4uSSMO5cEH8JxBR8h+p8rcghHiUZKFiQ3pnbfun/LrsPaEKjJfiSL0BabuvN4xVYR3hE5SaKsQW/ABdJUaLQ8OguKkED8voV7PtjAqVed+TWOlz+SM/SJkVWB/AlypJ7UFY3+QPIfn/9/qd371+/OkmTqERxA+n961fP6zQrf43xuo2yLK/w0H9/vaqqza9v35a4xfLNOomLvMzvqzdxvn4bLfK3ENfPb9+9ewsW67ds9RatEZaf/qPDUpaLlJxx4uC0nefuTelvWcLwxm//CThO62b4Gty/ksmR396yFX8T8Bbqwu+vE0RZvBrx4ww4ujRC97ozBAVwz16/QuIGXQrtRc5bJfrmtLRpIHuMingVQUXkS/Tc3IH4/fUvP1njpA7VjFFXBR+CwWGm3w8dAjehx7Uktxw9Shp3H9Vp1SG5S3RIyAMWJQOeP2/yAone+4Q1EQ6XA9/9ZM+CnQAiQwIUTfz1J4c2+vRWptPMo0D5Nrqryu4ch8TyokYek5uofGBwsbLx11m2AM+/v/5fuPKvr2b/dcvW/8srvAh+ffXTq//tQnnEnE1+iaYfdZb8UYMEM8h9glaXLU6opmB1CsWZMfP439bR83+3XeuQ7pBrQ2H7HJXV+TOIa7xro7tUOHixwYzCGasE8Z8HUqgvhEbZR85aLAu+EWPJ1fIYQBz2ZxFcvzgIFawc2WA1mfg5iKFBUvrIGJzikOuWgXDBPPC/qRH8LZCwNWj9EjxX13V221bvJds7B8kG+WfzNTsvirzwkfltl4Tr2WNEjKw2lQdycWXQlx4B/gWFlG9vZHLOqy/vHClTxzGUkaHkLsk2MjXHemHPyk/JAsovH36Efy6R/+EKQHM8qyRCwqQ3HSaTrUU/WuOt5e81qMGiSUDXpzI7kD2GGJu9XkdV9lLqWpcBsrtIq85lI2mecgy8Drq8dXb0aWr5yCzk9+hahWO5S7KoeHHSIbXKxHuHTfMLFFbijdsKsfFK/JA/ZWkeLQ5k8XXDUdsyckazX2ffStA1iu9ouUt1ARovG6fDd4pZPAzPt0G9Ig51tepAJlhHTsjOn6v2taAQ2GblJXjymc9ZeVNEsD/EDm2qA/dX6m4JJF6aMH1Jz0FZU3dJv0O56AYHIpS2IeHuezB6sSS45YqQ2prEJkvnJg/e1Zt8iI5eg036YtdbC7RD9PjsLDTG0zgOjbJPmh1YZ2xuJwWQ7fQlaLmpaKZpow1xG9rs6BCGSLTdMdoEmazedvo1XdvPAKFSdoc/VyF6eiD7RJudWaX4//KLvVrZHJLprRUatwmvfcpL9SJ36e9VXsg8GqYKeSFSKf071t/SDIy48QvOSy/rAdkhzSEmFG6YA2GtAdZdm/WvuYNwIAuPGtOg54/P+GoyFVE8WwQ/POibae5z6Fp477AVd03Qd1IGGEs7Btwesu2wBfrMbm3WXpYsWddrvLudrPEW0m27IE7WUYriYeCvEge2vPsbZDkUvgBn3bb7cPAjtdRg7y4FhaIUythjGhwS8OyxDcc6rbMFXCFwPfbJmptz4AMRPNvx2atqZF0vRY3Kg+3OKXS2bz9cTFLp8JsYy14Hwk9XXT42W2bqK3pxUkNP504w1b260sX9+XBhdzl3KInd32lujvGI8zenQ2oDjcXakEcJ4BTi3yOSh3gSxWPQn5IFuFnV67uMMJ1cEH2H83mXek3AVX2XJuXKL7wsVEhjUA+H2nkcVCQfliD2DxS8igrYBZxSuM9M64ex5XQofpPHZGHpnvEXO2a+o0HkjlqVdcJ5slgnWf+SbwCEHeO0LzN6Rpmu8qev2SdoaV55bi4djlDiic4OFXbT4pJEBZgWIk1U2M6epGn+1LsDwGMCnrw2xJPNpsgfweI6Qhke5/XaZ5Yu8yoovg4Zfg2BGaxn/0KhbI8ubvKT2Euf4LKAuaOaP9TBl0iU1fdRXNUFKFCk9iXUoLYLOlQrHyHLhMY5Kz8m99VZVHhpWR0O//25fcHta7UCxRWV88itZ92LcP1WrxaPLqdoJzUyxKokRlv/yWLBNOnV/VnZBxQFiITxm5pvWdoswg6d18gg0ZtV8vWew+dkXLZIzp83SYEt4g/RiwyjTbgPfskFI/Tn7k9ROY+gJgJCzCqNyeHOC1PfJwoQDgydE50sCwDWbICOw4FTjwi9RRko/Kh/aMlPnrRIqFeSfOQdie+qfyMpAEbqjSQf3yE6kwhw02tWXsBJ7Q44vJSyRXO1PEo7bGcrdFvZxbFkGnq2fQrUhwLnpRcJmQdEPeyFKEOP9WSPcIlBZN1pk5emV+XxQyA3IbKJML6TxyiBdZOUwOnhBhL20Wn3SrJg4/2cPzVjRRtN+yCTlw6f3L9gO/oiL7r+nQJoFvmgPY3iB5zTFd2Z9zzYQCYawjdraEg/a+4xKXj7ao8iQ0xMg685cAyBDw8cLFpURIBTMFUUsjkCPq1fTuuqyr0uxbS4fiTlKk3Kyh9hu5ZTAPkSCmbK5+GC7wzq2xhVEvv5mUkEwbeOr+li2AZaM+OMiB0K3sZ8A/FEqfNAjM5GiDb61xhvkjUIEMRH4j7PFgExd66m86wCRenNi630orCCgRmolXCjtgnthZsENGvXS+bDzROUFfH2+RrdxkBbyaDMCvvfnTaWbaphn1E0b0kPRm3aTAmO/keyGBD7p2Fp0+9KoWVOjziswAl1YhDmdLV7npZAEfIy2K6cugpHnjyC4gXNqr3rha7t43gh8zG5XI8la/v04zQqQbu9ejsKelxfQFTWBcC5ptRhkM7dHTaGsG8G/aCbcvBINvE8zTMWAreY71FAGy4EChTAFcJNQyFEZAiJb77KG89PtA1vDxjR1scqHUb8hDaqwOVudUcjf7/4txKECdqSh4AFVxy6ptwliKFbjXyY3k93aDWRAJjaM67yLK83KDJ64TVrPBaTLGg2IYH4+Bjvgn6MusWDtm4Lv7qx4BG/z3O4gsclnAlFHtkiNuKWwFEup3kFNbDgMVLa/FgOC6VjuzCBUsdwJCrcMMyZzVWCA0wcgtG7ij4KPopWmyf/Bj4joIKzypu8uZHAYnby4bY4vtKnCCaqjlkUIdThr6NsqfHZu4WEh4opDOuT2sHIrb3wWRxaxLmIgvhe7PcEPH0R6GwGEtg8ir0RXQeiDLWjCZlAyCi/1V8dIsZBLsslRKH+2SXk8JgIaIpEQO1B4YEtqWlv6A2nkw2411g7wVpr/4CSSnYjc3Dd9zV9Z44l7XWdAnmyBKfdYb4Bgzuo2pfq+twIXjFkXThfCGTb9zxvcjpBhIcPfov0ShpR7jRVBGIye0Zo5B2FJYaSJ/ZWyfei8PlzVURkVtEh9EryAO5ARJrWIebwjMlZnubFJ/CMUxCERr57L5nYqlIH5qudWJeSeMkM2t/W9OrArLyAJne9vboTIOJ4YA4kL54duTAEF5IUte8DXftPw40HyIaDHFwFP7cg6H48uQl7cnM8bdH2bv9OW46HEft9GGEeRtE9KN4k2D+QTakblYOK3Nf0U0o21AvM4db8rOwyT3gFEu0rmx4YfwZ9+tAoAbFLVldtemgHpEZJjT3wXuTFOvLMWNXhmkfbF1pD9TN4AqdAV69P7u+TNPFWj/EdXS/daegdf1Y2ZwhdKnefCBBF5mZll/1YCL0VZ7bPuMS5hdwhUE8/58skCxRVi/B1t8ANUNocz7TkvHXIIsoi8LFruuMH9+7wGMz7Y7wtd8x3GLsyfsA+2JkiYtJgyIR7sONLWutNlAV6zkR4/GiiZXcVvdYIRI+y9j4mWezgf2Cq+/TkjEh44JnWEa+ndyGRvQ+C7J/J5iovUaIct0g2gXa1yjMgTgDnlmazS5QVhrXd9z9zo6ZZBQciPbWO4HcOjmDsmyvbrdVLr8WIQtzWu3nKPwNErFmZa6/ivLflu5tVAYA5/p9t8cMlAookZnB7Zaz8HnlpzzuVvtrZ72n+uj31cMmfZe3vwiFQKB6ZLTNIvSaTmpdnfKRXeQK8ZGPO3aRSdSDMPZyWO+G+endXgMckEi0uX9fBhALdmE9P03yJ9NkDYdHPUbasUaJCax7d1vRiUuHRMielzRwxJo9tWhzsNj7WIMnNW1xnivwUZkt6mz88FMqbaBkmSHU379Efwy3CH8yPYeJ24uVA5Owge3FHo7M6xReO1CfG9seC+FxxDgYwVi/SaDlbw66jK3Ba2thHJ19Xh3WjdlidpgkQn1cop/U1KPO6OBgVfHL9pqOnlsld8gN1yIW3B9xUH5RDPF9fpfUykeWiNTxUzutYzvYeR0eX4Kk8oBcQJ+fQgBo4fgUrDCqUgSYMpiASeDeV2z+TnbIb+uvRwOjQGt+jydP0JCufDiYEDw3IIWgd1wruz3MTid27QN/zyu/pkqHvYEGiHQjXTL7Nh+OeJmqtlXlhTvSDbNHB8jzhbfVjDcoKarFojezCAeNuaiDDH3uSZ1wHIgv0tuAvv9grzIGefgoSDAyRtHyPcEjY3kjD9Q4H7sJ1Mf8MFKsrnzofxQkU66RE567XIEaS9nCZ32cfGmQx9XkH/BAbz/X5M6JqlJ7U1QrRNcbKwEHN++Q3q8JFwnazNSOu3ARE20rOwNi/It66yR9AGDMSozuJY1CW4ZDi+A84wV5JmowX3UVe1OsDOsu/yTdJbL+82mp+mUWmXtxGr5ia6QtX+3AxZoyrk3h9YO44kAWCB2TPom21PV8gaB5Ry57545rYXbXv0CFV1WUjib2cUShdrhcCdPEG9cLzBYMWi2TKHbHhPGqexvp+SqBDEj4fi7zeOEqgtu6wIaFOr9QEPqG4bHcdr7UcQqCgtSdSqo5iZRAP536KJ7wsD0RG7YV4ODR2m8Kp3hDvMJgWjyXgBU6ML2h+FXXK3v2wH+gkBb5JDjw7w6Qo8M5x4HeJG9dE95dqtieOwwqBq81xHRBVc+8wcP70PotYXRQgi1+CXRLvEF5H2+NbTWb2vzmvSvq4y4/a36MqtDCDRnsF10I6y+IUdtWQHH91k8RdY+jW4BiN3aDG+jcDRhoh1eg4I21lwzgjbBsbdWSwIYvFat8YJcag4E/Q3hClFwAMTVN5y0MTWN7y0NRu8Ye5Wob5ZHBG7F8kHrQVLDoGawJq6jXUdhYDP0lyDZ6iYnGVww2u/AEKAFnLM6/kCsQPeV2dVFWR3NUVCG01cg2U/7UOlBepVQIkEX62sWdh0uj1Wv8myBhxRBwyY9DTMQWAcuQMzj+tpzhNO8RCRgj5TQTqUjDFlBufZx7x8gEsZKTz7unZ42OYbE0I2fnzJimw5fQlz6rVAHj/AaIwYyf58kMChVv1AWA+9InX7NG0GR4/5eki0FzxyAMyAoH8NMoegplSDN5gS4zEOzsLjbLLkRUY7ewuCrQhtRIaKwUh87miwBy4IP7dPKKO3juMYvpd9kHQB2M3WQPXoKz5dLqO0miDLkyHJw6POGCvod3Z60Thu35Vo7olCO1OvYqSUMHmTAasIDRtUSJbBSW4rBuX4SC+rqETE+/ugQZpLlyDdZRkRJC6Q1T4pwi9cNsat5c5csvHVEaigEclnVX4rYS6/acETtfBJF6c/I1OXNW+9baaX0DWCDlMPib3WNk/QNbphmY/eduaXvP3rQSLH0m1cmQhprp3V6gMDMHdLWMy64EwaKfOELMM93KHe7sSPD4Hhh2p/Q+LBnbzzcquq/geVsRqL45CA5pmm4AGwzUc7Aa96R5MU+sxhrszMgcZ0qtD9bBBF657X0BZErdmPR8U2c4I1s0me3LILh7mgJLK9OMJGscyobp2Ne2bpX+vo3Yi3GX1tyyp8MN7Qx839g0Nfro4xmhGGcnQp6GdvdTs10OTjG5taNoNdmQX/qjuQ/6UpXm08E5GNCs7VEEUo8+QxbMSdDilOqHR7gul/g+QLFdOaqFJC6d1tkjBh6iKwjgI26fe0UXMMDxqt+1fogQfh7TtT7JBXxJpUvwuYHaRdHn/dOOOK4+kd/OwvDyTRx03RPUKXsUYTqM0Qu8K+Khv5eBRNSGNsLCHAaT/w+FJObb+AA/KoYOc9eG8QzyhJL8povghyZYBzwBxzNuwSgk+yAOhTho/gDR5BIXJG5G78iZYtwAOyIvRD8npfkdTM8D5FO83Htx7YKF7VHWRXYM/ULa6Q5l2/+tSgSZucv0nhN/pGkRlnl3kRcMrYZT0luMAtn8N7H0XpKYhCFb3jJhkyb4vntzfX+ahotxVL3uzyXntX0QgBUWIW2G7Em1isUHmONTnLCoOapP0l5ZXUdEqDl6ep8Yv5HbMStb1e/56O8n+x6uTy/+Jjz2GuywBVycoIPsj5/5g9nSI3XPvxFw78/MNpGEXFtfP44HIvImXhZi2Xzd0NK6xraLE5rd+0fWIiySF5PbMCtwk127J55tfe+Cs8SqCHsgCEA8xFO/tTKb5SRjlQFhkb2ahFSo30fKAKW/5prUt8b5HRQJ1nZ6HoRV5l2TRIUm8h9qGpkZhmFWifsLY5cWeKKvvoxhdAiqgWSW53ufbirPabPSkA+7XUNh/JIvhkH8aNu4hKgGmvPsxoFUz6AfdlMsDCmWZLDO4AhPMlLpX4t85uJa6wxKUS9DhKglV28f278w9FP9m3w+6tk8/ZqX/qxUHZ/jD/SB+CGGQY5vma119vcco8SCHSJEn2VkPZDv15a/u/54ut18i7HjbMuAtqTf23PjO3QvmYefwGLyzzcNPxCM53gHzf9RJ4RdESCh/WVXkqb/7U6r4j8An77XTYruSD20Jn6RJFPyRgoBWo1c0sK+Ybu7jHcZES4boLAWFtJIVhpTj03Gs0dFEnubF/I86KkB5vbwL3c/Gclj8C04TecAb/NCjCbMaoaFZCTHNQQpiz+j3ANtMEJbWbzl833FD/jvt5yR7QKdhIqVsd4KNbhJQNK6HgxKskx0jhUsLjR43dD+kbmqHML29ohACngWbHwGgOTgQdg7gdxYcWRec/e+Nc16m5xmC99o25iCuC2DZPyMvIvluSDAv2ed8mfdOuEGsMks8n6p1epovXkKlvoJWJ2Tczp13CaqnvHgYwPO9jooXvHS6FN8u8luExTNSB6M8f4ajzJYA55Py7Z8YmUU3LR5ZbrD/WSShwwtlqgz1NG77uNchc9S34uJzjhBYEMU8musiL9ZwyogIk0Dond9BESzPEC/MH/7LMx9ylJztPFvAyfTdAu10sNYteCACqBmj3z7f4BhEqbO2PMxNxBVYA2xwQ1QHMpl4TKEVGdt5NcFJJSGTvd9ljXU4ZjmN4odZho/xyNymB8I2IQLzp3VQTB7+Psa9STJi5was4QZ/MCdEg+wd6JVd2DUuLbw34oHDDM8gTZd58XKc48Od41bsHaf4cKcY0zWJD+0wP4TBAIfQPEYbekoh4vD8J9JV3SINhlNQT+L0GsT5wSTF3XGzdIzTLMOj7dni/2/v2prjtpX0XzmVx32INzm7b85WybLluMpOFEmOa/eFRc9AEtfUcIrk+HJ+/SHBGy7dQIMEeBn7JbEGDfQFH5q4NBrDZpnHoML3+Q9AzbnPkZ7Me5JjGvUR3jvmrTC61+qeEDsTkI1+5dfPY8PQsx2uGjgChr54ZXFRLVzb1Ern0d1WT/DLGE/An3KpNPtSO1/frd/UCAx9ghNqRt1i6GXyxA7F+Wy//YDRvDC6yvLT0zlu4woqec2mvvjuKu+xaXO3u+yY7BbLI+SwL8RzNHf5Nc8EmNPPF67y7Gk8DOXa0y7UZOPFEOtOi8c8ffx/trOk3hoROVLfFvKz01FfFIonzUTfFO109sW35p1FT431L35MaW8OV8Cn4tVPb7NziTUQNKq5jFgmqQ1s/NCQ+JbqGiFZ/3omsAw7VbbO7n8d0ahzULLD4ePTMU4ezmVODFl/5N49/M0d11gdzbz4sAc2+8aGMTos4fnE9ns79xwzxl/sdvzVtHZvUE/dauJAfKXZOoscc3+ABO650lM0JtxBr8fONTgAy7PDvo7midP025R2Lsoy3j3Wc4hfrhLtJMrN2ENbv3ps65+ObZEdyR/sS/GW1QP3DHdyYOW87umADzKSJr+1cKkmXMQbjNqlvnz5WnRT/+1+6WC6E/D6kYR2M3ybzWNOictHtvuUnc4uqUSQY9hVpkvhD4bU9G3QmI823xR38ddXX5mg6Zhmqka6aMVpH9cZU8IEP0QfPzTPKReIptyIbRa9Ce/pXie7jB/ZMvyOQIcHZXanvM4WfR1/qw1yXg/aqdq5jx69BQ8vDK3+AE3V+kzgcPltlzI53+moHqibuWZ5kuFPtxKPwco45a1NevPwtozzkvB02Wzhbh5CqN4ckjKJ05Hv4sm1V38niV/WTv7FMwJx3mcy3sBwUUL3afaILqvVRHY67CflI0OiMP0I9F9jBOKX9PE4fj+S/VPZPRgzP+slDSXkr96EHH3TwOHrmPLx0Swkz2SsXndXSH+Z8inqW/l1/lUnfcWYZ0VRzc3THz3opwcdPnTnEoVRafKWfWbptAng7WOWl9RnoGkhXhW5z3el3xzbkzPf52Yh40ZImaUqCwHJxEYZqUIqy3OW+2pvkYQxFMGu8vpByTpx1JTlU5M57Pe4sN1G9XTKdcuabEXn4XmCHJNv4nqmsE/+vfTlCm9k8xjwM7F/E7RGCm8ifSwPu/S0Z28Ot0nVbNyv6MadLV3HRVEH01UF5eRt5K4xL3reJaWeW25US96iXN6xMm6DD/28zlw3OC2nPdymP9t5DKW5YdUsK78oPiT7BzbpLLNp4f+ygx8dm+Y+5PGxnTUgstHbql+2lPpgvJYvstrFThyXeZLlEzMpc3Xq9/TcLU72+tVHhUd/TfkAd21E+pcApO94AfT2L4fEzG2BIVSdtnUsyu+4xhmqhsj/2mk4YQPEtTNbVlvtS1F8NwmGmiF6koe1VIub6tPymO0va1XHDU65IfIQbRiO6lWAo5tltQYm3iMZVHEcrH3NMLmam+2Obh9mRN92TVB7tWU1qlclXuM2dqb2pCi+mwRDzRA9ec3yp6Sog0uapDBiBpsx3aq2Rx60At9RfQwydvSKehNerqH1Oo2DXls95Die2uuug3lyb69gRK+/d9uv/PWkx63xOZKJvGI2bhTPO6VBBejkHyVDUzlgj96wzwn78jtLj/en9DDyA+xh64sS9eKlOxqFR3dIV32SKB/iorV4gIDhF2n20F0aPte+bPUjpSIg7dNVNrvOihEBlEPNEIO0vg/yvfSlt41D3+CoO+FNydTcMsTrPE3NIB48S9O/s/rob9EkjDN47krRi0PxZcxpulg3+Gf0fHtg2cz9/nxD00/eXEOd4mx4J2jUs2XNPOB/WcGjpT009Ufm2BKG9YuiyHYJ79mWw6uvxyyvFwH3SbXSuN09sv2p3povPinIf3XY/6Nejfz2k0TUinTL0vuf5YJ3p7RMjmmyq+T47adfNFWHBiUZpBaVErnJ/9Ca5FEt9cFqnF5mh6LM48pU+nhNDrvkGKeQOgoxcXjXBu+bVUtesmN9VnUoQW0pDFsJK1jWMsLsey6K37GZ5PkzARNmqPx1Yie2by7W9xe9oytNC6FnX2ZfDmk2JITinTr8KPfnf/788y+qPZ7/eWhC8/9Rx/jXd5wu42IX7/WRUyF+j4kBSi7JhFAEAVyv/wxgg/WiMO4u8C8NtUjM5IACTSKSPIhUoPqk0GjDMBYIWbipgqOLxE5NyrE0tgQ3hqoyrWO/C4fmCoPFfJpQczH4dduuzUO7Uft/FHNdudi3/W+zYE0WWBZEKQqCLtBCYZCl6EPhKDyYvAo4geERWmdCvfi9wAmy0HrgNKzKF4FTH8Xj0S/xeT2++uvjs8Smhh+37lPg8DOs+xf2Jn3s0cWxMnkdqdmKn7Aius1O+c6w1mvrymu9/sdZnIsW8wfJEhhbvcozgMsc44jwNMfBLQ6zuzivI3q9uIszhplTly8BM0Ms7GIwaz+vm/Jl0JRNKzsbT+YyPVujI+sRZnFjy0/El8fXjFPxMfAyxjbNiq7uH20GwHq/Qw3qWafvEgQGxZHKz8aHiVq5+LGlNxGiqueTzyz/dldftEb7ViSSOlUqcFoDOnhDX7hAVQ2DDafvmiDb0pjoZ3HINap1TcwVoSE8ndG0XNXIZVa+GlxtZsdyQVDNv2fptmu18KZlnVL/PuY7Z/mMG5ciW6k5uWDrG5iSNlvYxGzhKKECV2Jqh4Z1OqhIYHkQqDn3vx8P5MxWrLAq7G3mC7cCsM3/pXN3bwt/7ZQ7xDesai2pk600l3oJJzVyC3IIp1I0C/zgG+G4WD1FmHhQ2QQzIJFwIx5hTLkCvxJM2o51gF5H+vm7QKELChaDH55LYS7cldU6pZLrc6Uqi0DR14UxUV4ZW3LJ5jElqeOApcWg1CVS8Agiy/pRSVPKW+p/2zoAwNwfK+96A2inj+HVgcF9iM4ICUm45aLhumQQ/Vuctql1V0N2D/2Ps3xk1PQ7kCiB/Uyn8AyoMiYbQliaE5IshCzLBNnBQZwtrlz6eAFY4Umo5kbViyStrwvCCbhGYcoWxOvi97aLBhIv2fjLo6HNQxrBSqz046UkT5VE6cvO5uPVaeTy8VoMV82jNbdlztNUFHxCFHUvFaHg6gnEHh1+nAVckOSyPCBBEJjBBgsDM1AtCl/x/alFsNbl4tkGvjppJRmGHzePo16VTWCnS9WzDex00koyDD9uHju9KpvATp1jaBu4qSWVD2v5D5vHC1djM1hp8lFFutCuPRUQJY2MmgTdz2HO86m96AEtrSIUbjX5cnhREwmLyV6tO4xqbbk/tcJ50GVKE22STyYMg0DVJHOgkZw2G8MnKU/2CsBqO9XHgGDq+O8QpM4AWRqglhTdc+93OnnQVW1z2QbIjOCbdcNrLOBWB7YNecA1gm4ujzcZeMt6uldf63cD4/TiVD7WLe54ofJJXrXHM2mg5Pw0EW7eAxrVcwHkYli8yvLTE9+U9Q08/Hyx5ym1JPy6eVwMumwHBPxh1blR0LzmqsGg/fk8cAC8WLtqIET8v6/z7HREUSCQaJ3X/jzLh4gz1EUIBB3MMAHBQ2I0yLUGFwLI7d5jIfGygNOh9+W8HoeTr2DygYk9petCQmj22Ytjv846f+FiLQYi/m5FgCA509xFetyeN9L+svXYuEYNCiPZ4Av3/iYWzfOCZs5pLh01i89wu/RM74v4gf2eVNLk3yI4r9RK03CJkoPyyARnk4hLUovCd/FMXCDWgLHi7iO+A4jRfcpS+OISLvvh4xFxa0ZUL6UuQcBQuNmwMyiyJcCsP4fJMrCZMV+JG3CWzlLyOrkvL+N8H12f6rf4Crb/kJSPiA7ju9FyLamTQmps+DGcJyH300RQ9LqQMAF2xeIQkeY6sEJjuzSQr4EkB+WZYdLjhABPaHOe93QV14W19+JQWOlsaE1Qm22ONBpnUo8uO236IyvZ+ufZtZS6BM2v28bQoMj659k37EuF9uusaqDonNMm9icBwSVxwPLN711CWm1iJxPCmdePoGU6vh64zOaGxmJF6pZlE/vx91HX/CXrhNSS9QV8V2I2APV6rP8z1onKd4xgucf2WmDkaBsOckGwPKPkjvUEIvK2RF9h2UPaLjXlfIlsfhzT/6QYfcHZSnnKD/VjOixArHGg+bAgsjK1kUrOYA4s6rOJ2W9/gWd+r/IjPRbqXpac22ZckMs4b6YsL06HfcqMxxPtsYpACZx4iaVOMFElUqYkamHIEy3MFmEApOlG4TmIuB4IbeIrtQaYzfmtGoWuxT9XGrbWfwa/BmDNeCI/CldLH8y3/G+PbJfct7cPL8oyTz6eSrYBjBnlh+TCKLeOPrN6W4AiLPqfx/rPiKKX6HrseKABIZRrNOhKkLIjDLMTNR5CUz2oSVnSYgJsYKXOla5taOB8l755MtgWd9gmDZbGfDd1uIsf7DnM1jKLGGSGZBFLz2S2IKjkMENYMP0YAC5Lxh1znwKd+T1AjNrhS6KsIl8caH/HeRIfyt63XmZPH5MDJ4zwJ+PF+zAikXQPRipw2ha0ywZBy0ge5ooXaqCgaDMpSpFCFHvN+NvM2nz1QJ3/uzsVo0sv1Qnw/OtUlVS99P6Q4BiViEQsyAVn6R5xA60aeqLYa8Pfdn0iBZJn6/227PI6yNm3JDVCoMvn3oZcLRpn3Xb0AMvV7DWqSvwdp6cepGYNw+BiXuRydSlitoQhMTwKT2Gg3GjrgGe1gaVhvfh0kjJl2PbEcHOzv+g2fjqm7GX25ZBmMX5FuCeQ9l/6H1eOBli95ZEgG38xLNwlLK/02zEpD7s13ooTUfOvc0wE+Yr10kuyCL+eRbL1QR8KM1G6FcBq/UvKZUA047LRDT8rWSgO4ahtIPH6gaRJ7hbNvD1g6SpRmErduSKYeQWYdQr0AyVmpqvDR7+Wu0rSss53G/x6w1xuStHMLKRGfD7XJqy60r3bstcoyiyvJ17JU5x/e/V19xgfHthNXLLLU16x2H0zTO9bAnlq3/1Id3JcBDmervkl0IQc0isMSBo9aB4M7YB1QIP/8QMTC2BCsvxyr8HHu09vDpUsu0+3p4/FLk+aWPFN3LpChJffisdoNn8HC9PMZYNgdbhb/wpvfaCbcR4/BXNLz+Yvdinw4K7fbc5AmOtFly/OD7+exR7noM9m9jgv7u+TtPqFRTO9edIzlBsaft164oNBFQqzpROq8Cd6gk6ccChovPWnkM5sxqPrtIm5TqXf5wrQ7yqgxg8susqzp/lQIjNX5k1y0ebxoShE4Sh2xloAcpf9gMdK4DF0xYLT1jL5XJn8bfYQCf+uOwqfbCh08qxRKZtpBttzxaQJNXcx2SwM0ESlSLMYWcRVQG0TG0DLoWpOv+UKp8Vd1uUj233KTmoErfYzji2NUgKZXjoL2mC1zKKFjJG12DMMGBENSbhUqy6YB3N3yiuNH67jbzwX85tDUrfrKSUzPt1SGSsJLdXCbedZ1vQh8RR6YjX46DK7mzXy1c/BMqmCShlFC5syfhxA/ILSJYO8WncxfL7NdnGa/IvtOxmjt/Hh4aStaoTe7wnE7h5+nAWBmtiyMHppENTBpgqDNl0nCtNOwgXx5XsBEC7+W537n8ecfzNz/fZU74Z9TtiX31l6vD+lhzoJcd0lQ+N817PHVPV7WU+77rIIq2+FXdsGiL6+zDkkU5cDOGkGiAg3YCZhrtNolm0ySo848V4Sh1KBLa6gpUL7fDFE+Q0VgIyxQjBJlZcLVUmzh8vsia8TiU5NqDK3HxNZSyEp4u9n5K1QUzuxmxlG9b+vM/WVB6UXOYHahc2P8wQ6uSPJk5eCzbMO/HSyLYajP9iXwtEdCVXmdkcia7FJ6fczckeoqZ3YzQyj+t/GSyw9gdqFM15ZGYEkT+4INs868NPJtty8O0vTv7MyOTy0wZXUdZ9Sb/b1nspfmpVrhWfkosyWd+K5BL7qHy4OxRfDzpRAovZq9/M8t+ymYMzXig8x14qwNYi4jt2DUVtXi+5X0Tcstu25PO1ezA6o9d9qGQmn7d1gccfQvPdWXvHcUPWgqmqwvNvmz/bsKsmL8mVcxh/jQj/nq2vdshLIMfXTP5pSNJfX7e6RPcW//bT/mFU9H39MtQY0LyRze/X1mOV1ivL7pL4So7FTyiF+ComFYd3C/pSyu7j4BPCTiyF2MoWF218ndmL7V09xkl6UZbx7bNYagFVBOti8IKlFkCHhlsZ7KILYDaV0Vc0KWtWyg4Yrv9tlJ9CYcjEIGYnCwq09sX/HyscMsp9SDvFTSGwMrUkTdBmsVUCxrLVcJaWIRpSFyhxnaWRkHzFJgQFsKAJHTF9q4XAZl+yBh8hoHIYiiMNQajNRsitPOeRX+xLQRF0hrQcMXBQCQ39ELVH0LubP+JH7p371OMkZ4lhBKlOvSYQ2EaRXJHTeUjHIVKKgWduAGo3CZO+OimzwtuK7+HC6j3lfGTyRTGWSQ6Qky2IRws5dprANVHWtpY9XlQIctioRka2Bn5mRlUN/i1Rj0JdA7feFNgXqAQX7t64EFL8rtE3l2rer0S+zSgBO5xQaG8+yGjYVdD9XawZozCvlIEeZxMJwOLnTeA1FEJuh1MJhiOTTOAxFEIeh1MahDmVjt2XON3eK5jkygBtIBnIGKS1SDIcOGuehCOJWl9q9o7BDqDtFoRD0hUI5gQ/CAW+b7GqaxASou2mKTS6nobDpwPKnpCiq5Wu3f6nro5GAumlU1uVmNa09xOnFqXysl9jN432oFGZyeDFqqmGRjl9SRga7UAbx5cVFRBrwnPYuOyY7jE9baGDUUlA4YUyM7bcUlPZf59npiDFpCw2cWgoLp/a6hsak/R1qvy0iTmPf1xdL+5h5dB4rk5kmsjKlRYrXyX01bcz3FilgMkgKmJIohYGzmRutG5GPgFCGdidpYcop/8hKyJMKZSiTptjC5IZ9qRS+zpJDWeAdBlJBjEFCwvwLWXYNRdici7TA6giRHpOLTZxI/XbDquXAoV4AMtAFK+WwGSUSq3rKk/WQiioJrKZKRVvLYS/+Yqs6jN60vkOeX6au9MwPKev2MpKDtjPWGCUdWS4Xiah9yl8vxTqQFxp6i5fT+BifbMP4GysZ5DLWGycvXUjLvpXyBJHrfgpBIJokzkhBHuWhmqUld+i1toZFOiF1viaKUAbxFYqt6/gM3LFsf4fX7RlhT3RIbQqsoroieAXVlVJE70EGa9AXo4pQUXpX1We8Iz+CS0OlHOwVmcS24YElQNT3PzBKcDsEI3bYabxjT8c0BkcqTGbbeRwoiUcFBgl0EtPRAZlz92HAGWsUpu8Lle1rdmDVQDY5R50EXBpoVLY90SE1or4rOpSB+6JDsYXJ+xxnIpRBTIRimyZDaj9dk6EM1GQoto0PFhcVlD+w5OERmjQr5eCIkEloDF8m1by+gB2DTmJgK1BRdhssPgmgMew9OHkiNakWMBBlAtM+kUJqHRNCUhxgVAil8LgQCOismhxTJnYNhYVlQ2R1sE/HOHmAunQogh1qV2rFLbe16RuiUsCoVYgIu95vWVmy3IJcjBDbEYdobUbWs+/o1tZpQLPrZK7MsekuRkgSgzbFRXNlAHsOCCW8+4AQO4pDkIMmAO1kSE7cAB8LyTTomZBMZlW7HkF7PI5EJYBVlmlsKMyzoqgaT3GuOgmIPI3KamrId6M+m+Krq3+VyCKkKwHXH12hbekRfzUEO0il4LJDJLCxQk5DDAchtBOQbjfeoIhOYtrWjy6OxzRh+7uspU+Y/XCyq4yDTqOgydCS2yWQj9nxwACEzn5yf8OKygXzMGZCYEJ7WonHP2gUpkPPlojQEeqBpeXc1UxOORGNalLyllQfoUI7DqYfC0t58113yCgbm8T9zWigc5UCyLOACQOQGmQCqAkxIf29RX1bRCzFIkN6AsL0EWcllWITRSor/VaTbl+NBIt4kKlcetjWrYS+1BgKAftI6Hskh5ULFaBAeJlavVkgvJkEBLtXApiD2KUWwPh83oQ57P6ZrDLBHGBce3TFG9fNYaDGlVGD4LkeWIC7VNMYx8+boYXlTzJLJIetG40i0xr6Fwilb7rXECKPmQYzSDB0SDyoIBEreVVrJYiRg66jPvhatw9CiSulRHpzNZBgcbkeGCjeVFeKvKvfTWjt6sM3yaC7drr8q1G/D+s29LtG47/H1fUOr4gux9zVNC2Hots2IFTXnFLN8P1QroQ03w/kugdYEzSLXhjWPHdx/sDAEUGp5qnPV2aebiHrhB21UkjkQL5HKwtpGBfUqJX8u9SFzALcEiqMJgErhMQJcB1KasR0v2n0xzaSLzKhn1qZzqAMcLGq0cJwYWosrsar3X9HB9eEqq7ThnCkyB0xUX2PblRVzT7X0kiDTbZmMoF0e80w5QLp/E+7oHtxvLLxvtvonlfu7oksUAjgdTyrBaECbQgsD2Im+yAByYMNlJlNIu/aR922PWv3+s3Td3plw54YeGuw2RUz3gU0tKKcZwCNYXcagxgQn6rRKxu+ToCyiHprMZl44zLq2wXMAxIGMAV0i7QxgfFyqLvq7dGUSWmVxL+6yjEbr4fdIR6vomI6XFGZ0Hsvzah6f7zWn0AanCdObOhx5Qp60+XI9XKwJmQArSyEIXAniBN76cc1mOFFkqaVI+1bNhhBIQ1nAhKGJijdpdcbmsa11mhDDgElbYTUAJYLwt0M0J344W0fwBbmCrhaar4Arg+WC0CuacgD0LRCudfvbpouFYLRHDpRCBOo6Rx4TTRVg7uqXSYDo6o6UQhV1XwLvObw4/TFVZamRjVlghAqiskYmjUTlGxhnGpNPoioaRFWTiSZKKRWQ85l0ddDslSMUFENVBIjhEzTGFpFg3JI8olGRUtCCWNLUNQU2KoxWYZ3U+ITIVpF+wdOU9ik4CpNCMapkSbTjujzMJuwGX0e0xCm17OjaSkTmZK+GKejtIoh0ERJhHPbhHk5JLRxN12f1sZoJ4AqhFG0/Du8qvCrH3V55LhdX4UsmMJS7PugMRzfPlLlSEzGg+gr0lhEllL+DCKDqXz02npFvz3bNmns1oZmsqDL9CYfiWKzphEr0HkUfeZxy/PyEHZVQDr/WypSNipeC042NVZRk29SKEK4pXDqQcmxoiG+Ag/kgCsYIhgmB3JAqbikVoyptTyZprU70S7g8/Du/boKc/TpvnAbqCR+FddSlQ31PG2gDPIbzqR1Iv8n0eFV7RLERdfV+uwxLtj+Q1I+Chx0xW1VvKkj1VVT4PGqaHq78YaQxu3QPm4GuIInRcCa6Ign5Rb0ZJr3Yr+T7SPX8usXljJPn5rQ4hAFkgAOUUyrONQDMya6qwgkQTROhYz0ISZGhqSPvBFKFkc/ZrGOC3slv+hYxjRdfkl8TCgUfpVWM3DedlE0nsJ7xfSZ0dAurqVM6ElwsKb2fTWm+hy7EOoikqxLPpVwS2s+KWmpxeGBlGFcHZCMtR3JphyrEza4CZ2N0vrv79BhE2rO2Eh8egca4Ti5dUWgPxAkrg3wN3+UkQ9ny21HvyUDrgcDmUYGThxicCxuCsOCEaX1v26c2wzGLMiEwG5LRf8GIqV5Flsjph4egSBDlmUkRTQIrhHNGPBjtwvNIIRW5YzVhraRzNO+0Wpm7gphY2szm2qlI0DPQGOMcsGpg7kJPc+O2IJYGsQYhjgVnNqqFKINoMY6TGLIdE69ZUluwrDr7usCJiFLvGhRlzzvQUxs/4hTagcbolszp/wW8CibSk3gBoKeLeamMD1HvFUTj4Dp/NikWCu8iYzTSXJdq/JGrWnzxlWakeezjVC2ZJOa25nFKJR2pZzApsbhPL/jJ0JERzm7NwybQCG6jZ+OKRtSsOGKK5SGqcvoJG9hVe7fJpHiYiGVEUr7RtL4yFntzRVeHX9OZYr6hm+XTuT/axVeVW1bs90rtX+00SrBPtpjd2a9mMXJIGdtiv67cpWkZR1BaNyLd6k+z968TSJz0xqxhxv/Gc/olzzF+bdXX3eP8eGB3cTV9Lx/ZQnYXLRWMrlg+dmn1v3CTzrJe2DiS1TNZhf40NREI/A/yNrL1NtSG3npyXh8Y60T4hTH8tIVb4j6bJU/Mxmcsq2Kf9e8rIn695ysUzaEMuSUTXunqjlZRp+gGqF+9whUZDgR14n8H4VrD1k1NdE3qkZeayB7CwN1CD+Bvjt1219z8Ax8+Xmo6CrPnkz2MJGHMAj8ElbrVIwPW002xV3mYAiBeOtmEN7TirS3tSB/iJObXBr49lfr2IwPemHtYG14NokJFCBdCDzMozb85FUEPMgFhE9R6xrMg70h1tjJ9iyYuS19+4/4wNeYoDv5qazozSEpkzg1BNSaKuBKjgumhR8Eu23i7yyPfE03RhcwrLOy2wWtG0hdY0twJDLtlbaRKYDEJ9Ds+X9galzBicl/oKfemiasr7eNMYfZMQd3yKoj9uOA23WT9mxQVH8XL7NDUeZxUj+GUk3Dev26hFd3WYTVN+w0+edlN/eIrFnQkhN9tklcg9qfYPLYTVKBk9HlmkTVcX1XbT7hkSoq2KR3rYDdi2kthkcs8KxXs8FheK9rmmGHxGNmc/V0ZuHJic0WUFt4iYza+9LjZbqBJrYYHk/A2228DdOjbNMMO2R3M5urpzMLT84et4Da6iNz5C+i9jgd8AHw1fYMX1nk0b7m82B7iM+D2cWkdARLCuRmpZzy3y1rEvEjPGpiZp2EjGt15jkefaIz2cj2g2uFMNihtUfVnz9rGqkNX/Uyy/uy58+aJy/bH6o/qwVrtd57l+1ZWvBfnz+7OVW1n1jz10tWJA9DE8+rNg+MP847NNrRvDncZ92qT5GoI+mKu0c3WBnv4zK+yMukfpqgKt5Vs07+mCvfn6mfL/zI9m8Of57K46msVGZPH1NpTfn8mZn/82eazM+b+waFDxUqMZNKBfbn4cUpSfe93FdxWijraKyJy8r6r1n1e9OX1dAs2cO3vqU/sgOxodZ8L9mRHfbVkLtjT8f6UfPiz8Nt/JmNke19wd6yh3j37bpOks63m7BG7B0hm/35yyR+yOOnom1jqF/9WWF4//T1f/4NTVqEL1VqCAA= + + + 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 9bde1ba879..b39ffa09d7 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -332,9 +332,9 @@ 201507250039446_Merge2.cs - - - 201507301550466_ExportFramework.cs + + + 201508011828241_ExportFramework.cs @@ -633,8 +633,8 @@ 201507250039446_Merge2.cs - - 201507301550466_ExportFramework.cs + + 201508011828241_ExportFramework.cs diff --git a/src/Libraries/SmartStore.Services/Export/ExportExtensions.cs b/src/Libraries/SmartStore.Services/Export/ExportExtensions.cs new file mode 100644 index 0000000000..3ea12ec406 --- /dev/null +++ b/src/Libraries/SmartStore.Services/Export/ExportExtensions.cs @@ -0,0 +1,16 @@ +using System.Linq; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.Export +{ + public static class ExportExtensions + { + public static bool IsValid(this Provider provider) + { + return ( + provider != null && + provider.Value.SupportedFileTypes != null && provider.Value.SupportedFileTypes.Any() + ); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Export/ExportService.cs b/src/Libraries/SmartStore.Services/Export/ExportService.cs new file mode 100644 index 0000000000..14be36fc7e --- /dev/null +++ b/src/Libraries/SmartStore.Services/Export/ExportService.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Core.Data; +using SmartStore.Core.Domain; +using SmartStore.Core.Domain.Tasks; +using SmartStore.Core.Events; +using SmartStore.Core.Plugins; +using SmartStore.Services.Tasks; + +namespace SmartStore.Services.Export +{ + public partial class ExportService : IExportService + { + private const int _defaultSchedulingHours = 6; + + private readonly IRepository _exportProfileRepository; + private readonly IEventPublisher _eventPublisher; + private readonly IScheduleTaskService _scheduleTaskService; + private readonly IProviderManager _providerManager; + + public ExportService( + IRepository exportProfileRepository, + IEventPublisher eventPublisher, + IScheduleTaskService scheduleTaskService, + IProviderManager providerManager) + { + _exportProfileRepository = exportProfileRepository; + _eventPublisher = eventPublisher; + _scheduleTaskService = scheduleTaskService; + _providerManager = providerManager; + } + + #region Export profiles + + public virtual ExportProfile InsertExportProfile(Provider provider) + { + if (provider == null) + throw new ArgumentNullException("provider"); + + var guid = Guid.NewGuid(); + var name = provider.Metadata.FriendlyName; + var systemName = provider.Metadata.SystemName; + var taskType = "SmartStore.Services.ExportImport.{0}Task.{1}, SmartStore.Services".FormatInvariant(systemName, guid.ToString().Replace("-", "")); + + if (name.IsEmpty()) + name = systemName; + + var task = new ScheduleTask + { + Name = string.Concat(name, " Export Task"), + Seconds = _defaultSchedulingHours * 3600, + Type = taskType, + Enabled = false, + StopOnError = false, + IsHidden = true + }; + + _scheduleTaskService.InsertTask(task); + + var profile = new ExportProfile + { + Name = name, + Enabled = true, + ProviderSystemName = systemName, + ProfileGuid = guid, + SchedulingTaskId = task.Id, + FileType = provider.Value.SupportedFileTypes.FirstOrDefault() + }; + + _exportProfileRepository.Insert(profile); + + task.Alias = profile.Id.ToString(); + _scheduleTaskService.UpdateTask(task); + + _eventPublisher.EntityInserted(profile); + + return profile; + } + + public virtual void UpdateExportProfile(ExportProfile profile) + { + if (profile == null) + throw new ArgumentNullException("profile"); + + _exportProfileRepository.Update(profile); + + _eventPublisher.EntityUpdated(profile); + } + + public virtual void DeleteExportProfile(ExportProfile profile) + { + if (profile == null) + throw new ArgumentNullException("profile"); + + int scheduleTaskId = profile.SchedulingTaskId; + + _exportProfileRepository.Delete(profile); + + var scheduleTask = _scheduleTaskService.GetTaskById(scheduleTaskId); + _scheduleTaskService.DeleteTask(scheduleTask); + + _eventPublisher.EntityDeleted(profile); + } + + public virtual IQueryable GetExportProfiles(bool? enabled = null) + { + var query = + from x in _exportProfileRepository.Table.Expand(x => x.ScheduleTask) + select x; + + if (enabled.HasValue) + { + query = query.Where(x => x.Enabled == enabled.Value); + } + + return query.OrderBy(x => x.Name); + } + + public virtual ExportProfile GetExportProfileById(int id) + { + if (id == 0) + return null; + + return _exportProfileRepository.GetById(id); + } + + + public virtual IEnumerable> LoadAllExportProviders(int storeId = 0) + { + var allProviders = _providerManager.GetAllProviders(storeId) + .Where(x => x.IsValid()); + + return allProviders; + } + + public virtual Provider LoadProvider(string systemName, int storeId = 0) + { + var provider = _providerManager.GetProvider(systemName, storeId); + + return (provider.IsValid() ? provider : null); + } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Services/Export/IExportProvider.cs b/src/Libraries/SmartStore.Services/Export/IExportProvider.cs new file mode 100644 index 0000000000..752f6096ed --- /dev/null +++ b/src/Libraries/SmartStore.Services/Export/IExportProvider.cs @@ -0,0 +1,16 @@ +using System.IO; +using SmartStore.Core.Domain; +using SmartStore.Core.Domain.Export; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.Export +{ + public partial interface IExportProvider : IProvider, IUserEditable + { + MemoryStream Execute(ExportProfile profile); + + ExportFileType[] SupportedFileTypes { get; } + + ExportEntityType EntityType { get; } + } +} diff --git a/src/Libraries/SmartStore.Services/Export/IExportService.cs b/src/Libraries/SmartStore.Services/Export/IExportService.cs new file mode 100644 index 0000000000..3e8e2e60df --- /dev/null +++ b/src/Libraries/SmartStore.Services/Export/IExportService.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Linq; +using SmartStore.Core.Domain; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.Export +{ + public interface IExportService + { + /// + /// Inserts an export profile + /// + /// Export provider + /// New export profile + ExportProfile InsertExportProfile(Provider provider); + + /// + /// Updates an export profile + /// + /// Export profile + void UpdateExportProfile(ExportProfile profile); + + /// + /// Deletes an export profile + /// + /// Export profile + void DeleteExportProfile(ExportProfile profile); + + /// + /// Get queryable export profiles + /// + /// Whether to filter enabled or disabled profiles + /// Export profiles + IQueryable GetExportProfiles(bool? enabled = null); + + /// + /// Gets an export profile by identifier + /// + /// Export profile identifier + /// Export profile + ExportProfile GetExportProfileById(int id); + + /// + /// Load all export providers + /// + /// Store identifier + /// Export providers + IEnumerable> LoadAllExportProviders(int storeId = 0); + + /// + /// Load export provider by system name + /// + /// Provider system name + /// Store identifier + /// Export provider + Provider LoadProvider(string systemName, int storeId = 0); + } +} diff --git a/src/Libraries/SmartStore.Services/ExportImport/ExportService.cs b/src/Libraries/SmartStore.Services/ExportImport/ExportService.cs deleted file mode 100644 index c19a63129a..0000000000 --- a/src/Libraries/SmartStore.Services/ExportImport/ExportService.cs +++ /dev/null @@ -1,79 +0,0 @@ -using System; -using System.Linq; -using SmartStore.Core.Data; -using SmartStore.Core.Domain; -using SmartStore.Core.Events; -using SmartStore.Services.Tasks; - -namespace SmartStore.Services.ExportImport -{ - public partial class ExportService : IExportService - { - private readonly IRepository _exportProfileRepository; - private readonly IEventPublisher _eventPublisher; - private readonly Lazy _scheduleTaskService; - - public ExportService( - IRepository exportProfileRepository, - IEventPublisher eventPublisher, - Lazy scheduleTaskService) - { - _exportProfileRepository = exportProfileRepository; - _eventPublisher = eventPublisher; - _scheduleTaskService = scheduleTaskService; - } - - #region Export profiles - - public virtual void InsertExportProfile(ExportProfile profile) - { - if (profile == null) - throw new ArgumentNullException("profile"); - - _exportProfileRepository.Insert(profile); - - _eventPublisher.EntityInserted(profile); - } - - public virtual void UpdateExportProfile(ExportProfile profile) - { - if (profile == null) - throw new ArgumentNullException("profile"); - - _exportProfileRepository.Update(profile); - - _eventPublisher.EntityUpdated(profile); - } - - public virtual void DeleteExportProfile(ExportProfile profile) - { - if (profile == null) - throw new ArgumentNullException("profile"); - - if (profile.ScheduleTask != null) - { - _scheduleTaskService.Value.DeleteTask(profile.ScheduleTask); - } - - _exportProfileRepository.Delete(profile); - - _eventPublisher.EntityDeleted(profile); - } - - public virtual IQueryable GetExportProfiles(bool? enabled = null) - { - var query = - from x in _exportProfileRepository.Table.Expand(x => x.ScheduleTask) - select x; - - if (enabled.HasValue) - { - query = query.Where(x => x.Enabled == enabled.Value); - } - - return query.OrderBy(x => x.Name); - } - - #endregion - } -} diff --git a/src/Libraries/SmartStore.Services/ExportImport/IExportService.cs b/src/Libraries/SmartStore.Services/ExportImport/IExportService.cs deleted file mode 100644 index 0e8389011b..0000000000 --- a/src/Libraries/SmartStore.Services/ExportImport/IExportService.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Linq; -using SmartStore.Core.Domain; - -namespace SmartStore.Services.ExportImport -{ - public interface IExportService - { - /// - /// Inserts an export profile - /// - /// Export profile - void InsertExportProfile(ExportProfile profile); - - /// - /// Updates an export profile - /// - /// Export profile - void UpdateExportProfile(ExportProfile profile); - - /// - /// Deletes an export profile - /// - /// Export profile - void DeleteExportProfile(ExportProfile profile); - - /// - /// Get queryable export profiles - /// - /// Whether to filter enabled or disabled profiles - /// - IQueryable GetExportProfiles(bool? enabled = null); - } -} diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 33484e0895..db5a654dd3 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -162,8 +162,10 @@ - - + + + + diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index 29357d57db..e361097cc9 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -41,6 +41,7 @@ using SmartStore.Services.Directory; using SmartStore.Services.Discounts; using SmartStore.Services.Events; +using SmartStore.Services.Export; using SmartStore.Services.ExportImport; using SmartStore.Services.Filter; using SmartStore.Services.Forums; @@ -794,6 +795,7 @@ protected override void Load(ContainerBuilder builder) RegisterAsSpecificProvider(type, systemName, registration); RegisterAsSpecificProvider(type, systemName, registration); RegisterAsSpecificProvider(type, systemName, registration); + RegisterAsSpecificProvider(type, systemName, registration); } } @@ -919,6 +921,10 @@ private string ProviderTypeToKnownGroupName(Type implType) { return "CMS"; } + else if (typeof(IExportProvider).IsAssignableFrom(implType)) + { + return "Exporting"; + } return null; } diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index 75d3bfcdb8..dcb332fcb7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -738,7 +738,6 @@ td.adminTitle > .ctl-label { label { display: inline-block; padding-right: 20px; - margin-top: 1px; margin-bottom: 0; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 839265dfef..cd8bf2190a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -1,12 +1,18 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Export; using SmartStore.Core; using SmartStore.Core.Domain; +using SmartStore.Core.Domain.Export; +using SmartStore.Core.Plugins; using SmartStore.Services; -using SmartStore.Services.ExportImport; +using SmartStore.Services.Export; +using SmartStore.Services.Media; using SmartStore.Services.Security; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Plugins; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -16,13 +22,62 @@ public class ExportController : AdminControllerBase { private readonly ICommonServices _services; private readonly IExportService _exportService; + private readonly PluginMediator _pluginMediator; + private readonly IPictureService _pictureService; public ExportController( ICommonServices services, - IExportService exportService) + IExportService exportService, + PluginMediator pluginMediator, + IPictureService pictureService) { _services = services; _exportService = exportService; + _pluginMediator = pluginMediator; + _pictureService = pictureService; + } + + private void PrepareExportProfileModel(ExportProfileModel model, ExportProfile profile, Provider provider) + { + var partition = XmlHelper.Deserialize(profile.Partitioning); + + model.AvailableFileTypes = new List(); + + model.Id = profile.Id; + model.Name = profile.Name; + model.ProviderSystemName = profile.ProviderSystemName; + model.Enabled = profile.Enabled; + model.FileType = profile.FileType; + model.FileTypeName = profile.FileType.ToString().ToUpper(); + model.SchedulingHours = profile.ScheduleTask.Seconds / 3600; + + model.Offset = partition.Offset; + model.Limit = partition.Limit; + model.BatchSize = partition.BatchSize; + model.PerStore = partition.PerStore; + + if (provider != null) + { + model.ThumbnailUrl = _pluginMediator.GetIconUrl(provider.Metadata); + + if (model.ThumbnailUrl.IsEmpty()) + model.ThumbnailUrl = _pictureService.GetDefaultPictureUrl(48); + else + model.ThumbnailUrl = Url.Content(model.ThumbnailUrl); + + model.ProviderFriendlyName = _pluginMediator.GetLocalizedFriendlyName(provider.Metadata); + + model.EntityType = T("Enums.SmartStore.Core.Domain.ExportEntityType." + provider.Value.EntityType.ToString()); + + foreach (var fileType in provider.Value.SupportedFileTypes) + { + model.AvailableFileTypes.Add(new SelectListItem + { + Text = T("Enums.SmartStore.Core.Domain.ExportFileType." + fileType.ToString()), + Value = ((int)fileType).ToString() + }); + } + } } public ActionResult Index() @@ -45,6 +100,7 @@ public ActionResult List(GridCommand command) if (_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) { + var providers = _exportService.LoadAllExportProviders().ToList(); var query = _exportService.GetExportProfiles(); var profiles = query.ToList(); @@ -52,16 +108,8 @@ public ActionResult List(GridCommand command) model.Data = profiles.Select(x => { - var profileModel = new ExportProfileModel - { - Name = x.Name, - ProviderSystemName = x.ProviderSystemName, - EntityType = x.EntityType, - Enabled = x.Enabled, - FileType = "", // TODO - SchedulingHours = x.SchedulingHours, - LastExecution = "" // TODO - }; + var profileModel = new ExportProfileModel(); + PrepareExportProfileModel(profileModel, x, providers.FirstOrDefault(y => y.Metadata.SystemName == x.ProviderSystemName)); return profileModel; }); @@ -69,5 +117,136 @@ public ActionResult List(GridCommand command) return new JsonResult { Data = model }; } + + public ActionResult Create() + { + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + { + var model = new ExportProfileModel(); + + model.AvailableExportProviders = _exportService.LoadAllExportProviders() + .Select(x => + { + var item = new SelectListItem + { + Text = x.Metadata.FriendlyName, + Value = x.Metadata.SystemName + }; + + return item; + }).ToList(); + + return PartialView(model); + } + + return Content(T("Admin.AccessDenied.Description")); + } + + [HttpPost] + public ActionResult Create(ExportProfileModel model) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + if (model.ProviderSystemName.HasValue()) + { + var provider = _exportService.LoadProvider(model.ProviderSystemName); + if (provider != null) + { + var profile = _exportService.InsertExportProfile(provider); + + PrepareExportProfileModel(model, profile, provider); + + return RedirectToAction("Edit", new { id = profile.Id }); + } + } + + NotifyError(T("Admin.Configuration.Export.ProviderSystemName.Validate", model.ProviderSystemName.NaIfEmpty())); + + return RedirectToAction("List"); + } + + public ActionResult Edit(int id) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var profile = _exportService.GetExportProfileById(id); + if (profile == null) + return RedirectToAction("List"); + + var provider = _exportService.LoadProvider(profile.ProviderSystemName); + + var model = new ExportProfileModel(); + + PrepareExportProfileModel(model, profile, provider); + + return View(model); + } + + [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [FormValueRequired("save", "save-continue")] + public ActionResult Edit(ExportProfileModel model, bool continueEditing) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var profile = _exportService.GetExportProfileById(model.Id); + if (profile == null) + return RedirectToAction("List"); + + if (ModelState.IsValid) + { + var partition = new ExportPartition + { + Offset = model.Offset, + Limit = model.Limit, + BatchSize = model.BatchSize, + PerStore = model.PerStore + }; + + profile.Name = model.Name; + profile.Enabled = model.Enabled; + profile.ScheduleTask.Seconds = model.SchedulingHours * 3600; + profile.FileType = model.FileType; + profile.Partitioning = XmlHelper.Serialize(partition); + + _exportService.UpdateExportProfile(profile); + + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); + + return (continueEditing ? RedirectToAction("Edit", new { id = profile.Id }) : RedirectToAction("List")); + } + + PrepareExportProfileModel(model, profile, _exportService.LoadProvider(model.ProviderSystemName)); + + return View(model); + } + + [HttpPost, ActionName("Delete")] + public ActionResult DeleteConfirmed(int id) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var profile = _exportService.GetExportProfileById(id); + if (profile == null) + return RedirectToAction("List"); + + try + { + _exportService.DeleteExportProfile(profile); + + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + + return RedirectToAction("List"); + } + catch (Exception exc) + { + NotifyError(exc); + } + + return RedirectToAction("Edit", new { id = profile.Id }); + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Export/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Export/ExportProfileModel.cs index c95bd05837..547211434c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Export/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Export/ExportProfileModel.cs @@ -1,4 +1,6 @@ -using System.Web.Mvc; +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Core.Domain.Export; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Mvc; @@ -6,20 +8,42 @@ namespace SmartStore.Admin.Models.Export { public partial class ExportProfileModel : EntityModelBase { - [SmartResourceDisplayName("Admin.Configuration.Export.Name")] - public string Name { get; set; } + [SmartResourceDisplayName("Common.Image")] + public string ThumbnailUrl { get; set; } + public string ThumbnailClasses + { + get + { + return "grid-icon"; + } + } [SmartResourceDisplayName("Admin.Configuration.Export.ProviderSystemName")] public string ProviderSystemName { get; set; } + public List AvailableExportProviders { get; set; } + + [SmartResourceDisplayName("Common.Provider")] + public string ProviderFriendlyName { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.EntityType")] public string EntityType { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.SupportedFileTypes")] + public string SupportedFileTypes { get; set; } + + + [SmartResourceDisplayName("Admin.Configuration.Export.Name")] + public string Name { get; set; } + [SmartResourceDisplayName("Common.Enabled")] public bool Enabled { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.FileType")] - public string FileType { get; set; } + public ExportFileType FileType { get; set; } + public List AvailableFileTypes { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.FileType")] + public string FileTypeName { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.SchedulingHours")] public int SchedulingHours { get; set; } @@ -27,5 +51,18 @@ public partial class ExportProfileModel : EntityModelBase [SmartResourceDisplayName("Admin.Configuration.Export.LastExecution")] [AllowHtml] public string LastExecution { get; set; } + + + [SmartResourceDisplayName("Admin.Configuration.Export.Segmentation.Offset")] + public int Offset { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Segmentation.Limit")] + public int Limit { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Segmentation.BatchSize")] + public int BatchSize { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Segmentation.PerStore")] + public bool PerStore { get; set; } } } \ 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 0f7013c4e4..ef978b7e3a 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -501,6 +501,8 @@ + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml new file mode 100644 index 0000000000..b6f318d5b4 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml @@ -0,0 +1,27 @@ +@using SmartStore.Admin.Models.Export +@model ExportProfileModel +@{ + Layout = null; +} + +
+ @if(Model.AvailableExportProviders.Count > 0) + { +
+ + + + + +
+ @Html.SmartLabelFor(model => model.ProviderSystemName) + + @Html.DropDownListFor(model => model.ProviderSystemName, Model.AvailableExportProviders) +
+
+ } + else + { + @T("Admin.Configuration.Export.NoProfiles") + } +
\ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml new file mode 100644 index 0000000000..1a594c0333 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -0,0 +1,135 @@ +@model ExportProfileModel +@using SmartStore.Core.Domain.Export; +@using SmartStore.Admin.Models.Export; +@{ + var title = string.Concat(T("Admin.Common.Edit"), " ", T("Common.Profile")); + ViewBag.Title = title; +} + +@using (Html.BeginForm()) +{ +
+
+ + @title - @Model.Name @Html.ActionLink("(" + T("Admin.Common.BackToList") + ")", "List") +
+
+ + + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ @Html.SmartLabelFor(model => model.ProviderFriendlyName) + + @Model.ProviderFriendlyName.NaIfEmpty() (@(Model.ProviderSystemName.NaIfEmpty())) +
+ @Html.SmartLabelFor(model => model.Name) + + @Html.EditorFor(model => model.Name) + @Html.ValidationMessageFor(model => model.Name) +
+ @Html.SmartLabelFor(model => model.Enabled) + + @Html.EditorFor(model => model.Enabled) + @Html.ValidationMessageFor(model => model.Enabled) +
+ @Html.SmartLabelFor(model => model.EntityType) + + @Html.DisplayFor(model => model.EntityType) +
+ @Html.SmartLabelFor(model => model.FileType) + + @Html.DropDownListFor(model => model.FileType, Model.AvailableFileTypes) + @Html.ValidationMessageFor(model => model.FileType) +
+ @Html.SmartLabelFor(model => model.SchedulingHours) + + @Html.EditorFor(model => model.SchedulingHours) + @Html.ValidationMessageFor(model => model.SchedulingHours) +
+
+
+ @T("Common.Partition") +
+
+
+ @Html.SmartLabelFor(model => model.Offset) + + @Html.EditorFor(model => model.Offset) + @Html.ValidationMessageFor(model => model.Offset) +
+ @Html.SmartLabelFor(model => model.Limit) + + @Html.EditorFor(model => model.Limit) + @Html.ValidationMessageFor(model => model.Limit) +
+ @Html.SmartLabelFor(model => model.BatchSize) + + @Html.EditorFor(model => model.BatchSize) + @Html.ValidationMessageFor(model => model.BatchSize) +
+ @Html.SmartLabelFor(model => model.PerStore) + + @Html.EditorFor(model => model.PerStore) + @Html.ValidationMessageFor(model => model.PerStore) +
+
+} + +@Html.DeleteConfirmation("export-profile-delete") + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 17122852e5..e559017485 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -1,16 +1,17 @@ -@using SmartStore.Admin.Models.Export -@using Telerik.Web.Mvc.UI +@using SmartStore.Core.Domain.Export; +@using SmartStore.Admin.Models.Export; +@using Telerik.Web.Mvc.UI; @{ ViewBag.Title = T("Common.Export").Text; } @@ -34,26 +35,118 @@ }) .Columns(columns => { + columns.Bound(x => x.ThumbnailUrl) + .Width(70) + .Centered() + .Template( + @ + @item.Id + + ) + .ClientTemplate("<#= Id #>"); columns.Bound(x => x.Name) .Width(400) .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) .ClientTemplate("\"><#= Name #>"); - columns.Bound(x => x.ProviderSystemName); - columns.Bound(x => x.EntityType); + columns.Bound(x => x.ProviderSystemName) + .Template(x => "{0} ({1})".FormatInvariant(x.ProviderFriendlyName, x.ProviderSystemName)) + .ClientTemplate("<#= ProviderFriendlyName #> (<#= ProviderSystemName #>)"); columns.Bound(x => x.Enabled) .Width(100) .Template(item => @Html.SymbolForBool(item.Enabled)) .ClientTemplate(@Html.SymbolForBool("Enabled")) .Centered(); - columns.Bound(x => x.FileType); - columns.Bound(x => x.SchedulingHours); + columns.Bound(x => x.EntityType); + columns.Bound(x => x.FileTypeName) + .Centered(); + columns.Bound(x => x.SchedulingHours) + .Centered(); columns.Bound(x => x.LastExecution) .ReadOnly(); }) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Export")) - .ClientEvents(events => events.OnRowDataBound("onRowDataBound_ExportProfiles")) - .EnableCustomBinding(true)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Export")) + .ClientEvents(events => events.OnRowDataBound("onRowDataBound_ExportProfiles")) + .EnableCustomBinding(true)) + + + + diff --git a/src/Presentation/SmartStore.Web/Administration/sitemap.config b/src/Presentation/SmartStore.Web/Administration/sitemap.config index 0ba3a9156e..6a43d810d8 100644 --- a/src/Presentation/SmartStore.Web/Administration/sitemap.config +++ b/src/Presentation/SmartStore.Web/Administration/sitemap.config @@ -127,7 +127,7 @@ - + From c01cce0526f11e417a62552df9f8e3e55256bdc2 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 4 Aug 2015 10:45:30 +0200 Subject: [PATCH 003/874] Some changes to new export framework --- .../Domain/DataExchange/ExportCore.cs | 14 + .../Domain/DataExchange/ExportFilter.cs | 121 +++++++ .../{Export => DataExchange}/ExportProfile.cs | 53 ++- .../Domain/Export/ExportCore.cs | 54 ---- .../SmartStore.Core/SmartStore.Core.csproj | 5 +- .../ExportProfileMap.cs | 7 +- .../201508011828241_ExportFramework.resx | 126 -------- ...1508040836122_ExportFramework.Designer.cs} | 2 +- ....cs => 201508040836122_ExportFramework.cs} | 150 ++++++++- .../201508040836122_ExportFramework.resx | 126 ++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 12 +- .../DataExchange/ExportExtensions.cs | 15 + .../{Export => DataExchange}/ExportService.cs | 13 +- .../IExportProvider.cs | 9 +- .../IExportService.cs | 2 +- .../Export/ExportExtensions.cs | 16 - .../SmartStore.Services.csproj | 8 +- .../DependencyRegistrar.cs | 2 +- .../Controllers/ExportController.cs | 203 ++++++++---- .../Models/DataExchange/ExportProfileModel.cs | 158 +++++++++ .../Models/Export/ExportProfileModel.cs | 68 ---- .../Administration/SmartStore.Admin.csproj | 2 +- .../Administration/Views/Export/Create.cshtml | 8 +- .../Administration/Views/Export/Edit.cshtml | 306 +++++++++++++++--- .../Administration/Views/Export/List.cshtml | 22 +- .../Administration/Views/Plugin/List.cshtml | 2 +- 26 files changed, 1042 insertions(+), 462 deletions(-) create mode 100644 src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs create mode 100644 src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs rename src/Libraries/SmartStore.Core/Domain/{Export => DataExchange}/ExportProfile.cs (62%) delete mode 100644 src/Libraries/SmartStore.Core/Domain/Export/ExportCore.cs rename src/Libraries/SmartStore.Data/Mapping/{Export => DataExchange}/ExportProfileMap.cs (75%) delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.resx rename src/Libraries/SmartStore.Data/Migrations/{201508011828241_ExportFramework.Designer.cs => 201508040836122_ExportFramework.Designer.cs} (92%) rename src/Libraries/SmartStore.Data/Migrations/{201508011828241_ExportFramework.cs => 201508040836122_ExportFramework.cs} (53%) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.resx create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs rename src/Libraries/SmartStore.Services/{Export => DataExchange}/ExportService.cs (90%) rename src/Libraries/SmartStore.Services/{Export => DataExchange}/IExportProvider.cs (55%) rename src/Libraries/SmartStore.Services/{Export => DataExchange}/IExportService.cs (97%) delete mode 100644 src/Libraries/SmartStore.Services/Export/ExportExtensions.cs create mode 100644 src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs delete mode 100644 src/Presentation/SmartStore.Web/Administration/Models/Export/ExportProfileModel.cs diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs new file mode 100644 index 0000000000..2f513eab63 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs @@ -0,0 +1,14 @@ +using System; + +namespace SmartStore.Core.Domain.DataExchange +{ + public enum ExportEntityType + { + Product = 0, + Category, + Manufacturer, + Customer, + Order, + NewsletterSubscriber + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs new file mode 100644 index 0000000000..a192083317 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Domain.Shipping; + +namespace SmartStore.Core.Domain.DataExchange +{ + [Serializable] + public class ExportFilter + { + /// + /// Store identifier; 0 to load all records + /// + public int StoreId { get; set; } + + /// + /// Entity created from + /// + public DateTime? CreatedFrom { get; set; } + + /// + /// Entity created to + /// + public DateTime? CreatedTo { get; set; } + + #region Product + + /// + /// Minimum price + /// + public decimal? PriceMinimum { get; set; } + + /// + /// Maximum price + /// + public decimal? PriceMaximum { get; set; } + + /// + /// Minimum product availability + /// + public int? AvailabilityMinimum { get; set; } + + /// + /// Maximum product availability + /// + public int? AvailabilityMaximum { get; set; } + + /// + /// A value indicating whether to load only published or non published products + /// + public bool? IsPublished { get; set; } + + /// + /// Category identifiers + /// + public int[] CategoryIds { get; set; } + + /// + /// A value indicating whether to load products without any catgory mapping + /// + public bool? WithoutCategories { get; set; } + + /// + /// Manufacturer identifiers + /// + public int[] ManufacturerIds { get; set; } + + /// + /// A value indicating whether to load products without any manufacturer mapping + /// + public bool? WithoutManufacturers { get; set; } + + /// + /// Identifiers of product tags + /// + public int? ProductTagIds { get; set; } + + /// + /// A value indicating whether loaded products are marked as featured + /// + public bool? IncludeFeaturedProducts { get; set; } + + /// + /// A value indicating whether to load only featured products + /// + public bool? OnlyFeaturedProducts { get; set; } + + /// + /// Filter by product type + /// + public ProductType? ProductType { get; set; } + + #endregion + + #region Order + + /// + /// Filter by order status + /// + public OrderStatus? OrderStatus { get; set; } + + /// + /// Filter by payment status + /// + public PaymentStatus? PaymentStatus { get; set; } + + /// + /// Filter by shipping status + /// + public ShippingStatus? ShippingStatus { get; set; } + + /// + /// Identifiers of customer roles + /// + public int[] CustomerRoleIds { get; set; } + + #endregion + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Export/ExportProfile.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs similarity index 62% rename from src/Libraries/SmartStore.Core/Domain/Export/ExportProfile.cs rename to src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs index ed71e475a0..fdd4eb5e34 100644 --- a/src/Libraries/SmartStore.Core/Domain/Export/ExportProfile.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs @@ -1,16 +1,27 @@ using System; -using SmartStore.Core.Domain.Export; +using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Tasks; namespace SmartStore.Core.Domain { public class ExportProfile : BaseEntity { + public ExportProfile() + { + Limit = 100; + BatchSize = 1000; + } + /// /// The name of the profile /// public string Name { get; set; } + /// + /// The name of the folder (file system) + /// + public string FolderName { get; set; } + /// /// The system name of the export provider /// @@ -21,26 +32,6 @@ public class ExportProfile : BaseEntity ///
public bool Enabled { get; set; } - /// - /// The export file type identifier - /// - public int FileTypeId { get; set; } - - /// - /// The export file type - /// - public ExportFileType FileType - { - get - { - return (ExportFileType)FileTypeId; - } - set - { - FileTypeId = (int)value; - } - } - /// /// The scheduling task identifier /// @@ -52,29 +43,29 @@ public ExportFileType FileType public Guid ProfileGuid { get; set; } /// - /// XML with partioning information + /// XML with filtering information /// - public string Partitioning { get; set; } + public string Filtering { get; set; } /// - /// XML with filtering information + /// The number of records to be skipped /// - public string Filtering { get; set; } + public int Offset { get; set; } /// - /// Last start time the profile has been executed + /// How many records to be loaded per database round-trip /// - public DateTime? LastExecutionStartUtc { get; set; } + public int Limit { get; set; } /// - /// /// Last end time the profile has been executed + /// The maximum number of records of one processed batch /// - public DateTime? LastExecutionEndUtc { get; set; } + public int BatchSize { get; set; } /// - /// Message of the last execution + /// Whether to start a separate run-through for each store /// - public string LastExecutionMessage { get; set; } + public bool PerStore { get; set; } /// /// The scheduling task diff --git a/src/Libraries/SmartStore.Core/Domain/Export/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/Export/ExportCore.cs deleted file mode 100644 index b5cca2b555..0000000000 --- a/src/Libraries/SmartStore.Core/Domain/Export/ExportCore.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; - -namespace SmartStore.Core.Domain.Export -{ - public enum ExportFileType - { - Xml = 0, - Xls, - Csv, - Txt, - Pdf - } - - - public enum ExportEntityType - { - Product = 0, - Category, - Manufacturer, - Customer, - Order - } - - - [Serializable] - public class ExportPartition - { - public ExportPartition() - { - Limit = 100; - BatchSize = 1000; - } - - /// - /// The number of records to be skipped - /// - public int Offset { get; set; } - - /// - /// How many records to be loaded per database round-trip - /// - public int Limit { get; set; } - - /// - /// The maximum number of records of one processed batch - /// - public int BatchSize { get; set; } - - /// - /// Whether to start a separate run-through for each store - /// - public bool PerStore { get; set; } - } -} diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index 21718e5f5a..8e4537dd4c 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -159,8 +159,9 @@ - - + + + diff --git a/src/Libraries/SmartStore.Data/Mapping/Export/ExportProfileMap.cs b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs similarity index 75% rename from src/Libraries/SmartStore.Data/Mapping/Export/ExportProfileMap.cs rename to src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs index a6e66ad885..22d4cc1c4f 100644 --- a/src/Libraries/SmartStore.Data/Mapping/Export/ExportProfileMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs @@ -1,7 +1,7 @@ using System.Data.Entity.ModelConfiguration; using SmartStore.Core.Domain; -namespace SmartStore.Data.Mapping.Export +namespace SmartStore.Data.Mapping.DataExchange { public partial class ExportProfileMap : EntityTypeConfiguration { @@ -11,12 +11,9 @@ public ExportProfileMap() this.HasKey(x => x.Id); this.Property(x => x.Name).IsRequired().HasMaxLength(100); + this.Property(x => x.FolderName).IsRequired().HasMaxLength(100); this.Property(x => x.ProviderSystemName).IsRequired().HasMaxLength(4000); - this.Property(x => x.Partitioning).IsMaxLength(); this.Property(x => x.Filtering).IsMaxLength(); - this.Property(x => x.LastExecutionMessage).HasMaxLength(4000); - - this.Ignore(x => x.FileType); this.HasRequired(x => x.ScheduleTask) .WithMany() diff --git a/src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.resx b/src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.resx deleted file mode 100644 index ce78c71be0..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.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 - - - H4sIAAAAAAAEAOy923IcObIg+L5m+w8yPc2s9UglVZdZn7KqHSMpUko7EsVmUtLpfqEFI8HMaEZGZMWFlx6bL9uH/aT9hQUQl8T9HhGZWfkiJQMOB+BwONwdDsf/9//8v7/9z+d1+uoRFGWSZ7+/fvfmp9evQBbniyRb/v66ru7/x99e/8//+//8P347X6yfX33v4H5GcLBmVv7+elVVm1/fvi3jFVhH5Zt1Ehd5md9Xb+J8/TZa5G/f//TTf7x99+4tgCheQ1yvXv12XWdVsgb4D/jnWZ7FYFPVUfolX4C0bL/DkjnG+uoyWoNyE8Xg99fzdVRU8yovwJsPURW9fnWSJhHsxhyk969fRVmWV1EFO/nrtxLMqyLPlvMN/BClNy8bAOHuo7QEbed/3YKbjuOn92gcb7cVO1RxXVb52hLhu59bwrxlqzuR93VPOEi6c0ji6gWNGpPv99d/ryP86VuWVK9fsQ3+epYWCJii8BkmMwRLsjcfkgLE8OvLGxLRX14x4H/pueT9m/dvfnrz019endVpVRfg9wzUVRGlf3l1Vd+lSfyf4OUmfwDZ71mdpmTXYedhGfUBfroq8g0oqpdrcN8OaLZ4/eotXe8tW7GvRtRphjnLqp/fv351CRuP7lLQcwZBEjyqjyADRVSBxVVUVaDIEA6Ax8+1zrSF/u1ag6wIl9TrV1+i588gW1ar31//AtfQRfIMFt2HtgOQrHABwjpVUQNBB9WNfgBlXCSbhod829Y0lZSbNHr5nMdRGmCgRo19LRag0E2hGtOs/ADuI8iTHZrTPE9BlGkQ/fZ2u6KU6+z8eZMXFWz0PkFksV5oVP3j+nJeX+9+GmSBwT8fE8iE85eyAmtNH/760zCdOM8Q8MKOg3k0F5DDEAb9rKnxoG16Uadw9DdR+eCLrWX9j3XSI2p+2+KBCydBjIZnRTpH8GcA+QRJCbl3jJY+R2V1/gziGg1tXsFBfqvirlWoE4GbZG2H5Dxb+KL4AsoyWgZZCkyrl9FjssTyQsx0ALHc61fXIMVA5SrZNNogLUhvafCLIl9f5ykrrimo23leFzEaU64FvYmKJagcdwy6a9YbBqpWviGRHHcNeVtarWwQgY2tlADNauQwiPNsUfqJX1RX0dO/7fSOBplj8zU7L4q88EV1CZ4raKg6SkYvwewsj+d1HENJ7NouSTaxRhWCS2flp2QBV6vvBME/lwUc7RWAUjqrGK43q6vft5wGbSz6/16DGizOoRxPT6oqildrPBLrPaAdR/lGiPC4H8jbIgjmrQbDnsBJQAYxZX9jZYSeEQ7UWretVsOvVGQfcETR2OnYKdcu7CSLihczFdhr034/zJ70BcpNzW4YqmWplnuBnQgC7Va40G8b8K12K4fitFsFqEi7VXWaQGXRd6qWbgjEV8ORkDW81PUP+VOW5tHCSUwvkuhNh+AolhVypKWRtxfgWwk6XN+K1FfnEKAazNDv2urEaGihepZnFZwMjYQL5h4BmVqIh2no/BmOqVR7v8O0NCsvwZMvP83KmyKC/SX0V1dU3zYLtNK+ii2WML5tSkQHUVKPMlDe1lWR5AWG89FK0U46vsMDtRrI1aJxV+TjD+4mH2Vo12CTvgQZn1E7o4zp7GzwJk7jePA25vXdv0BcDW+CneaLl8G3srMCuG4dIick1CmKBHi6IREaSYc0GgDW++M4r7PK26IH2eJLlNVRmr5Y789S82hrlJRjmEedsWNuHnUGlemASJLrRnRLAwsHQsGozDsa0Muqo7vlrtuQeI7KjWaZqjwrv/wySHxME0eicygZNq5u61NeqnaIgUZ4lResL9zeZC40xtpQfY/K8ikvFuO33Jw/zUtvDwHyNzTxRXBXxWsB1hoo1OgqekGy+wuoVrmLO6qtX76hEB2llopBCUJNGwL0HKf1AizO8KSDAu2Ns8XwZ8x9u2iDK17UTb4PowR3bc6hRrGBbTTkH2O07ShxB9AKwD6zZ2+98kuSJet6jaMZT9aNytFquSBO1hGUQlcF/NWGZf/t9as5CrKE3KTFHD0PhLnBdg1KSOu4CkeLCwhuFrXqFEJkLkyLfFHH1WmdLeBKggsbKsdFcldXoIntcpCvZ1EVpfnyjQ71UeLK29oSzZfReqIHQ/Q9SmtvbLMSLkhoJYG4coh/kdpkW7oJLTIdS96S9bdGmkU1zm6zqetlynENhVy6x7Uqb6sllu+KaIgdCFl3W8UPCzQb45CbaYevjV0iQpCcw9W0amgIt+cKGpdmm3XIsOpeUFgaUgKTPFmAm1W9vssI74Mrsu9w4u9S75nDwqFc+Qc/hrsjE9Qh7HEwqfGikruH2JU6wjbHulddtki7Tb1twWzAt0wdxSBpUP3+zcDbRgnZDcNoAOZdV3XaVtHwVy+OSoVCPDYkQs153yyKCtjmxyKvN7AjYfSLdhOYZYvkMVm4nRQNs5XbnnWNvrcbG/5h2jtZrJPsLF+viWCbwRrruBas4Zbsb3LCyXn6mn3K1+AqgK7Y4QmnMHwBVQSFBvLdq5xyYbRQ1NiYjIPau0kq5SXnMCM7SdP8qffogscEPHmrvCebTZE/gsU1lMbZcl6v/Wb6Mq8CY+zQ3cANI2UG7d3HcEjbkI+b/CT2thw+J+ukQp3De6P3FM8f6uEXXZTV91GMVJAC3ay9hEbUVmoM1uxHyGKDNzIrPyb31VlUeJtgHZ4Q2so1+KNOCvC1WkGKN9uJN6e0OLfKj0paBwphOqmRblwlMdKNThYLpg/eY5qV24j8MNHdvjP3LUubBd4h9B4jnJVmwX2953A6erRaNOfPm6TAivqH6IXFaYbiJK6SR4wiBNt/isp5BLUmEGpWaWyW95lgb1A4xMmyAIBUHD3CA7bIbsDz8JrorLwGcV2QuQncpUeL6OwlTkHTKV8ZR2K8AkWSe6++Hife+zFiz7UyK9HJc6AryrPyAk5/d5Ttrd0tFjjHRZR2GM9WyKVh7qi2v7EQPZ8/Q7vGey3MyvPSm5ywN2fQwFrmKBbB04iJMmgUzbJHuDQhui7KwFNtrPL4IdghBDLXMMaTxyiBdZOUwOrpNxb21HnDSrKAI/+cPzWjRjsNxOY7KdBASO5fsEPgIi+6Pp4CaH15Hl1F8UOOzGmUyc77nBlZgwjjrKEltEB6o9dbo8BGfxuHEmaSGoxN/EkYjJgAYNEiI0LKh1NW4VpAHTytX07rqto6FzzWFoL+kZSrNCmrMEjbxZ8CyLxQrlP+G1ecZ1A7x+iS2P9Ei0QSfAf6mi6GbaA1TM6I8N3gbcw3EE+UWg7EHCfOP9KdsjncoiBxnWcLD0ydO+s8Q6d0QfirFVsUZjAwU7SCbdQ2ofFxk4BmTXoLfLiHgrLqDyPP8vVdkrXncgGZEPa5C24oTzabNPHXmH+AZLkabinSdkxw9D+SxYDYPw1Lm36n8ZUnPSI/YRLuxCJc1AVIgUvk3u6GXPADTB5B8YIqW3pPyFTBllVPoxK0u08Qs7vH9wVEZV2AJg2yVJ0MkrS2bzNsJHaPFv2gUZuRFodB4BMtjQcqjHu9DRMBBYq8COX4oJAiaoTGOV/ljT8lKuw9HfKopWZL7LdIYfRLV3rbQt/kW1/5NvxFDsXFJClAbUOQSGmgCt25pQG5qB2qXBawQwM5Bhhto34MY8TcQ6a0sWCSECvLEbUOL2wQywfUucUUoVMsiGwAHJxjx8lDRGXfSUBF/0VgsjEIYR3HcZVgPMohtDCK3jMQso6zYI597g/B5V1uQBQ9pgFkHWagHPuLLdD7pEmQR0YAy/svrqIYj7qCbHyaWo7jvYmWysHdUnC8PCWKpeKUhHGUpt+jIoFKnciOVHZfUU8xQQa1ZLNkUtVxqljUVgO3GK3tEJ3HRb8NouBBGpBnQrJcyoUUkC0bsmfDis6yoHx3aQhphxkw2y6TPhxBd/tiEWdwhRwv8BCi2TfPbtjfdHF4IqZVavtfx7hmeVuaKN+Bcpp2MxMiUuNbiWylQPeX5Deigju1uqZsrXLrk9/WZeaSxKjxkjnUbKOryrO83qA72gvvmeExeby2YjYzOD4SrzJfHt1iQsSzjQPRmfE3ucYU4w1vsoLKlt/CcduSEth2cxK4GIbwSOjHwDkvjD0SLaqW8+V5tfo2xTUE4xAByocihPa6YNTZ3R43jDoUx61Y3pZmKx7owg26/xKoZZ0Df7y7Eqd5Bfl01Gs9mqdVwuxM3TIKdaHneGEmyMiaG32hAgBbT5vlSRW6STVP/g382qau/pQ3eZMRhMXtHJHT4vlKB4952B0GEUDXUbZUxmwFS0oQ8m5c+PiFHb45tGun4AeZwoAjFU4P+D0BT1/URpWjNA59+OhgsugPIAX2jbEXthHTwt5vT6o6oG2P2TJOk+cA/LIDdD1wfCakrX9U3BXiqyHRUC9kGDwA9Ndh7uKD3ODFjJ8DXX48mHcsQp/c8iJEDKE9uWXkjZs4oXD65xw5ipex8pnJzIrxdCOnrdR5NUiO0WSLxnIFh4250PU1RJoggV8wyOkage+4ipXrJshjEAKyX9cpMMoCHcavMt+AwU+tTpMUPeTep3f2vP3YXUoNg67LPowNCCrxtZXHZouml/AqN0WYySMaJZOIj9lwNxt6L1Dollt/heU8nT9DQUW+KDqCjbyNfxjuBElqHSvPm9z2HioK12HTgWIFXYZ+QyI67jbytjQbgeG9Ausj+jQvPoFnnJB69NZbxRQ9Zqx0+Qe5U+GqBdvaWQGOXhlMx1UjbyuQpRXqIGhWXoAITUSnIEzo15buVFsONbgDsAWWXwLof0iMIh5wABPO5daCrr9BrLgr/hqBv2wgsR3lw+DygST3n0JG0NxqeN+GrqS+dEP9IVmH8goDyA+fm0Mm/Q8iSwIJkaP02OX4rlEjbghOOIYo7VaI0jGoaA+Dio5xNvsXZ+N0ykVv7vwZl6icUxSEQCGsjeYqrb+d0eAJpiO8Qpkn2vZx1E0rYvCTgFn1egxdXie9Q7mwEeFGSdTapFP3I8onkG7u6/QfoMTJT4Mgu8xdcPno8Lb30CV6u+S2umVHGywtLTJQlnCt6lIvcHVumfUsGZG0hvqqvbya19lBt6pdxE+nc/S/jvaJvK2ORh/rpG+1+e2QXVoTNBboFWDN6+1hWjF4DjxsQxd5sY78431abPMoVaeYCpJdf8xXbQImfz65v0/SJICZinMCe9sygfTqWdmEg5zE1HORzjcHTGJLwvDR56isZpuTxQJaOyNcpwppNaCuf86XSeZ4ZRrV7zJLS1G4XxVuSCpRGDrRf0uAERHrXCkfs86D2Co6bchPP/eqbrKwgr7SIPIOM3C2vWZtDlW3W5jbrUbD95uF4VQuKaDt/QUycklDbgZUQG0KQk5sGsyW1ufPSKGJ0pO6WiGVpkl9dA3ixjcoGIGqhnAajCpwc2JWy3aCoBpQr6/yUnJDpi8WDoQv5XotAHHq4k2+SWJFH3G5vJN0sbiXDIxtN7GDSdxDXCTsHF3C9Ysptu3SNYA2RoYirIBsgikQYRfFEFxXJWD2XX6KisVVnmRV+SkpK9mhvwBO0ns5nGAMCmDbkXQRiEZ7DQcsEH8MjFwAsoDWeaeIdKDyXJIslJD8UiCO9nJIL8O+J6i9XQ8NjTx70yI42vTyti6SoqxGiQpH6usoDemM/jCtQBbbRNnLCO2IQ9I1FhnkDpS+9THJYtszvTPiQZQBX7nFS/PdWA29H7yhfyYbpCtFqSYFVaCHe1d5BrSPWwZ6Abl71W94Zveyt+WWTLOGhFtht9H2MNsNkCniNm223HqvJhepsnMMJN9FCkDaURrKL+VTRy6PWP0Wx3F7lrel2TBNvVwu8Rhl6/jw9qBiZKHy6d885Z8BIuKszDWC9n0AgXSzKgAwbfDnAA1CQQuKJGYa83wK+nvk7eINFxkSLKTDN/ZFYceyN9H6O3BCW4qEud1WbsQbk45EDy2wrrRVrO1bciOQDIvaKwS7oxCA77wQyssspOnhsAN1CJipO+5DCrF08KGp4eTSbJlBWjfv23pH5J0/x2m9gGudukasOvkKc87WrlTcPFoC+DTh2f7M2UDIEnJyDPHKKscWEtlNXNG6u4e+TGE6Sist83o/vzuN8n13V4DHJNJIvTAH3Lug1jmZ7Y7qCbf4lUqMVzhaf+AbICqtxXVc84o135LK+76cQXBJmHsCM5S4E7LeQT3KqI0mCB8AITtLk0ZKOC3r0zRf9vFr1ksa1S7fEDh2I8a97cwoceNo8MhBHk6F7DAKWYqg9e0WcMtOonKOlYRA3mzUdMWDhxCC414gb+tzlC3raOkdsxnq7oZtbqt8EeIAzukSXsPs3mZrt7O1+M4Ej7/aKuN5FR5p80jdwIJvmgd2jtdid/GC5UAHnMQ+JfaiOO2GrC9XuWWadrWTzdJuYqxbKLqPVKFwu6YhvPbqbSes92qcZCr5NwZ+0+E5btnytqbxtXQz01JdafIN0T6s/UcN5mCMo8yLNFrO1nCwKGW39q5GiIui1eE+IzGAt6vJS9dMxjUosWgTC3MRpFBiKgE58a6G9jqyE6H2lqoCnEcJq5d1AZLbYlprRMhAD4N2retSSA5kJM1KtLqu0nqZZJwQ0V5KzOtYJHyCqE6BhAKrUpmJECehcCW5Gx4qjQSBcje8bVTvfJfhj6hsBxgu8OWKvv0/ZuIAZW4Efb4BJwa8BE+lu0sX1X5DoNgNHus8uqNkHRnTfYwoje6WhHMfdxiFjE5M7O0WcMvWonKOiYVA3izbdMWdX1H9o6Ikb2vPvcfzVV6EWI6WzV5g0TV2q8EsummctH9Gn/tOeVSPTvMgI3MzWfotUWSmcIXC3ZWGsL2AQ+zPYk+LkxbA+lWUqoKb5ZSn6fe8wiZZjBMPWWsDCEX5hkW0Gzos6tVJVj45BPPITZsep9iuYQhxS8ITxowcjLdgFLB+djPRNY95b1AcFUE1Hw4cQzqQWtRc1/x6D/lvu8Pu0HN+ORIRkmXYLpEGhl56RJFwuZHl1mkUmeUqSZzoLye4FIkGMsVZTnhJiKNskLcVykicRj40CdtaTXf4u+rBjLRV/vQ1+wQViSusLXraS8j4+ojT69zkSFLuztXGaazRwS5UKq0ALK1FFgBVIJT37pr/VsbK5bzhRiSS56KNyusyAUId5CYBQnSU6/K2dKeav/wyiPOsACDYDflQqU8holaiITzMFGhEa5Bkp12W0ob5PbcOkxSlprNrLGVAsU7KUpODkIWSZ1NUQ/KSSA3up1wyuF2uYYO4LiBWjgBH+eQsnwbVF6eRjdsnAQdv2SMVarBFzOk5ZmveaRGr0pJ6aRsqxMfVreD1QJcYx0mB181yM7r7ZIQEYV2TrWIyYstfES9j7hunqZM4BmU5ToNX6AI0NFqNxHzol8yVF0MHys/MClm7rM5OsrZPpuwgWHHdcpvQ+ShC5W3hTNDeDzQHksOBIqNs7aarvXwfYYqL59Y50lnJoUijbtrBbQJzTRdJQFEniUzo8m4SQP7yrO2Ml0DDOI4STd4WptLOSDQ0WwiBL6I2OaEqFsXwFpL92Wj7ZoJP978n4MkTBUqI7ZQQQYIn1BR3+Bq5a3eEcBgbgt2DFEJZK3m0wmpP0HWwBRL2Dv9Uda0B8N8CPKX/UfDL28IE+ljk9WbgU+aB7mqNGdx4uX1mxotQQTYHJEGD2CGHuEX8ud503q5huTS/JYEYaU6UiaU5CeAvzdtOeIl0jOMo1xVC5uCl8UGu8bGiZZq15C8uhA+lieSJk7ho59ZaUjRPrTX/HYWEvC1MoJGeXcFteb/qjAe8K34K+tVOb2cF/Sqb5YNKmLwo02wdrCNhsF1FL+hWSlBkTRrtIU+SJBxzVhcFyOKXUZ5f6hq7jrZhox9AnKyj9PWrqwL+wlLs9bu/vX41RxkUfn/9N+e1QAdi+c7T96gaU6zM67sKisR0lsXpDUJrRKy/Wg+LagylrR+jsRvUGJwb/Ir2WCOkGh1npK3UGWeEbWOjjgw2ZLGU7RujhCPcRRKkIkTpBQBD01Te8tAElrc8NLVb/MOn9sQ8NDiTdmt92FawWBmsCah419kCLE7Wg46EfAX4BygAZLsA6dVXIH7I6+qkgnx0V1dgTFOaa7z8r/UIr5u2qkeoa04n9/dJmsA1Gcw+2AxOA3w/CBk16GXjAkD5dQZ5i9adnFkKYiKj6oebSNTlUdRwjjbDJTKIygewkE3JoCM8e3wc/iVZ1ND58yYpsCPhS55Vq5Hb/AeIhqcnub6aR44+gLvE+9oKgeokxvvmpzxdjMAffMMjMSbR8GmUPYxiADNtjiJiyDZnZ2M2178vNF6Ts7toBOWi3U2xAtinwht63dfQICjaNK43RZSVUYx+blWD0ZseZcnIGr8GZZ0OT/azaIMc6eMSnG90pNHO67teRx93yFd1Ea+iEozpwL+KEtdL3+xrqkPPS9scss+hwNnUzWnJ6F7jDyAF1UG9vKWwhK8BOnoj7hYbHVt8ispL8NR6cS5zdM8mpl4l9D/qpE9phEeeeBndsoDbY09RORcpIQSyjclTxg02LYhCBukSSdecAwU739C3Elrpn5KyyiUPJooAb9tD3G1n5VDccbIC1PZNkI/JPTbktIMQAd5+K8HiR1KtuMHooblBGVSxHRyuhXK+Kfgbp4Tj+s8UcZ1ly516dtmkiJL1DBVLerYtEveMKLft2TVYALAGC1KInUvfAiWhtEyhBeYGo69h/fw67Ls86WBXypOdLhE+FE8Uu/RqYySNOUhW2jEAEqHHQnmFr4kFocNryw2aUigwjwEr8rb6Y1BPD28j1XzdxMO8s7U9iTHdYbc1NJts95FbK2poW32hlSlDKAhGHacll9NKF2sLroFoImzHZS5vq6OX7wqldtAQyKgXYYIfLA4kUDpqmmu72xoaRbf7yK1LNbStQGEUrTGUdqMBiXU6L4HjL2SOgkXeVuc3I6SC6LkPM+kUIlBu4FCFWdl1FmdLiwJ4ozqEZ3m9GcmpfQ2JsUmQg2gMr13f2jgJbuYgQ3bsGCNrmhpnWF+gsUXkTx2snVm55Q7sPpza3zqQA8p+SzZyO3EbuOloxJJUOZ5bWR1+RBJQ6a4sg/fakokeet1AOb6FZGKMQyJ53w4JoudfNY+/+aL5ex21o/cyOrKkuiqSePAQ3r6hwSN2xxjNKCMZOsK48200OuLQJKNbG5p2k4S5jhve+iF/ytI8WgR4zmpWdsiCqfGf4QLJStDhtTSBkLT+AZLlysJ00dylq7NFCqCaFQ0fyNAI+DOcJy8Uf0sVpZOyzGMUlbzolBXxqUdALUmm+em0Kn//rsVBo/C4RnAQaayQNrOq6VYPJepYW6joWgfhr1de4rcgvPRKhOKoV8rbCqINXhJvdoyXXbG7iplvgz120qbVSAKzg33hchMc/DstNsHBulfWaQG+4xpU8F2gq/YNwUPgOI3SKIuBp1lWDn7LbCwH2kCSQRnIJop2EYW1qeA4saEEDn/iFDggyGQ0AY+bujAelyc12oiaPoboKP/kbQXRQW6KKH6AFB8p0Btf0g1r3WGeAa7h4x9AmjyC4sWx+ui6j3FwHbvoJbF3tiF/8kBQEuJ2KwP4DlIA0iBAGsor1ROJMoRQOjrd9SsS0ylIJJ3oEH0cf7l2MYRcB7L1Kl4tjvYKZMfsGvyB3ox0dxBQaI7rQLEOQiQUC7YIQplLYQ6frkFU5tlFXjTcNL4bpOVfgN3eQQ4I/DqgvjxlNrXtDdjh88bMq+j+/jIfI0XNyWKdZPqrvX/9KciDIJRsC5NXboeu2Lla1ARNJLa0CEJgdwrBPI3NHCtnZ3AT8ounYDEddzbV+g+ws11FRavfWJ4QNud5DhXJKQ4RYRnMAxkmLGSaFEtwSYIC8hAKixjMYxlG6dgHUbxlbol1QUupWxKetDGkYAJLQw4b9I4x15BoL5EC6fvt7JFVne9yrQiOeWUw+i4HOfRtkcw3cK11d9x7WeByIhVVUZov3yjxHrfHweW5mPBfN3QiEMe9AiWvukhSOLAAb7ZDvn76mrXDviKOltyTYDkn4Hda5UpWFy15owr8Y8hGtax9swo+cRitGp0pCVRYLOmiROVnPSiH6iw4VWiPctNW4A38eNSwYTZDSDAxmSTvuk+91NnznQBSw08uirU8ZbsSPIQCaF+d1w0dcAwg/IKLvaPA+zOIJhX3Ss6Lx19y3Fmzx7L1sdRuoqW/WQaRHJeW89L6a6BHynR7tXJXvt1OZSnYbsli2T5KwYTgze9RkUTZNl37Wb6+S7LIUyPWIz/ysryt+UMdgJU1d++rRHUIGqaRL1FW30cxmsTiCs62NuosTLOWvmnN7SbckVDYfiSLcMg+hb02FZUAU842GtkQLfpBozY6xDkpy2SZwVWUYD6aLVRn0O/CHAt3UYLIt2955tSdXKBLsLY34Up8Jc/bmbaXx0tQZMYPYY59sK/za119vcdIm/Nea6LKk6oRzKHa7RWb3y2Ng9METKvKHGvG9UOenBi0LXesGtRyGayri5Vcxa6DpXFYjZis6jJsqn6Isya2sfCq4VEfHFygt2iCOVpvwHMFP603w6fHR6l5/qiTwv+yOrFesqrI0xBxIROdXrFLyEK82spUZ0HK8pzLeIT+INM6tiNUO1sNhsoixnkelW4BYY1bqeTV0kBZX+ZYsEMSwvMQcDM57iIGu8hJmkQa2ymApJ7IxT1Y6hdfTalN8xpaXcJoj9wub0tCN/84mj1aRJroyDzNi/kfdVSA8np5N/igGt/S4l9wJZDB/cFjR5vrpiM0NCshJriRgzhAEiUPHZLBhEVDCLX2c5I9oEDfKXPY2Wpbu6NnGeqdZsqa0450k4CidYU7bz89juNeo5JsYeI/Q9yy6IKiEdM5uq59vQxBLwZoQ8/RQIWrvmfdWxp0u67FENzKlYCF9J1umxDY8FyhoocqO908UggxosvNefSlbP47ygt5WyPFKjCtfitUpzgDNTov0/MMwXsrSHMQQ14JMwjNsWpeKu/UBjpk/Jwv8/5QczIHJDv2ap2e5ouXMd5azbMKrpfuPOoSVE958TBGcAAUTC94CZ/VRQGy+MXfs45xnj/HqyhbAvxYqCtqxf4hbUQccIdGeKusRYTX6YD5+FVtDfsNkp8Z/cgYcMmQKCj1WGhQr4102y2Hh93Q09p58fKmQ3LcUeVtaXbUXwbZ27qJ0Txc8ssg2STMs4X/zXU7+ZwjBP5kNTFYLvJiDSeduJs2VHufk3UCWewmb7RV72AbtMDK1S45gHbo2jM7RizbzrMFnNngGpadefMlwjdzPa2cFstRNMvbasjvq2E1WKYxnBz8MuY+uhVYA+z2u0tdTG5cv3xDoTlyo7wtTKjBbYxAfGrg51e08j6MmTok959G8cMsw1Ga8/qOOMJ19ldLMB6XRNAJHswFHirDzhSZqCWsJ8x6ooPlLFRthZDeaFljAt+0BtR4HEH81uR1kxuwhuq0V3CRCN1RkMjbmkY7+56AJ9jV1fgtu1ps5u4jOA3LvHgJwMssqiMfH/l4ND5uhXsANmYwHbn4yMWjcTGeiiT2ClvO1+s8e8NiOrKxvK0w7iM4oo9FXm/GZ0vY8gSrUOMaGCgKe0hXwUmcXoM4R69W23tsUegARPemR3JccUOvuCkdtsoYOOsg39liewjmf3tYG7bW86g0bE0MwZnaEjAvC/tb4bMK8zd9/eMCPOQFOE9r1TnuQK2Gyp/wOcqWdbQcbCe7v0/SxM0O6uuW25/HpSRv62SxKEDp/abDB5CCAHc8HNlT/h50MzrxNtKxx20PRewgbCG/eXAQfp5ZEJWQ/7qX4DyioShMR9aXt6WR/KZ3umwl/0tZgTWkwxNWEsZu/hqRbui4qOFcDC1zf0jWICvdDkK5ldIjOy6W42I5pMVykRf12jNsAOMo33CojmtFwbUEnT7WSd9y83uiA388g94JbfJNEu9j6AHHv8KgAzkUp/4pQD3TSSWPkBb9g+SuC5bGc1ytitUaIroH8VColXqTh8IE+fNfIFa+EjhM4D9KfDW+dx3lwIq8bdBZ2Rqzpy8ndbXKi4AIr6HmsEmIlAuuOAeSoSQbSxIFkGLlloYn8wFIwQTX/uWwtjFb26Vj0nsSWtr3LZCu5wSk1xaAnSDw0+fcJfge1lqit7kJLEfpL2+LIBNqcmfCM7Xvuw4kQwcSLAyZJec6PcwtB08e7kjBBCc8ctigL/mRDYmEiqhc2dvgoqQhYwhxguCPIkXe1sROC42n5v0wrTrlMLCI81xvomTp4sBod+fyTYfjyLrOvDNUVIzWTBmoYZTHYd+22JCXdS3c7ngRecSn9suQQXVcjc6rcSBJfhrH53DK0vZYE6iuIZt2wXf9B0ooE2i9jxNk0UxCHKP3QPwTFQa90j8H2QLd/YnS9MUX10lVRfEKGTvvLhLrjGzb2u+9av+srW0sKy/BU/kZoKXuefTSi0wxxqPklLclppj3WQxekipx+MsgqUzCyJOgaseQgcxnKxA/5LVX+vPmSZs3HKrjolEsmkkiIffyjYz5KtlsEHx73SoU3ll5Ez2fPwOCGq6oIKLuSmOwd65240WQUPqW3NPHSg3FCxVi2FuBDNs6A03rcC9RGFf0en1C3EpAIXxMza/bqlmKDfzi+0Ci/ZjPfiAJaC65xpdZ7KGGtbBzklnX6AIb4uur6AWxwKekRCGe7kJLgvAoteRtsSTzFVp4JnY02Iwdq3CdSXjolq+8XWemdbh1Zlwx6DoLt8COK0vBxC9xCuhnlh1XA0J0BYokX/hHqVVRivGVfojmFZx2tCRD+CVCOV8D3WeaZUmVROkuSzKyi0ZS7JauIRddFKBWXtHQtsEZ0v1/bLHMmmzW8txJLONExMm/8VtEmCxOkR4YBwZ+wyE8imd5W2GuI7tdqhXgwUmpp8vn0bc/VdNj5faQCqNuJoXSh1tZt1vwrbyRQ3GCVAHqqeqleDV0KQ2dU1LRiI6CRN7WVZcj9J2fCOjxvJ/GSWvuaizysoTLIvXnMhbVkc92lc8stBqfAPhj4Ltmr1x+Bo8g9b5jtMqLqr+uNnYs3QUED9e4xpjb9IkiBg6Ikt4i0CxHSAX1819hCAGZHBQFKMZoa8SnOnQ3/MAfdfNUkd/FDvym16eoVGeAdCGd29UF2ZUF5VWFcFcU5qB91MYhV2R2nyzrorFYWzxHkS9va5p40gNMtUjEVri8SBI9vyEwHDnWmWP3NVEuTqPgwjmoXtn8d2QbxdrHN7ACXWbRefjjtF6AWTZPYJNR729zjwC7isoS3RyDBVWQQIMO4eC0uEkq5Wt4YVoZJYj/C6ii9gafSt0P19gHQISKj9DeOHMV+N7BNcigSD4pfySL5pDFC1uD5Z95NjwdmqZ+FNGm1f+5vpvUh6b2EzVvfiM/zdEGF0C+FEleYKnsdZ6MBnYTqbKiusyFfE+u18SO3Fy26a+DIHaFxjRyrTcrclZepNGy7Ft0v8yhbinYrg5pAic3fYE0JGeFnoIvYH0Hik6upvnd61dYW//99U/cdFGw6LYM9gJAgxj0ld6pKzWvHrSwgts1zYSQH0/KMo8TTJhunp43eYHixe8TlNA5XoFFjdimfGCGdp4tXjWmqrzK1qptZg5ZwxQ05D5I62QDqQtZ6PfX/xc3QKNm+jPhbTNUz5lWeDK2pEbH41B+lHDWk6zi1bQki5NNlBp3iMFgqOqhyerbYks+gA2S0VllTHyTTrT1EC/DmuIu9S0zeqmOdr+9JbhMzXx/r0ENFswCvkVrQcp88ioi5hNC2zChojkBE37In7I0RxmT6BZ+evPmHTsvv33NmpiYVyjABkXSnkVlHC14uQ5X8iIA++qHMgL76qfPpBPd3cKpmfaWvFJqwrJUBQ3DOrIp3YSASak+c5Jyh7hUOJJxeVQ4XyZdYO8aT82pxGIjWcxO7JGEGU/Ykq0K2FmxYnaKm9Ujmkz0CubUpC9EtcnYuz0sv0rwe5m37f9SnhaDixiZhrThYEkbAraVIJ+eZdVDGIFP1fNk0oG2ys4wZhuKY8o07Du2QzAm8/Yt38buMyY9hAkYk54nI8bcPlk9CWN2J2RaWckCiphxe2BnzoYcXnPJiG2oIHa8rBMjsJCMrvsg1T4kZaMGn2zgpCAfdDuaBJRSRlJVEjFVB2/DVMo2BAxmyLhepGlXug1huirDkaVvwVzsB6BJ96NN6YC0TgOyiGrZUCbofmXaP5FLiIfTT6KTWDMi3QgyzohEJv3o6k+tx91CvkkeQfGCo9o0GhYFrNDhHJQ3GrWI18h+DrZ9qnoznhYmpLMRVxH1puasXh3otyQNC3AVFBzmop9Jm7HYRnfGUpCOYjwulc6XSRfIbEM7waWGhiwLPzCP7rEtKxvDBAy6h9YsSph5H2GjqNBatCJgEWOScDZcKcQ/hXWr6sgIfKWi8z5YuS1TU8OgeEIjlqQVFVLQlee0TQr4T9XWzshF7YjGE5Da+TTpCllvpzjbcEcX1RmJn/d4d1eNYyIG3sNdHmcGTbLlF1Ct8sXtNYDYEhTFfYZ8A0rno76qiInpWjb8a9CeyI7ChQG9kfMKKnVw2h4ho4DbDr200yJoIV1IQCuyCFswp8T0C1o5ghGWsnKOjEzJpspki7h70V3HjAyciA37K8PmDMhiNWe9cGq5pA8jMI+EpvvENrTo0U0zvVrCshCNWxSHrJKS4dlJ2J8RmUpIa5P2qYrTHYh3N663b7PIOIAHFR6K9/e+LQ7FecwCxjJjWpexnyYpigTvGtB2k4YPTgUGvTkp+NXlQI32OtH2Ar+uv2wFFT36u0r2ZOGaEW1iErJPr0DpRjGCxNLNl9F+SKQRmUReNfnYmptc0LzAPd+mO5PxkLKWiF9FFWyYVt2ggHO3Gdt2jXONhjIC+xrNoUk/yKyIk/DwaZovr/Ky0vMtByni1Q7Ihj95xHvFk9Luj8CH0jnZG95rX4K+7RlHxSUssIwD++el7ZiQQy/gQxmD7wYfykYwEivK5sek+a7O1B75a/CYgKdPIN3c12mGdF6qQOec11RX+Om5mg7Oel3rcr99O7qd42q7gY3nyDecZwufflNxMvZHbwrOKrDWKwIcpIipOyAbJuYR75UiIO3+CFwpnZO9UARQ77u9o2ccFZewwDIOdFAEhOgFfChj8N3gQ9kIRmJF2fyYNN/VmU4RyNNULwUpKOG2nqdWt8ZphHsl+YRdH2MvFs3BXkg81POTrHxCUQA5l6WOZgoCTsZnDYgtt5GIRZqhgIF3g9cEHR+J2wRzYaTiQfhJee17XmFXVZwXi1uCY1TsIakj40ES3JYTZU1JuFLM7rvBm5qhjMSnmrkz5dmm1nScC4p1UqL0YO1IOpc/Gqj8bExdTci/TA0r/lW3pjgZQgADnCCaEUkMHvz8cCJinD+jlKpRelJXK8SLTfo5ZoIUOdUMaovTq8kr2mVbM+nAXh06Wg1pBClpNccm/Zn8OPIiL+o1drtqGZwHFXFzD2XDugLUVnwaJCBH3okROEtO3P1hI5yD25CPaFgpI7VZwS05iUE+ESuJezEWL4kJvD/MdIv/bd7rU042AShlI2sOIpEK2Ifo287tmbL+j8V4gvkwaXpbaxeEWMM1BkKmGfIQ4qvBLGO+HeU7QdfHFXjUfBjz3Q6oXwS/6LUkYsDhVTACuYz7hHy9IywoGcOoOhw/PybN4wqTsWLzyLRhJLEIWMSK7Xsx5mwoRGweQBxIf1P1YgRGUlHXpHm65sQcpbUHaLCAXLSfPg9x30fjur20GLo8U9/QexLdc/J9XjEZlyhrqTKVkRVc8rmJG1SkHts9LjUayghMazSHJv2YPFWZcCSN5LPip2YZj8W9TWsC1hXK7B3lW2oQUzEtNW8mncAVpt3ccZiOmkcZOOn2bhv3x+LdHxaU9HysHZ6fi31iNl26Ew5yAIbbx4Qm0t6PynZ7mLzkY3JfnUUoIAJ2eRWVYPEjqVZbDpKxi6aeiC27KjZcqWtGJhZF3B/ubrthr0bgPcNpMOJEIYbJGZNSInoW0vGLsJaKKV21RnWDAvaUrYLpZajRUEbkaeUcmvSjq7NbPPyNXGJ2jExVHY2b6Vb3RxE1H8xUTC2cT5POUBWnVVsv8wqY2EhbOKnKikCsVVYC7/6wpqTnYymr/Fzsvo10DZ7g8rnKIYKyWz9a37uqkogNBfA2DKlsbq+89CYjGYFbTeZvLzz4ooGYKQLamsZiT2j/2DTktmC8kh6hhJX41pyaSDSYLGem7bVHBuv+bC/ijo+wXsXzsPubS9fvxmbuWEXHFxS0iulsfXHiBkS5/CRcvTssKBzCiJwonCOT9nsE04YXdBl7zSJWGOiAwQYsZp+kd15as6wnY+nOMhqb8tRmByJXrkFVFxl69QqY3IwQg4vVAQLSTnMWNrFnOrNqDKNoy6p52gs9edtpQ7knqxD80l5IAWilmeS42bOoaPay0zpbpEB5NqOoI9ZSaHA7TUXelPz8kBjDYJuFQc9GUUO0U2HSi22tCVVkZiTabUNaY3Am3M/9QzuMKfh1L3cRbhS6sApZhcE5dR9jLXSDmIJN9zDygs6Np+NQIbQ2c6JzusS9ZEzlCEbgSuUc7RFLzjcgTu7b2/8nVVUkd3UFTFlUXVvBsuKKDiys6cEesrTZiMZjcbM53geWF4/k6wb9eSvhSOnubo9LqGAo0FgpGw7dEfmYjZbl9EvFY7hjaCzuvGHkWRRi2NGNRMngbrJdTd7Jthxlt4zXmnjpT7/i/Mc++Y5lwjfu66/BM/Uq7Lblm2ipyA/HwyrWjcMKoTDLVTBYHC7jWYvze1QkUVb103KWr++SDAPewhWTPILi5SZZK/La2uFREE6BwoGmxh0S3b0kBz6YF9axo+PJBds5NekZWW/qxa8an6FBZ4BiJzh+j+07i2HtxtLYQ0vPYFR/r2EJ5IxvWeK1Kkg8O7E0qA4J1gc18Ck3A1FHd4PjRXNq0jOy3q7xvusO4CD2AzD0IQn43ZHqByTKu6HoPXWmCKZhcKU7jhvjvrH8FN432/m2WAQ742xjB/c9Sut+hBy3WrKmEpnFKsF4AiwVdX/ky0a3and29RgNeLqVZMQfFquKRTH14rIyDUz1fxcv2k5p8hOr6wehk9/Oo/UmBR/ypyzNI3lmAzF4YO5ikIvchl0/B+ctcV9G5C4xpY1OA6iak3HYTQIKOOoYUC+OSDlMDC7isB7Shsck6C2fP8F8NvUerR7KCCyqniqTDpD1doBBdY4IDnIAttxH94K096My4f66EIhw/OanoUdMVk+xH8svPRjszNL29olZDccy3havm0WTnlAVd4id7Rl5PBZ2Yd6QuqW8K5Pw3iGIz95Sv0jSCmV3119Ts8BhxJNMdS8WVXTF5VrbDspe/QinWAt6FjCXydNel6vyAilGyToqXs6f41WULcF1VIGzuoBNxC/yKGJdTWHMMKpkFRysbUVomLV9H0Y6G/dpBM40ngUzaS1FsxsMiv+w40yqSniWpNFPzIvCzozNhEKCW3AfVX8ytjuN4odZBvsSP8zruzIukibWVnubWFdRxIKSOjZMqW12r64Ym45mBN42nU8bZ9bOcbXOCNPUG5mn99GnYDiWCRl6D627kzgVvNsu5WIxuIh5e0gbdpWgtzw4mJ5V1eMYgUPV87Q3pwYn9/dJCr+AW11yIA5SyJMdkBVPcphHf8RP2oUxOElGWJO2p85+hl+vtFM+5VWkD5S6bs6KpiZ66HtqTVFP/L3QEeF4H+GK+QJZP1qC24siX2v5TlFH7BIlwe0coPKGxmc7g96M4pzUEt+kF2S9XWG+m9yW9bY1BmU8opnJ2Y7vy/hMx5PdpA/bWhOaEFXyCCfnc768JX6jyVTYEdI6YmOiB7EzJ+StiJQ4pvM7aFZoxzOKbaGdOyPdkK66E+yrFZUi4IEYdj/djqoRjMybe6k6nq1A/JDXbLg991meJdcQgTBrrrCuVQ5d09ZFTM2Ncee423Z4I3C87XwbrQK28oTpy+O6gKRYXkUvOK3/LEsQXt1DKopa4mTmdAW7fOaqxsxfEgmi1xp1ZgSuNJoBo34Q9XaGC7s3czi2MeURGQIT3nR6rMqweQG36pbG9ELZdnQTsL9uvk26xNadbDV8zuMoTf4NFl0fbz9H2bJG3gAZA8qriDieg7bhdUVTAu7ue75zXK0fxwh8rJ83k050dSbkWAPTTmfSWZpyribcTtx6msh62xOr7RzfKIV1KlgDFF2U3Toqqq93/wJxhYrAM5zvGHcsyrK8wlh+/VaCs7RAbFD+/roqal5kItRzUAmur5avXzXF0jvAHDPSyM6fN3lRQfLdJykQYaMAtOjm8Qos6hTcROWDCBtZrkX29xrUYHG+jpIUWh1RvEJbnHjEAkAt+u6Cpgjj9p6reR81PdNPBe5/HOe1eJRkuRZZqxB8AdUqF46QAtCj00RqC1vQRujbNmrUiilaBTI96ySldJq6Mi2Ss6gCy7xIhMuuLXzRjyaJq7oQ4miLTAmiwkRBGJMHvUWVFEC2bAVgetREckUhTiqvpdnAlRNBwxjMRwP/Jcrq+wiTS7U4SDAtah1OK2Td/ihkvl4P0SBpYzmEhOvDX3QdQSwgWwW4TE/07nUuubijIfQI4d6MbvU9Qi1F2DMKwJjYWAkQLwYGRIvyNM2XV3kpxNWVaZF0WrgIydYM0iFBBgGYV8gOvQYlVtGECAVwWuSX4KmUif6uTL8m8zQ9yconyVLsS40QyVAYMwHSa1Uc0IQv6noCinVSllB/bGL6hL1iYAyUQbgtZ1F6UlcrpAQ3+azlDajgtY3huB4ZB/eFZmhu8k0SS/HgUhNEkQyDWS8+Fnm9kfYCl2oRYaemCEfrITbceb+h8IHGwySWrAJAvZj9mNzDjbBYaLELAM2xqxCa0U8mMPpCMzSXeSUcXV+oRcO/jy6ml/AddYM9T6ZZbR+GNkQioxj9hLV2tMQDpOJhUu/DavtGv/Il7h/7GpuZeiZO2K9Q1GRPRejGoHgYQDge5QsQTo2ZN2NKPPySgJRSOIW/GSJFwl1FA8q8yW4NW7Rm2oQJbmukwrx7Fp1vz+41jfWZXUSYiSw7Wh06F1uT7dVQrbqE7ypKDIP++qdJJ75EWEhI+9KW6wmzgkYqpumdWIWjAPQqvPjKkFCjl137sjAZb8B6k0ZihhTBmTowXlSIWRhjGaPAyYDoFQuQAcixyhXJwuhN3+42jdD23V500qD5VijQ9IX63nQ3MoS92V5x0bELiErIAj9AslwJd3AKwBTdhwSqEKWEs1kYM1Vbt14EdzC0fEdG3orZjo5y1jJIH3YmZhEiGtAcFT4mUKNr4lS1a3e9iZKlkHhdmcH8YlIoBQsNYmTvfwYVtCt1UyyG1A+cjWMSUoAPO7NFK92aZQF1WuVaFD4gMyck4SGWbZjgNnMPEefF4i4Lzve1nUUMtVB482kI/RQWeVnCiqkCJQtjMHrh6jdZ9fBXJdNamiK9whI9K53b23I9vaU+FrF7hTgnlZw23tKHgUQF0dEjDc0e6G7P2OWV+gP8fmT88Sd3UmyEuDvKJxAzB6HskTxNHQPKCY84by/wmSxPOQW0fIDySiLKyQ5nFRRUNCCgIHE8G5J6t/QxqpJ2NKzRwKgqGroZU4tGKqAVc3Q8ELdRh9mmTEdWsmUNkgRDsiDZjoC6qilzIC59mHrbH6ryFJVAyocnriCiHXfmqyCaBKuAUjJ0AajU7bd6KomSrijHw2RbCUQlJpsKjzUElbrdW8VFHIx8DCyoiCakQqEgB4dqWHbpzjNuTzabNAGLm5zsJ08UJbx8VKpqImIRgSIKWimxCugmn4IglNvypwndemib8XWVQtKsxznskusbFke5KEgmrGAwQlG9AIQTohUpYsJxhhLw9BO8cvGueKpXJIbFL/IacIQemYhEdFRSKNr0MnRrpEnpw8Nqh8VVUdDJUOpLMQ8txdiG9foCB2o+Kr3G4EiucZQG0tmvUhyEcPLBiMBFJGLC2BTkEWIcRe2kWqbj6qQMJa+jZQBpVQWLmZNR24iApErsgeiqX6NCcLuB6teqHyHHWbJ0+OLtNSirAjI9WBDRkzwVDWrJB6uvLKIpF4mpIKdBC6JtYztif7KSUZy3fbSpgJJCQMXQRPBCejGBpipyCXEOTKEuwZuCNiyIfAQMpIgeRESxghQsopGIwAT9yklBA+rHQU+tN1lodCLvtJLrXPwQ3Q0toqMCTwQPpXAgcMBCb8Q2jF3ljOBxCagiJbIPQU6TNIUyro+GV1CFATUYDl0jAH0YhCMRqY15397HVFCJg9WPiq2iotM2Qt+AXBxikRySTYADvUQR9NuL1gKiqSvIB6isJyKf5A6AgobqJgSEJO4qeBOyuyChJB4PJB8NBysiEnFlQ0EYHtUIxDjL1/gkZHtzREwPDk49Dhbce+UJkQroIyW1u+lyDR4T8PQJpJv7Os2QdKQKFFaMrqbW4NAg8KaqYTMK6SZrxoHc3a0e5drkgeSj42BF9CLuGSkIxaMaeG2iBjt+3153EtODg1OPgwX35iIhUgF9pKR2WZt5mioZhQZQLAESTugsaK5+qVYRhWJgxtheWrttLp+Jh06CqHtOQMqG31+h0xCBRCVyi4go6UiC73mF9Qec1p68yCcmhwxcPR5JLX+xq8AtoZtkClyox1wOvGWuJwoIqK6hGKeyopDb+NuNKjqq8Su2rXas4aw9LRElkAammZZolqbe6ERS3RZVmn5mFeVDNqovjg5U3odVBguaNDmwudhfolVSVwAlHxcPLKIbebVXQSQBsjEoguNV9SRhwDTDoKGlROmCaHVUYdCNQZZb8payhCYkjGYEBKiEGpGeDCQSAQ2oW9chmaO99K3kjAbGaB6bsYThiQaXmBgcRb2EBnl3XiU2CDiTtU6MJYToINDJOERMYAfaNJlK9R5dIZx8MCJwEW263AAKughRDezFbdpUyVMGQtd9lRQ1psFYslOUSOF2mwNKHg4mriAflrKeKhyMzdZgEBsmbkMRGzYYJdu0F4ZkFKSA1o+PzgA9AAHprM9aZnZdgtjfIqUXC6JZP1tI6SrUu8tYTOOQQBFWwgOZdF4RQmJNiHHCRbp0KbdXsMerqASLH0m1ItKf8KTRVZEPTlNTRDYi1YuCajrEMnYK5VwUZbG53WaikdNQXEE/UGE9Ff0sJJO6DQEppXMUipLfyAk1Jiddy3K8VOUhCUs3NLjcQ8mINKKfANHIqS2kVOK1mZF0Eo/ANCQJBNmTlOqoEl4+JFU1EaUkyZ8URFO2MLAeK2pbu0L1lewG67hsLPG6zpVj3CY+EJOSkIGQj4sGlMVe6q9AMniGXJhk9rDbbU4yORFoQP0YKHgVSfQqmhilKHZORmVn07mLg9V6FFhAnQ3MwHvZ0yyugZ0KVKo4jTQXQqoEhKiCWCrQCe2UUkeIdGChve28noWksAZnRQaMZHn6NDI7sXkCb8l84SJ5JAdXiRBpLbFs4vIbKuWTHLncmKRyp4enompdyoEtBqlanZ70G2uNcg0r/BNSWItxKbwVnhQbx3dBx5zp7wgxgIowARF82Ni5cUkkzuJpQDJNRe1g1fUVV62kGUr1BNa0OfD1K0Va1ltZMlXB8nZAo1if9tiEQkGdolYlIBw6IFKozZgi+DpR59q1XTxKbK7crabwiMtM2RHjOZUwlUfygC4VKM5ILJ0yCkxLAhLaO3MAiUsuo5r+h6KKIkGyabYFYxRaCphiUlBanSZaPwnGXRgvs4OqS/r926S2F1X0O/mQczKqEqXqCP2SmtN0UCi8qEJimmhiqC4IE6VRL9MNNUUOK8R9WTisBSc6T8v1XetK9dW4rvPglYrqENRWKqYszID0bx5olxRazIUajzWZlOgs5qhLhmw/UeoeyCdNyyUeSqfhtmAp+00FvJneOZGovp1H600Kts9nyqnDQOqHRFfwphCDbuAMtP2zGdS9CxF9JJDyAYkriOhDPuyhoJAE4QhXRLYtK7Z4HshkKIpt3Joso27VhJ+/+anXfqRVtKtCVlOx2hRHDFYtjE1MKzK6EHBI0k1ENOYFX80pmk11i8HLsRiRmn+52IbyisZHO4VDTxZBDkjWUfFy/hyvomwJriNoqvWPIglcz9pKCj+vrq44sVWuy5isxyvccrbPQoUlJf7DmIY0tOEgqUohqEYjHJpckleolMfA2jrykeqqCnOsSN/UUmUV0TU08BGxrH3FFqWrYj9YxXYVlKjjbF39C1xafVsCKR+YuIIwaxvxRpgqbZsY4Qj6dv8y2K0icIgHUgyFhRWShXisTEUWDtfAQULco2H6S8K2Ik1eSXoZ0nzFKZAPLMDop9FuL4p8rSKdClyli0lriRU/5kE3pZonRz0u6W5yC8IRwMZj29YJTDQC8cAkI966u+XevRMJeDm4SihLa4lFPfnan1LYy/GK5Bv39F9Q8qmYTQhnNjAVizlRarSAWuHjfLeC9wAFAbamdeVDNkUhjFqTvUCoCsc1bU9EdsFDigHCvunXBW9nWVIlUaq4gaKqIB+5sp44Fpx7G1EZDq5CP+xlKOHbj7f8u416Ykrrmg9chsKExKaXpgxbFFBdO6muSVrJlyz1GVrF0PIhyytJc7OyD3AqCKrAPnBSO92OZLgT6XYg7c4zxI7z29sGAQoqjpIMFH3Zb2/RY5HrqP3w21sIEoNNVUfpl3wB0rIr6J4x39Zsv7yab6IYifP/MX/96nmdZuXvr1dVtfn17dsSoy7frJO4yMv8vnoT5+u30SJ/+/6nn/7j7bt3b9cNjrcxZdf9xvS2bwmuLTjbTClKt74AF0lRVh+iKrqLSkjxs8WaA5uvo6L6evcvEFc4uvqZkSW/9VTuGuweJCSPK/mZQ2VIP+pqod+tiw+1iN1kb1DX3qgOZbcUvYCDRAIBjxdIzk356hDBHC6eqOiWTtv92QKSI0/rdbb9m+U/eW30L12/+WKO4QMgHkgmEVEFFviScpNGL00OZwYjXWSNs93pBSjbEnOMs/IDuI/qtGKIv/3M4/rtLcMBLK+95ZiNkQAsBxvxN/MOrQ+DU6gcOFxTf1dZ/Aq9ZgAZZP5SVmDN4xOVm2M/z6K7FDBD7D+a40GP3aKpY6lFfjfH1j4yDGcTPTPM4uRLraiJpv9jnSw4Mm4LLPBBHk2QlMGvVlMIqRIrSlag4NARn81xfY7K6vwZ6oJ4265gj75VMY1XAuLYBlQ11C10AI74W5+MooEeYmdEIP1ito8EVL0UbiAA1dV3Vf6dpElU0ijaTxbyBMR5tmCw9B/N8TSeOBJJ82V8eQvndvM1Oy+KnNEpqAKLeYIa63WdcYuX/G63ZuXixlnKyISLk0yZ13EMZYW4h0SZZR/5GSE+26h4n5LFAmSshtd9tdrxlugk6QpAczGruF2PLrTHKxTJXOHOSGPJ2+p+lpcApZMJZoRnGEFNNM4iYoqshBSyqZHJxFtoXKGN0lWtWGULfbFTV0Wqqt340OwzFh3+Mub2+CVZA35b2n7dmYW3DTr2WWuysGqD5SWvOsyK6trjbQ26xBzjt7IP6/5WpDROtsy+nxxKT3ynSRahhxVFKLsyc6xtzgKe1akCOwmQcatv+9VCpXuG7ZecfCM+22z7l+jFJXrPx59scNwUEWyZ2+ypAguu2yyiCiy+8sohXbIzkobYr4Jt7H7b+Vgi56pI8gK2zypj3VeL5VHka2Zp4C92GPjtbfvVwurKGZsrt6vN96L7Zo7lGmzSF7Yj/UdrPHyXqAILsXjGSMMzm9qnMbOi8QcLBa/GJx6MYtd9tOhHvmB4tvliQYcCSIQUXWLjM4CbSnNIS3sN+s92uARdIz5bbDfYPohxNm5WWrBlVj1coOe/ozR94TpJlOyMkCeH6nmuQGByOVZQVh9GzjebCj/1DsdSvCSiCszxfcKva5KImi8Wu1desO4J/MVKQS54pW771cbELMunvGAPCfqvth6/ecnO1/aznQHQHPFBqbaA3JhEackNVgSyM+u2DUj5AqpV7mkLUqgcVq6m/kAqGtmo9ExPBmRjlcRpvQALMrJ+xrrApUAO7TQv1subIMrtsXdZNRuKSBsRgNlYmLiHGBey29o4Dna6FWA2XpssWddrfPJ/sm5eVKH9N3y5BfboWY1dUG5xIINrXIOyKpK4khNKBWdhRtRpKo3y4Ap3R8xpb2d6ST4NdhdhaI1yGPm47QCLhy6x4NduIByDkgUO+HBMsRRpX2rjubkqwByk0HpijwiZot3l88CMHYKTx/O+4IZZJMRn22UgwcgV2hzzNGF+7AlP99XKfoj5rWX71R5TewjJHSaKysc8YpmvoBki3YP4UttYH/KiAR/yo8oHJcf8KVmAm1W9vss4S5EpMsf5PSmTOzYssv9osU7quzQpV6yIIz5PGVwZ3pO0dw70q+66cgBR7i7ARxbbouhFpsgqLBCODL/giq8bCPcFCYz1gpxli+QxWQh8d0KAAxGdnnaBVLtbrBNoqazX3PEZXWLPX2AN5RGvhQqKrej79DX7BG33K27bZMtsXHlNHblwFUNYWKigiv4TvDw1KQQo25QqscMo5Qeu0A7vTVKx2x7x2SaUMc2fel8LTqzPRTaKICxa2GyK/BEsrqMKSth5zZzhCYotpEFeadCLIex7f5NXUSqmjxDCaQzyRqRA1gdjN/lJzGhfdIlFcGGyTirUoS6TDhViyBZa9PShZjqIPtj4mrL6PoqrugAFCnm/hIolKy1kMOatfITcRONsvtjY1tsHLmnDevvdoj9tHZHSwJbZHA7/UScF+FqtUEK+LmUMfUosgrBuYatuiPFT5Rbrt4ZyCy78GCkaJ4sFg41dy1pom9ndZuakZ3f73T6CiZ1Z8ruF+p+lzfIk0m5SRoCg3MrX2yynr/cS/GIIe2qcP2+SAsdqfoheSjFlWBj7VnByBIxBtLbkUBbaTVSyyVwp1YYvtpCmVE3u4hJXatVrdI55siwA4HVTvtTu0LSveINurnKnp0yxzbrsb6KzC5MosJFfbaWzlzgFn0G2ZCOBxRCuLVyBIsm5eZTBOLSCFQyMhpPEIggbyqPDMOF1E6bIBucF5IPukI1FSpfZWFwLfE8uSrvaZyt0NZ21vmRQVpGR0fP5M7S0+MjIbYENvvOSoy3+ZBGeFj2fQbNvmaOzUSZKjS6y0smgbTbLHuGKhZW701BOKRMDWcX3xw9ijzJTZO3Qw/VPHqMkje6SlEMvh3JrSTwIMYTVKa8CO19qYQ3kT83Y21RC7OSKyq2spOT+Bfs7LvKi698pgLYpZynJAS0OO6L4IUcuBJQWgjvsYAst7W15jkfO8DZPBylvE7tC2uN78byLIWxbaI7wVS2wEJZ0A4u2KhcCKSi2WnVo3zmtX07rquLSNnCl1ph/JOUqTcpKgZ4FsaBMI2tSANn9CtphvGdMDGFx/ACtIVw1idmLDlSJVVA4i8oax9d0IUCz/WrtDT3jogWpAos9CD3IFqWC3tElbhjxVdUPcBNGz2Tx8btKSLcWz7OFUXssnAWHte7F8wzFoYgYTQRgHdtEYQEi9lECWkc7GbSnArSyxMiHFRgzTPXmghwn3K9BKXybSjAOLbDVWLrT9fJks0kT1lYQApjj/wGS5YpZ6N03C+oI7Dx7y+5HsmCRtJ8s6CUYzyfr8fR7hFq8KMAc2lIJFinQlAfxQcMEQAq4mKr+434GByjG2r/syPnPmDL7wCGUH4y/IE6X2Wj+JWi3M4Gjgi91wPwFRGVdgOZNKyF2CsKhBVGILVfogBf9UOImAWxDy/DhJe/t4wqtQ9ZAgWLuxE4iCYhjG4gG6iZ6COdRbF+KV46EBNuZMJ5+4/a7cd9F9NkH8sirDhPJg/71i1fpeiw8e2DKrDzssthGpmhXgia72iLxw5bZ+AtbPYa3bKgSc4ytvsLhI79bn46WZ3m9QdcVFtwdXb7cQneQYHXD1s0CPvrHyr+MX2kIy4iD5oyteSOaizggC3dG6rUu88Qz22TneXcQe/Kquyr2ULwcj2X71Ub5DJuQ9TSvoLEuxSootnFz+meW6+ZaFt0nKj+GyalxNYGxslMxvtQCc4KDkDiU2882vVyCefJv7uZm99UxLLC8yZtrPmL8Olj7/n8VHYdwhZbu5mv04h2LkyyYPMR1YMfX/gQA7qKb5bBvdciph+/tf0/A0xdOS+QKd0bhaqWn522RBonLbRFZzWGUrbY5UXIzpsjmxMQ2h59U7IBcnNiMKrCJcDnmI+PL3a5ThVknFC73y1Vjr5qAV2NDKnHuG83EPr3WFeGfvFaA0MPTp8QyDGt1TcvcH27YiIFc1ymQJWoxALcKAxB73KgCC9s5SdEjCX3mFcZ05kptdPI2BFOMWlBs44RrEoYgbZbOS8M64+RwLq1dSa9EiCCcWqBTH8laYaBcWuomQGjbKcDcZgkLA9X09AAWjt3nqoj4/MrE590RycSZqqcsJjC5CGFl9V31PcK6efEJPON8KaxnnCqy3tZ35BmnadXOQP5wGpuLW1yLYfdVT5lP0s0bOSsvQIR01m5n4W5UsMWHz63kVdEgDEsidGdaNZbdZ1yy/4KLIFTZkYGdGDgc53qy7BS86q8GhD44JKkgO5yTwRwP6NS4jsdo+3eMdjw+OpTjo6niatrV0r63EkQ5azKruKtlsvq7r5AJRLG1GG5Gz8fwkt8tsOGMPQym9pvNBYl0c1+n/wAlvrfO3pRgCq3xXuZytH1Z+CWj4e5ZiX5/vf9vXBAZvWb+u0swGY1iLF7vmuUsXeK7haGwOVks0HuATJe2n22Mji45E2ttbL//OQW7pQw+TfNll87Oni+VtQfiyaY5XuBRBRbnI3AIV3nJp9clvk8+S9RO10q6DC0Zz01TiWrQ/bNpX7KLbgvN8f6IynY0NEry++TzeAmeSvfVpqw97GrjFRW6ZLr1i4giShRPfp983q/yNP2eIzXqGsTo6RiHdatFMdB6he2eZOUTrwXQJTtnonhG/rdYPJS1sbU0/olRusTqqlKgF5RCvFEV7hWmrs5FXqwjQSpmttQe8zxK2YwWVImFIyt4CuLQia9O7u+TNBF4VKkCC3woTRHromu/je8+mpVN/E73qBtDMabQImpGEkTkFi2EnpWX2FVM0ZTWEOrK53yZZMJrenypHeYuvZUUOQewM3tVOzuep/LdHNtvVdKaw+xUF0lRVjzrE5/tZp5Htf067h4FxfEmyl44jbb5aINHGEvnFp9XQX6H3x6TLOZkNFdo0Ucu1diZbWqxhu3esfta99Ua03shpvc2mP6ZbJC5H6X83VymyEIrWOUZEOXCpgos1k+XxJdZP9vPY0j5qfR6vAa8b/Q2K8lFrZfV3NXDdHy8WbYRvoKTz22RLU5xXg+2zOIc5Cn/DKoKGpJlzi8/vtQC86oAQIVbUG4xRzU0rpJYiJktc7g1+D0SP6rdlkx0PBv8WNX3UHoieUQ/M+onlWhcDsJJh2BXZVTogJ/w3DlbZpDUTZpp9uSKLrKJax/nWd4hHrOdarWR+qrnYiNRuaw1df2hHPHhDIMAqsXdXQEek4hft3TJvm1REzF3d9box9cdFsdzXHHVYbj5c5Qta5R4nsFCfh83jOY0XzBGbfPFNvKwccsKH/TqiyxwtsEMbd0z3hMphrCQBdtnruSNSIEs5ihaMkRpvli5M3Y6b9ifMwp4qIjSvXNPdKLLT4Z3WBxkuLzqrir/XY/P6hSnIRBuBX2hxfFllvxRgzkQWOVMkYXrLY2WszXsD0qgwQ9dUGwRdFkx7mf8Yfo1+KfWyZq7ofOqwBEXZV4XvnaHCKPLOjdCsw96W9d7fjnRJfYYBdeImSKbg+CLIl9fpfWSfX2SLrE6iM/rmFtXxOedWQVdFJUf53dYHGPexFX3gcNDWCb4fWxG4W0+WexedcpsMc2XafaCXdfkj5acqp3d0/j/nBbZVFf0+6hPz8tfPR7HYFhZ5WH2BdSiKAB2bJupe8T3e16xa48pOmDdHBHen/sc+W4/NRF/zmsCFFupKYpd7Ism0ioGSRGMN/yPNSgrqJ2jlSXQB+jiKQ8rd12z2tOjfvIINsy1AoTJ42qBuPqu+vnk70W7vhYtCWR3CmOHldrlxmffZcvGDmbv4szxfAsj0JuSocPPp9rnQbFOShSS0Nx58tzzGWwu+78Wxa6uwbDXDvr0bbSd1n/dGQ46f67QpaX0pK5WsNEkxoEJIbhJhdmBs+zQDcNlYa/7hwir76gyWyCa3CesniQqt8feinhdIwIw87a+oom9yR8A4zQgv1tiO4ljUJYynFSphUaO4pqg1imTF6LynVntF3lRr/0DaXo0DutYUXeYRXuTb5KYRdF/nGrx89e7be91z67E2UKuduJG294lbsJ8idkiwNrAeFwXh6TyMKsDt8ii6D9OtjoQCUSvS1IF1jcGhJcFLJ2LjfRk/YrtV3NM6IUaBk37ycYBVlaizCzkd3tsspkUldtjx1m3hXibkqPMspNZkb+wcpVTo4qoj0Veb4Ryqi/Z52sZl/3ew0qW7vMUQgotTaH6RBUcBZYhzxzzbQ6ktmEREEBtw3hcxaGk8q76u3b/YtkucfeBndfgufBbMM102q8VSb1hlgluTJQRgCqwxMenkSI+2xxFCt4DcngDKKzl1V55b30IXM5HrtTm+Lk5xpKgFhRbzgu6dlhzeJki+/6K0fKlFi7E5u0qMWKu0Bpvc8VZ6p+UAdlz3FldFCCLX/hbBGIImxaaetcRG09Al9j3mT6wlK0ZHsrCso8qkcQhPtvydX1XoZTNsyxOYcdE7M1COLaALjirW+gh7FvAaaf75wZVYxFDeraoHJsY0rbFViQoxsZCOLagGAsL4dgCrMuvPTGEo3yCcj5BOmaUXgAgJJkBeIi2hcQ0AA/RtpDMBuBWwRuoCne1tPtqyR9irnPhtG61CdBti2x7x2e9J7/bXJO5r7MFWIieGWXLbLA+RcXiKk+yqvwBCgDnlg3sk4BY7HMrED/kdXVSVUVyV1dAauepIT1aLP9rnWqaakDsd29ZyKeo3CIUKXCezl7z3kg08o11gCWyC6DQhYYvFAdnkEVEKoYa0iZ4pVjwoV7br3aYeBV0+9USk2DMbiP8EpUPYKGmpgzGrs9nj4/v+R43X+0wnT9vkgIHvXzJs2rFI+UAXPH/A0QCKrPlbhz8ISlAXH0Adwl7u0cGZOOC6qu1eWc/5anAHSWD8mlJxEFyKKeWTqPsgTe1hADO+PnFKgRwwz87k6NGZU5Yu6yDMsx9uRP22V3EukfZQvt9AeskwszVYgiLlVZDnbFI/o2X6U0RZWUUo5/cxqaA82+NZ1I1pH+L16Cs2bziOlgb6bhBGRYU9BRD+LQgGpEcyioSotfyFANSgNlcXiniVVQCqRdWCGBjVyXiuxdUgb3Hr80RKPT3dWX2WJHBhRIe11WzRyu9dMaVbA58wuR934djGdKMugbrKMm4mxUSEPM2PkXlJXhqjfXLHEW8xoJ8bwqwnTkO6gzubyW0nT4lZeX/ELoApUtOYTM0wxwe9R5Ids0Q3y0dFMLDCcutYt/yPn1M7rH9FJC5BChdmMsMzTDM1bXNYiG/W0jQEix+JNVKyGRcoR1eQaoY4vOfgHHD8KoHf452hb9VxwhuEbwbJoey537RGRxbZmEdCLy19l7aWdn1AF9HjDidSQhgP3ZomG5EtpKo3EbziZNNglQNTqdkihxwCm4ssWUWejHIkNbPq77Ed1tsgg5SBRYeQlCW3C35/qMNN23JjpU/npsEAAcsVXuJESD8yDFllqLugGFIqEFJ6NC2aHx98irgc91/ryNMZRrV9quF4pEl1VWRxOKTYb7UAbPw3JcvtaGkrL+ufZX307WPwgNnh2PlzgZqtlfhoCUgrm0IySABsVAZtMekvsejimNR5+PQD/lTlubRQpC/jCmy2ai6qlK1RwBgEykbg6wEHQ4uWJ8vtug7lJ8/QLJcsa8QEt8tQiDrbJECtMswwY/Ed2v5eoZuvIokbFOwW9vzJU6Q5b09IzSu27O47pDbs/9WesllP7p0S3h0k/dP5Ioi6cniA9YUSQ9tGL8Rj9HFbWSEZRhODRvr3QyBkUrtN1ssp7AEvfYiQNYX2fmdRNY8+X18W27vVhA6RWoSsfqsmg6L46tb4qq7Lclviih+gAMTHVqyZRZYURiiSE+hCixPFoH4CJQtszonTB5BIX4tmCv8E6wef2cFicljFY3psujbFJyDd98dHCDitWntP7Z3NEymw1R1kV2DP3AGTE/lhUDlpLYo6w/ERoEuuYVmoLCKVDi/1zWIyjy7yItmtlhfO1NogxdPO8C2O+vi4Aqd8cojOZSA9vMmTj7Pl9pwanR/f8knjSa/W3h9FuskE4b40SU2lCaWr/jqoARkP0N7Jtv0c/znGRS+ITZ+GpvT5q9DsduS+yoqWtWFv+lKltj650QY6RIbZWdLY9GRtKh8MqM94HnNEL7pblSQhUEBFvigQjxuGmKK3fYo7zq+mW9A3EcmbufeL52wCrVLbmE7fMOIxZDLTzySrxthQLYG1vI2zkWSwsXHBcWyZVYi9Olr1pJB9r4AVWztFd+f5zdUk+WpUCgwuygXVugGUjSEfTDj/9GTJh0GJw7Bg8G4b49SaO0bN7TyFz8sHWBnh3jct3Fh5alnetp5+R4VCVRi+5Vwlq/vkizy3zX0+N3n0QrpQDvIQ81sFuiDRURuxT5c2nyxOGGMsvo+itHtvAKatcLbwDIYy3AwQRyYVRgOyJbs5fHumzmWH8mCRdJ+srg/JTgI+2QfrBOVABNBdFzMFTrgRT+UuEkACy28LJNlBq3fBHPEjH2AUVRuf4aHUpRy15eYMnvDGwVJsljZMpvAM9FTQNuvh+MagRI0fhB7L5giS2vua119vcco2sSOvFHHg+z67jfIlhdun9tHh0NbSWpkicotwhvAcwU/8Y+OEd/tLk78USeF6L5E991hOZ/lWVXkqcjLK4M5fIMg7JoLsNhGX2UnaRJxYhN/GtWgdMzJvFuCG98MHUR6N3dOg4lwCbpB5TjbB4kIFoHtGzfDunkx/6OOClBeL++YAxa20NIMOln8qy4r/nidK7QwarDxIUPMl9rsZVcFmIMUxIJbFVTRGHuNDCNeDqKNkSqwufGRPaDjPaH+whUeYPDXTQItfcSPfsKwR+Mg+RR1d19dDZ0hHT0FKzuE78qmOIa28etMdY7QvGDgdWyAfrqcEojrTe0qlkYAFIy7AH+wYPoyPc+iu5TdKMjvFthAXBeA6xPx2cJhxz8q9Mn6RaF8mfcuLWZHoIum3Ak/Vev0NF+8sF0kv9voQVkFGbvzu12C6ikvHlh1SAxjJUTggnvBq6VLks9LYTGMdSvnz/EqypYApydTNyYD3RnR1nbK925ZNzanR+tlVXdVyKlefnB78YG/le5wJR0t9s85LORymFJFtprDRV6so6riomX4Uhvl2O/hIOkCre/SpFyxuwfxeUrBuksBd9JR5ygZ4Hm2gDPKzAtTtDMiDLNJ+ymAktaCuepq0urDSLOmeRbH9qstJl46kt+Hs16mMlJXYA2wuwnqlp6GKonKxVhV1x+GfXCjzIFF82nMDVWQZM86wd6eMNxpFD/MMnwUSWYZ9mM9CVIHJjTGNAw7Brt2EdAPE/YCw97dAyYDd27AGio+vgdlIowOrGqGZldtCPTc+VXEhhFtvx7wsesZnKhlXryE4SYWm4sFqkVx5KKd46IukDYIEzHIPOJ6jyy0Pyz0EWSgSOJA8R8sNpeUx1oUu25E/id4aV4CpzBtv1ph4pAcbQohH5/E6TWIc99c3T0aB85V1N11lg3r9xjmtHVWzhasW7f7tjNs+K0IwoY9Ggc2VNT9c7HhPK0Zd33zZYrAdNmzlernKqeSpd2jl56ytEPjIkvldYdhYsmb9E5v0Yd6Z0nEfHLWm8o3AqKyLkATHOfpFCFRuXhD1PV31fpoHvOCqt0TEtq0wKKLrA5Tk5w7TUWfDtiWaef/QwKbLL0dyiw2d4ZUoDjy5IHz5EVe1OtwpxwcOgeuNMAx0MkG0Sb/DgZfOtX5BCYQi6r/aHGwmG+SmMXTfzzg85KrAr8O0CZY9nVLkricvJJqBLt9indR5GsZd7NlNpwpw0mXWK3tf4G44pZ089HuCiF/edD22mAkuDIYWXobWoX99KV5RphFyBU74e5ffpKiJyAOWGJgswYWfM49w5cIRE4uPEXtgQzPbZPCK6l88WTH9sL3yu3zmO4zcyLMwRgUAfkxqRjDQDvaACaBv6EivCmhuCYx2Xn/ehMlS0/tv8PidL4vq7qzJmggjQJd16CRNF+mjFn2jf2ezMGCdehAIVA0Mif3igbDrrL2aRzj50hb7zLLAILifVw24U5PGmrEwrfY2bLpL2Cg92ZRYF6UpmyOH6rEQkesqiheoTXw7iLhz1QFxS6436txv/fB/bMa989y3BOJukvwVH4GVQWKcM47MU4HwWeKaCD5J2ydd+ip4CzXvmDJD3eQNp4asCexNWcrED/kdagkQRw6F3VWj2NXN/9dTpKF3wxDtlMbyinGLYey6fdN9Hz+DDgyUAUWVI2euxBmzqVJF1ltXzudMiyc4rUrgiVA4igxzhAiZtRkUVzrnA9PBDCmHDsmYfJbvZO9RxfXBXrb4Sp6Qa2GeldXhNXtbV1DTMOsO7Z5Fpuo3Bx7qNdJ986FzpItLLcFYLPR5PpLnAJRCm6qwBLfFSiSfMHtEHSRzelwFaW4NuMUoQpszBw4feI3aqmSKVxLoWI1Z1lSJVEqXOBs2QGvc5xgJfk3zuuHBuK7r7D4XHYUAxzDrPXzYIHxbuHjUmw4C474mhJb5oBVgtAel+D6ElWwM1x/DdD5Q5fL0ndvI3E57WxqBMPw+lWXVuAdawhsvztgey/B9v6gdfSzIi9LaI2kQTiKxeZiC2tRHC5XTbaTekZCuUVAjRj5BJv6DB5Bym1qxHcb12VeVF2QJ+OuJEssQi/rNBUipAosVMRNe6DLEG37earILvQcI5chtP9oYzzfg6IABYeLKpjyxCV8sriLAr22jXI3MrG5/WebCDucA/RTVLLGIVmwMyIKfkBJDz3zzTVIXFLNyWru6snPn+g6/vbMxdcCJE5vXBLKqWrvKp/snb6K77R4TjNC4TLB4nrDTG0T58pPMPndxmUUp/UCzLJ5AmtHjCHOl1oddERliYJxYUElPu7gAWz0haYyqzB0Xy1cjUnF5nRoP40ZqfYFVFEbvcwoZ3SJHUbpWz5coR1eAc2Iz9PHv12DDIqnk/JHsliCilUE6TKLY0Fc4595xgyd/G6L7UcRbVrVSoSUKrbFjd47F8wUV2iL9zRHwp9d0GyZ1TEu3KTZRyy2Xy3XMnrUl1/O+Ov429VJWeZxgl+i5fYsuJXimNHbk80mTRCnEwqLYqNS12O3pw5apoTAPXLB7/7SBm7neV0In4Ex2u161KJtDxGz74tnN2+iAi1vx252xLLs5m9vhfPtwxK9/82KIba1ZOwgcaMZkbnDvtO80HfSjxNaNGMzAg5wg3/CbXWVL26vQVkVCVKSztAodRLCpDZny1J1GkATYaFvy5NNmAYCMItBlz3FR0O9sZmmc73dEtc2VI+F8ND8qyANTOcf1NOWx+o5/T3CABMv6JzfRLd4Jpvo0yRN4efefWsw22wV2ZTbzDWN05OmDLKQ086g9mNM645BRX6RoPl8NSsv6zT9/fV9lJbAcvTezHMFinVSomRATZK+WzIhopKHdDW5vBcMPAluwF7q5jxnj0MegM80HfbcUwhck8kbY1aR1ZBJG0vWEKPfxZ3mUFjgGjxFxeIqT7KqbANib7+VYPEjqVatM1gVUaKtzMeQcFUM+ELbkOcM0LgC8Im+w7u4SenIEE7gdKq6jYbD1Qmh4jBIPfmIxRZS4rC4d5GB9OP3V3Naw739/yZaqnUbETifyAsXbmFMtBgesa/q4uSSMO5cEH8JxBR8h+p8rcghHiUZKFiQ3pnbfun/LrsPaEKjJfiSL0BabuvN4xVYR3hE5SaKsQW/ABdJUaLQ8OguKkED8voV7PtjAqVed+TWOlz+SM/SJkVWB/AlypJ7UFY3+QPIfn/9/qd371+/OkmTqERxA+n961fP6zQrf43xuo2yLK/w0H9/vaqqza9v35a4xfLNOomLvMzvqzdxvn4bLfK3ENfPb9+9ewsW67ds9RatEZaf/qPDUpaLlJxx4uC0nefuTelvWcLwxm//CThO62b4Gty/ksmR396yFX8T8Bbqwu+vE0RZvBrx4ww4ujRC97ozBAVwz16/QuIGXQrtRc5bJfrmtLRpIHuMingVQUXkS/Tc3IH4/fUvP1njpA7VjFFXBR+CwWGm3w8dAjehx7Uktxw9Shp3H9Vp1SG5S3RIyAMWJQOeP2/yAone+4Q1EQ6XA9/9ZM+CnQAiQwIUTfz1J4c2+vRWptPMo0D5Nrqryu4ch8TyokYek5uofGBwsbLx11m2AM+/v/5fuPKvr2b/dcvW/8srvAh+ffXTq//tQnnEnE1+iaYfdZb8UYMEM8h9glaXLU6opmB1CsWZMfP439bR83+3XeuQ7pBrQ2H7HJXV+TOIa7xro7tUOHixwYzCGasE8Z8HUqgvhEbZR85aLAu+EWPJ1fIYQBz2ZxFcvzgIFawc2WA1mfg5iKFBUvrIGJzikOuWgXDBPPC/qRH8LZCwNWj9EjxX13V221bvJds7B8kG+WfzNTsvirzwkfltl4Tr2WNEjKw2lQdycWXQlx4B/gWFlG9vZHLOqy/vHClTxzGUkaHkLsk2MjXHemHPyk/JAsovH36Efy6R/+EKQHM8qyRCwqQ3HSaTrUU/WuOt5e81qMGiSUDXpzI7kD2GGJu9XkdV9lLqWpcBsrtIq85lI2mecgy8Drq8dXb0aWr5yCzk9+hahWO5S7KoeHHSIbXKxHuHTfMLFFbijdsKsfFK/JA/ZWkeLQ5k8XXDUdsyckazX2ffStA1iu9ouUt1ARovG6fDd4pZPAzPt0G9Ig51tepAJlhHTsjOn6v2taAQ2GblJXjymc9ZeVNEsD/EDm2qA/dX6m4JJF6aMH1Jz0FZU3dJv0O56AYHIpS2IeHuezB6sSS45YqQ2prEJkvnJg/e1Zt8iI5eg036YtdbC7RD9PjsLDTG0zgOjbJPmh1YZ2xuJwWQ7fQlaLmpaKZpow1xG9rs6BCGSLTdMdoEmazedvo1XdvPAKFSdoc/VyF6eiD7RJudWaX4//KLvVrZHJLprRUatwmvfcpL9SJ36e9VXsg8GqYKeSFSKf071t/SDIy48QvOSy/rAdkhzSEmFG6YA2GtAdZdm/WvuYNwIAuPGtOg54/P+GoyFVE8WwQ/POibae5z6Fp477AVd03Qd1IGGEs7Btwesu2wBfrMbm3WXpYsWddrvLudrPEW0m27IE7WUYriYeCvEge2vPsbZDkUvgBn3bb7cPAjtdRg7y4FhaIUythjGhwS8OyxDcc6rbMFXCFwPfbJmptz4AMRPNvx2atqZF0vRY3Kg+3OKXS2bz9cTFLp8JsYy14Hwk9XXT42W2bqK3pxUkNP504w1b260sX9+XBhdzl3KInd32lujvGI8zenQ2oDjcXakEcJ4BTi3yOSh3gSxWPQn5IFuFnV67uMMJ1cEH2H83mXek3AVX2XJuXKL7wsVEhjUA+H2nkcVCQfliD2DxS8igrYBZxSuM9M64ex5XQofpPHZGHpnvEXO2a+o0HkjlqVdcJ5slgnWf+SbwCEHeO0LzN6Rpmu8qev2SdoaV55bi4djlDiic4OFXbT4pJEBZgWIk1U2M6epGn+1LsDwGMCnrw2xJPNpsgfweI6Qhke5/XaZ5Yu8yoovg4Zfg2BGaxn/0KhbI8ubvKT2Euf4LKAuaOaP9TBl0iU1fdRXNUFKFCk9iXUoLYLOlQrHyHLhMY5Kz8m99VZVHhpWR0O//25fcHta7UCxRWV88itZ92LcP1WrxaPLqdoJzUyxKokRlv/yWLBNOnV/VnZBxQFiITxm5pvWdoswg6d18gg0ZtV8vWew+dkXLZIzp83SYEt4g/RiwyjTbgPfskFI/Tn7k9ROY+gJgJCzCqNyeHOC1PfJwoQDgydE50sCwDWbICOw4FTjwi9RRko/Kh/aMlPnrRIqFeSfOQdie+qfyMpAEbqjSQf3yE6kwhw02tWXsBJ7Q44vJSyRXO1PEo7bGcrdFvZxbFkGnq2fQrUhwLnpRcJmQdEPeyFKEOP9WSPcIlBZN1pk5emV+XxQyA3IbKJML6TxyiBdZOUwOnhBhL20Wn3SrJg4/2cPzVjRRtN+yCTlw6f3L9gO/oiL7r+nQJoFvmgPY3iB5zTFd2Z9zzYQCYawjdraEg/a+4xKXj7ao8iQ0xMg685cAyBDw8cLFpURIBTMFUUsjkCPq1fTuuqyr0uxbS4fiTlKk3Kyh9hu5ZTAPkSCmbK5+GC7wzq2xhVEvv5mUkEwbeOr+li2AZaM+OMiB0K3sZ8A/FEqfNAjM5GiDb61xhvkjUIEMRH4j7PFgExd66m86wCRenNi630orCCgRmolXCjtgnthZsENGvXS+bDzROUFfH2+RrdxkBbyaDMCvvfnTaWbaphn1E0b0kPRm3aTAmO/keyGBD7p2Fp0+9KoWVOjziswAl1YhDmdLV7npZAEfIy2K6cugpHnjyC4gXNqr3rha7t43gh8zG5XI8la/v04zQqQbu9ejsKelxfQFTWBcC5ptRhkM7dHTaGsG8G/aCbcvBINvE8zTMWAreY71FAGy4EChTAFcJNQyFEZAiJb77KG89PtA1vDxjR1scqHUb8hDaqwOVudUcjf7/4txKECdqSh4AFVxy6ptwliKFbjXyY3k93aDWRAJjaM67yLK83KDJ64TVrPBaTLGg2IYH4+Bjvgn6MusWDtm4Lv7qx4BG/z3O4gsclnAlFHtkiNuKWwFEup3kFNbDgMVLa/FgOC6VjuzCBUsdwJCrcMMyZzVWCA0wcgtG7ij4KPopWmyf/Bj4joIKzypu8uZHAYnby4bY4vtKnCCaqjlkUIdThr6NsqfHZu4WEh4opDOuT2sHIrb3wWRxaxLmIgvhe7PcEPH0R6GwGEtg8ir0RXQeiDLWjCZlAyCi/1V8dIsZBLsslRKH+2SXk8JgIaIpEQO1B4YEtqWlv6A2nkw2411g7wVpr/4CSSnYjc3Dd9zV9Z44l7XWdAnmyBKfdYb4Bgzuo2pfq+twIXjFkXThfCGTb9zxvcjpBhIcPfov0ShpR7jRVBGIye0Zo5B2FJYaSJ/ZWyfei8PlzVURkVtEh9EryAO5ARJrWIebwjMlZnubFJ/CMUxCERr57L5nYqlIH5qudWJeSeMkM2t/W9OrArLyAJne9vboTIOJ4YA4kL54duTAEF5IUte8DXftPw40HyIaDHFwFP7cg6H48uQl7cnM8bdH2bv9OW46HEft9GGEeRtE9KN4k2D+QTakblYOK3Nf0U0o21AvM4db8rOwyT3gFEu0rmx4YfwZ9+tAoAbFLVldtemgHpEZJjT3wXuTFOvLMWNXhmkfbF1pD9TN4AqdAV69P7u+TNPFWj/EdXS/daegdf1Y2ZwhdKnefCBBF5mZll/1YCL0VZ7bPuMS5hdwhUE8/58skCxRVi/B1t8ANUNocz7TkvHXIIsoi8LFruuMH9+7wGMz7Y7wtd8x3GLsyfsA+2JkiYtJgyIR7sONLWutNlAV6zkR4/GiiZXcVvdYIRI+y9j4mWezgf2Cq+/TkjEh44JnWEa+ndyGRvQ+C7J/J5iovUaIct0g2gXa1yjMgTgDnlmazS5QVhrXd9z9zo6ZZBQciPbWO4HcOjmDsmyvbrdVLr8WIQtzWu3nKPwNErFmZa6/ivLflu5tVAYA5/p9t8cMlAookZnB7Zaz8HnlpzzuVvtrZ72n+uj31cMmfZe3vwiFQKB6ZLTNIvSaTmpdnfKRXeQK8ZGPO3aRSdSDMPZyWO+G+endXgMckEi0uX9fBhALdmE9P03yJ9NkDYdHPUbasUaJCax7d1vRiUuHRMielzRwxJo9tWhzsNj7WIMnNW1xnivwUZkt6mz88FMqbaBkmSHU379Efwy3CH8yPYeJ24uVA5Owge3FHo7M6xReO1CfG9seC+FxxDgYwVi/SaDlbw66jK3Ba2thHJ19Xh3WjdlidpgkQn1cop/U1KPO6OBgVfHL9pqOnlsld8gN1yIW3B9xUH5RDPF9fpfUykeWiNTxUzutYzvYeR0eX4Kk8oBcQJ+fQgBo4fgUrDCqUgSYMpiASeDeV2z+TnbIb+uvRwOjQGt+jydP0JCufDiYEDw3IIWgd1wruz3MTid27QN/zyu/pkqHvYEGiHQjXTL7Nh+OeJmqtlXlhTvSDbNHB8jzhbfVjDcoKarFojezCAeNuaiDDH3uSZ1wHIgv0tuAvv9grzIGefgoSDAyRtHyPcEjY3kjD9Q4H7sJ1Mf8MFKsrnzofxQkU66RE567XIEaS9nCZ32cfGmQx9XkH/BAbz/X5M6JqlJ7U1QrRNcbKwEHN++Q3q8JFwnazNSOu3ARE20rOwNi/It66yR9AGDMSozuJY1CW4ZDi+A84wV5JmowX3UVe1OsDOsu/yTdJbL+82mp+mUWmXtxGr5ia6QtX+3AxZoyrk3h9YO44kAWCB2TPom21PV8gaB5Ry57545rYXbXv0CFV1WUjib2cUShdrhcCdPEG9cLzBYMWi2TKHbHhPGqexvp+SqBDEj4fi7zeOEqgtu6wIaFOr9QEPqG4bHcdr7UcQqCgtSdSqo5iZRAP536KJ7wsD0RG7YV4ODR2m8Kp3hDvMJgWjyXgBU6ML2h+FXXK3v2wH+gkBb5JDjw7w6Qo8M5x4HeJG9dE95dqtieOwwqBq81xHRBVc+8wcP70PotYXRQgi1+CXRLvEF5H2+NbTWb2vzmvSvq4y4/a36MqtDCDRnsF10I6y+IUdtWQHH91k8RdY+jW4BiN3aDG+jcDRhoh1eg4I21lwzgjbBsbdWSwIYvFat8YJcag4E/Q3hClFwAMTVN5y0MTWN7y0NRu8Ye5Wob5ZHBG7F8kHrQVLDoGawJq6jXUdhYDP0lyDZ6iYnGVww2u/AEKAFnLM6/kCsQPeV2dVFWR3NUVCG01cg2U/7UOlBepVQIkEX62sWdh0uj1Wv8myBhxRBwyY9DTMQWAcuQMzj+tpzhNO8RCRgj5TQTqUjDFlBufZx7x8gEsZKTz7unZ42OYbE0I2fnzJimw5fQlz6rVAHj/AaIwYyf58kMChVv1AWA+9InX7NG0GR4/5eki0FzxyAMyAoH8NMoegplSDN5gS4zEOzsLjbLLkRUY7ewuCrQhtRIaKwUh87miwBy4IP7dPKKO3juMYvpd9kHQB2M3WQPXoKz5dLqO0miDLkyHJw6POGCvod3Z60Thu35Vo7olCO1OvYqSUMHmTAasIDRtUSJbBSW4rBuX4SC+rqETE+/ugQZpLlyDdZRkRJC6Q1T4pwi9cNsat5c5csvHVEaigEclnVX4rYS6/acETtfBJF6c/I1OXNW+9baaX0DWCDlMPib3WNk/QNbphmY/eduaXvP3rQSLH0m1cmQhprp3V6gMDMHdLWMy64EwaKfOELMM93KHe7sSPD4Hhh2p/Q+LBnbzzcquq/geVsRqL45CA5pmm4AGwzUc7Aa96R5MU+sxhrszMgcZ0qtD9bBBF657X0BZErdmPR8U2c4I1s0me3LILh7mgJLK9OMJGscyobp2Ne2bpX+vo3Yi3GX1tyyp8MN7Qx839g0Nfro4xmhGGcnQp6GdvdTs10OTjG5taNoNdmQX/qjuQ/6UpXm08E5GNCs7VEEUo8+QxbMSdDilOqHR7gul/g+QLFdOaqFJC6d1tkjBh6iKwjgI26fe0UXMMDxqt+1fogQfh7TtT7JBXxJpUvwuYHaRdHn/dOOOK4+kd/OwvDyTRx03RPUKXsUYTqM0Qu8K+Khv5eBRNSGNsLCHAaT/w+FJObb+AA/KoYOc9eG8QzyhJL8povghyZYBzwBxzNuwSgk+yAOhTho/gDR5BIXJG5G78iZYtwAOyIvRD8npfkdTM8D5FO83Htx7YKF7VHWRXYM/ULa6Q5l2/+tSgSZucv0nhN/pGkRlnl3kRcMrYZT0luMAtn8N7H0XpKYhCFb3jJhkyb4vntzfX+ahotxVL3uzyXntX0QgBUWIW2G7Em1isUHmONTnLCoOapP0l5ZXUdEqDl6ep8Yv5HbMStb1e/56O8n+x6uTy/+Jjz2GuywBVycoIPsj5/5g9nSI3XPvxFw78/MNpGEXFtfP44HIvImXhZi2Xzd0NK6xraLE5rd+0fWIiySF5PbMCtwk127J55tfe+Cs8SqCHsgCEA8xFO/tTKb5SRjlQFhkb2ahFSo30fKAKW/5prUt8b5HRQJ1nZ6HoRV5l2TRIUm8h9qGpkZhmFWifsLY5cWeKKvvoxhdAiqgWSW53ufbirPabPSkA+7XUNh/JIvhkH8aNu4hKgGmvPsxoFUz6AfdlMsDCmWZLDO4AhPMlLpX4t85uJa6wxKUS9DhKglV28f278w9FP9m3w+6tk8/ZqX/qxUHZ/jD/SB+CGGQY5vma119vcco8SCHSJEn2VkPZDv15a/u/54ut18i7HjbMuAtqTf23PjO3QvmYefwGLyzzcNPxCM53gHzf9RJ4RdESCh/WVXkqb/7U6r4j8An77XTYruSD20Jn6RJFPyRgoBWo1c0sK+Ybu7jHcZES4boLAWFtJIVhpTj03Gs0dFEnubF/I86KkB5vbwL3c/Gclj8C04TecAb/NCjCbMaoaFZCTHNQQpiz+j3ANtMEJbWbzl833FD/jvt5yR7QKdhIqVsd4KNbhJQNK6HgxKskx0jhUsLjR43dD+kbmqHML29ohACngWbHwGgOTgQdg7gdxYcWRec/e+Nc16m5xmC99o25iCuC2DZPyMvIvluSDAv2ed8mfdOuEGsMks8n6p1epovXkKlvoJWJ2Tczp13CaqnvHgYwPO9jooXvHS6FN8u8luExTNSB6M8f4ajzJYA55Py7Z8YmUU3LR5ZbrD/WSShwwtlqgz1NG77uNchc9S34uJzjhBYEMU8musiL9ZwyogIk0Dond9BESzPEC/MH/7LMx9ylJztPFvAyfTdAu10sNYteCACqBmj3z7f4BhEqbO2PMxNxBVYA2xwQ1QHMpl4TKEVGdt5NcFJJSGTvd9ljXU4ZjmN4odZho/xyNymB8I2IQLzp3VQTB7+Psa9STJi5was4QZ/MCdEg+wd6JVd2DUuLbw34oHDDM8gTZd58XKc48Od41bsHaf4cKcY0zWJD+0wP4TBAIfQPEYbekoh4vD8J9JV3SINhlNQT+L0GsT5wSTF3XGzdIzTLMOj7dni/2/v2prjtpX0XzmVx32INzm7b85WybLluMpOFEmOa/eFRc9AEtfUcIrk+HJ+/SHBGy7dQIMEeBn7JbEGDfQFH5q4NBrDZpnHoML3+Q9AzbnPkZ7Me5JjGvUR3jvmrTC61+qeEDsTkI1+5dfPY8PQsx2uGjgChr54ZXFRLVzb1Ern0d1WT/DLGE/An3KpNPtSO1/frd/UCAx9ghNqRt1i6GXyxA7F+Wy//YDRvDC6yvLT0zlu4woqec2mvvjuKu+xaXO3u+yY7BbLI+SwL8RzNHf5Nc8EmNPPF67y7Gk8DOXa0y7UZOPFEOtOi8c8ffx/trOk3hoROVLfFvKz01FfFIonzUTfFO109sW35p1FT431L35MaW8OV8Cn4tVPb7NziTUQNKq5jFgmqQ1s/NCQ+JbqGiFZ/3omsAw7VbbO7n8d0ahzULLD4ePTMU4ezmVODFl/5N49/M0d11gdzbz4sAc2+8aGMTos4fnE9ns79xwzxl/sdvzVtHZvUE/dauJAfKXZOoscc3+ABO650lM0JtxBr8fONTgAy7PDvo7midP025R2Lsoy3j3Wc4hfrhLtJMrN2ENbv3ps65+ObZEdyR/sS/GW1QP3DHdyYOW87umADzKSJr+1cKkmXMQbjNqlvnz5WnRT/+1+6WC6E/D6kYR2M3ybzWNOictHtvuUnc4uqUSQY9hVpkvhD4bU9G3QmI823xR38ddXX5mg6Zhmqka6aMVpH9cZU8IEP0QfPzTPKReIptyIbRa9Ce/pXie7jB/ZMvyOQIcHZXanvM4WfR1/qw1yXg/aqdq5jx69BQ8vDK3+AE3V+kzgcPltlzI53+moHqibuWZ5kuFPtxKPwco45a1NevPwtozzkvB02Wzhbh5CqN4ckjKJ05Hv4sm1V38niV/WTv7FMwJx3mcy3sBwUUL3afaILqvVRHY67CflI0OiMP0I9F9jBOKX9PE4fj+S/VPZPRgzP+slDSXkr96EHH3TwOHrmPLx0Swkz2SsXndXSH+Z8inqW/l1/lUnfcWYZ0VRzc3THz3opwcdPnTnEoVRafKWfWbptAng7WOWl9RnoGkhXhW5z3el3xzbkzPf52Yh40ZImaUqCwHJxEYZqUIqy3OW+2pvkYQxFMGu8vpByTpx1JTlU5M57Pe4sN1G9XTKdcuabEXn4XmCHJNv4nqmsE/+vfTlCm9k8xjwM7F/E7RGCm8ifSwPu/S0Z28Ot0nVbNyv6MadLV3HRVEH01UF5eRt5K4xL3reJaWeW25US96iXN6xMm6DD/28zlw3OC2nPdymP9t5DKW5YdUsK78oPiT7BzbpLLNp4f+ygx8dm+Y+5PGxnTUgstHbql+2lPpgvJYvstrFThyXeZLlEzMpc3Xq9/TcLU72+tVHhUd/TfkAd21E+pcApO94AfT2L4fEzG2BIVSdtnUsyu+4xhmqhsj/2mk4YQPEtTNbVlvtS1F8NwmGmiF6koe1VIub6tPymO0va1XHDU65IfIQbRiO6lWAo5tltQYm3iMZVHEcrH3NMLmam+2Obh9mRN92TVB7tWU1qlclXuM2dqb2pCi+mwRDzRA9ec3yp6Sog0uapDBiBpsx3aq2Rx60At9RfQwydvSKehNerqH1Oo2DXls95Die2uuug3lyb69gRK+/d9uv/PWkx63xOZKJvGI2bhTPO6VBBejkHyVDUzlgj96wzwn78jtLj/en9DDyA+xh64sS9eKlOxqFR3dIV32SKB/iorV4gIDhF2n20F0aPte+bPUjpSIg7dNVNrvOihEBlEPNEIO0vg/yvfSlt41D3+CoO+FNydTcMsTrPE3NIB48S9O/s/rob9EkjDN47krRi0PxZcxpulg3+Gf0fHtg2cz9/nxD00/eXEOd4mx4J2jUs2XNPOB/WcGjpT009Ufm2BKG9YuiyHYJ79mWw6uvxyyvFwH3SbXSuN09sv2p3povPinIf3XY/6Nejfz2k0TUinTL0vuf5YJ3p7RMjmmyq+T47adfNFWHBiUZpBaVErnJ/9Ca5FEt9cFqnF5mh6LM48pU+nhNDrvkGKeQOgoxcXjXBu+bVUtesmN9VnUoQW0pDFsJK1jWMsLsey6K37GZ5PkzARNmqPx1Yie2by7W9xe9oytNC6FnX2ZfDmk2JITinTr8KPfnf/788y+qPZ7/eWhC8/9Rx/jXd5wu42IX7/WRUyF+j4kBSi7JhFAEAVyv/wxgg/WiMO4u8C8NtUjM5IACTSKSPIhUoPqk0GjDMBYIWbipgqOLxE5NyrE0tgQ3hqoyrWO/C4fmCoPFfJpQczH4dduuzUO7Uft/FHNdudi3/W+zYE0WWBZEKQqCLtBCYZCl6EPhKDyYvAo4geERWmdCvfi9wAmy0HrgNKzKF4FTH8Xj0S/xeT2++uvjs8Smhh+37lPg8DOs+xf2Jn3s0cWxMnkdqdmKn7Aius1O+c6w1mvrymu9/sdZnIsW8wfJEhhbvcozgMsc44jwNMfBLQ6zuzivI3q9uIszhplTly8BM0Ms7GIwaz+vm/Jl0JRNKzsbT+YyPVujI+sRZnFjy0/El8fXjFPxMfAyxjbNiq7uH20GwHq/Qw3qWafvEgQGxZHKz8aHiVq5+LGlNxGiqueTzyz/dldftEb7ViSSOlUqcFoDOnhDX7hAVQ2DDafvmiDb0pjoZ3HINap1TcwVoSE8ndG0XNXIZVa+GlxtZsdyQVDNv2fptmu18KZlnVL/PuY7Z/mMG5ciW6k5uWDrG5iSNlvYxGzhKKECV2Jqh4Z1OqhIYHkQqDn3vx8P5MxWrLAq7G3mC7cCsM3/pXN3bwt/7ZQ7xDesai2pk600l3oJJzVyC3IIp1I0C/zgG+G4WD1FmHhQ2QQzIJFwIx5hTLkCvxJM2o51gF5H+vm7QKELChaDH55LYS7cldU6pZLrc6Uqi0DR14UxUV4ZW3LJ5jElqeOApcWg1CVS8Agiy/pRSVPKW+p/2zoAwNwfK+96A2inj+HVgcF9iM4ICUm45aLhumQQ/Vuctql1V0N2D/2Ps3xk1PQ7kCiB/Uyn8AyoMiYbQliaE5IshCzLBNnBQZwtrlz6eAFY4Umo5kbViyStrwvCCbhGYcoWxOvi97aLBhIv2fjLo6HNQxrBSqz046UkT5VE6cvO5uPVaeTy8VoMV82jNbdlztNUFHxCFHUvFaHg6gnEHh1+nAVckOSyPCBBEJjBBgsDM1AtCl/x/alFsNbl4tkGvjppJRmGHzePo16VTWCnS9WzDex00koyDD9uHju9KpvATp1jaBu4qSWVD2v5D5vHC1djM1hp8lFFutCuPRUQJY2MmgTdz2HO86m96AEtrSIUbjX5cnhREwmLyV6tO4xqbbk/tcJ50GVKE22STyYMg0DVJHOgkZw2G8MnKU/2CsBqO9XHgGDq+O8QpM4AWRqglhTdc+93OnnQVW1z2QbIjOCbdcNrLOBWB7YNecA1gm4ujzcZeMt6uldf63cD4/TiVD7WLe54ofJJXrXHM2mg5Pw0EW7eAxrVcwHkYli8yvLTE9+U9Q08/Hyx5ym1JPy6eVwMumwHBPxh1blR0LzmqsGg/fk8cAC8WLtqIET8v6/z7HREUSCQaJ3X/jzLh4gz1EUIBB3MMAHBQ2I0yLUGFwLI7d5jIfGygNOh9+W8HoeTr2DygYk9petCQmj22Ytjv846f+FiLQYi/m5FgCA509xFetyeN9L+svXYuEYNCiPZ4Av3/iYWzfOCZs5pLh01i89wu/RM74v4gf2eVNLk3yI4r9RK03CJkoPyyARnk4hLUovCd/FMXCDWgLHi7iO+A4jRfcpS+OISLvvh4xFxa0ZUL6UuQcBQuNmwMyiyJcCsP4fJMrCZMV+JG3CWzlLyOrkvL+N8H12f6rf4Crb/kJSPiA7ju9FyLamTQmps+DGcJyH300RQ9LqQMAF2xeIQkeY6sEJjuzSQr4EkB+WZYdLjhABPaHOe93QV14W19+JQWOlsaE1Qm22ONBpnUo8uO236IyvZ+ufZtZS6BM2v28bQoMj659k37EuF9uusaqDonNMm9icBwSVxwPLN711CWm1iJxPCmdePoGU6vh64zOaGxmJF6pZlE/vx91HX/CXrhNSS9QV8V2I2APV6rP8z1onKd4xgucf2WmDkaBsOckGwPKPkjvUEIvK2RF9h2UPaLjXlfIlsfhzT/6QYfcHZSnnKD/VjOixArHGg+bAgsjK1kUrOYA4s6rOJ2W9/gWd+r/IjPRbqXpac22ZckMs4b6YsL06HfcqMxxPtsYpACZx4iaVOMFElUqYkamHIEy3MFmEApOlG4TmIuB4IbeIrtQaYzfmtGoWuxT9XGrbWfwa/BmDNeCI/CldLH8y3/G+PbJfct7cPL8oyTz6eSrYBjBnlh+TCKLeOPrN6W4AiLPqfx/rPiKKX6HrseKABIZRrNOhKkLIjDLMTNR5CUz2oSVnSYgJsYKXOla5taOB8l755MtgWd9gmDZbGfDd1uIsf7DnM1jKLGGSGZBFLz2S2IKjkMENYMP0YAC5Lxh1znwKd+T1AjNrhS6KsIl8caH/HeRIfyt63XmZPH5MDJ4zwJ+PF+zAikXQPRipw2ha0ywZBy0ge5ooXaqCgaDMpSpFCFHvN+NvM2nz1QJ3/uzsVo0sv1Qnw/OtUlVS99P6Q4BiViEQsyAVn6R5xA60aeqLYa8Pfdn0iBZJn6/227PI6yNm3JDVCoMvn3oZcLRpn3Xb0AMvV7DWqSvwdp6cepGYNw+BiXuRydSlitoQhMTwKT2Gg3GjrgGe1gaVhvfh0kjJl2PbEcHOzv+g2fjqm7GX25ZBmMX5FuCeQ9l/6H1eOBli95ZEgG38xLNwlLK/02zEpD7s13ooTUfOvc0wE+Yr10kuyCL+eRbL1QR8KM1G6FcBq/UvKZUA047LRDT8rWSgO4ahtIPH6gaRJ7hbNvD1g6SpRmErduSKYeQWYdQr0AyVmpqvDR7+Wu0rSss53G/x6w1xuStHMLKRGfD7XJqy60r3bstcoyiyvJ17JU5x/e/V19xgfHthNXLLLU16x2H0zTO9bAnlq3/1Id3JcBDmervkl0IQc0isMSBo9aB4M7YB1QIP/8QMTC2BCsvxyr8HHu09vDpUsu0+3p4/FLk+aWPFN3LpChJffisdoNn8HC9PMZYNgdbhb/wpvfaCbcR4/BXNLz+Yvdinw4K7fbc5AmOtFly/OD7+exR7noM9m9jgv7u+TtPqFRTO9edIzlBsaft164oNBFQqzpROq8Cd6gk6ccChovPWnkM5sxqPrtIm5TqXf5wrQ7yqgxg8susqzp/lQIjNX5k1y0ebxoShE4Sh2xloAcpf9gMdK4DF0xYLT1jL5XJn8bfYQCf+uOwqfbCh08qxRKZtpBttzxaQJNXcx2SwM0ESlSLMYWcRVQG0TG0DLoWpOv+UKp8Vd1uUj233KTmoErfYzji2NUgKZXjoL2mC1zKKFjJG12DMMGBENSbhUqy6YB3N3yiuNH67jbzwX85tDUrfrKSUzPt1SGSsJLdXCbedZ1vQh8RR6YjX46DK7mzXy1c/BMqmCShlFC5syfhxA/ILSJYO8WncxfL7NdnGa/IvtOxmjt/Hh4aStaoTe7wnE7h5+nAWBmtiyMHppENTBpgqDNl0nCtNOwgXx5XsBEC7+W537n8ecfzNz/fZU74Z9TtiX31l6vD+lhzoJcd0lQ+N817PHVPV7WU+77rIIq2+FXdsGiL6+zDkkU5cDOGkGiAg3YCZhrtNolm0ySo848V4Sh1KBLa6gpUL7fDFE+Q0VgIyxQjBJlZcLVUmzh8vsia8TiU5NqDK3HxNZSyEp4u9n5K1QUzuxmxlG9b+vM/WVB6UXOYHahc2P8wQ6uSPJk5eCzbMO/HSyLYajP9iXwtEdCVXmdkcia7FJ6fczckeoqZ3YzQyj+t/GSyw9gdqFM15ZGYEkT+4INs868NPJtty8O0vTv7MyOTy0wZXUdZ9Sb/b1nspfmpVrhWfkosyWd+K5BL7qHy4OxRfDzpRAovZq9/M8t+ymYMzXig8x14qwNYi4jt2DUVtXi+5X0Tcstu25PO1ezA6o9d9qGQmn7d1gccfQvPdWXvHcUPWgqmqwvNvmz/bsKsmL8mVcxh/jQj/nq2vdshLIMfXTP5pSNJfX7e6RPcW//bT/mFU9H39MtQY0LyRze/X1mOV1ivL7pL4So7FTyiF+ComFYd3C/pSyu7j4BPCTiyF2MoWF218ndmL7V09xkl6UZbx7bNYagFVBOti8IKlFkCHhlsZ7KILYDaV0Vc0KWtWyg4Yrv9tlJ9CYcjEIGYnCwq09sX/HyscMsp9SDvFTSGwMrUkTdBmsVUCxrLVcJaWIRpSFyhxnaWRkHzFJgQFsKAJHTF9q4XAZl+yBh8hoHIYiiMNQajNRsitPOeRX+xLQRF0hrQcMXBQCQ39ELVH0LubP+JH7p371OMkZ4lhBKlOvSYQ2EaRXJHTeUjHIVKKgWduAGo3CZO+OimzwtuK7+HC6j3lfGTyRTGWSQ6Qky2IRws5dprANVHWtpY9XlQIctioRka2Bn5mRlUN/i1Rj0JdA7feFNgXqAQX7t64EFL8rtE3l2rer0S+zSgBO5xQaG8+yGjYVdD9XawZozCvlIEeZxMJwOLnTeA1FEJuh1MJhiOTTOAxFEIeh1MahDmVjt2XON3eK5jkygBtIBnIGKS1SDIcOGuehCOJWl9q9o7BDqDtFoRD0hUI5gQ/CAW+b7GqaxASou2mKTS6nobDpwPKnpCiq5Wu3f6nro5GAumlU1uVmNa09xOnFqXysl9jN432oFGZyeDFqqmGRjl9SRga7UAbx5cVFRBrwnPYuOyY7jE9baGDUUlA4YUyM7bcUlPZf59npiDFpCw2cWgoLp/a6hsak/R1qvy0iTmPf1xdL+5h5dB4rk5kmsjKlRYrXyX01bcz3FilgMkgKmJIohYGzmRutG5GPgFCGdidpYcop/8hKyJMKZSiTptjC5IZ9qRS+zpJDWeAdBlJBjEFCwvwLWXYNRdici7TA6giRHpOLTZxI/XbDquXAoV4AMtAFK+WwGSUSq3rKk/WQiioJrKZKRVvLYS/+Yqs6jN60vkOeX6au9MwPKev2MpKDtjPWGCUdWS4Xiah9yl8vxTqQFxp6i5fT+BifbMP4GysZ5DLWGycvXUjLvpXyBJHrfgpBIJokzkhBHuWhmqUld+i1toZFOiF1viaKUAbxFYqt6/gM3LFsf4fX7RlhT3RIbQqsoroieAXVlVJE70EGa9AXo4pQUXpX1We8Iz+CS0OlHOwVmcS24YElQNT3PzBKcDsEI3bYabxjT8c0BkcqTGbbeRwoiUcFBgl0EtPRAZlz92HAGWsUpu8Lle1rdmDVQDY5R50EXBpoVLY90SE1or4rOpSB+6JDsYXJ+xxnIpRBTIRimyZDaj9dk6EM1GQoto0PFhcVlD+w5OERmjQr5eCIkEloDF8m1by+gB2DTmJgK1BRdhssPgmgMew9OHkiNakWMBBlAtM+kUJqHRNCUhxgVAil8LgQCOismhxTJnYNhYVlQ2R1sE/HOHmAunQogh1qV2rFLbe16RuiUsCoVYgIu95vWVmy3IJcjBDbEYdobUbWs+/o1tZpQLPrZK7MsekuRkgSgzbFRXNlAHsOCCW8+4AQO4pDkIMmAO1kSE7cAB8LyTTomZBMZlW7HkF7PI5EJYBVlmlsKMyzoqgaT3GuOgmIPI3KamrId6M+m+Krq3+VyCKkKwHXH12hbekRfzUEO0il4LJDJLCxQk5DDAchtBOQbjfeoIhOYtrWjy6OxzRh+7uspU+Y/XCyq4yDTqOgydCS2yWQj9nxwACEzn5yf8OKygXzMGZCYEJ7WonHP2gUpkPPlojQEeqBpeXc1UxOORGNalLyllQfoUI7DqYfC0t58113yCgbm8T9zWigc5UCyLOACQOQGmQCqAkxIf29RX1bRCzFIkN6AsL0EWcllWITRSor/VaTbl+NBIt4kKlcetjWrYS+1BgKAftI6Hskh5ULFaBAeJlavVkgvJkEBLtXApiD2KUWwPh83oQ57P6ZrDLBHGBce3TFG9fNYaDGlVGD4LkeWIC7VNMYx8+boYXlTzJLJIetG40i0xr6Fwilb7rXECKPmQYzSDB0SDyoIBEreVVrJYiRg66jPvhatw9CiSulRHpzNZBgcbkeGCjeVFeKvKvfTWjt6sM3yaC7drr8q1G/D+s29LtG47/H1fUOr4gux9zVNC2Hots2IFTXnFLN8P1QroQ03w/kugdYEzSLXhjWPHdx/sDAEUGp5qnPV2aebiHrhB21UkjkQL5HKwtpGBfUqJX8u9SFzALcEiqMJgErhMQJcB1KasR0v2n0xzaSLzKhn1qZzqAMcLGq0cJwYWosrsar3X9HB9eEqq7ThnCkyB0xUX2PblRVzT7X0kiDTbZmMoF0e80w5QLp/E+7oHtxvLLxvtvonlfu7oksUAjgdTyrBaECbQgsD2Im+yAByYMNlJlNIu/aR922PWv3+s3Td3plw54YeGuw2RUz3gU0tKKcZwCNYXcagxgQn6rRKxu+ToCyiHprMZl44zLq2wXMAxIGMAV0i7QxgfFyqLvq7dGUSWmVxL+6yjEbr4fdIR6vomI6XFGZ0Hsvzah6f7zWn0AanCdObOhx5Qp60+XI9XKwJmQArSyEIXAniBN76cc1mOFFkqaVI+1bNhhBIQ1nAhKGJijdpdcbmsa11mhDDgElbYTUAJYLwt0M0J344W0fwBbmCrhaar4Arg+WC0CuacgD0LRCudfvbpouFYLRHDpRCBOo6Rx4TTRVg7uqXSYDo6o6UQhV1XwLvObw4/TFVZamRjVlghAqiskYmjUTlGxhnGpNPoioaRFWTiSZKKRWQ85l0ddDslSMUFENVBIjhEzTGFpFg3JI8olGRUtCCWNLUNQU2KoxWYZ3U+ITIVpF+wdOU9ik4CpNCMapkSbTjujzMJuwGX0e0xCm17OjaSkTmZK+GKejtIoh0ERJhHPbhHk5JLRxN12f1sZoJ4AqhFG0/Du8qvCrH3V55LhdX4UsmMJS7PugMRzfPlLlSEzGg+gr0lhEllL+DCKDqXz02npFvz3bNmns1oZmsqDL9CYfiWKzphEr0HkUfeZxy/PyEHZVQDr/WypSNipeC042NVZRk29SKEK4pXDqQcmxoiG+Ag/kgCsYIhgmB3JAqbikVoyptTyZprU70S7g8/Du/boKc/TpvnAbqCR+FddSlQ31PG2gDPIbzqR1Iv8n0eFV7RLERdfV+uwxLtj+Q1I+Chx0xW1VvKkj1VVT4PGqaHq78YaQxu3QPm4GuIInRcCa6Ign5Rb0ZJr3Yr+T7SPX8usXljJPn5rQ4hAFkgAOUUyrONQDMya6qwgkQTROhYz0ISZGhqSPvBFKFkc/ZrGOC3slv+hYxjRdfkl8TCgUfpVWM3DedlE0nsJ7xfSZ0dAurqVM6ElwsKb2fTWm+hy7EOoikqxLPpVwS2s+KWmpxeGBlGFcHZCMtR3JphyrEza4CZ2N0vrv79BhE2rO2Eh8egca4Ti5dUWgPxAkrg3wN3+UkQ9ny21HvyUDrgcDmUYGThxicCxuCsOCEaX1v26c2wzGLMiEwG5LRf8GIqV5Flsjph4egSBDlmUkRTQIrhHNGPBjtwvNIIRW5YzVhraRzNO+0Wpm7gphY2szm2qlI0DPQGOMcsGpg7kJPc+O2IJYGsQYhjgVnNqqFKINoMY6TGLIdE69ZUluwrDr7usCJiFLvGhRlzzvQUxs/4hTagcbolszp/wW8CibSk3gBoKeLeamMD1HvFUTj4Dp/NikWCu8iYzTSXJdq/JGrWnzxlWakeezjVC2ZJOa25nFKJR2pZzApsbhPL/jJ0JERzm7NwybQCG6jZ+OKRtSsOGKK5SGqcvoJG9hVe7fJpHiYiGVEUr7RtL4yFntzRVeHX9OZYr6hm+XTuT/axVeVW1bs90rtX+00SrBPtpjd2a9mMXJIGdtiv67cpWkZR1BaNyLd6k+z968TSJz0xqxhxv/Gc/olzzF+bdXX3eP8eGB3cTV9Lx/ZQnYXLRWMrlg+dmn1v3CTzrJe2DiS1TNZhf40NREI/A/yNrL1NtSG3npyXh8Y60T4hTH8tIVb4j6bJU/Mxmcsq2Kf9e8rIn695ysUzaEMuSUTXunqjlZRp+gGqF+9whUZDgR14n8H4VrD1k1NdE3qkZeayB7CwN1CD+Bvjt1219z8Ax8+Xmo6CrPnkz2MJGHMAj8ElbrVIwPW002xV3mYAiBeOtmEN7TirS3tSB/iJObXBr49lfr2IwPemHtYG14NokJFCBdCDzMozb85FUEPMgFhE9R6xrMg70h1tjJ9iyYuS19+4/4wNeYoDv5qazozSEpkzg1BNSaKuBKjgumhR8Eu23i7yyPfE03RhcwrLOy2wWtG0hdY0twJDLtlbaRKYDEJ9Ds+X9galzBicl/oKfemiasr7eNMYfZMQd3yKoj9uOA23WT9mxQVH8XL7NDUeZxUj+GUk3Dev26hFd3WYTVN+w0+edlN/eIrFnQkhN9tklcg9qfYPLYTVKBk9HlmkTVcX1XbT7hkSoq2KR3rYDdi2kthkcs8KxXs8FheK9rmmGHxGNmc/V0ZuHJic0WUFt4iYza+9LjZbqBJrYYHk/A2228DdOjbNMMO2R3M5urpzMLT84et4Da6iNz5C+i9jgd8AHw1fYMX1nk0b7m82B7iM+D2cWkdARLCuRmpZzy3y1rEvEjPGpiZp2EjGt15jkefaIz2cj2g2uFMNihtUfVnz9rGqkNX/Uyy/uy58+aJy/bH6o/qwVrtd57l+1ZWvBfnz+7OVW1n1jz10tWJA9DE8+rNg+MP847NNrRvDncZ92qT5GoI+mKu0c3WBnv4zK+yMukfpqgKt5Vs07+mCvfn6mfL/zI9m8Of57K46msVGZPH1NpTfn8mZn/82eazM+b+waFDxUqMZNKBfbn4cUpSfe93FdxWijraKyJy8r6r1n1e9OX1dAs2cO3vqU/sgOxodZ8L9mRHfbVkLtjT8f6UfPiz8Nt/JmNke19wd6yh3j37bpOks63m7BG7B0hm/35yyR+yOOnom1jqF/9WWF4//T1f/4NTVqEL1VqCAA= - - - dbo - - \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.Designer.cs similarity index 92% rename from src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.Designer.cs rename to src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.Designer.cs index 0e3246e714..24f974b961 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.Designer.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.Designer.cs @@ -13,7 +13,7 @@ public sealed partial class ExportFramework : IMigrationMetadata string IMigrationMetadata.Id { - get { return "201508011828241_ExportFramework"; } + get { return "201508040836122_ExportFramework"; } } string IMigrationMetadata.Source diff --git a/src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.cs similarity index 53% rename from src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.cs rename to src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.cs index 86c6f7c509..84a392c3ff 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508011828241_ExportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.cs @@ -1,6 +1,5 @@ namespace SmartStore.Data.Migrations { - using System; using System.Data.Entity.Migrations; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Security; @@ -16,16 +15,16 @@ public override void Up() { Id = c.Int(nullable: false, identity: true), Name = c.String(nullable: false, maxLength: 100), + FolderName = c.String(nullable: false, maxLength: 100), ProviderSystemName = c.String(nullable: false, maxLength: 4000), Enabled = c.Boolean(nullable: false), - FileTypeId = c.Int(nullable: false), SchedulingTaskId = c.Int(nullable: false), ProfileGuid = c.Guid(nullable: false), - Partitioning = c.String(), Filtering = c.String(), - LastExecutionStartUtc = c.DateTime(), - LastExecutionEndUtc = c.DateTime(), - LastExecutionMessage = c.String(maxLength: 4000), + Offset = c.Int(nullable: false), + Limit = c.Int(nullable: false), + BatchSize = c.Int(nullable: false), + PerStore = c.Boolean(nullable: false), }) .PrimaryKey(t => t.Id) .ForeignKey("dbo.ScheduleTask", t => t.SchedulingTaskId) @@ -66,6 +65,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Common.Profile", "Profile", "Profil"); builder.AddOrUpdate("Common.Partition", "Partition", "Aufteilung"); builder.AddOrUpdate("Common.Image", "Image", "Bild"); + builder.AddOrUpdate("Common.Filter", "Filter", "Filter"); + builder.AddOrUpdate("Common.Projection", "Projection", "Projektion"); + builder.AddOrUpdate("Common.Publishing", "Publishing", "Verffentlichung"); + builder.AddOrUpdate("Common.Website", "Website", "Web-Seite"); builder.AddOrUpdate("Admin.Configuration.Export.ProviderSystemName.Validate", @@ -115,43 +118,156 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Informationen zur letzten Ausfhrung des Exports."); - builder.AddOrUpdate("Admin.Configuration.Export.Segmentation.Offset", + builder.AddOrUpdate("Admin.Configuration.Export.Offset", "Offset", "Abstand", "Specifies the number of records to be skipped.", "Legt die Anzahl der zu berspringenden Datenstze fest."); - builder.AddOrUpdate("Admin.Configuration.Export.Segmentation.Limit", + builder.AddOrUpdate("Admin.Configuration.Export.Limit", "Limit", "Begrenzung", "Specifies how many records to be loaded per database round-trip.", "Legt die Anzahl der Datenstze fest, die pro Datenbankaufruf geladen werden sollen."); - builder.AddOrUpdate("Admin.Configuration.Export.Segmentation.BatchSize", + builder.AddOrUpdate("Admin.Configuration.Export.BatchSize", "Batch size", "Stapelgre", "Specifies the maximum number of records of one processed batch.", "Legt die maximale Anzahl der Datenstze eines Vearbeitungsstapels fest."); - builder.AddOrUpdate("Admin.Configuration.Export.Segmentation.PerStore", + builder.AddOrUpdate("Admin.Configuration.Export.PerStore", "Per store", "Per Shop", "Specifies whether to start a separate run-through for each store.", "Legt fest, ob fr jeden Shop ein separater Verarbeitungsdurchlauf erfolgen soll."); - - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportFileType.Xml", "XML (Extensible Markup Language)", "XML (Extensible Markup Language)"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportFileType.Xls", "XLS (Microsoft Excel)", "XLS (Microsoft Excel)"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportFileType.Csv", "CSV (Delimiter Separated Values)", "CSV (Trennzeichen getrennt)"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportFileType.Txt", "TXT (Plain text)", "TXT (Einfacher Text)"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportFileType.Pdf", "PDF (Portable Document Format)", "PDF (Portables Dokumentenformat)"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportEntityType.Product", "Product", "Produkt"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportEntityType.Category", "Category", "Warengruppe"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportEntityType.Manufacturer", "Manufacturer", "Hersteller"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportEntityType.Customer", "Customer", "Kunde"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportEntityType.Order", "Order", "Auftrag"); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.StoreId", + "Store", + "Shop", + "Filter by store.", + "Nach Shop filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.CreatedFrom", + "Created from", + "Erstellt ab", + "Filter by created date.", + "Nach dem Erstellungsdatum filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.CreatedTo", + "Created to", + "Erstellt bis", + "Filter by created date.", + "Nach dem Erstellungsdatum filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.PriceMinimum", + "Price from", + "Preis von", + "Filter by price.", + "Nach dem Preis filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.PriceMaximum", + "Price to", + "Preis bis", + "Filter by price.", + "Nach dem Preis filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.AvailabilityMinimum", + "Availability from", + "Verfgbar von", + "Filter by availability quantity.", + "Nach der Verfgbarkeitsmenge filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.AvailabilityMaximum", + "Availability to", + "Verfgbar bis", + "Filter by availability quantity.", + "Nach der Verfgbarkeitsmenge filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.IsPublished", + "Published", + "Verffentlicht", + "Filter by publishing.", + "Nach Verffentlichung filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.CategoryIds", + "Categories", + "Warengruppen", + "Filter by categtories.", + "Nach Warengruppen filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.WithoutCategories", + "Without category mapping", + "Ohne Warengruppenzuordnung", + "Filter by missing category mapping.", + "Nach fehlender Warengruppenzuordnung filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.ManufacturerIds", + "Manufacturers", + "Hersteller", + "Filter by manufacturers.", + "Nach Hersteller filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.WithoutManufacturers", + "Without manufacturer mapping", + "Ohne Herstellerzuordnung", + "Filter by missing manufacturer mapping.", + "Nach fehlender Herstellerzuordnung filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.ProductTagIds", + "Product tags", + "Produkt-Tags", + "Filter by product tags.", + "Nach Produkt-Tags filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.IncludeFeaturedProducts", + "Include featured products", + "Empfohlene Produkte einbeziehen", + "Specifies whether to include featured products.", + "Legt fest, ob empfohlene Produkte einbezogen werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.OnlyFeaturedProducts", + "Only featured products", + "Nur empfohlene Produkte", + "Filter by featured products.", + "Nach empfohlenen Produkten filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.ProductType", + "Product type", + "Produkttyp", + "Filter by product type.", + "Nach Produkttyp filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.OrderStatus", + "Order status", + "Auftragsstatus", + "Filter by order status.", + "Nach Auftragsstaus filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.PaymentStatus", + "Payment status", + "Zahlungsstatus", + "Filter by payment status.", + "Nach Zahlungsstatus filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.ShippingStatus", + "Shipping status", + "Versandstatus", + "Filter by shipping status.", + "Nach Versandstatus filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.CustomerRoleIds", + "Customer roles", + "Kundengruppen", + "Filter by customer roles.", + "Nach Kundengruppen filtern."); } } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.resx b/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.resx new file mode 100644 index 0000000000..c1ffae86af --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.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 b39ffa09d7..bbd83bec0a 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -121,7 +121,7 @@ - + @@ -332,9 +332,9 @@ 201507250039446_Merge2.cs - - - 201508011828241_ExportFramework.cs + + + 201508040836122_ExportFramework.cs @@ -633,8 +633,8 @@ 201507250039446_Merge2.cs - - 201508011828241_ExportFramework.cs + + 201508040836122_ExportFramework.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs new file mode 100644 index 0000000000..3c0c8472f9 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs @@ -0,0 +1,15 @@ +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.DataExchange +{ + public static class ExportExtensions + { + public static bool IsValid(this Provider provider) + { + return ( + provider != null && + provider.Value.FileType.HasValue() + ); + } + } +} diff --git a/src/Libraries/SmartStore.Services/Export/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs similarity index 90% rename from src/Libraries/SmartStore.Services/Export/ExportService.cs rename to src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index 14be36fc7e..2b8a2f2ba1 100644 --- a/src/Libraries/SmartStore.Services/Export/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -7,8 +7,9 @@ using SmartStore.Core.Events; using SmartStore.Core.Plugins; using SmartStore.Services.Tasks; +using SmartStore.Utilities; -namespace SmartStore.Services.Export +namespace SmartStore.Services.DataExchange { public partial class ExportService : IExportService { @@ -38,14 +39,15 @@ public virtual ExportProfile InsertExportProfile(Provider provi if (provider == null) throw new ArgumentNullException("provider"); - var guid = Guid.NewGuid(); var name = provider.Metadata.FriendlyName; var systemName = provider.Metadata.SystemName; - var taskType = "SmartStore.Services.ExportImport.{0}Task.{1}, SmartStore.Services".FormatInvariant(systemName, guid.ToString().Replace("-", "")); if (name.IsEmpty()) name = systemName; + var folderName = SeoHelper.GetSeName(name, true, false).ToValidPath(); + var taskType = "SmartStore.Services.DataExchange.{0}Task.{1}, SmartStore.Services".FormatInvariant(systemName, CommonHelper.GenerateRandomDigitCode(10)); + var task = new ScheduleTask { Name = string.Concat(name, " Export Task"), @@ -61,11 +63,10 @@ public virtual ExportProfile InsertExportProfile(Provider provi var profile = new ExportProfile { Name = name, + FolderName = folderName, Enabled = true, ProviderSystemName = systemName, - ProfileGuid = guid, - SchedulingTaskId = task.Id, - FileType = provider.Value.SupportedFileTypes.FirstOrDefault() + SchedulingTaskId = task.Id }; _exportProfileRepository.Insert(profile); diff --git a/src/Libraries/SmartStore.Services/Export/IExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs similarity index 55% rename from src/Libraries/SmartStore.Services/Export/IExportProvider.cs rename to src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs index 752f6096ed..318c0e2586 100644 --- a/src/Libraries/SmartStore.Services/Export/IExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs @@ -1,16 +1,17 @@ using System.IO; using SmartStore.Core.Domain; -using SmartStore.Core.Domain.Export; +using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Plugins; -namespace SmartStore.Services.Export +namespace SmartStore.Services.DataExchange { public partial interface IExportProvider : IProvider, IUserEditable { + // TODO: a more complex result type is required. e.g. IEnumerable.... MemoryStream Execute(ExportProfile profile); - ExportFileType[] SupportedFileTypes { get; } - ExportEntityType EntityType { get; } + + string FileType { get; } } } diff --git a/src/Libraries/SmartStore.Services/Export/IExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs similarity index 97% rename from src/Libraries/SmartStore.Services/Export/IExportService.cs rename to src/Libraries/SmartStore.Services/DataExchange/IExportService.cs index 3e8e2e60df..bc6ca8b092 100644 --- a/src/Libraries/SmartStore.Services/Export/IExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs @@ -3,7 +3,7 @@ using SmartStore.Core.Domain; using SmartStore.Core.Plugins; -namespace SmartStore.Services.Export +namespace SmartStore.Services.DataExchange { public interface IExportService { diff --git a/src/Libraries/SmartStore.Services/Export/ExportExtensions.cs b/src/Libraries/SmartStore.Services/Export/ExportExtensions.cs deleted file mode 100644 index 3ea12ec406..0000000000 --- a/src/Libraries/SmartStore.Services/Export/ExportExtensions.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System.Linq; -using SmartStore.Core.Plugins; - -namespace SmartStore.Services.Export -{ - public static class ExportExtensions - { - public static bool IsValid(this Provider provider) - { - return ( - provider != null && - provider.Value.SupportedFileTypes != null && provider.Value.SupportedFileTypes.Any() - ); - } - } -} diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index db5a654dd3..cf282b9d21 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -160,12 +160,12 @@ + - - - - + + + diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index e361097cc9..b003f82cef 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -41,7 +41,7 @@ using SmartStore.Services.Directory; using SmartStore.Services.Discounts; using SmartStore.Services.Events; -using SmartStore.Services.Export; +using SmartStore.Services.DataExchange; using SmartStore.Services.ExportImport; using SmartStore.Services.Filter; using SmartStore.Services.Forums; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index cd8bf2190a..510772fdb3 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -1,16 +1,18 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Web.Mvc; -using SmartStore.Admin.Models.Export; +using SmartStore.Admin.Models.DataExchange; using SmartStore.Core; using SmartStore.Core.Domain; -using SmartStore.Core.Domain.Export; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Plugins; using SmartStore.Services; -using SmartStore.Services.Export; +using SmartStore.Services.Catalog; +using SmartStore.Services.DataExchange; using SmartStore.Services.Media; using SmartStore.Services.Security; +using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Plugins; using Telerik.Web.Mvc; @@ -24,60 +26,108 @@ public class ExportController : AdminControllerBase private readonly IExportService _exportService; private readonly PluginMediator _pluginMediator; private readonly IPictureService _pictureService; + private readonly ICategoryService _categoryService; public ExportController( ICommonServices services, IExportService exportService, PluginMediator pluginMediator, - IPictureService pictureService) + IPictureService pictureService, + ICategoryService categoryService) { _services = services; _exportService = exportService; _pluginMediator = pluginMediator; _pictureService = pictureService; + _categoryService = categoryService; } private void PrepareExportProfileModel(ExportProfileModel model, ExportProfile profile, Provider provider) { - var partition = XmlHelper.Deserialize(profile.Partitioning); - - model.AvailableFileTypes = new List(); + model.Providing = new ExportProfileModel.Provider + { + SystemName = profile.ProviderSystemName + }; model.Id = profile.Id; model.Name = profile.Name; - model.ProviderSystemName = profile.ProviderSystemName; model.Enabled = profile.Enabled; - model.FileType = profile.FileType; - model.FileTypeName = profile.FileType.ToString().ToUpper(); model.SchedulingHours = profile.ScheduleTask.Seconds / 3600; - model.Offset = partition.Offset; - model.Limit = partition.Limit; - model.BatchSize = partition.BatchSize; - model.PerStore = partition.PerStore; - if (provider != null) { - model.ThumbnailUrl = _pluginMediator.GetIconUrl(provider.Metadata); + model.Providing.ThumbnailUrl = _pluginMediator.GetIconUrl(provider.Metadata); - if (model.ThumbnailUrl.IsEmpty()) - model.ThumbnailUrl = _pictureService.GetDefaultPictureUrl(48); + if (model.Providing.ThumbnailUrl.IsEmpty()) + model.Providing.ThumbnailUrl = _pictureService.GetDefaultPictureUrl(48); else - model.ThumbnailUrl = Url.Content(model.ThumbnailUrl); + model.Providing.ThumbnailUrl = Url.Content(model.Providing.ThumbnailUrl); + + model.Providing.Url = provider.Metadata.PluginDescriptor.Url; + model.Providing.ConfigurationUrl = Url.Action("ConfigurePlugin", new { systemName = provider.Metadata.SystemName }); + model.Providing.FriendlyName = _pluginMediator.GetLocalizedFriendlyName(provider.Metadata); + model.Providing.Author = provider.Metadata.PluginDescriptor.Author; + model.Providing.Version = provider.Metadata.PluginDescriptor.Version.ToString(); + model.Providing.Description = _pluginMediator.GetLocalizedDescription(provider.Metadata); + + model.Providing.EntityType = provider.Value.EntityType; + model.Providing.EntityTypeName = T("Enums.SmartStore.Core.Domain.ExportEntityType." + provider.Value.EntityType.ToString()); + model.Providing.FileType = provider.Value.FileType.ToUpper(); + } + } - model.ProviderFriendlyName = _pluginMediator.GetLocalizedFriendlyName(provider.Metadata); + private void PrepareExportProfileModelForEdit(ExportProfileModel model, ExportProfile profile, Provider provider) + { + var filter = XmlHelper.Deserialize(profile.Filtering); - model.EntityType = T("Enums.SmartStore.Core.Domain.ExportEntityType." + provider.Value.EntityType.ToString()); + var allStores = _services.StoreService.GetAllStores(); + var allCategories = _categoryService.GetAllCategories(showHidden: true); + var mappedCategories = allCategories.ToDictionary(x => x.Id); - foreach (var fileType in provider.Value.SupportedFileTypes) - { - model.AvailableFileTypes.Add(new SelectListItem - { - Text = T("Enums.SmartStore.Core.Domain.ExportFileType." + fileType.ToString()), - Value = ((int)fileType).ToString() - }); - } - } + var yes = T("Admin.Common.Yes").Text; + var no = T("Admin.Common.No").Text; + + model.StoreCount = allStores.Count; + model.AllString = T("Admin.Common.All"); + model.UnspecifiedString = T("Common.Unspecified"); + model.Offset = profile.Offset; + model.Limit = profile.Limit; + model.BatchSize = profile.BatchSize; + model.PerStore = profile.PerStore; + + model.Filtering = new ExportProfileModel.Filter + { + StoreId = filter.StoreId, + CreatedFrom = filter.CreatedFrom, + CreatedTo = filter.CreatedTo, + PriceMinimum = filter.PriceMinimum, + PriceMaximum = filter.PriceMaximum, + AvailabilityMinimum = filter.AvailabilityMinimum, + AvailabilityMaximum = filter.AvailabilityMaximum, + IsPublished = filter.IsPublished, + CategoryIds = filter.CategoryIds, + WithoutCategories = filter.WithoutCategories, + ManufacturerIds = filter.ManufacturerIds, + WithoutManufacturers = filter.WithoutManufacturers, + ProductTagIds = filter.ProductTagIds, + IncludeFeaturedProducts = filter.IncludeFeaturedProducts, + OnlyFeaturedProducts = filter.OnlyFeaturedProducts, + ProductType = filter.ProductType, + OrderStatus = filter.OrderStatus, + PaymentStatus = filter.PaymentStatus, + ShippingStatus = filter.ShippingStatus, + CustomerRoleIds = filter.CustomerRoleIds + }; + + model.Filtering.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); + + model.Filtering.AvailableStores = allStores + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + model.Filtering.AvailableCategories = allCategories + .Select(x => new SelectListItem { Text = x.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = x.Id.ToString() }) + .ToList(); } public ActionResult Index() @@ -123,8 +173,9 @@ public ActionResult Create() if (_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) { var model = new ExportProfileModel(); + model.Providing = new ExportProfileModel.Provider(); - model.AvailableExportProviders = _exportService.LoadAllExportProviders() + model.Providing.AvailableExportProviders = _exportService.LoadAllExportProviders() .Select(x => { var item = new SelectListItem @@ -148,20 +199,18 @@ public ActionResult Create(ExportProfileModel model) if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); - if (model.ProviderSystemName.HasValue()) + if (model.Providing.SystemName.HasValue()) { - var provider = _exportService.LoadProvider(model.ProviderSystemName); + var provider = _exportService.LoadProvider(model.Providing.SystemName); if (provider != null) { var profile = _exportService.InsertExportProfile(provider); - PrepareExportProfileModel(model, profile, provider); - return RedirectToAction("Edit", new { id = profile.Id }); } } - NotifyError(T("Admin.Configuration.Export.ProviderSystemName.Validate", model.ProviderSystemName.NaIfEmpty())); + NotifyError(T("Admin.Configuration.Export.ProviderSystemName.Validate", model.Providing.SystemName.NaIfEmpty())); return RedirectToAction("List"); } @@ -180,6 +229,7 @@ public ActionResult Edit(int id) var model = new ExportProfileModel(); PrepareExportProfileModel(model, profile, provider); + PrepareExportProfileModelForEdit(model, profile, provider); return View(model); } @@ -195,32 +245,60 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) if (profile == null) return RedirectToAction("List"); - if (ModelState.IsValid) - { - var partition = new ExportPartition - { - Offset = model.Offset, - Limit = model.Limit, - BatchSize = model.BatchSize, - PerStore = model.PerStore - }; - - profile.Name = model.Name; - profile.Enabled = model.Enabled; - profile.ScheduleTask.Seconds = model.SchedulingHours * 3600; - profile.FileType = model.FileType; - profile.Partitioning = XmlHelper.Serialize(partition); - - _exportService.UpdateExportProfile(profile); - - NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); + var provider = _exportService.LoadProvider(profile.ProviderSystemName); - return (continueEditing ? RedirectToAction("Edit", new { id = profile.Id }) : RedirectToAction("List")); + if (!ModelState.IsValid) + { + PrepareExportProfileModel(model, profile, provider); + PrepareExportProfileModelForEdit(model, profile, provider); + return View(model); } - PrepareExportProfileModel(model, profile, _exportService.LoadProvider(model.ProviderSystemName)); - - return View(model); + var filter = new ExportFilter + { + StoreId = model.Filtering.StoreId ?? 0, + CreatedFrom = model.Filtering.CreatedFrom, + CreatedTo = model.Filtering.CreatedTo, + PriceMinimum = model.Filtering.PriceMinimum, + PriceMaximum = model.Filtering.PriceMaximum, + AvailabilityMinimum = model.Filtering.AvailabilityMinimum, + AvailabilityMaximum = model.Filtering.AvailabilityMaximum, + IsPublished = model.Filtering.IsPublished, + CategoryIds = model.Filtering.CategoryIds, + WithoutCategories = model.Filtering.WithoutCategories, + ManufacturerIds = model.Filtering.ManufacturerIds, + WithoutManufacturers = model.Filtering.WithoutManufacturers, + ProductTagIds = model.Filtering.ProductTagIds, + IncludeFeaturedProducts = model.Filtering.IncludeFeaturedProducts, + OnlyFeaturedProducts = model.Filtering.OnlyFeaturedProducts, + ProductType = model.Filtering.ProductType, + OrderStatus = model.Filtering.OrderStatus, + PaymentStatus = model.Filtering.PaymentStatus, + ShippingStatus = model.Filtering.ShippingStatus, + CustomerRoleIds = model.Filtering.CustomerRoleIds + }; + + profile.Name = model.Name; + profile.Enabled = model.Enabled; + profile.ScheduleTask.Seconds = model.SchedulingHours * 3600; + profile.Offset = model.Offset; + profile.Limit = model.Limit; + profile.BatchSize = model.BatchSize; + profile.PerStore = model.PerStore; + + if (profile.Name.IsEmpty()) + profile.Name = provider.Metadata.FriendlyName; + + if (profile.Name.IsEmpty()) + profile.Name = provider.Metadata.SystemName; + + profile.Filtering = XmlHelper.Serialize(filter); + + _exportService.UpdateExportProfile(profile); + + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); + + return (continueEditing ? RedirectToAction("Edit", new { id = profile.Id }) : RedirectToAction("List")); } [HttpPost, ActionName("Delete")] @@ -248,5 +326,10 @@ public ActionResult DeleteConfirmed(int id) return RedirectToAction("Edit", new { id = profile.Id }); } + + public ActionResult Execute(int id) + { + return null; + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs new file mode 100644 index 0000000000..ca98c7ab9b --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Domain.Shipping; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Mvc; + +namespace SmartStore.Admin.Models.DataExchange +{ + public partial class ExportProfileModel : EntityModelBase + { + [SmartResourceDisplayName("Admin.Configuration.Export.Name")] + public string Name { get; set; } + + [SmartResourceDisplayName("Common.Enabled")] + public bool Enabled { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.SchedulingHours")] + public int SchedulingHours { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.LastExecution")] + [AllowHtml] + public string LastExecution { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Offset")] + public int Offset { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Limit")] + public int Limit { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.BatchSize")] + public int BatchSize { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.PerStore")] + public bool PerStore { get; set; } + + public Provider Providing { get; set; } + public Filter Filtering { get; set; } + + public int StoreCount { get; set; } + public string AllString { get; set; } + public string UnspecifiedString { get; set; } + + + public class Provider + { + [SmartResourceDisplayName("Common.Image")] + public string ThumbnailUrl { get; set; } + + [SmartResourceDisplayName("Common.Website")] + public string Url { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Plugins.Fields.Configure")] + public string ConfigurationUrl { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.ProviderSystemName")] + public string SystemName { get; set; } + public List AvailableExportProviders { get; set; } + + [SmartResourceDisplayName("Common.Provider")] + public string FriendlyName { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Plugins.Fields.Author")] + public string Author { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Plugins.Fields.Version")] + public string Version { get; set; } + + [SmartResourceDisplayName("Common.Description")] + [AllowHtml] + public string Description { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.EntityType")] + public ExportEntityType EntityType { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.EntityType")] + public string EntityTypeName { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.FileType")] + public string FileType { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.SupportedFileTypes")] + public string SupportedFileTypes { get; set; } + } + + + public class Filter + { + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.StoreId")] + public int? StoreId { get; set; } + public List AvailableStores { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.IsPublished")] + public bool? IsPublished { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ProductType")] + public ProductType? ProductType { get; set; } + public List AvailableProductTypes { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.CreatedFrom")] + public DateTime? CreatedFrom { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.CreatedTo")] + public DateTime? CreatedTo { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.PriceMinimum")] + public decimal? PriceMinimum { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.PriceMaximum")] + public decimal? PriceMaximum { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.AvailabilityMinimum")] + public int? AvailabilityMinimum { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.AvailabilityMaximum")] + public int? AvailabilityMaximum { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.CategoryIds")] + public int[] CategoryIds { get; set; } + public List AvailableCategories { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.WithoutCategories")] + public bool? WithoutCategories { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ManufacturerIds")] + public int[] ManufacturerIds { get; set; } + public List AvailableManufacturers { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.WithoutManufacturers")] + public bool? WithoutManufacturers { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ProductTagIds")] + public int? ProductTagIds { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.IncludeFeaturedProducts")] + public bool? IncludeFeaturedProducts { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.OnlyFeaturedProducts")] + public bool? OnlyFeaturedProducts { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.OrderStatus")] + public OrderStatus? OrderStatus { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.PaymentStatus")] + public PaymentStatus? PaymentStatus { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ShippingStatus")] + public ShippingStatus? ShippingStatus { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.CustomerRoleIds")] + public int[] CustomerRoleIds { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Export/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Export/ExportProfileModel.cs deleted file mode 100644 index 547211434c..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Models/Export/ExportProfileModel.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System.Collections.Generic; -using System.Web.Mvc; -using SmartStore.Core.Domain.Export; -using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; - -namespace SmartStore.Admin.Models.Export -{ - public partial class ExportProfileModel : EntityModelBase - { - [SmartResourceDisplayName("Common.Image")] - public string ThumbnailUrl { get; set; } - public string ThumbnailClasses - { - get - { - return "grid-icon"; - } - } - - [SmartResourceDisplayName("Admin.Configuration.Export.ProviderSystemName")] - public string ProviderSystemName { get; set; } - public List AvailableExportProviders { get; set; } - - [SmartResourceDisplayName("Common.Provider")] - public string ProviderFriendlyName { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.EntityType")] - public string EntityType { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.SupportedFileTypes")] - public string SupportedFileTypes { get; set; } - - - [SmartResourceDisplayName("Admin.Configuration.Export.Name")] - public string Name { get; set; } - - [SmartResourceDisplayName("Common.Enabled")] - public bool Enabled { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.FileType")] - public ExportFileType FileType { get; set; } - public List AvailableFileTypes { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.FileType")] - public string FileTypeName { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.SchedulingHours")] - public int SchedulingHours { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.LastExecution")] - [AllowHtml] - public string LastExecution { get; set; } - - - [SmartResourceDisplayName("Admin.Configuration.Export.Segmentation.Offset")] - public int Offset { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Segmentation.Limit")] - public int Limit { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Segmentation.BatchSize")] - public int BatchSize { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Segmentation.PerStore")] - public bool PerStore { get; set; } - } -} \ 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 ef978b7e3a..0188006d32 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -248,7 +248,7 @@ - + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml index b6f318d5b4..e8b3dc3b2d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml @@ -1,20 +1,20 @@ -@using SmartStore.Admin.Models.Export +@using SmartStore.Admin.Models.DataExchange @model ExportProfileModel @{ Layout = null; }
- @if(Model.AvailableExportProviders.Count > 0) + @if(Model.Providing.AvailableExportProviders.Count > 0) {
- @Html.SmartLabelFor(model => model.ProviderSystemName) + @Html.SmartLabelFor(model => model.Providing.SystemName) - @Html.DropDownListFor(model => model.ProviderSystemName, Model.AvailableExportProviders) + @Html.DropDownListFor(model => model.Providing.SystemName, Model.Providing.AvailableExportProviders)
diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 1a594c0333..15e1640b40 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -1,16 +1,22 @@ @model ExportProfileModel -@using SmartStore.Core.Domain.Export; -@using SmartStore.Admin.Models.Export; +@using SmartStore.Core.Domain.DataExchange; +@using SmartStore.Admin.Models.DataExchange; @{ - var title = string.Concat(T("Admin.Common.Edit"), " ", T("Common.Profile")); + var title = string.Concat(T("Admin.Common.Edit"), " ", T("Common.Profile")); ViewBag.Title = title; } @using (Html.BeginForm()) { + @Html.HiddenFor(model => model.Id) +
- + @if (Model.Providing.ThumbnailUrl.HasValue()) + { + + } +   @title - @Model.Name @Html.ActionLink("(" + T("Admin.Common.BackToList") + ")", "List")
@@ -22,109 +28,323 @@
-
- + @Html.ValidationSummary(false) + + @Html.SmartStore().TabStrip().Name("export-profile-edit").Items(x => + { + x.Add().Text(T("Common.General").Text).Content(TabGeneral()).Selected(true); + x.Add().Text(T("Common.Filter").Text).Content(TabFilter()); + x.Add().Text(T("Common.Projection").Text).Content(TabProjection()); + x.Add().Text(T("Common.Partition").Text).Content(TabPartition()); + x.Add().Text(T("Admin.Configuration").Text).Content(TabConfiguration()); + x.Add().Text(T("Common.Publishing").Text).Content(TabDeployment()); + + EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "export-profile-edit", this.Html, this.Model)); + }) +} + +@helper TabGeneral() +{ +
+ + + + + + + + + + + + + @if (Model.Providing.Url.HasValue()) + { + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ @Html.SmartLabelFor(model => model.Providing.FriendlyName) + + @Model.Providing.FriendlyName.NaIfEmpty() + (@(Model.Providing.SystemName.NaIfEmpty())) +
+ @Html.SmartLabelFor(model => model.Providing.Author) + + @Html.DisplayFor(model => model.Providing.Author) +
+ @Html.SmartLabelFor(model => model.Providing.Version) + + @Html.DisplayFor(model => model.Providing.Version) +
- @Html.SmartLabelFor(model => model.ProviderFriendlyName) + @Html.SmartLabelFor(model => model.Providing.Url) - @Model.ProviderFriendlyName.NaIfEmpty() (@(Model.ProviderSystemName.NaIfEmpty())) + @Model.Providing.Url
+ @Html.SmartLabelFor(model => model.Providing.EntityTypeName) + + @Html.DisplayFor(model => model.Providing.EntityTypeName) +
+ @Html.SmartLabelFor(model => model.Providing.FileType) + + @Html.DisplayFor(model => model.Providing.FileType) +
+ @Html.SmartLabelFor(model => model.Providing.Description) + + @if (Model.Providing.Description.HasValue()) + { + @Html.Raw(Model.Providing.Description) + } + else + { + @("".NaIfEmpty()) + } +
 
+ @Html.SmartLabelFor(model => model.Name) + + @Html.EditorFor(model => model.Name) + @Html.ValidationMessageFor(model => model.Name) +
+ @Html.SmartLabelFor(model => model.Enabled) + + @Html.EditorFor(model => model.Enabled) + @Html.ValidationMessageFor(model => model.Enabled) +
+ @Html.SmartLabelFor(model => model.SchedulingHours) + + @Html.EditorFor(model => model.SchedulingHours) + @Html.ValidationMessageFor(model => model.SchedulingHours) +
+} + +@helper TabFilter() +{ + + @if (Model.StoreCount > 1) + { + } + + @if (Model.Providing.EntityType == ExportEntityType.Product) + { + + + + + + + + + + + + - - + + + + + + //TODO manus + //TODO product tags + } + else if (Model.Providing.EntityType == ExportEntityType.Order) + { + } +
- @Html.SmartLabelFor(model => model.Name) + @Html.SmartLabelFor(model => model.Filtering.StoreId) - @Html.EditorFor(model => model.Name) - @Html.ValidationMessageFor(model => model.Name) + @Html.DropDownListFor(model => model.Filtering.StoreId, Model.Filtering.AvailableStores, Model.AllString) + @Html.ValidationMessageFor(model => model.Filtering.StoreId)
- @Html.SmartLabelFor(model => model.Enabled) + @Html.SmartLabelFor(model => model.Filtering.IsPublished) - @Html.EditorFor(model => model.Enabled) - @Html.ValidationMessageFor(model => model.Enabled) + @Html.EditorFor(model => model.Filtering.IsPublished) + @Html.ValidationMessageFor(model => model.Filtering.IsPublished)
- @Html.SmartLabelFor(model => model.EntityType) + @Html.SmartLabelFor(model => model.Filtering.ProductType) - @Html.DisplayFor(model => model.EntityType) + @Html.DropDownListFor(model => model.Filtering.ProductType, Model.Filtering.AvailableProductTypes, Model.AllString)
- @Html.SmartLabelFor(model => model.FileType) + @Html.SmartLabelFor(model => model.Filtering.CreatedFrom) - @Html.DropDownListFor(model => model.FileType, Model.AvailableFileTypes) - @Html.ValidationMessageFor(model => model.FileType) + @Html.EditorFor(model => model.Filtering.CreatedFrom) + @Html.ValidationMessageFor(model => model.Filtering.CreatedFrom)
- @Html.SmartLabelFor(model => model.SchedulingHours) + @Html.SmartLabelFor(model => model.Filtering.CreatedTo) - @Html.EditorFor(model => model.SchedulingHours) - @Html.ValidationMessageFor(model => model.SchedulingHours) + @Html.EditorFor(model => model.Filtering.CreatedTo) + @Html.ValidationMessageFor(model => model.Filtering.CreatedTo) +
+ @Html.SmartLabelFor(model => model.Filtering.PriceMinimum) + + @Html.EditorFor(model => model.Filtering.PriceMinimum) + @Html.ValidationMessageFor(model => model.Filtering.PriceMinimum) +
+ @Html.SmartLabelFor(model => model.Filtering.PriceMaximum) + + @Html.EditorFor(model => model.Filtering.PriceMaximum) + @Html.ValidationMessageFor(model => model.Filtering.PriceMaximum) +
+ @Html.SmartLabelFor(model => model.Filtering.AvailabilityMinimum) + + @Html.EditorFor(model => model.Filtering.AvailabilityMinimum) + @Html.ValidationMessageFor(model => model.Filtering.AvailabilityMinimum)
-
-
- @T("Common.Partition") -
-
+
+ @Html.SmartLabelFor(model => model.Filtering.AvailabilityMaximum) + + @Html.EditorFor(model => model.Filtering.AvailabilityMaximum) + @Html.ValidationMessageFor(model => model.Filtering.AvailabilityMaximum) +
+ @Html.SmartLabelFor(model => model.Filtering.CategoryIds) + + @Html.ListBoxFor(model => model.Filtering.CategoryIds, + new MultiSelectList(Model.Filtering.AvailableCategories, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(model => model.Filtering.CategoryIds)
- @Html.SmartLabelFor(model => model.Offset) + @Html.SmartLabelFor(model => model.Filtering.WithoutCategories) - @Html.EditorFor(model => model.Offset) - @Html.ValidationMessageFor(model => model.Offset) + @Html.EditorFor(model => model.Filtering.WithoutCategories) + @Html.ValidationMessageFor(model => model.Filtering.WithoutCategories)
- @Html.SmartLabelFor(model => model.Limit) + @Html.SmartLabelFor(model => model.Filtering.WithoutManufacturers) - @Html.EditorFor(model => model.Limit) - @Html.ValidationMessageFor(model => model.Limit) + @Html.EditorFor(model => model.Filtering.WithoutManufacturers) + @Html.ValidationMessageFor(model => model.Filtering.WithoutManufacturers)
- @Html.SmartLabelFor(model => model.BatchSize) + @Html.SmartLabelFor(model => model.Filtering.IncludeFeaturedProducts) - @Html.EditorFor(model => model.BatchSize) - @Html.ValidationMessageFor(model => model.BatchSize) + @Html.EditorFor(model => model.Filtering.IncludeFeaturedProducts) + @Html.ValidationMessageFor(model => model.Filtering.IncludeFeaturedProducts)
- @Html.SmartLabelFor(model => model.PerStore) + @Html.SmartLabelFor(model => model.Filtering.OnlyFeaturedProducts) - @Html.EditorFor(model => model.PerStore) - @Html.ValidationMessageFor(model => model.PerStore) + @Html.EditorFor(model => model.Filtering.OnlyFeaturedProducts) + @Html.ValidationMessageFor(model => model.Filtering.OnlyFeaturedProducts)
+} - -
+@helper TabProjection() +{ +} + +@helper TabPartition() +{ + + + + + + + + + + + + + + + + + +
+ @Html.SmartLabelFor(model => model.Offset) + + @Html.EditorFor(model => model.Offset) + @Html.ValidationMessageFor(model => model.Offset) +
+ @Html.SmartLabelFor(model => model.Limit) + + @Html.EditorFor(model => model.Limit) + @Html.ValidationMessageFor(model => model.Limit) +
+ @Html.SmartLabelFor(model => model.BatchSize) + + @Html.EditorFor(model => model.BatchSize) + @Html.ValidationMessageFor(model => model.BatchSize) +
+ @Html.SmartLabelFor(model => model.PerStore) + + @Html.EditorFor(model => model.PerStore) + @Html.ValidationMessageFor(model => model.PerStore) +
+} + +@helper TabConfiguration() +{ +} + +@helper TabDeployment() +{ } @Html.DeleteConfirmation("export-profile-delete") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index e559017485..a38c3a9ed5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -1,5 +1,5 @@ -@using SmartStore.Core.Domain.Export; -@using SmartStore.Admin.Models.Export; +@using SmartStore.Core.Domain.DataExchange; +@using SmartStore.Admin.Models.DataExchange; @using Telerik.Web.Mvc.UI; @{ ViewBag.Title = T("Common.Export").Text; @@ -35,29 +35,29 @@ }) .Columns(columns => { - columns.Bound(x => x.ThumbnailUrl) + columns.Bound(x => x.Providing.ThumbnailUrl) .Width(70) .Centered() .Template( @ - @item.Id + @item.Id ) - .ClientTemplate("<#= Id #>"); + .ClientTemplate("<#= Id #>"); columns.Bound(x => x.Name) .Width(400) .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) .ClientTemplate("\"><#= Name #>"); - columns.Bound(x => x.ProviderSystemName) - .Template(x => "{0} ({1})".FormatInvariant(x.ProviderFriendlyName, x.ProviderSystemName)) - .ClientTemplate("<#= ProviderFriendlyName #> (<#= ProviderSystemName #>)"); + columns.Bound(x => x.Providing.SystemName) + .Template(x => "{0} ({1})".FormatInvariant(x.Providing.FriendlyName, x.Providing.SystemName)) + .ClientTemplate("<#= Providing.FriendlyName #> (<#= Providing.SystemName #>)"); columns.Bound(x => x.Enabled) .Width(100) .Template(item => @Html.SymbolForBool(item.Enabled)) .ClientTemplate(@Html.SymbolForBool("Enabled")) .Centered(); - columns.Bound(x => x.EntityType); - columns.Bound(x => x.FileTypeName) + columns.Bound(x => x.Providing.EntityTypeName); + columns.Bound(x => x.Providing.FileType) .Centered(); columns.Bound(x => x.SchedulingHours) .Centered(); @@ -115,7 +115,7 @@ smallIcon: dialog.find('.modal-body'), callbackSuccess: function (resp) { dialog.find('.modal-body').html(resp); - $('#ProviderSystemName').selectWrapper(); + $('select[name="Providing.SystemName"]').selectWrapper(); }, callbackComplete: function () { showPrimaryButton(true); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/List.cshtml index ea9c2f4f27..6458d88ec1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/List.cshtml @@ -67,7 +67,7 @@ { - Website + @T("Common.Website") } @if (plugin.Author.HasValue()) From f081152dab4f0b6e2487d74033937c24d081d0d0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 4 Aug 2015 17:43:17 +0200 Subject: [PATCH 004/874] More about export profile data filtering --- .../Domain/DataExchange/ExportFilter.cs | 17 +- .../201508040836122_ExportFramework.cs | 14 +- .../Controllers/ExportController.cs | 180 +++++++++++----- .../Models/DataExchange/ExportFilterModel.cs | 88 ++++++++ .../Models/DataExchange/ExportProfileModel.cs | 73 +------ .../Administration/SmartStore.Admin.csproj | 1 + .../Administration/Views/Export/Edit.cshtml | 199 +++++++++++++----- 7 files changed, 372 insertions(+), 200 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs index a192083317..db02950efb 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs @@ -75,17 +75,12 @@ public class ExportFilter /// /// Identifiers of product tags /// - public int? ProductTagIds { get; set; } + public int[] ProductTagIds { get; set; } /// - /// A value indicating whether loaded products are marked as featured + /// A value indicating whether to load products that are marked as featured (relates only to categories and manufacturers) /// - public bool? IncludeFeaturedProducts { get; set; } - - /// - /// A value indicating whether to load only featured products - /// - public bool? OnlyFeaturedProducts { get; set; } + public bool? FeaturedProducts { get; set; } /// /// Filter by product type @@ -99,17 +94,17 @@ public class ExportFilter /// /// Filter by order status /// - public OrderStatus? OrderStatus { get; set; } + public OrderStatus[] OrderStatus { get; set; } /// /// Filter by payment status /// - public PaymentStatus? PaymentStatus { get; set; } + public PaymentStatus[] PaymentStatus { get; set; } /// /// Filter by shipping status /// - public ShippingStatus? ShippingStatus { get; set; } + public ShippingStatus[] ShippingStatus { get; set; } /// /// Identifiers of customer roles diff --git a/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.cs index 84a392c3ff..b62177e5a4 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.cs @@ -157,7 +157,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Export.Filter.CreatedFrom", "Created from", - "Erstellt ab", + "Erstellt von", "Filter by created date.", "Nach dem Erstellungsdatum filtern."); @@ -227,17 +227,11 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Filter by product tags.", "Nach Produkt-Tags filtern."); - builder.AddOrUpdate("Admin.Configuration.Export.Filter.IncludeFeaturedProducts", - "Include featured products", - "Empfohlene Produkte einbeziehen", - "Specifies whether to include featured products.", - "Legt fest, ob empfohlene Produkte einbezogen werden sollen."); - - builder.AddOrUpdate("Admin.Configuration.Export.Filter.OnlyFeaturedProducts", + builder.AddOrUpdate("Admin.Configuration.Export.Filter.FeaturedProducts", "Only featured products", "Nur empfohlene Produkte", - "Filter by featured products.", - "Nach empfohlenen Produkten filtern."); + "Filter by featured products. Is only applied when the filtering by categories and manufacturers.", + "Nach empfohlenen Produkten filtern. Wird nur bei der Filterung nach Warengruppen und Hersteller angewendet."); builder.AddOrUpdate("Admin.Configuration.Export.Filter.ProductType", "Product type", diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 510772fdb3..ce04e4a736 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -6,9 +6,13 @@ using SmartStore.Core.Domain; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Plugins; using SmartStore.Services; using SmartStore.Services.Catalog; +using SmartStore.Services.Customers; using SmartStore.Services.DataExchange; using SmartStore.Services.Media; using SmartStore.Services.Security; @@ -27,19 +31,28 @@ public class ExportController : AdminControllerBase private readonly PluginMediator _pluginMediator; private readonly IPictureService _pictureService; private readonly ICategoryService _categoryService; + private readonly IManufacturerService _manufacturerService; + private readonly IProductTagService _productTagService; + private readonly ICustomerService _customerService; public ExportController( ICommonServices services, IExportService exportService, PluginMediator pluginMediator, IPictureService pictureService, - ICategoryService categoryService) + ICategoryService categoryService, + IManufacturerService manufacturerService, + IProductTagService productTagService, + ICustomerService customerService) { _services = services; _exportService = exportService; _pluginMediator = pluginMediator; _pictureService = pictureService; _categoryService = categoryService; + _manufacturerService = manufacturerService; + _productTagService = productTagService; + _customerService = customerService; } private void PrepareExportProfileModel(ExportProfileModel model, ExportProfile profile, Provider provider) @@ -81,53 +94,88 @@ private void PrepareExportProfileModelForEdit(ExportProfileModel model, ExportPr var filter = XmlHelper.Deserialize(profile.Filtering); var allStores = _services.StoreService.GetAllStores(); - var allCategories = _categoryService.GetAllCategories(showHidden: true); - var mappedCategories = allCategories.ToDictionary(x => x.Id); - var yes = T("Admin.Common.Yes").Text; - var no = T("Admin.Common.No").Text; - - model.StoreCount = allStores.Count; model.AllString = T("Admin.Common.All"); - model.UnspecifiedString = T("Common.Unspecified"); + //model.UnspecifiedString = T("Common.Unspecified"); + model.StoreCount = allStores.Count; model.Offset = profile.Offset; model.Limit = profile.Limit; model.BatchSize = profile.BatchSize; model.PerStore = profile.PerStore; - model.Filtering = new ExportProfileModel.Filter + Action initModelBase = x => { - StoreId = filter.StoreId, - CreatedFrom = filter.CreatedFrom, - CreatedTo = filter.CreatedTo, - PriceMinimum = filter.PriceMinimum, - PriceMaximum = filter.PriceMaximum, - AvailabilityMinimum = filter.AvailabilityMinimum, - AvailabilityMaximum = filter.AvailabilityMaximum, - IsPublished = filter.IsPublished, - CategoryIds = filter.CategoryIds, - WithoutCategories = filter.WithoutCategories, - ManufacturerIds = filter.ManufacturerIds, - WithoutManufacturers = filter.WithoutManufacturers, - ProductTagIds = filter.ProductTagIds, - IncludeFeaturedProducts = filter.IncludeFeaturedProducts, - OnlyFeaturedProducts = filter.OnlyFeaturedProducts, - ProductType = filter.ProductType, - OrderStatus = filter.OrderStatus, - PaymentStatus = filter.PaymentStatus, - ShippingStatus = filter.ShippingStatus, - CustomerRoleIds = filter.CustomerRoleIds + x.StoreId = filter.StoreId; + x.CreatedFrom = filter.CreatedFrom; + x.CreatedTo = filter.CreatedTo; + + x.AvailableStores = allStores + .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) + .ToList(); }; - model.Filtering.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); + if (model.Providing.EntityType == ExportEntityType.Product) + { + var allCategories = _categoryService.GetAllCategories(showHidden: true); + var mappedCategories = allCategories.ToDictionary(x => x.Id); + var allManufacturers = _manufacturerService.GetAllManufacturers(true); + var allProductTags = _productTagService.GetAllProductTags(); - model.Filtering.AvailableStores = allStores - .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) - .ToList(); + model.ProductFiltering = new ExportProductFilterModel + { + PriceMinimum = filter.PriceMinimum, + PriceMaximum = filter.PriceMaximum, + AvailabilityMinimum = filter.AvailabilityMinimum, + AvailabilityMaximum = filter.AvailabilityMaximum, + IsPublished = filter.IsPublished, + CategoryIds = filter.CategoryIds, + WithoutCategories = filter.WithoutCategories, + ManufacturerIds = filter.ManufacturerIds, + WithoutManufacturers = filter.WithoutManufacturers, + ProductTagIds = filter.ProductTagIds, + FeaturedProducts = filter.FeaturedProducts, + ProductType = filter.ProductType + }; + + model.ProductFiltering.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); + + model.ProductFiltering.AvailableCategories = allCategories + .Select(x => new SelectListItem { Text = x.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = x.Id.ToString() }) + .ToList(); + + model.ProductFiltering.AvailableManufacturers = allManufacturers + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + model.ProductFiltering.AvailableProductTags = allProductTags + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + initModelBase(model.ProductFiltering); + } + else if (model.Providing.EntityType == ExportEntityType.Order) + { + var allCustomerRoles = _customerService.GetAllCustomerRoles(true); - model.Filtering.AvailableCategories = allCategories - .Select(x => new SelectListItem { Text = x.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = x.Id.ToString() }) - .ToList(); + model.OrderFiltering = new ExportOrderFilterModel + { + OrderStatus = filter.OrderStatus, + PaymentStatus = filter.PaymentStatus, + ShippingStatus = filter.ShippingStatus, + CustomerRoleIds = filter.CustomerRoleIds + }; + + model.OrderFiltering.AvailableOrderStates = OrderStatus.Pending.ToSelectList(false).ToList(); + model.OrderFiltering.AvailablePaymentStates = PaymentStatus.Pending.ToSelectList(false).ToList(); + model.OrderFiltering.AvailableShippingStates = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); + + model.OrderFiltering.AvailableCustomerRoles = allCustomerRoles + .OrderBy(x => x.Name) + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + initModelBase(model.OrderFiltering); + } } public ActionResult Index() @@ -254,30 +302,48 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) return View(model); } - var filter = new ExportFilter + ExportFilter filter = null; + + Action getFromModelBase = x => { - StoreId = model.Filtering.StoreId ?? 0, - CreatedFrom = model.Filtering.CreatedFrom, - CreatedTo = model.Filtering.CreatedTo, - PriceMinimum = model.Filtering.PriceMinimum, - PriceMaximum = model.Filtering.PriceMaximum, - AvailabilityMinimum = model.Filtering.AvailabilityMinimum, - AvailabilityMaximum = model.Filtering.AvailabilityMaximum, - IsPublished = model.Filtering.IsPublished, - CategoryIds = model.Filtering.CategoryIds, - WithoutCategories = model.Filtering.WithoutCategories, - ManufacturerIds = model.Filtering.ManufacturerIds, - WithoutManufacturers = model.Filtering.WithoutManufacturers, - ProductTagIds = model.Filtering.ProductTagIds, - IncludeFeaturedProducts = model.Filtering.IncludeFeaturedProducts, - OnlyFeaturedProducts = model.Filtering.OnlyFeaturedProducts, - ProductType = model.Filtering.ProductType, - OrderStatus = model.Filtering.OrderStatus, - PaymentStatus = model.Filtering.PaymentStatus, - ShippingStatus = model.Filtering.ShippingStatus, - CustomerRoleIds = model.Filtering.CustomerRoleIds + filter.StoreId = x.StoreId ?? 0; + filter.CreatedFrom = x.CreatedFrom; + filter.CreatedTo = x.CreatedTo; }; + if (model.ProductFiltering != null && provider.Value.EntityType == ExportEntityType.Product) + { + filter = new ExportFilter + { + PriceMinimum = model.ProductFiltering.PriceMinimum, + PriceMaximum = model.ProductFiltering.PriceMaximum, + AvailabilityMinimum = model.ProductFiltering.AvailabilityMinimum, + AvailabilityMaximum = model.ProductFiltering.AvailabilityMaximum, + IsPublished = model.ProductFiltering.IsPublished, + CategoryIds = model.ProductFiltering.CategoryIds, + WithoutCategories = model.ProductFiltering.WithoutCategories, + ManufacturerIds = model.ProductFiltering.ManufacturerIds, + WithoutManufacturers = model.ProductFiltering.WithoutManufacturers, + ProductTagIds = model.ProductFiltering.ProductTagIds, + FeaturedProducts = model.ProductFiltering.FeaturedProducts, + ProductType = model.ProductFiltering.ProductType + }; + + getFromModelBase(model.ProductFiltering); + } + else if (model.OrderFiltering != null && provider.Value.EntityType == ExportEntityType.Order) + { + filter = new ExportFilter + { + OrderStatus = model.OrderFiltering.OrderStatus, + PaymentStatus = model.OrderFiltering.PaymentStatus, + ShippingStatus = model.OrderFiltering.ShippingStatus, + CustomerRoleIds = model.OrderFiltering.CustomerRoleIds + }; + + getFromModelBase(model.OrderFiltering); + } + profile.Name = model.Name; profile.Enabled = model.Enabled; profile.ScheduleTask.Seconds = model.SchedulingHours * 3600; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs new file mode 100644 index 0000000000..ca812df0b6 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Domain.Shipping; +using SmartStore.Web.Framework; + +namespace SmartStore.Admin.Models.DataExchange +{ + public class ExportFilterModelBase + { + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.StoreId")] + public int? StoreId { get; set; } + public List AvailableStores { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.CreatedFrom")] + public DateTime? CreatedFrom { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.CreatedTo")] + public DateTime? CreatedTo { get; set; } + } + + + public class ExportProductFilterModel : ExportFilterModelBase + { + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.IsPublished")] + public bool? IsPublished { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ProductType")] + public ProductType? ProductType { get; set; } + public List AvailableProductTypes { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.PriceMinimum")] + public decimal? PriceMinimum { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.PriceMaximum")] + public decimal? PriceMaximum { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.AvailabilityMinimum")] + public int? AvailabilityMinimum { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.AvailabilityMaximum")] + public int? AvailabilityMaximum { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.CategoryIds")] + public int[] CategoryIds { get; set; } + public List AvailableCategories { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.WithoutCategories")] + public bool? WithoutCategories { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ManufacturerIds")] + public int[] ManufacturerIds { get; set; } + public List AvailableManufacturers { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.WithoutManufacturers")] + public bool? WithoutManufacturers { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ProductTagIds")] + public int[] ProductTagIds { get; set; } + public List AvailableProductTags { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.FeaturedProducts")] + public bool? FeaturedProducts { get; set; } + } + + + public class ExportOrderFilterModel : ExportFilterModelBase + { + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.OrderStatus")] + public OrderStatus[] OrderStatus { get; set; } + public List AvailableOrderStates { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.PaymentStatus")] + public PaymentStatus[] PaymentStatus { get; set; } + public List AvailablePaymentStates { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ShippingStatus")] + public ShippingStatus[] ShippingStatus { get; set; } + public List AvailableShippingStates { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.CustomerRoleIds")] + public int[] CustomerRoleIds { get; set; } + public List AvailableCustomerRoles { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index ca98c7ab9b..4dd20aa2e1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -39,11 +39,12 @@ public partial class ExportProfileModel : EntityModelBase public bool PerStore { get; set; } public Provider Providing { get; set; } - public Filter Filtering { get; set; } + + public ExportProductFilterModel ProductFiltering { get; set; } + public ExportOrderFilterModel OrderFiltering { get; set; } public int StoreCount { get; set; } public string AllString { get; set; } - public string UnspecifiedString { get; set; } public class Provider @@ -86,73 +87,5 @@ public class Provider [SmartResourceDisplayName("Admin.Configuration.Export.SupportedFileTypes")] public string SupportedFileTypes { get; set; } } - - - public class Filter - { - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.StoreId")] - public int? StoreId { get; set; } - public List AvailableStores { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.IsPublished")] - public bool? IsPublished { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ProductType")] - public ProductType? ProductType { get; set; } - public List AvailableProductTypes { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.CreatedFrom")] - public DateTime? CreatedFrom { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.CreatedTo")] - public DateTime? CreatedTo { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.PriceMinimum")] - public decimal? PriceMinimum { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.PriceMaximum")] - public decimal? PriceMaximum { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.AvailabilityMinimum")] - public int? AvailabilityMinimum { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.AvailabilityMaximum")] - public int? AvailabilityMaximum { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.CategoryIds")] - public int[] CategoryIds { get; set; } - public List AvailableCategories { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.WithoutCategories")] - public bool? WithoutCategories { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ManufacturerIds")] - public int[] ManufacturerIds { get; set; } - public List AvailableManufacturers { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.WithoutManufacturers")] - public bool? WithoutManufacturers { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ProductTagIds")] - public int? ProductTagIds { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.IncludeFeaturedProducts")] - public bool? IncludeFeaturedProducts { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.OnlyFeaturedProducts")] - public bool? OnlyFeaturedProducts { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.OrderStatus")] - public OrderStatus? OrderStatus { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.PaymentStatus")] - public PaymentStatus? PaymentStatus { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ShippingStatus")] - public ShippingStatus? ShippingStatus { get; set; } - - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.CustomerRoleIds")] - public int[] CustomerRoleIds { get; set; } - } } } \ 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 0188006d32..0d06106427 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -247,6 +247,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 15e1640b40..ad73a907f4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -151,144 +151,228 @@ @helper TabFilter() { - @if (Model.StoreCount > 1) + @if (Model.ProductFiltering != null) { + if (Model.StoreCount > 1) + { + + + + + } - } - - @if (Model.Providing.EntityType == ExportEntityType.Product) - { - + - //TODO manus - + - //TODO product tags } - else if (Model.Providing.EntityType == ExportEntityType.Order) + else if (Model.OrderFiltering != null) { + if (Model.StoreCount > 1) + { + + + + + } + + + + + + + + + + + + + + + + + + + + + + + + }
+ @Html.SmartLabelFor(model => model.ProductFiltering.StoreId) + + @Html.DropDownListFor(model => model.ProductFiltering.StoreId, Model.ProductFiltering.AvailableStores, Model.AllString) + @Html.ValidationMessageFor(model => model.ProductFiltering.StoreId) +
- @Html.SmartLabelFor(model => model.Filtering.StoreId) + @Html.SmartLabelFor(model => model.ProductFiltering.CreatedFrom) - @Html.DropDownListFor(model => model.Filtering.StoreId, Model.Filtering.AvailableStores, Model.AllString) - @Html.ValidationMessageFor(model => model.Filtering.StoreId) + @Html.EditorFor(model => model.ProductFiltering.CreatedFrom) + @Html.ValidationMessageFor(model => model.ProductFiltering.CreatedFrom)
- @Html.SmartLabelFor(model => model.Filtering.IsPublished) + @Html.SmartLabelFor(model => model.ProductFiltering.CreatedTo) - @Html.EditorFor(model => model.Filtering.IsPublished) - @Html.ValidationMessageFor(model => model.Filtering.IsPublished) + @Html.EditorFor(model => model.ProductFiltering.CreatedTo) + @Html.ValidationMessageFor(model => model.ProductFiltering.CreatedTo)
- @Html.SmartLabelFor(model => model.Filtering.ProductType) + @Html.SmartLabelFor(model => model.ProductFiltering.ProductType) - @Html.DropDownListFor(model => model.Filtering.ProductType, Model.Filtering.AvailableProductTypes, Model.AllString) + @Html.DropDownListFor(model => model.ProductFiltering.ProductType, Model.ProductFiltering.AvailableProductTypes, Model.AllString)
- @Html.SmartLabelFor(model => model.Filtering.CreatedFrom) + @Html.SmartLabelFor(model => model.ProductFiltering.IsPublished) - @Html.EditorFor(model => model.Filtering.CreatedFrom) - @Html.ValidationMessageFor(model => model.Filtering.CreatedFrom) + @Html.EditorFor(model => model.ProductFiltering.IsPublished) + @Html.ValidationMessageFor(model => model.ProductFiltering.IsPublished)
- @Html.SmartLabelFor(model => model.Filtering.CreatedTo) + @Html.SmartLabelFor(model => model.ProductFiltering.PriceMinimum) - @Html.EditorFor(model => model.Filtering.CreatedTo) - @Html.ValidationMessageFor(model => model.Filtering.CreatedTo) + @Html.EditorFor(model => model.ProductFiltering.PriceMinimum) + @Html.ValidationMessageFor(model => model.ProductFiltering.PriceMinimum)
- @Html.SmartLabelFor(model => model.Filtering.PriceMinimum) + @Html.SmartLabelFor(model => model.ProductFiltering.PriceMaximum) - @Html.EditorFor(model => model.Filtering.PriceMinimum) - @Html.ValidationMessageFor(model => model.Filtering.PriceMinimum) + @Html.EditorFor(model => model.ProductFiltering.PriceMaximum) + @Html.ValidationMessageFor(model => model.ProductFiltering.PriceMaximum)
- @Html.SmartLabelFor(model => model.Filtering.PriceMaximum) + @Html.SmartLabelFor(model => model.ProductFiltering.AvailabilityMinimum) - @Html.EditorFor(model => model.Filtering.PriceMaximum) - @Html.ValidationMessageFor(model => model.Filtering.PriceMaximum) + @Html.EditorFor(model => model.ProductFiltering.AvailabilityMinimum) + @Html.ValidationMessageFor(model => model.ProductFiltering.AvailabilityMinimum)
- @Html.SmartLabelFor(model => model.Filtering.AvailabilityMinimum) + @Html.SmartLabelFor(model => model.ProductFiltering.AvailabilityMaximum) - @Html.EditorFor(model => model.Filtering.AvailabilityMinimum) - @Html.ValidationMessageFor(model => model.Filtering.AvailabilityMinimum) + @Html.EditorFor(model => model.ProductFiltering.AvailabilityMaximum) + @Html.ValidationMessageFor(model => model.ProductFiltering.AvailabilityMaximum)
- @Html.SmartLabelFor(model => model.Filtering.AvailabilityMaximum) + @Html.SmartLabelFor(model => model.ProductFiltering.WithoutCategories) - @Html.EditorFor(model => model.Filtering.AvailabilityMaximum) - @Html.ValidationMessageFor(model => model.Filtering.AvailabilityMaximum) + @Html.EditorFor(model => model.ProductFiltering.WithoutCategories) + @Html.ValidationMessageFor(model => model.ProductFiltering.WithoutCategories)
- @Html.SmartLabelFor(model => model.Filtering.CategoryIds) + @Html.SmartLabelFor(model => model.ProductFiltering.CategoryIds) - @Html.ListBoxFor(model => model.Filtering.CategoryIds, - new MultiSelectList(Model.Filtering.AvailableCategories, "Value", "Text"), + @Html.ListBoxFor(model => model.ProductFiltering.CategoryIds, + new MultiSelectList(Model.ProductFiltering.AvailableCategories, "Value", "Text"), new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(model => model.Filtering.CategoryIds) + @Html.ValidationMessageFor(model => model.ProductFiltering.CategoryIds)
- @Html.SmartLabelFor(model => model.Filtering.WithoutCategories) + @Html.SmartLabelFor(model => model.ProductFiltering.WithoutManufacturers) - @Html.EditorFor(model => model.Filtering.WithoutCategories) - @Html.ValidationMessageFor(model => model.Filtering.WithoutCategories) + @Html.EditorFor(model => model.ProductFiltering.WithoutManufacturers) + @Html.ValidationMessageFor(model => model.ProductFiltering.WithoutManufacturers)
- @Html.SmartLabelFor(model => model.Filtering.WithoutManufacturers) + @Html.SmartLabelFor(model => model.ProductFiltering.ManufacturerIds) - @Html.EditorFor(model => model.Filtering.WithoutManufacturers) - @Html.ValidationMessageFor(model => model.Filtering.WithoutManufacturers) + @Html.ListBoxFor(model => model.ProductFiltering.ManufacturerIds, + new MultiSelectList(Model.ProductFiltering.AvailableManufacturers, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(model => model.ProductFiltering.ManufacturerIds)
- @Html.SmartLabelFor(model => model.Filtering.IncludeFeaturedProducts) + @Html.SmartLabelFor(model => model.ProductFiltering.ProductTagIds) - @Html.EditorFor(model => model.Filtering.IncludeFeaturedProducts) - @Html.ValidationMessageFor(model => model.Filtering.IncludeFeaturedProducts) + @Html.ListBoxFor(model => model.ProductFiltering.ProductTagIds, + new MultiSelectList(Model.ProductFiltering.AvailableProductTags, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(model => model.ProductFiltering.ProductTagIds)
- @Html.SmartLabelFor(model => model.Filtering.OnlyFeaturedProducts) + @Html.SmartLabelFor(model => model.ProductFiltering.FeaturedProducts) - @Html.EditorFor(model => model.Filtering.OnlyFeaturedProducts) - @Html.ValidationMessageFor(model => model.Filtering.OnlyFeaturedProducts) + @Html.EditorFor(model => model.ProductFiltering.FeaturedProducts) + @Html.ValidationMessageFor(model => model.ProductFiltering.FeaturedProducts)
+ @Html.SmartLabelFor(model => model.OrderFiltering.StoreId) + + @Html.DropDownListFor(model => model.OrderFiltering.StoreId, Model.OrderFiltering.AvailableStores, Model.AllString) + @Html.ValidationMessageFor(model => model.OrderFiltering.StoreId) +
+ @Html.SmartLabelFor(model => model.OrderFiltering.CreatedFrom) + + @Html.EditorFor(model => model.OrderFiltering.CreatedFrom) + @Html.ValidationMessageFor(model => model.OrderFiltering.CreatedFrom) +
+ @Html.SmartLabelFor(model => model.OrderFiltering.CreatedTo) + + @Html.EditorFor(model => model.OrderFiltering.CreatedTo) + @Html.ValidationMessageFor(model => model.OrderFiltering.CreatedTo) +
+ @Html.SmartLabelFor(model => model.OrderFiltering.OrderStatus) + + @Html.ListBoxFor(model => model.OrderFiltering.OrderStatus, + new MultiSelectList(Model.OrderFiltering.AvailableOrderStates, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(model => model.OrderFiltering.OrderStatus) +
+ @Html.SmartLabelFor(model => model.OrderFiltering.PaymentStatus) + + @Html.ListBoxFor(model => model.OrderFiltering.PaymentStatus, + new MultiSelectList(Model.OrderFiltering.AvailablePaymentStates, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(model => model.OrderFiltering.PaymentStatus) +
+ @Html.SmartLabelFor(model => model.OrderFiltering.ShippingStatus) + + @Html.ListBoxFor(model => model.OrderFiltering.ShippingStatus, + new MultiSelectList(Model.OrderFiltering.AvailableShippingStates, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(model => model.OrderFiltering.ShippingStatus) +
+ @Html.SmartLabelFor(model => model.OrderFiltering.CustomerRoleIds) + + @Html.ListBoxFor(model => model.OrderFiltering.CustomerRoleIds, + new MultiSelectList(Model.OrderFiltering.AvailableCustomerRoles, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(model => model.OrderFiltering.CustomerRoleIds) +
} @@ -351,5 +435,16 @@ From aa4a544b9c34c383df7ca54e0dfc6459c4d56f15 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 6 Aug 2015 15:07:26 +0200 Subject: [PATCH 005/874] Export framework manages provider specific configuration data --- .../Domain/DataExchange/ExportProfile.cs | 13 +- .../Domain/DataExchange/ExportProjection.cs | 18 + .../SmartStore.Core/SmartStore.Core.csproj | 1 + src/Libraries/SmartStore.Core/XmlHelper.cs | 45 ++- .../Mapping/DataExchange/ExportProfileMap.cs | 2 + .../201508040836122_ExportFramework.resx | 126 ------- ...1508051818404_ExportFramework.Designer.cs} | 2 +- ....cs => 201508051818404_ExportFramework.cs} | 21 +- .../201508051818404_ExportFramework.resx | 126 +++++++ .../SmartStore.Data/SmartStore.Data.csproj | 10 +- .../DataExchange/IExportProvider.cs | 17 +- .../Messages/QueuedMessagesSendTask.cs | 3 +- .../Controllers/ExportController.cs | 264 +++++++++---- .../Models/DataExchange/ExportFilterModel.cs | 2 +- .../Models/DataExchange/ExportPreviewModel.cs | 10 + .../Models/DataExchange/ExportProfileModel.cs | 25 +- .../DataExchange/ExportProjectionModel.cs | 17 + .../Administration/SmartStore.Admin.csproj | 3 + .../Administration/Views/Export/Create.cshtml | 6 +- .../Administration/Views/Export/Edit.cshtml | 357 ++++++++++-------- .../Administration/Views/Export/List.cshtml | 18 +- .../Views/Export/Preview.cshtml | 20 + 22 files changed, 698 insertions(+), 408 deletions(-) create mode 100644 src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.resx rename src/Libraries/SmartStore.Data/Migrations/{201508040836122_ExportFramework.Designer.cs => 201508051818404_ExportFramework.Designer.cs} (92%) rename src/Libraries/SmartStore.Data/Migrations/{201508040836122_ExportFramework.cs => 201508051818404_ExportFramework.cs} (92%) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.resx create mode 100644 src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportPreviewModel.cs create mode 100644 src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs create mode 100644 src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs index fdd4eb5e34..907701cc2b 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs @@ -38,14 +38,19 @@ public ExportProfile() public int SchedulingTaskId { get; set; } /// - /// The profile GUID is the folder name in the file system + /// XML with filtering information /// - public Guid ProfileGuid { get; set; } + public string Filtering { get; set; } /// - /// XML with filtering information + /// XML with projection information /// - public string Filtering { get; set; } + public string Projection { get; set; } + + /// + /// Provider specific configuration data + /// + public string ProviderConfigData { get; set; } /// /// The number of records to be skipped diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs new file mode 100644 index 0000000000..3020a71b1c --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs @@ -0,0 +1,18 @@ +using System; + +namespace SmartStore.Core.Domain.DataExchange +{ + [Serializable] + public class ExportProjection + { + /// + /// The language to be applied to the export + /// + public int LanguageId { get; set; } + + /// + /// The currency to be applied to the export + /// + public int CurrencyId { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index 8e4537dd4c..4dbef97117 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -157,6 +157,7 @@ + diff --git a/src/Libraries/SmartStore.Core/XmlHelper.cs b/src/Libraries/SmartStore.Core/XmlHelper.cs index 7f546eba8a..b73c914f03 100644 --- a/src/Libraries/SmartStore.Core/XmlHelper.cs +++ b/src/Libraries/SmartStore.Core/XmlHelper.cs @@ -120,7 +120,7 @@ public static DateTime DeserializeDateTime(string dateTime) /// /// Object type /// Object instance - /// XML + /// XML string public static string Serialize(T instance) { if (instance != null) @@ -136,11 +136,32 @@ public static string Serialize(T instance) return null; } + /// + /// Serializes an object instance to a XML formatted string + /// + /// Object instance + /// Object type + /// XML string + public static string Serialize(object instance, Type type) + { + if (instance != null) + { + using (var writer = new StringWriter()) + { + var xmlSerializer = new XmlSerializer(type); + + xmlSerializer.Serialize(writer, instance); + return writer.ToString(); + } + } + return null; + } + /// /// Deserializes a XML formatted string to an object instance /// /// Object type - /// XML + /// XML string /// Object instance public static T Deserialize(string xml) { @@ -156,6 +177,26 @@ public static T Deserialize(string xml) return (T)Activator.CreateInstance(typeof(T)); } + /// + /// Deserializes a XML formatted string to an object instance + /// + /// XML string + /// Object type + /// Object instance + public static object Deserialize(string xml, Type type) + { + if (xml.HasValue()) + { + using (var reader = new StringReader(xml)) + { + var serializer = new XmlSerializer(type); + + return serializer.Deserialize(reader); + } + } + return Activator.CreateInstance(type); + } + #endregion } } diff --git a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs index 22d4cc1c4f..c64b24cefe 100644 --- a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs @@ -14,6 +14,8 @@ public ExportProfileMap() this.Property(x => x.FolderName).IsRequired().HasMaxLength(100); this.Property(x => x.ProviderSystemName).IsRequired().HasMaxLength(4000); this.Property(x => x.Filtering).IsMaxLength(); + this.Property(x => x.Projection).IsMaxLength(); + this.Property(x => x.ProviderConfigData).IsMaxLength(); this.HasRequired(x => x.ScheduleTask) .WithMany() diff --git a/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.resx b/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.resx deleted file mode 100644 index c1ffae86af..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.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/201508040836122_ExportFramework.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.Designer.cs similarity index 92% rename from src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.Designer.cs rename to src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.Designer.cs index 24f974b961..ebbd4a40ae 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.Designer.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.Designer.cs @@ -13,7 +13,7 @@ public sealed partial class ExportFramework : IMigrationMetadata string IMigrationMetadata.Id { - get { return "201508040836122_ExportFramework"; } + get { return "201508051818404_ExportFramework"; } } string IMigrationMetadata.Source diff --git a/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.cs similarity index 92% rename from src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.cs rename to src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.cs index b62177e5a4..af3d696b92 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508040836122_ExportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.cs @@ -19,8 +19,9 @@ public override void Up() ProviderSystemName = c.String(nullable: false, maxLength: 4000), Enabled = c.Boolean(nullable: false), SchedulingTaskId = c.Int(nullable: false), - ProfileGuid = c.Guid(nullable: false), Filtering = c.String(), + Projection = c.String(), + ProviderConfigData = c.String(), Offset = c.Int(nullable: false), Limit = c.Int(nullable: false), BatchSize = c.Int(nullable: false), @@ -79,6 +80,11 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "There were no export profiles found.", "Es wurden keine Exportprofile gefunden."); + builder.AddOrUpdate("Admin.Configuration.Export.NoProviderConfigurationRequired", + "The export provider {0} requires no further configuration.", + "Der Export-Provider {0} bentigt keine weitergehende Konfiguration."); + + builder.AddOrUpdate("Admin.Configuration.Export.ProviderSystemName", "Provider", @@ -262,6 +268,19 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Kundengruppen", "Filter by customer roles.", "Nach Kundengruppen filtern."); + + + builder.AddOrUpdate("Admin.Configuration.Export.Projection.LanguageId", + "Language", + "Sprache", + "Specifies the language to be applied to the export.", + "Legt die auf den Export anzuwendende Sprache fest."); + + builder.AddOrUpdate("Admin.Configuration.Export.Projection.CurrencyId", + "Currency", + "Whrung", + "Specifies the currency to be applied to the export.", + "Legt die auf den Export anzuwendende Whrung fest."); } } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.resx b/src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.resx new file mode 100644 index 0000000000..3219191ea2 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.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 bbd83bec0a..a203443917 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -332,9 +332,9 @@ 201507250039446_Merge2.cs - - - 201508040836122_ExportFramework.cs + + + 201508051818404_ExportFramework.cs @@ -633,8 +633,8 @@ 201507250039446_Merge2.cs - - 201508040836122_ExportFramework.cs + + 201508051818404_ExportFramework.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs index 318c0e2586..1f3877ae3a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using SmartStore.Core.Domain; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Plugins; @@ -7,11 +8,19 @@ namespace SmartStore.Services.DataExchange { public partial interface IExportProvider : IProvider, IUserEditable { - // TODO: a more complex result type is required. e.g. IEnumerable.... - MemoryStream Execute(ExportProfile profile); - ExportEntityType EntityType { get; } string FileType { get; } + + /// + /// Get configuration information + /// + /// The partial view name for the configuration + /// Type of the view model + /// Whether configuration is required + bool RequiresConfiguration(out string partialViewName, out Type modelType); + + // TODO: a more complex result type is required. e.g. IEnumerable.... + MemoryStream Execute(ExportProfile profile); } } diff --git a/src/Libraries/SmartStore.Services/Messages/QueuedMessagesSendTask.cs b/src/Libraries/SmartStore.Services/Messages/QueuedMessagesSendTask.cs index b3058c31a8..7d4c8ab9e8 100644 --- a/src/Libraries/SmartStore.Services/Messages/QueuedMessagesSendTask.cs +++ b/src/Libraries/SmartStore.Services/Messages/QueuedMessagesSendTask.cs @@ -26,7 +26,8 @@ public void Execute(TaskExecutionContext ctx) MaxSendTries = maxTries, PageIndex = i, PageSize = pageSize, - Expand = "Attachments" + Expand = "Attachments", + UnsentOnly = true }; var queuedEmails = _queuedEmailService.SearchEmails(q); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index ce04e4a736..6f7ac50ab8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -1,6 +1,11 @@ using System; +using System.Globalization; +using System.IO; using System.Linq; +using System.Text; using System.Web.Mvc; +using System.Web.Routing; +using System.Xml.Serialization; using SmartStore.Admin.Models.DataExchange; using SmartStore.Core; using SmartStore.Core.Domain; @@ -14,6 +19,8 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Customers; using SmartStore.Services.DataExchange; +using SmartStore.Services.Directory; +using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Security; using SmartStore.Web.Framework; @@ -34,6 +41,8 @@ public class ExportController : AdminControllerBase private readonly IManufacturerService _manufacturerService; private readonly IProductTagService _productTagService; private readonly ICustomerService _customerService; + private readonly ILanguageService _languageService; + private readonly ICurrencyService _currencyService; public ExportController( ICommonServices services, @@ -43,7 +52,9 @@ public ExportController( ICategoryService categoryService, IManufacturerService manufacturerService, IProductTagService productTagService, - ICustomerService customerService) + ICustomerService customerService, + ILanguageService languageService, + ICurrencyService currencyService) { _services = services; _exportService = exportService; @@ -53,56 +64,98 @@ public ExportController( _manufacturerService = manufacturerService; _productTagService = productTagService; _customerService = customerService; + _languageService = languageService; + _currencyService = currencyService; } - private void PrepareExportProfileModel(ExportProfileModel model, ExportProfile profile, Provider provider) + #region Utilities + + private string GetThumbnailUrl(Provider provider) { - model.Providing = new ExportProfileModel.Provider - { - SystemName = profile.ProviderSystemName - }; + var url = _pluginMediator.GetIconUrl(provider.Metadata); + + if (url.IsEmpty()) + url = _pictureService.GetDefaultPictureUrl(48); + else + url = Url.Content(url); + return url; + } + + private void PrepareExportProfileModel(ExportProfileModel model, ExportProfile profile, Provider provider) + { model.Id = profile.Id; model.Name = profile.Name; model.Enabled = profile.Enabled; model.SchedulingHours = profile.ScheduleTask.Seconds / 3600; + model.Provider = new ExportProfileModel.ProviderModel + { + SystemName = profile.ProviderSystemName + }; + if (provider != null) { - model.Providing.ThumbnailUrl = _pluginMediator.GetIconUrl(provider.Metadata); - - if (model.Providing.ThumbnailUrl.IsEmpty()) - model.Providing.ThumbnailUrl = _pictureService.GetDefaultPictureUrl(48); - else - model.Providing.ThumbnailUrl = Url.Content(model.Providing.ThumbnailUrl); - - model.Providing.Url = provider.Metadata.PluginDescriptor.Url; - model.Providing.ConfigurationUrl = Url.Action("ConfigurePlugin", new { systemName = provider.Metadata.SystemName }); - model.Providing.FriendlyName = _pluginMediator.GetLocalizedFriendlyName(provider.Metadata); - model.Providing.Author = provider.Metadata.PluginDescriptor.Author; - model.Providing.Version = provider.Metadata.PluginDescriptor.Version.ToString(); - model.Providing.Description = _pluginMediator.GetLocalizedDescription(provider.Metadata); - - model.Providing.EntityType = provider.Value.EntityType; - model.Providing.EntityTypeName = T("Enums.SmartStore.Core.Domain.ExportEntityType." + provider.Value.EntityType.ToString()); - model.Providing.FileType = provider.Value.FileType.ToUpper(); + model.Provider.ThumbnailUrl = GetThumbnailUrl(provider); + + model.Provider.Url = provider.Metadata.PluginDescriptor.Url; + model.Provider.ConfigurationUrl = Url.Action("ConfigurePlugin", new { systemName = provider.Metadata.SystemName }); + model.Provider.FriendlyName = _pluginMediator.GetLocalizedFriendlyName(provider.Metadata); + model.Provider.Author = provider.Metadata.PluginDescriptor.Author; + model.Provider.Version = provider.Metadata.PluginDescriptor.Version.ToString(); + model.Provider.Description = _pluginMediator.GetLocalizedDescription(provider.Metadata); + + model.Provider.EntityType = provider.Value.EntityType; + model.Provider.EntityTypeName = T("Enums.SmartStore.Core.Domain.ExportEntityType." + provider.Value.EntityType.ToString()); + model.Provider.FileType = provider.Value.FileType.ToUpper(); } } private void PrepareExportProfileModelForEdit(ExportProfileModel model, ExportProfile profile, Provider provider) { var filter = XmlHelper.Deserialize(profile.Filtering); + var projection = XmlHelper.Deserialize(profile.Projection); var allStores = _services.StoreService.GetAllStores(); + var allLanguages = _languageService.GetAllLanguages(true); + var allCurrencies = _currencyService.GetAllCurrencies(true); model.AllString = T("Admin.Common.All"); - //model.UnspecifiedString = T("Common.Unspecified"); + model.UnspecifiedString = T("Common.Unspecified"); model.StoreCount = allStores.Count; model.Offset = profile.Offset; model.Limit = profile.Limit; model.BatchSize = profile.BatchSize; model.PerStore = profile.PerStore; + if (provider != null) + { + string partialName; + Type dataType; + if (provider.Value.RequiresConfiguration(out partialName, out dataType)) + { + model.Provider.ConfigPartialViewName = partialName; + model.Provider.ConfigDataType = dataType; + model.Provider.ConfigData = XmlHelper.Deserialize(profile.ProviderConfigData, dataType); + } + } + + // projection + model.Projection = new ExportProjectionModel + { + LanguageId = projection.LanguageId, + CurrencyId = projection.CurrencyId + }; + + model.Projection.AvailableLanguages = allLanguages + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + model.Projection.AvailableCurrencies = allCurrencies + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + // filtering Action initModelBase = x => { x.StoreId = filter.StoreId; @@ -114,14 +167,14 @@ private void PrepareExportProfileModelForEdit(ExportProfileModel model, ExportPr .ToList(); }; - if (model.Providing.EntityType == ExportEntityType.Product) + if (model.Provider.EntityType == ExportEntityType.Product) { var allCategories = _categoryService.GetAllCategories(showHidden: true); var mappedCategories = allCategories.ToDictionary(x => x.Id); var allManufacturers = _manufacturerService.GetAllManufacturers(true); var allProductTags = _productTagService.GetAllProductTags(); - model.ProductFiltering = new ExportProductFilterModel + model.ProductFilter = new ExportProductFilterModel { PriceMinimum = filter.PriceMinimum, PriceMaximum = filter.PriceMaximum, @@ -137,27 +190,27 @@ private void PrepareExportProfileModelForEdit(ExportProfileModel model, ExportPr ProductType = filter.ProductType }; - model.ProductFiltering.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); + model.ProductFilter.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); - model.ProductFiltering.AvailableCategories = allCategories + model.ProductFilter.AvailableCategories = allCategories .Select(x => new SelectListItem { Text = x.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = x.Id.ToString() }) .ToList(); - model.ProductFiltering.AvailableManufacturers = allManufacturers + model.ProductFilter.AvailableManufacturers = allManufacturers .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) .ToList(); - model.ProductFiltering.AvailableProductTags = allProductTags + model.ProductFilter.AvailableProductTags = allProductTags .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) .ToList(); - initModelBase(model.ProductFiltering); + initModelBase(model.ProductFilter); } - else if (model.Providing.EntityType == ExportEntityType.Order) + else if (model.Provider.EntityType == ExportEntityType.Order) { var allCustomerRoles = _customerService.GetAllCustomerRoles(true); - model.OrderFiltering = new ExportOrderFilterModel + model.OrderFilter = new ExportOrderFilterModel { OrderStatus = filter.OrderStatus, PaymentStatus = filter.PaymentStatus, @@ -165,20 +218,22 @@ private void PrepareExportProfileModelForEdit(ExportProfileModel model, ExportPr CustomerRoleIds = filter.CustomerRoleIds }; - model.OrderFiltering.AvailableOrderStates = OrderStatus.Pending.ToSelectList(false).ToList(); - model.OrderFiltering.AvailablePaymentStates = PaymentStatus.Pending.ToSelectList(false).ToList(); - model.OrderFiltering.AvailableShippingStates = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); + model.OrderFilter.AvailableOrderStates = OrderStatus.Pending.ToSelectList(false).ToList(); + model.OrderFilter.AvailablePaymentStates = PaymentStatus.Pending.ToSelectList(false).ToList(); + model.OrderFilter.AvailableShippingStates = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); - model.OrderFiltering.AvailableCustomerRoles = allCustomerRoles + model.OrderFilter.AvailableCustomerRoles = allCustomerRoles .OrderBy(x => x.Name) .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) .ToList(); - initModelBase(model.OrderFiltering); + initModelBase(model.OrderFilter); } } - public ActionResult Index() + #endregion + + public ActionResult Index() { return RedirectToAction("List"); } @@ -221,9 +276,9 @@ public ActionResult Create() if (_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) { var model = new ExportProfileModel(); - model.Providing = new ExportProfileModel.Provider(); + model.Provider = new ExportProfileModel.ProviderModel(); - model.Providing.AvailableExportProviders = _exportService.LoadAllExportProviders() + model.Provider.AvailableExportProviders = _exportService.LoadAllExportProviders() .Select(x => { var item = new SelectListItem @@ -247,9 +302,9 @@ public ActionResult Create(ExportProfileModel model) if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); - if (model.Providing.SystemName.HasValue()) + if (model.Provider.SystemName.HasValue()) { - var provider = _exportService.LoadProvider(model.Providing.SystemName); + var provider = _exportService.LoadProvider(model.Provider.SystemName); if (provider != null) { var profile = _exportService.InsertExportProfile(provider); @@ -258,7 +313,7 @@ public ActionResult Create(ExportProfileModel model) } } - NotifyError(T("Admin.Configuration.Export.ProviderSystemName.Validate", model.Providing.SystemName.NaIfEmpty())); + NotifyError(T("Admin.Configuration.Export.ProviderSystemName.Validate", model.Provider.SystemName.NaIfEmpty())); return RedirectToAction("List"); } @@ -282,9 +337,9 @@ public ActionResult Edit(int id) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] - [FormValueRequired("save", "save-continue")] - public ActionResult Edit(ExportProfileModel model, bool continueEditing) + [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [FormValueRequired("save", "save-continue")] + public ActionResult Edit(ExportProfileModel model, bool continueEditing) { if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); @@ -302,8 +357,31 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) return View(model); } - ExportFilter filter = null; + profile.Name = model.Name; + profile.Enabled = model.Enabled; + profile.ScheduleTask.Seconds = model.SchedulingHours * 3600; + profile.Offset = model.Offset; + profile.Limit = model.Limit; + profile.BatchSize = model.BatchSize; + profile.PerStore = model.PerStore; + + if (profile.Name.IsEmpty()) + profile.Name = provider.Metadata.FriendlyName; + + if (profile.Name.IsEmpty()) + profile.Name = provider.Metadata.SystemName; + + // projection + var projection = new ExportProjection + { + LanguageId = model.Projection.LanguageId, + CurrencyId = model.Projection.CurrencyId + }; + + profile.Projection = XmlHelper.Serialize(projection); + // filtering + ExportFilter filter = null; Action getFromModelBase = x => { filter.StoreId = x.StoreId ?? 0; @@ -311,54 +389,57 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) filter.CreatedTo = x.CreatedTo; }; - if (model.ProductFiltering != null && provider.Value.EntityType == ExportEntityType.Product) + if (model.ProductFilter != null && provider.Value.EntityType == ExportEntityType.Product) { filter = new ExportFilter { - PriceMinimum = model.ProductFiltering.PriceMinimum, - PriceMaximum = model.ProductFiltering.PriceMaximum, - AvailabilityMinimum = model.ProductFiltering.AvailabilityMinimum, - AvailabilityMaximum = model.ProductFiltering.AvailabilityMaximum, - IsPublished = model.ProductFiltering.IsPublished, - CategoryIds = model.ProductFiltering.CategoryIds, - WithoutCategories = model.ProductFiltering.WithoutCategories, - ManufacturerIds = model.ProductFiltering.ManufacturerIds, - WithoutManufacturers = model.ProductFiltering.WithoutManufacturers, - ProductTagIds = model.ProductFiltering.ProductTagIds, - FeaturedProducts = model.ProductFiltering.FeaturedProducts, - ProductType = model.ProductFiltering.ProductType + PriceMinimum = model.ProductFilter.PriceMinimum, + PriceMaximum = model.ProductFilter.PriceMaximum, + AvailabilityMinimum = model.ProductFilter.AvailabilityMinimum, + AvailabilityMaximum = model.ProductFilter.AvailabilityMaximum, + IsPublished = model.ProductFilter.IsPublished, + CategoryIds = model.ProductFilter.CategoryIds, + WithoutCategories = model.ProductFilter.WithoutCategories, + ManufacturerIds = model.ProductFilter.ManufacturerIds, + WithoutManufacturers = model.ProductFilter.WithoutManufacturers, + ProductTagIds = model.ProductFilter.ProductTagIds, + FeaturedProducts = model.ProductFilter.FeaturedProducts, + ProductType = model.ProductFilter.ProductType }; - getFromModelBase(model.ProductFiltering); + getFromModelBase(model.ProductFilter); } - else if (model.OrderFiltering != null && provider.Value.EntityType == ExportEntityType.Order) + else if (model.OrderFilter != null && provider.Value.EntityType == ExportEntityType.Order) { filter = new ExportFilter { - OrderStatus = model.OrderFiltering.OrderStatus, - PaymentStatus = model.OrderFiltering.PaymentStatus, - ShippingStatus = model.OrderFiltering.ShippingStatus, - CustomerRoleIds = model.OrderFiltering.CustomerRoleIds + OrderStatus = model.OrderFilter.OrderStatus, + PaymentStatus = model.OrderFilter.PaymentStatus, + ShippingStatus = model.OrderFilter.ShippingStatus, + CustomerRoleIds = model.OrderFilter.CustomerRoleIds }; - getFromModelBase(model.OrderFiltering); + getFromModelBase(model.OrderFilter); } - profile.Name = model.Name; - profile.Enabled = model.Enabled; - profile.ScheduleTask.Seconds = model.SchedulingHours * 3600; - profile.Offset = model.Offset; - profile.Limit = model.Limit; - profile.BatchSize = model.BatchSize; - profile.PerStore = model.PerStore; - - if (profile.Name.IsEmpty()) - profile.Name = provider.Metadata.FriendlyName; + profile.Filtering = XmlHelper.Serialize(filter); - if (profile.Name.IsEmpty()) - profile.Name = provider.Metadata.SystemName; + // provider configuration + profile.ProviderConfigData = null; + try + { + string partialName; + Type dataType; - profile.Filtering = XmlHelper.Serialize(filter); + if (provider.Value.RequiresConfiguration(out partialName, out dataType) && model.CustomProperties.ContainsKey("ProviderConfigData")) + { + profile.ProviderConfigData = XmlHelper.Serialize(model.CustomProperties["ProviderConfigData"], dataType); + } + } + catch (Exception exc) + { + NotifyError(exc); + } _exportService.UpdateExportProfile(profile); @@ -393,6 +474,27 @@ public ActionResult DeleteConfirmed(int id) return RedirectToAction("Edit", new { id = profile.Id }); } + public ActionResult Preview(int id) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var profile = _exportService.GetExportProfileById(id); + if (profile == null) + return RedirectToAction("List"); + + var provider = _exportService.LoadProvider(profile.ProviderSystemName); + + var model = new ExportPreviewModel + { + Id = profile.Id, + Name = profile.Name, + ThumbnailUrl = GetThumbnailUrl(provider) + }; + + return View(model); + } + public ActionResult Execute(int id) { return null; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs index ca812df0b6..3294830cb9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs @@ -9,7 +9,7 @@ namespace SmartStore.Admin.Models.DataExchange { - public class ExportFilterModelBase + public abstract class ExportFilterModelBase { [SmartResourceDisplayName("Admin.Configuration.Export.Filter.StoreId")] public int? StoreId { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportPreviewModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportPreviewModel.cs new file mode 100644 index 0000000000..d9aa966dcb --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportPreviewModel.cs @@ -0,0 +1,10 @@ +using SmartStore.Web.Framework.Mvc; + +namespace SmartStore.Admin.Models.DataExchange +{ + public class ExportPreviewModel : EntityModelBase + { + public string Name { get; set; } + public string ThumbnailUrl { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 4dd20aa2e1..4638c6fd0e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; +using System.Dynamic; using System.Web.Mvc; -using SmartStore.Core.Domain.Catalog; +using System.Web.Routing; using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Domain.Payments; -using SmartStore.Core.Domain.Shipping; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Mvc; @@ -13,6 +11,10 @@ namespace SmartStore.Admin.Models.DataExchange { public partial class ExportProfileModel : EntityModelBase { + public int StoreCount { get; set; } + public string AllString { get; set; } + public string UnspecifiedString { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.Name")] public string Name { get; set; } @@ -38,17 +40,20 @@ public partial class ExportProfileModel : EntityModelBase [SmartResourceDisplayName("Admin.Configuration.Export.PerStore")] public bool PerStore { get; set; } - public Provider Providing { get; set; } + public ProviderModel Provider { get; set; } - public ExportProductFilterModel ProductFiltering { get; set; } - public ExportOrderFilterModel OrderFiltering { get; set; } + public ExportProductFilterModel ProductFilter { get; set; } + public ExportOrderFilterModel OrderFilter { get; set; } - public int StoreCount { get; set; } - public string AllString { get; set; } + public ExportProjectionModel Projection { get; set; } - public class Provider + public class ProviderModel { + public string ConfigPartialViewName { get; set; } + public Type ConfigDataType { get; set; } + public object ConfigData { get; set; } + [SmartResourceDisplayName("Common.Image")] public string ThumbnailUrl { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs new file mode 100644 index 0000000000..0cd5ddd22f --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Web.Framework; + +namespace SmartStore.Admin.Models.DataExchange +{ + public class ExportProjectionModel + { + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.LanguageId")] + public int LanguageId { get; set; } + public List AvailableLanguages { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.CurrencyId")] + public int CurrencyId { get; set; } + public List AvailableCurrencies { get; set; } + } +} \ 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 0d06106427..1fb41c6bf1 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -248,6 +248,8 @@ + + @@ -504,6 +506,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml index e8b3dc3b2d..3eb5eac1ea 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml @@ -5,16 +5,16 @@ }
- @if(Model.Providing.AvailableExportProviders.Count > 0) + @if(Model.Provider.AvailableExportProviders.Count > 0) {
- @Html.SmartLabelFor(model => model.Providing.SystemName) + @Html.SmartLabelFor(model => model.Provider.SystemName) - @Html.DropDownListFor(model => model.Providing.SystemName, Model.Providing.AvailableExportProviders) + @Html.DropDownListFor(model => model.Provider.SystemName, Model.Provider.AvailableExportProviders)
diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index ad73a907f4..f825f3b48a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -8,39 +8,39 @@ @using (Html.BeginForm()) { - @Html.HiddenFor(model => model.Id) +
+ @Html.HiddenFor(model => model.Id) -
-
- @if (Model.Providing.ThumbnailUrl.HasValue()) - { - - } -   - @title - @Model.Name @Html.ActionLink("(" + T("Admin.Common.BackToList") + ")", "List") +
+
+ +   + @title - @Model.Name @Html.ActionLink("(" + T("Admin.Common.BackToList") + ")", "List") +
+
+  @T("Admin.Common.Preview") + + + +
-
- - - -
-
- - @Html.ValidationSummary(false) - @Html.SmartStore().TabStrip().Name("export-profile-edit").Items(x => - { - x.Add().Text(T("Common.General").Text).Content(TabGeneral()).Selected(true); - x.Add().Text(T("Common.Filter").Text).Content(TabFilter()); - x.Add().Text(T("Common.Projection").Text).Content(TabProjection()); - x.Add().Text(T("Common.Partition").Text).Content(TabPartition()); - x.Add().Text(T("Admin.Configuration").Text).Content(TabConfiguration()); - x.Add().Text(T("Common.Publishing").Text).Content(TabDeployment()); + @Html.ValidationSummary(false) - EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "export-profile-edit", this.Html, this.Model)); - }) + @Html.SmartStore().TabStrip().Name("export-profile-edit").Items(x => + { + x.Add().Text(T("Common.General").Text).Content(TabGeneral()).Selected(true); + x.Add().Text(T("Common.Partition").Text).Content(TabPartition()); + x.Add().Text(T("Common.Filter").Text).Content(TabFilter()); + x.Add().Text(T("Common.Projection").Text).Content(TabProjection()); + x.Add().Text(T("Admin.Configuration").Text).Content(TabConfiguration()); + x.Add().Text(T("Common.Publishing").Text).Content(TabDeployment()); + + EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "export-profile-edit", this.Html, this.Model)); + }) +
} @helper TabGeneral() @@ -48,64 +48,64 @@ - @if (Model.Providing.Url.HasValue()) + @if (Model.Provider.Url.HasValue()) { } + +
- @Html.SmartLabelFor(model => model.Providing.FriendlyName) + @Html.SmartLabelFor(x => x.Provider.FriendlyName) - @Model.Providing.FriendlyName.NaIfEmpty() - (@(Model.Providing.SystemName.NaIfEmpty())) + @Model.Provider.FriendlyName.NaIfEmpty() + (@(Model.Provider.SystemName.NaIfEmpty()))
- @Html.SmartLabelFor(model => model.Providing.Author) + @Html.SmartLabelFor(x => x.Provider.Author) - @Html.DisplayFor(model => model.Providing.Author) + @Html.DisplayFor(x => x.Provider.Author)
- @Html.SmartLabelFor(model => model.Providing.Version) + @Html.SmartLabelFor(x => x.Provider.Version) - @Html.DisplayFor(model => model.Providing.Version) + @Html.DisplayFor(x => x.Provider.Version)
- @Html.SmartLabelFor(model => model.Providing.Url) + @Html.SmartLabelFor(x => x.Provider.Url) - @Model.Providing.Url + @Model.Provider.Url
- @Html.SmartLabelFor(model => model.Providing.EntityTypeName) + @Html.SmartLabelFor(x => x.Provider.EntityTypeName) - @Html.DisplayFor(model => model.Providing.EntityTypeName) + @Html.DisplayFor(x => x.Provider.EntityTypeName)
- @Html.SmartLabelFor(model => model.Providing.FileType) + @Html.SmartLabelFor(x => x.Provider.FileType) - @Html.DisplayFor(model => model.Providing.FileType) + @Html.DisplayFor(x => x.Provider.FileType)
- @Html.SmartLabelFor(model => model.Providing.Description) + @Html.SmartLabelFor(x => x.Provider.Description) - @if (Model.Providing.Description.HasValue()) + @if (Model.Provider.Description.HasValue()) { - @Html.Raw(Model.Providing.Description) + @Html.Raw(Model.Provider.Description) } else { @@ -120,29 +120,71 @@
- @Html.SmartLabelFor(model => model.Name) + @Html.SmartLabelFor(x => x.Name) - @Html.EditorFor(model => model.Name) - @Html.ValidationMessageFor(model => model.Name) + @Html.EditorFor(x => x.Name) + @Html.ValidationMessageFor(x => x.Name)
- @Html.SmartLabelFor(model => model.Enabled) + @Html.SmartLabelFor(x => x.Enabled) - @Html.EditorFor(model => model.Enabled) - @Html.ValidationMessageFor(model => model.Enabled) + @Html.EditorFor(x => x.Enabled) + @Html.ValidationMessageFor(x => x.Enabled)
- @Html.SmartLabelFor(model => model.SchedulingHours) + @Html.SmartLabelFor(x => x.SchedulingHours) - @Html.EditorFor(model => model.SchedulingHours) - @Html.ValidationMessageFor(model => model.SchedulingHours) + @Html.EditorFor(x => x.SchedulingHours) + @Html.ValidationMessageFor(x => x.SchedulingHours) +
+} + +@helper TabPartition() +{ + + + + + + + + + + + + + + + +
+ @Html.SmartLabelFor(x => x.Offset) + + @Html.EditorFor(x => x.Offset) + @Html.ValidationMessageFor(x => x.Offset) +
+ @Html.SmartLabelFor(x => x.Limit) + + @Html.EditorFor(x => x.Limit) + @Html.ValidationMessageFor(x => x.Limit) +
+ @Html.SmartLabelFor(x => x.BatchSize) + + @Html.EditorFor(x => x.BatchSize) + @Html.ValidationMessageFor(x => x.BatchSize) +
+ @Html.SmartLabelFor(x => x.PerStore) + + @Html.EditorFor(x => x.PerStore) + @Html.ValidationMessageFor(x => x.PerStore)
@@ -151,226 +193,226 @@ @helper TabFilter() { - @if (Model.ProductFiltering != null) + @if (Model.ProductFilter != null) { if (Model.StoreCount > 1) { } } - else if (Model.OrderFiltering != null) + else if (Model.OrderFilter != null) { if (Model.StoreCount > 1) { } } @@ -378,46 +420,24 @@ } @helper TabProjection() -{ -} - -@helper TabPartition() {
- @Html.SmartLabelFor(model => model.ProductFiltering.StoreId) + @Html.SmartLabelFor(x => x.ProductFilter.StoreId) - @Html.DropDownListFor(model => model.ProductFiltering.StoreId, Model.ProductFiltering.AvailableStores, Model.AllString) - @Html.ValidationMessageFor(model => model.ProductFiltering.StoreId) + @Html.DropDownListFor(x => x.ProductFilter.StoreId, Model.ProductFilter.AvailableStores, Model.AllString) + @Html.ValidationMessageFor(x => x.ProductFilter.StoreId)
- @Html.SmartLabelFor(model => model.ProductFiltering.CreatedFrom) + @Html.SmartLabelFor(x => x.ProductFilter.CreatedFrom) - @Html.EditorFor(model => model.ProductFiltering.CreatedFrom) - @Html.ValidationMessageFor(model => model.ProductFiltering.CreatedFrom) + @Html.EditorFor(x => x.ProductFilter.CreatedFrom) + @Html.ValidationMessageFor(x => x.ProductFilter.CreatedFrom)
- @Html.SmartLabelFor(model => model.ProductFiltering.CreatedTo) + @Html.SmartLabelFor(x => x.ProductFilter.CreatedTo) - @Html.EditorFor(model => model.ProductFiltering.CreatedTo) - @Html.ValidationMessageFor(model => model.ProductFiltering.CreatedTo) + @Html.EditorFor(x => x.ProductFilter.CreatedTo) + @Html.ValidationMessageFor(x => x.ProductFilter.CreatedTo)
- @Html.SmartLabelFor(model => model.ProductFiltering.ProductType) + @Html.SmartLabelFor(x => x.ProductFilter.ProductType) - @Html.DropDownListFor(model => model.ProductFiltering.ProductType, Model.ProductFiltering.AvailableProductTypes, Model.AllString) + @Html.DropDownListFor(x => x.ProductFilter.ProductType, Model.ProductFilter.AvailableProductTypes, Model.AllString)
- @Html.SmartLabelFor(model => model.ProductFiltering.IsPublished) + @Html.SmartLabelFor(x => x.ProductFilter.IsPublished) - @Html.EditorFor(model => model.ProductFiltering.IsPublished) - @Html.ValidationMessageFor(model => model.ProductFiltering.IsPublished) + @Html.EditorFor(x => x.ProductFilter.IsPublished) + @Html.ValidationMessageFor(x => x.ProductFilter.IsPublished)
- @Html.SmartLabelFor(model => model.ProductFiltering.PriceMinimum) + @Html.SmartLabelFor(x => x.ProductFilter.PriceMinimum) - @Html.EditorFor(model => model.ProductFiltering.PriceMinimum) - @Html.ValidationMessageFor(model => model.ProductFiltering.PriceMinimum) + @Html.EditorFor(x => x.ProductFilter.PriceMinimum) + @Html.ValidationMessageFor(x => x.ProductFilter.PriceMinimum)
- @Html.SmartLabelFor(model => model.ProductFiltering.PriceMaximum) + @Html.SmartLabelFor(x => x.ProductFilter.PriceMaximum) - @Html.EditorFor(model => model.ProductFiltering.PriceMaximum) - @Html.ValidationMessageFor(model => model.ProductFiltering.PriceMaximum) + @Html.EditorFor(x => x.ProductFilter.PriceMaximum) + @Html.ValidationMessageFor(x => x.ProductFilter.PriceMaximum)
- @Html.SmartLabelFor(model => model.ProductFiltering.AvailabilityMinimum) + @Html.SmartLabelFor(x => x.ProductFilter.AvailabilityMinimum) - @Html.EditorFor(model => model.ProductFiltering.AvailabilityMinimum) - @Html.ValidationMessageFor(model => model.ProductFiltering.AvailabilityMinimum) + @Html.EditorFor(x => x.ProductFilter.AvailabilityMinimum) + @Html.ValidationMessageFor(x => x.ProductFilter.AvailabilityMinimum)
- @Html.SmartLabelFor(model => model.ProductFiltering.AvailabilityMaximum) + @Html.SmartLabelFor(x => x.ProductFilter.AvailabilityMaximum) - @Html.EditorFor(model => model.ProductFiltering.AvailabilityMaximum) - @Html.ValidationMessageFor(model => model.ProductFiltering.AvailabilityMaximum) + @Html.EditorFor(x => x.ProductFilter.AvailabilityMaximum) + @Html.ValidationMessageFor(x => x.ProductFilter.AvailabilityMaximum)
- @Html.SmartLabelFor(model => model.ProductFiltering.WithoutCategories) + @Html.SmartLabelFor(x => x.ProductFilter.WithoutCategories) - @Html.EditorFor(model => model.ProductFiltering.WithoutCategories) - @Html.ValidationMessageFor(model => model.ProductFiltering.WithoutCategories) + @Html.EditorFor(x => x.ProductFilter.WithoutCategories) + @Html.ValidationMessageFor(x => x.ProductFilter.WithoutCategories)
- @Html.SmartLabelFor(model => model.ProductFiltering.CategoryIds) + @Html.SmartLabelFor(x => x.ProductFilter.CategoryIds) - @Html.ListBoxFor(model => model.ProductFiltering.CategoryIds, - new MultiSelectList(Model.ProductFiltering.AvailableCategories, "Value", "Text"), + @Html.ListBoxFor(x => x.ProductFilter.CategoryIds, + new MultiSelectList(Model.ProductFilter.AvailableCategories, "Value", "Text"), new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(model => model.ProductFiltering.CategoryIds) + @Html.ValidationMessageFor(x => x.ProductFilter.CategoryIds)
- @Html.SmartLabelFor(model => model.ProductFiltering.WithoutManufacturers) + @Html.SmartLabelFor(x => x.ProductFilter.WithoutManufacturers) - @Html.EditorFor(model => model.ProductFiltering.WithoutManufacturers) - @Html.ValidationMessageFor(model => model.ProductFiltering.WithoutManufacturers) + @Html.EditorFor(x => x.ProductFilter.WithoutManufacturers) + @Html.ValidationMessageFor(x => x.ProductFilter.WithoutManufacturers)
- @Html.SmartLabelFor(model => model.ProductFiltering.ManufacturerIds) + @Html.SmartLabelFor(x => x.ProductFilter.ManufacturerIds) - @Html.ListBoxFor(model => model.ProductFiltering.ManufacturerIds, - new MultiSelectList(Model.ProductFiltering.AvailableManufacturers, "Value", "Text"), + @Html.ListBoxFor(x => x.ProductFilter.ManufacturerIds, + new MultiSelectList(Model.ProductFilter.AvailableManufacturers, "Value", "Text"), new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(model => model.ProductFiltering.ManufacturerIds) + @Html.ValidationMessageFor(x => x.ProductFilter.ManufacturerIds)
- @Html.SmartLabelFor(model => model.ProductFiltering.ProductTagIds) + @Html.SmartLabelFor(x => x.ProductFilter.ProductTagIds) - @Html.ListBoxFor(model => model.ProductFiltering.ProductTagIds, - new MultiSelectList(Model.ProductFiltering.AvailableProductTags, "Value", "Text"), + @Html.ListBoxFor(x => x.ProductFilter.ProductTagIds, + new MultiSelectList(Model.ProductFilter.AvailableProductTags, "Value", "Text"), new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(model => model.ProductFiltering.ProductTagIds) + @Html.ValidationMessageFor(x => x.ProductFilter.ProductTagIds)
- @Html.SmartLabelFor(model => model.ProductFiltering.FeaturedProducts) + @Html.SmartLabelFor(x => x.ProductFilter.FeaturedProducts) - @Html.EditorFor(model => model.ProductFiltering.FeaturedProducts) - @Html.ValidationMessageFor(model => model.ProductFiltering.FeaturedProducts) + @Html.EditorFor(x => x.ProductFilter.FeaturedProducts) + @Html.ValidationMessageFor(x => x.ProductFilter.FeaturedProducts)
- @Html.SmartLabelFor(model => model.OrderFiltering.StoreId) + @Html.SmartLabelFor(x => x.OrderFilter.StoreId) - @Html.DropDownListFor(model => model.OrderFiltering.StoreId, Model.OrderFiltering.AvailableStores, Model.AllString) - @Html.ValidationMessageFor(model => model.OrderFiltering.StoreId) + @Html.DropDownListFor(x => x.OrderFilter.StoreId, Model.OrderFilter.AvailableStores, Model.AllString) + @Html.ValidationMessageFor(x => x.OrderFilter.StoreId)
- @Html.SmartLabelFor(model => model.OrderFiltering.CreatedFrom) + @Html.SmartLabelFor(x => x.OrderFilter.CreatedFrom) - @Html.EditorFor(model => model.OrderFiltering.CreatedFrom) - @Html.ValidationMessageFor(model => model.OrderFiltering.CreatedFrom) + @Html.EditorFor(x => x.OrderFilter.CreatedFrom) + @Html.ValidationMessageFor(x => x.OrderFilter.CreatedFrom)
- @Html.SmartLabelFor(model => model.OrderFiltering.CreatedTo) + @Html.SmartLabelFor(x => x.OrderFilter.CreatedTo) - @Html.EditorFor(model => model.OrderFiltering.CreatedTo) - @Html.ValidationMessageFor(model => model.OrderFiltering.CreatedTo) + @Html.EditorFor(x => x.OrderFilter.CreatedTo) + @Html.ValidationMessageFor(x => x.OrderFilter.CreatedTo)
- @Html.SmartLabelFor(model => model.OrderFiltering.OrderStatus) + @Html.SmartLabelFor(x => x.OrderFilter.OrderStatus) - @Html.ListBoxFor(model => model.OrderFiltering.OrderStatus, - new MultiSelectList(Model.OrderFiltering.AvailableOrderStates, "Value", "Text"), + @Html.ListBoxFor(x => x.OrderFilter.OrderStatus, + new MultiSelectList(Model.OrderFilter.AvailableOrderStates, "Value", "Text"), new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(model => model.OrderFiltering.OrderStatus) + @Html.ValidationMessageFor(x => x.OrderFilter.OrderStatus)
- @Html.SmartLabelFor(model => model.OrderFiltering.PaymentStatus) + @Html.SmartLabelFor(x => x.OrderFilter.PaymentStatus) - @Html.ListBoxFor(model => model.OrderFiltering.PaymentStatus, - new MultiSelectList(Model.OrderFiltering.AvailablePaymentStates, "Value", "Text"), + @Html.ListBoxFor(x => x.OrderFilter.PaymentStatus, + new MultiSelectList(Model.OrderFilter.AvailablePaymentStates, "Value", "Text"), new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(model => model.OrderFiltering.PaymentStatus) + @Html.ValidationMessageFor(x => x.OrderFilter.PaymentStatus)
- @Html.SmartLabelFor(model => model.OrderFiltering.ShippingStatus) + @Html.SmartLabelFor(x => x.OrderFilter.ShippingStatus) - @Html.ListBoxFor(model => model.OrderFiltering.ShippingStatus, - new MultiSelectList(Model.OrderFiltering.AvailableShippingStates, "Value", "Text"), + @Html.ListBoxFor(x => x.OrderFilter.ShippingStatus, + new MultiSelectList(Model.OrderFilter.AvailableShippingStates, "Value", "Text"), new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(model => model.OrderFiltering.ShippingStatus) + @Html.ValidationMessageFor(x => x.OrderFilter.ShippingStatus)
- @Html.SmartLabelFor(model => model.OrderFiltering.CustomerRoleIds) + @Html.SmartLabelFor(x => x.OrderFilter.CustomerRoleIds) - @Html.ListBoxFor(model => model.OrderFiltering.CustomerRoleIds, - new MultiSelectList(Model.OrderFiltering.AvailableCustomerRoles, "Value", "Text"), + @Html.ListBoxFor(x => x.OrderFilter.CustomerRoleIds, + new MultiSelectList(Model.OrderFilter.AvailableCustomerRoles, "Value", "Text"), new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(model => model.OrderFiltering.CustomerRoleIds) + @Html.ValidationMessageFor(x => x.OrderFilter.CustomerRoleIds)
- - - - - - - -
- @Html.SmartLabelFor(model => model.Offset) - - @Html.EditorFor(model => model.Offset) - @Html.ValidationMessageFor(model => model.Offset) -
- @Html.SmartLabelFor(model => model.Limit) - - @Html.EditorFor(model => model.Limit) - @Html.ValidationMessageFor(model => model.Limit) -
- @Html.SmartLabelFor(model => model.BatchSize) + @Html.SmartLabelFor(x => x.Projection.LanguageId) - @Html.EditorFor(model => model.BatchSize) - @Html.ValidationMessageFor(model => model.BatchSize) + @Html.DropDownListFor(x => x.Projection.LanguageId, Model.Projection.AvailableLanguages, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.Projection.LanguageId)
- @Html.SmartLabelFor(model => model.PerStore) + @Html.SmartLabelFor(x => x.Projection.CurrencyId) - @Html.EditorFor(model => model.PerStore) - @Html.ValidationMessageFor(model => model.PerStore) + @Html.DropDownListFor(x => x.Projection.CurrencyId, Model.Projection.AvailableCurrencies, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.Projection.CurrencyId)
@@ -425,6 +445,23 @@ @helper TabConfiguration() { +
+
+ @if (Model.Provider.ConfigPartialViewName.HasValue()) + { + var viewData = new ViewDataDictionary(); + viewData.TemplateInfo.HtmlFieldPrefix = "CustomProperties[ProviderConfigData]"; + @Html.Hidden("CustomProperties[ProviderConfigData].__Type__", Model.Provider.ConfigDataType.AssemblyQualifiedName) + @Html.Partial(Model.Provider.ConfigPartialViewName, Model.Provider.ConfigData, viewData) + } + else + { +
+ @Html.Raw(T("Admin.Configuration.Export.NoProviderConfigurationRequired", Model.Provider.FriendlyName.NaIfEmpty())) +
+ } +
+
} @helper TabDeployment() @@ -437,14 +474,14 @@ jQuery(document).ready(function () { // show/hide product category filter - $('select[name="ProductFiltering.WithoutCategories"]').change(function () { + $('select[name="ProductFilter.WithoutCategories"]').change(function () { $('#FilterCategoryIdsContainer').toggle(_.isEmpty($(this).val())); }).trigger('change'); // show/hide product manufacturer filter - $('select[name="ProductFiltering.WithoutManufacturers"]').change(function () { + $('select[name="ProductFilter.WithoutManufacturers"]').change(function () { $('#FilterManufacturerIdsContainer').toggle(_.isEmpty($(this).val())); }).trigger('change'); }); - + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index a38c3a9ed5..10fafd92f6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -35,29 +35,29 @@ }) .Columns(columns => { - columns.Bound(x => x.Providing.ThumbnailUrl) + columns.Bound(x => x.Provider.ThumbnailUrl) .Width(70) .Centered() .Template( @ - @item.Id + @item.Id ) - .ClientTemplate("<#= Id #>"); + .ClientTemplate("<#= Id #>"); columns.Bound(x => x.Name) .Width(400) .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) .ClientTemplate("\"><#= Name #>"); - columns.Bound(x => x.Providing.SystemName) - .Template(x => "{0} ({1})".FormatInvariant(x.Providing.FriendlyName, x.Providing.SystemName)) - .ClientTemplate("<#= Providing.FriendlyName #> (<#= Providing.SystemName #>)"); + columns.Bound(x => x.Provider.SystemName) + .Template(x => "{0} ({1})".FormatInvariant(x.Provider.FriendlyName, x.Provider.SystemName)) + .ClientTemplate("<#= Provider.FriendlyName #> (<#= Provider.SystemName #>)"); columns.Bound(x => x.Enabled) .Width(100) .Template(item => @Html.SymbolForBool(item.Enabled)) .ClientTemplate(@Html.SymbolForBool("Enabled")) .Centered(); - columns.Bound(x => x.Providing.EntityTypeName); - columns.Bound(x => x.Providing.FileType) + columns.Bound(x => x.Provider.EntityTypeName); + columns.Bound(x => x.Provider.FileType) .Centered(); columns.Bound(x => x.SchedulingHours) .Centered(); @@ -115,7 +115,7 @@ smallIcon: dialog.find('.modal-body'), callbackSuccess: function (resp) { dialog.find('.modal-body').html(resp); - $('select[name="Providing.SystemName"]').selectWrapper(); + $('select[name="Provider.SystemName"]').selectWrapper(); }, callbackComplete: function () { showPrimaryButton(true); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml new file mode 100644 index 0000000000..710655ebfc --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml @@ -0,0 +1,20 @@ +@using SmartStore.Admin.Models.DataExchange +@model ExportPreviewModel +@{ + var title = string.Concat(T("Admin.Common.Preview"), " ", T("Common.Export")); + ViewBag.Title = title; +} + +
+
+ +   + @title - @Model.Name @Html.ActionLink("(" + T("Common.Back") + ")", "Edit", new { id = Model.Id }) +
+
+
+
+ +
+ TODO +
\ No newline at end of file From b547e25d3032ae4e24c0865c187fa50cbf072375 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 7 Aug 2015 13:10:49 +0200 Subject: [PATCH 006/874] Added export deployment entity --- .../Domain/DataExchange/ExportCore.cs | 8 ++ .../Domain/DataExchange/ExportDeployment.cs | 80 +++++++++++ .../Domain/DataExchange/ExportFilter.cs | 7 +- .../Domain/DataExchange/ExportProfile.cs | 22 +++ .../Domain/DataExchange/ExportProjection.cs | 4 +- .../SmartStore.Core/SmartStore.Core.csproj | 1 + .../DataExchange/ExportDeploymentMap.cs | 29 ++++ .../Mapping/DataExchange/ExportProfileMap.cs | 1 + .../201508051818404_ExportFramework.resx | 126 ------------------ ...1508070937542_ExportFramework.Designer.cs} | 2 +- ....cs => 201508070937542_ExportFramework.cs} | 125 ++++++++++++++++- .../201508070937542_ExportFramework.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 11 +- .../DataExchange/ExportService.cs | 21 ++- .../DataExchange/IExportService.cs | 8 ++ .../Controllers/ExportController.cs | 96 ++++++++++--- .../DataExchange/ExportDeploymentModel.cs | 49 +++++++ .../Models/DataExchange/ExportFilterModel.cs | 2 +- .../Models/DataExchange/ExportProfileModel.cs | 13 +- .../DataExchange/ExportProjectionModel.cs | 4 +- .../Administration/SmartStore.Admin.csproj | 3 + .../DataExchange/ExportDeplomentValidator.cs | 16 +++ .../DataExchange/ExportProfileValidator.cs | 16 +++ .../Administration/Views/Export/Edit.cshtml | 61 ++++++++- .../Administration/Views/Export/List.cshtml | 6 +- 25 files changed, 666 insertions(+), 171 deletions(-) create mode 100644 src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs create mode 100644 src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportDeploymentMap.cs delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.resx rename src/Libraries/SmartStore.Data/Migrations/{201508051818404_ExportFramework.Designer.cs => 201508070937542_ExportFramework.Designer.cs} (92%) rename src/Libraries/SmartStore.Data/Migrations/{201508051818404_ExportFramework.cs => 201508070937542_ExportFramework.cs} (62%) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201508070937542_ExportFramework.resx create mode 100644 src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs create mode 100644 src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportDeplomentValidator.cs create mode 100644 src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs index 2f513eab63..7ddc34bf58 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs @@ -11,4 +11,12 @@ public enum ExportEntityType Order, NewsletterSubscriber } + + public enum ExportDeploymentType + { + FileSystem = 0, + Email, + Http, + Ftp + } } diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs new file mode 100644 index 0000000000..0f350e499f --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs @@ -0,0 +1,80 @@ +using SmartStore.Core.Domain.DataExchange; + +namespace SmartStore.Core.Domain +{ + public class ExportDeployment : BaseEntity + { + /// + /// The profile identifier + /// + public int ProfileId { get; set; } + + /// + /// Name of the deployment + /// + public string Name { get; set; } + + /// + /// Whether the deployment is enabled + /// + public bool Enabled { get; set; } + + /// + /// Whether to deploy in a folder that can be reached from the internet + /// + public bool IsPublic { get; set; } + + /// + /// The deployment type identifier + /// + public int DeploymentTypeId { get; set; } + + /// + /// The deployment type + /// + public ExportDeploymentType DeploymentType + { + get + { + return (ExportDeploymentType)DeploymentTypeId; + } + set + { + DeploymentTypeId = (int)value; + } + } + + public string Username { get; set; } + + public string Password { get; set; } + + /// + /// Deployment URL + /// + public string Url { get; set; } + + /// + /// The file system path + /// + public string FileSystemPath { get; set; } + + /// + /// Multiple email addresses can be separated by semicolon + /// + public string EmailAddresses { get; set; } + + /// + /// Subject of the email + /// + public string EmailSubject { get; set; } + + /// + /// Identifier of the email account + /// + public int EmailAccountId { get; set; } + + // TODO: more FTP options + + public virtual ExportProfile Profile { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs index db02950efb..4d7ecd7316 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs @@ -10,6 +10,11 @@ namespace SmartStore.Core.Domain.DataExchange [Serializable] public class ExportFilter { + public ExportFilter() + { + IsPublished = true; + } + /// /// Store identifier; 0 to load all records /// @@ -50,7 +55,7 @@ public class ExportFilter /// /// A value indicating whether to load only published or non published products /// - public bool? IsPublished { get; set; } + public bool IsPublished { get; set; } /// /// Category identifiers diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs index 907701cc2b..0de9030c7e 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Tasks; @@ -6,6 +7,8 @@ namespace SmartStore.Core.Domain { public class ExportProfile : BaseEntity { + private ICollection _deployments; + public ExportProfile() { Limit = 100; @@ -72,9 +75,28 @@ public ExportProfile() /// public bool PerStore { get; set; } + /// + /// Whether to combine and compress the export files in a ZIP archive + /// + public bool CreateZipArchive { get; set; } + + /// + /// Email addresses (semicolon separated) where to send a notification message of the completion of the export + /// + public string CompletedEmailAddresses { get; set; } + /// /// The scheduling task /// public virtual ScheduleTask ScheduleTask { get; set; } + + /// + /// Gets or sets export deployments + /// + public virtual ICollection Deployments + { + get { return _deployments ?? (_deployments = new HashSet()); } + set { _deployments = value; } + } } } diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs index 3020a71b1c..8696afbdce 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs @@ -8,11 +8,11 @@ public class ExportProjection /// /// The language to be applied to the export /// - public int LanguageId { get; set; } + public int? LanguageId { get; set; } /// /// The currency to be applied to the export /// - public int CurrencyId { get; set; } + public int? CurrencyId { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index 4dbef97117..e04d7f01e5 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -157,6 +157,7 @@ + diff --git a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportDeploymentMap.cs b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportDeploymentMap.cs new file mode 100644 index 0000000000..0a57740d6e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportDeploymentMap.cs @@ -0,0 +1,29 @@ +using System.Data.Entity.ModelConfiguration; +using SmartStore.Core.Domain; + +namespace SmartStore.Data.Mapping.DataExchange +{ + public class ExportDeploymentMap : EntityTypeConfiguration + { + public ExportDeploymentMap() + { + this.ToTable("ExportDeployment"); + this.HasKey(x => x.Id); + + this.Property(x => x.Name).IsRequired().HasMaxLength(100); + this.Property(x => x.Username).HasMaxLength(400); + this.Property(x => x.Password).HasMaxLength(400); + this.Property(x => x.Url).HasMaxLength(4000); + this.Property(x => x.FileSystemPath).HasMaxLength(400); + this.Property(x => x.EmailAddresses).HasMaxLength(4000); + this.Property(x => x.EmailSubject).HasMaxLength(400); + + this.Ignore(x => x.DeploymentType); + + this.HasRequired(x => x.Profile) + .WithMany(x => x.Deployments) + .HasForeignKey(x => x.ProfileId) + .WillCascadeOnDelete(true); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs index c64b24cefe..7d62b17bd6 100644 --- a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs @@ -16,6 +16,7 @@ public ExportProfileMap() this.Property(x => x.Filtering).IsMaxLength(); this.Property(x => x.Projection).IsMaxLength(); this.Property(x => x.ProviderConfigData).IsMaxLength(); + this.Property(x => x.CompletedEmailAddresses).HasMaxLength(400); this.HasRequired(x => x.ScheduleTask) .WithMany() diff --git a/src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.resx b/src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.resx deleted file mode 100644 index 3219191ea2..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.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/201508051818404_ExportFramework.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201508070937542_ExportFramework.Designer.cs similarity index 92% rename from src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.Designer.cs rename to src/Libraries/SmartStore.Data/Migrations/201508070937542_ExportFramework.Designer.cs index ebbd4a40ae..eb6be6802a 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.Designer.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508070937542_ExportFramework.Designer.cs @@ -13,7 +13,7 @@ public sealed partial class ExportFramework : IMigrationMetadata string IMigrationMetadata.Id { - get { return "201508051818404_ExportFramework"; } + get { return "201508070937542_ExportFramework"; } } string IMigrationMetadata.Source diff --git a/src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508070937542_ExportFramework.cs similarity index 62% rename from src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.cs rename to src/Libraries/SmartStore.Data/Migrations/201508070937542_ExportFramework.cs index af3d696b92..c85249ef25 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508051818404_ExportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508070937542_ExportFramework.cs @@ -9,6 +9,28 @@ public partial class ExportFramework : DbMigration, ILocaleResourcesProvider, ID { public override void Up() { + CreateTable( + "dbo.ExportDeployment", + c => new + { + Id = c.Int(nullable: false, identity: true), + ProfileId = c.Int(nullable: false), + Name = c.String(nullable: false, maxLength: 100), + Enabled = c.Boolean(nullable: false), + IsPublic = c.Boolean(nullable: false), + DeploymentTypeId = c.Int(nullable: false), + Username = c.String(maxLength: 400), + Password = c.String(maxLength: 400), + Url = c.String(maxLength: 4000), + FileSystemPath = c.String(maxLength: 400), + EmailAddresses = c.String(maxLength: 4000), + EmailSubject = c.String(maxLength: 400), + EmailAccountId = c.Int(nullable: false), + }) + .PrimaryKey(t => t.Id) + .ForeignKey("dbo.ExportProfile", t => t.ProfileId, cascadeDelete: true) + .Index(t => t.ProfileId); + CreateTable( "dbo.ExportProfile", c => new @@ -26,6 +48,8 @@ public override void Up() Limit = c.Int(nullable: false), BatchSize = c.Int(nullable: false), PerStore = c.Boolean(nullable: false), + CreateZipArchive = c.Boolean(nullable: false), + CompletedEmailAddresses = c.String(maxLength: 400), }) .PrimaryKey(t => t.Id) .ForeignKey("dbo.ScheduleTask", t => t.SchedulingTaskId) @@ -35,9 +59,12 @@ public override void Up() public override void Down() { + DropForeignKey("dbo.ExportDeployment", "ProfileId", "dbo.ExportProfile"); DropForeignKey("dbo.ExportProfile", "SchedulingTaskId", "dbo.ScheduleTask"); DropIndex("dbo.ExportProfile", new[] { "SchedulingTaskId" }); + DropIndex("dbo.ExportDeployment", new[] { "ProfileId" }); DropTable("dbo.ExportProfile"); + DropTable("dbo.ExportDeployment"); } public bool RollbackOnFailure @@ -68,7 +95,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Common.Image", "Image", "Bild"); builder.AddOrUpdate("Common.Filter", "Filter", "Filter"); builder.AddOrUpdate("Common.Projection", "Projection", "Projektion"); - builder.AddOrUpdate("Common.Publishing", "Publishing", "Verffentlichung"); + builder.AddOrUpdate("Common.Deployment", "Deployment", "Bereitstellung"); builder.AddOrUpdate("Common.Website", "Website", "Web-Seite"); @@ -105,6 +132,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Specifies the name of the export profile.", "Legt den Namen des Exportprofils fest."); + builder.AddOrUpdate("Admin.Configuration.Export.Name.Validate", + "Please enter the name of the profile.", + "Bitte den Namen des Profils eingeben."); + builder.AddOrUpdate("Admin.Configuration.Export.FileType", "File type", "Dateityp", @@ -148,12 +179,23 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Specifies whether to start a separate run-through for each store.", "Legt fest, ob fr jeden Shop ein separater Verarbeitungsdurchlauf erfolgen soll."); + builder.AddOrUpdate("Admin.Configuration.Export.CreateZipArchive", + "Create ZIP archive", + "ZIP-Archiv erstellen", + "Specifies whether to combine and compress the export files in a ZIP archive.", + "Legt fest, ob die Exportdateien in einem ZIP-Archiv zusammengefasst und komprimiert werden sollen."); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportEntityType.Product", "Product", "Produkt"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportEntityType.Category", "Category", "Warengruppe"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportEntityType.Manufacturer", "Manufacturer", "Hersteller"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportEntityType.Customer", "Customer", "Kunde"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.ExportEntityType.Order", "Order", "Auftrag"); + builder.AddOrUpdate("Admin.Configuration.Export.CompletedEmailAddresses", + "Notification (email addresses)", + "Benachrichtigung (E-Mail-Addressen)", + "Specifies the email addresses (semicolon separated) where to send a notification message of the completion of the export.", + "Legt die E-Mail Addressen (Semikolon getrennt) fest, an die eine Benachrichtigung ber die Fertigstellung des Exports verschickt werden soll."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.Product", "Product", "Produkt"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.Category", "Category", "Warengruppe"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.Manufacturer", "Manufacturer", "Hersteller"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.Customer", "Customer", "Kunde"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.Order", "Order", "Auftrag"); builder.AddOrUpdate("Admin.Configuration.Export.Filter.StoreId", "Store", @@ -281,6 +323,77 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Whrung", "Specifies the currency to be applied to the export.", "Legt die auf den Export anzuwendende Whrung fest."); + + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.FileSystem", "File system", "Dateisystem"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.Email", "Email", "E-Mail"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.Http", "HTTP", "HTTP"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.Ftp", "FTP", "FTP"); + + + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.Name", + "Name", + "Name", + "Specifies the name of the deployment.", + "Legt den Namen der Bereitstellung fest."); + + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.Name.Validate", + "Please enter the name of the deployment.", + "Bitte den Namen der Bereitstellung eingeben."); + + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.IsPublic", + "Make public", + "ffentlich machen", + "Specifies whether to publish the exported data (e.g. whether they are accessible on the internet).", + "Legt fest, ob die exportierten Daten ffentlich gemacht werden sollen (z.B. ob sie ber das Internet erreichbar sind)."); + + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.DeploymentType", + "Type of deployment", + "Typ der Bereitstellung", + "Specifies the deployment type.", + "Legt den Bereitstellungstyp fest."); + + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.Username", + "User name", + "Benutzername", + "Specifies the user name.", + "Legt den Benutzernamen fest."); + + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.Password", + "Password", + "Passwort", + "Specifies the password.", + "Legt das Passwort fest."); + + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.Url", + "URL", + "URL", + "Specifies the URL on which the data should be deployed.", + "Legt die URL fest, unter der die Daten bereitgestellt werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.FileSystemPath", + "Relative path", + "Relativer Pfad", + "Specifies the relative path for the deployed data.", + "Legt den relativen Pfad fr die bereitgestellten Daten fest."); + + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.EmailAddresses", + "Email addresses", + "E-Mail-Addressen", + "Specifies the email addresses (semicolon separated) where to send the data.", + "Legt die E-Mail Addressen (Semikolon getrennt) fest, an die die Daten verschickt werden soll."); + + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.EmailSubject", + "Email subject", + "E-Mail Betreff", + "Specifies the subject of the data should be sent.", + "Legt den Betreff der verschickten Daten fest."); + + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.EmailAccountId", + "Email account", + "E-Mail Konto", + "Specifies the email account through which the data should be sent.", + "Legt das E-Mail Konto fest, ber welches die Daten verschickt werden sollen."); } } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201508070937542_ExportFramework.resx b/src/Libraries/SmartStore.Data/Migrations/201508070937542_ExportFramework.resx new file mode 100644 index 0000000000..80b65fc188 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508070937542_ExportFramework.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 a203443917..48259842a2 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -120,6 +120,7 @@ + @@ -332,9 +333,9 @@ 201507250039446_Merge2.cs - - - 201508051818404_ExportFramework.cs + + + 201508070937542_ExportFramework.cs @@ -633,8 +634,8 @@ 201507250039446_Merge2.cs - - 201508051818404_ExportFramework.cs + + 201508070937542_ExportFramework.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index 2b8a2f2ba1..d678942acc 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -16,17 +16,20 @@ public partial class ExportService : IExportService private const int _defaultSchedulingHours = 6; private readonly IRepository _exportProfileRepository; + private readonly IRepository _exportDeploymentRepository; private readonly IEventPublisher _eventPublisher; private readonly IScheduleTaskService _scheduleTaskService; private readonly IProviderManager _providerManager; public ExportService( IRepository exportProfileRepository, + IRepository exportDeploymentRepository, IEventPublisher eventPublisher, IScheduleTaskService scheduleTaskService, IProviderManager providerManager) { _exportProfileRepository = exportProfileRepository; + _exportDeploymentRepository = exportDeploymentRepository; _eventPublisher = eventPublisher; _scheduleTaskService = scheduleTaskService; _providerManager = providerManager; @@ -123,7 +126,12 @@ public virtual ExportProfile GetExportProfileById(int id) if (id == 0) return null; - return _exportProfileRepository.GetById(id); + var profile = _exportProfileRepository.Table + .Expand(x => x.ScheduleTask) + .Expand(x => x.Deployments) + .FirstOrDefault(x => x.Id == id); + + return profile; } @@ -142,6 +150,17 @@ public virtual Provider LoadProvider(string systemName, int sto return (provider.IsValid() ? provider : null); } + + public virtual void DeleteExportDeployment(ExportDeployment deployment) + { + if (deployment == null) + throw new ArgumentNullException("deployment"); + + _exportDeploymentRepository.Delete(deployment); + + _eventPublisher.EntityDeleted(deployment); + } + #endregion } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs index bc6ca8b092..bb511ebc8d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs @@ -40,6 +40,7 @@ public interface IExportService /// Export profile ExportProfile GetExportProfileById(int id); + /// /// Load all export providers /// @@ -54,5 +55,12 @@ public interface IExportService /// Store identifier /// Export provider Provider LoadProvider(string systemName, int storeId = 0); + + + /// + /// Deletes an export deployment + /// + /// Export deployment + void DeleteExportDeployment(ExportDeployment deployment); } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 6f7ac50ab8..4a036ed0e5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -1,11 +1,6 @@ using System; -using System.Globalization; -using System.IO; using System.Linq; -using System.Text; using System.Web.Mvc; -using System.Web.Routing; -using System.Xml.Serialization; using SmartStore.Admin.Models.DataExchange; using SmartStore.Core; using SmartStore.Core.Domain; @@ -82,7 +77,28 @@ private string GetThumbnailUrl(Provider provider) return url; } - private void PrepareExportProfileModel(ExportProfileModel model, ExportProfile profile, Provider provider) + private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment) + { + var model = new ExportDeploymentModel + { + Id = deployment.Id, + Name = deployment.Name, + Enabled = deployment.Enabled, + IsPublic = deployment.IsPublic, + DeploymentType = deployment.DeploymentType, + DeploymentTypeName = T("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType." + deployment.DeploymentType.ToString()), + Username = deployment.Username, + Password = deployment.Password, + Url = deployment.Url, + FileSystemPath = deployment.FileSystemPath, + EmailAddresses = deployment.EmailAddresses, + EmailSubject = deployment.EmailSubject, + EmailAccountId = deployment.EmailAccountId + }; + return model; + } + + private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile, Provider provider) { model.Id = profile.Id; model.Name = profile.Name; @@ -106,12 +122,12 @@ private void PrepareExportProfileModel(ExportProfileModel model, ExportProfile p model.Provider.Description = _pluginMediator.GetLocalizedDescription(provider.Metadata); model.Provider.EntityType = provider.Value.EntityType; - model.Provider.EntityTypeName = T("Enums.SmartStore.Core.Domain.ExportEntityType." + provider.Value.EntityType.ToString()); + model.Provider.EntityTypeName = T("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType." + provider.Value.EntityType.ToString()); model.Provider.FileType = provider.Value.FileType.ToUpper(); } } - private void PrepareExportProfileModelForEdit(ExportProfileModel model, ExportProfile profile, Provider provider) + private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile profile, Provider provider) { var filter = XmlHelper.Deserialize(profile.Filtering); var projection = XmlHelper.Deserialize(profile.Projection); @@ -127,16 +143,25 @@ private void PrepareExportProfileModelForEdit(ExportProfileModel model, ExportPr model.Limit = profile.Limit; model.BatchSize = profile.BatchSize; model.PerStore = profile.PerStore; + model.CreateZipArchive = profile.CreateZipArchive; + model.CompletedEmailAddresses = profile.CompletedEmailAddresses; if (provider != null) { - string partialName; - Type dataType; - if (provider.Value.RequiresConfiguration(out partialName, out dataType)) + try { - model.Provider.ConfigPartialViewName = partialName; - model.Provider.ConfigDataType = dataType; - model.Provider.ConfigData = XmlHelper.Deserialize(profile.ProviderConfigData, dataType); + string partialName; + Type dataType; + if (provider.Value.RequiresConfiguration(out partialName, out dataType)) + { + model.Provider.ConfigPartialViewName = partialName; + model.Provider.ConfigDataType = dataType; + model.Provider.ConfigData = XmlHelper.Deserialize(profile.ProviderConfigData, dataType); + } + } + catch (Exception exc) + { + NotifyError(exc); } } @@ -229,6 +254,9 @@ private void PrepareExportProfileModelForEdit(ExportProfileModel model, ExportPr initModelBase(model.OrderFilter); } + + // deplyoment + model.Deployments = profile.Deployments.Select(x => PrepareDeploymentModel(x)).ToList(); } #endregion @@ -262,7 +290,7 @@ public ActionResult List(GridCommand command) model.Data = profiles.Select(x => { var profileModel = new ExportProfileModel(); - PrepareExportProfileModel(profileModel, x, providers.FirstOrDefault(y => y.Metadata.SystemName == x.ProviderSystemName)); + PrepareProfileModel(profileModel, x, providers.FirstOrDefault(y => y.Metadata.SystemName == x.ProviderSystemName)); return profileModel; }); @@ -331,8 +359,8 @@ public ActionResult Edit(int id) var model = new ExportProfileModel(); - PrepareExportProfileModel(model, profile, provider); - PrepareExportProfileModelForEdit(model, profile, provider); + PrepareProfileModel(model, profile, provider); + PrepareProfileModelForEdit(model, profile, provider); return View(model); } @@ -352,8 +380,8 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) if (!ModelState.IsValid) { - PrepareExportProfileModel(model, profile, provider); - PrepareExportProfileModelForEdit(model, profile, provider); + PrepareProfileModel(model, profile, provider); + PrepareProfileModelForEdit(model, profile, provider); return View(model); } @@ -364,6 +392,8 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) profile.Limit = model.Limit; profile.BatchSize = model.BatchSize; profile.PerStore = model.PerStore; + profile.CreateZipArchive = model.CreateZipArchive; + profile.CompletedEmailAddresses = model.CompletedEmailAddresses; if (profile.Name.IsEmpty()) profile.Name = provider.Metadata.FriendlyName; @@ -499,5 +529,33 @@ public ActionResult Execute(int id) { return null; } + + #region Export deployment + + [HttpPost, GridAction(EnableCustomBinding = true)] + public ActionResult DeploymentList(GridCommand command, int id) + { + var model = new GridModel(); + + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + { + var profile = _exportService.GetExportProfileById(id); + if (profile != null) + { + model.Total = profile.Deployments.Count; + + model.Data = profile.Deployments.Select(x => PrepareDeploymentModel(x)).ToList(); + } + } + + return new JsonResult { Data = model }; + } + + public ActionResult DeploymentEdit(int id) + { + return null; + } + + #endregion } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs new file mode 100644 index 0000000000..df0c40302e --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs @@ -0,0 +1,49 @@ +using System.ComponentModel.DataAnnotations; +using FluentValidation.Attributes; +using SmartStore.Admin.Validators.DataExchange; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Mvc; + +namespace SmartStore.Admin.Models.DataExchange +{ + [Validator(typeof(ExportDeploymentValidator))] + public class ExportDeploymentModel : EntityModelBase + { + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.Name")] + public string Name { get; set; } + + [SmartResourceDisplayName("Common.Enabled")] + public bool Enabled { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.DeploymentType")] + public ExportDeploymentType DeploymentType { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.DeploymentType")] + public string DeploymentTypeName { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.IsPublic")] + public bool IsPublic { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.Username")] + public string Username { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.Password")] + [DataType(DataType.Password)] + public string Password { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.Url")] + public string Url { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.FileSystemPath")] + public string FileSystemPath { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.EmailAddresses")] + public string EmailAddresses { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.EmailSubject")] + public string EmailSubject { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.EmailAccountId")] + public int EmailAccountId { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs index 3294830cb9..589dc68f9f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs @@ -26,7 +26,7 @@ public abstract class ExportFilterModelBase public class ExportProductFilterModel : ExportFilterModelBase { [SmartResourceDisplayName("Admin.Configuration.Export.Filter.IsPublished")] - public bool? IsPublished { get; set; } + public bool IsPublished { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ProductType")] public ProductType? ProductType { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 4638c6fd0e..77f5ff7b6d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -1,14 +1,15 @@ using System; using System.Collections.Generic; -using System.Dynamic; using System.Web.Mvc; -using System.Web.Routing; +using FluentValidation.Attributes; +using SmartStore.Admin.Validators.DataExchange; using SmartStore.Core.Domain.DataExchange; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Mvc; namespace SmartStore.Admin.Models.DataExchange { + [Validator(typeof(ExportProfileValidator))] public partial class ExportProfileModel : EntityModelBase { public int StoreCount { get; set; } @@ -40,6 +41,12 @@ public partial class ExportProfileModel : EntityModelBase [SmartResourceDisplayName("Admin.Configuration.Export.PerStore")] public bool PerStore { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.CreateZipArchive")] + public bool CreateZipArchive { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.CompletedEmailAddresses")] + public string CompletedEmailAddresses { get; set; } + public ProviderModel Provider { get; set; } public ExportProductFilterModel ProductFilter { get; set; } @@ -47,6 +54,8 @@ public partial class ExportProfileModel : EntityModelBase public ExportProjectionModel Projection { get; set; } + public List Deployments { get; set; } + public class ProviderModel { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs index 0cd5ddd22f..d95ea87ec8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs @@ -7,11 +7,11 @@ namespace SmartStore.Admin.Models.DataExchange public class ExportProjectionModel { [SmartResourceDisplayName("Admin.Configuration.Export.Projection.LanguageId")] - public int LanguageId { get; set; } + public int? LanguageId { get; set; } public List AvailableLanguages { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Projection.CurrencyId")] - public int CurrencyId { get; set; } + public int? CurrencyId { get; set; } public List AvailableCurrencies { get; set; } } } \ 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 1fb41c6bf1..b7bcbd5add 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -247,6 +247,7 @@ + @@ -366,6 +367,8 @@ + + diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportDeplomentValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportDeplomentValidator.cs new file mode 100644 index 0000000000..4f1539698d --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportDeplomentValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; +using SmartStore.Admin.Models.DataExchange; +using SmartStore.Services.Localization; + +namespace SmartStore.Admin.Validators.DataExchange +{ + public partial class ExportDeploymentValidator : AbstractValidator + { + public ExportDeploymentValidator(ILocalizationService localization) + { + RuleFor(x => x.Name) + .NotEmpty() + .WithMessage(localization.GetResource("Admin.Configuration.Export.Deployment.Name.Validate")); + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs new file mode 100644 index 0000000000..d0a1e14ca1 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs @@ -0,0 +1,16 @@ +using FluentValidation; +using SmartStore.Admin.Models.DataExchange; +using SmartStore.Services.Localization; + +namespace SmartStore.Admin.Validators.DataExchange +{ + public partial class ExportProfileValidator : AbstractValidator + { + public ExportProfileValidator(ILocalizationService localization) + { + RuleFor(x => x.Name) + .NotEmpty() + .WithMessage(localization.GetResource("Admin.Configuration.Export.Name.Validate")); + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index f825f3b48a..7bf4b772b2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -36,7 +36,7 @@ x.Add().Text(T("Common.Filter").Text).Content(TabFilter()); x.Add().Text(T("Common.Projection").Text).Content(TabProjection()); x.Add().Text(T("Admin.Configuration").Text).Content(TabConfiguration()); - x.Add().Text(T("Common.Publishing").Text).Content(TabDeployment()); + x.Add().Text(T("Common.Deployment").Text).Content(TabDeployment()); EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "export-profile-edit", this.Html, this.Model)); }) @@ -466,6 +466,65 @@ @helper TabDeployment() { + +
+ @(Html.Telerik().Grid(Model.Deployments) + .Name("export-deployment-grid") + .DataKeys(keys => keys.Add(x => x.Id) + .RouteKey("Id")) + .Columns(columns => + { + columns.Bound(x => x.Name) + .Width(400) + .Template(x => Html.ActionLink(x.Name, "DeploymentEdit", new { id = x.Id })) + .ClientTemplate("\"><#= Name #>"); + columns.Bound(x => x.DeploymentTypeName); + columns.Bound(x => x.Enabled) + .Width(100) + .Template(item => @Html.SymbolForBool(item.Enabled)) + .ClientTemplate(@Html.SymbolForBool("Enabled")) + .Centered(); + }) + .ToolBar(commands => + { + commands.Custom() + .Name("export-deployment-insert-button") + .HtmlAttributes(new { @class = "btn btn-primary" }) + .Text(" " + @T("Common.AddNew")); + }) + .DataBinding(dataBinding => dataBinding.Ajax().Select("DeploymentList", "Export", new { id = Model.Id })) + .ClientEvents(events => events.OnRowDataBound("onRowDataBound_ExportDeployments")) + .EnableCustomBinding(true)) +
+ +

+ + + + + + + + + + +
+ @Html.SmartLabelFor(x => x.CreateZipArchive) + + @Html.EditorFor(x => x.CreateZipArchive) + @Html.ValidationMessageFor(x => x.CreateZipArchive) +
+ @Html.SmartLabelFor(x => x.CompletedEmailAddresses) + + @Html.EditorFor(x => x.CompletedEmailAddresses, new { @class = "control-xlarge" }) + @Html.ValidationMessageFor(x => x.CompletedEmailAddresses) +
} @Html.DeleteConfirmation("export-profile-delete") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 10fafd92f6..9c04d96eba 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -29,10 +29,8 @@ @(Html.Telerik().Grid() .Name("export-profile-grid") - .DataKeys(x => - { - x.Add(y => y.Id).RouteKey("Id"); - }) + .DataKeys(keys => keys.Add(x => x.Id) + .RouteKey("Id")) .Columns(columns => { columns.Bound(x => x.Provider.ThumbnailUrl) From 9c609eb5417912b5675f0cdf95ae0f50d37980f9 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 8 Aug 2015 13:43:44 +0200 Subject: [PATCH 007/874] CRUD export deployment --- .../Domain/Messages/EmailAccount.cs | 7 +- .../201508070937542_ExportFramework.cs | 25 +- .../DataExchange/ExportService.cs | 11 + .../DataExchange/IExportService.cs | 8 +- .../Controllers/ExportController.cs | 216 +++++++++++++++--- .../DataExchange/ExportDeploymentModel.cs | 30 ++- .../Models/DataExchange/ExportProfileModel.cs | 1 - .../Administration/SmartStore.Admin.csproj | 3 + .../DataExchange/ExportDeplomentValidator.cs | 28 ++- .../DataExchange/ExportProfileValidator.cs | 2 +- .../Views/Export/CreateDeployment.cshtml | 21 ++ .../Administration/Views/Export/Edit.cshtml | 18 +- .../Views/Export/EditDeployment.cshtml | 26 +++ .../Administration/Views/Export/List.cshtml | 2 +- .../Export/_CreateOrUpdateDeployment.cshtml | 127 ++++++++++ 15 files changed, 466 insertions(+), 59 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Administration/Views/Export/CreateDeployment.cshtml create mode 100644 src/Presentation/SmartStore.Web/Administration/Views/Export/EditDeployment.cshtml create mode 100644 src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdateDeployment.cshtml diff --git a/src/Libraries/SmartStore.Core/Domain/Messages/EmailAccount.cs b/src/Libraries/SmartStore.Core/Domain/Messages/EmailAccount.cs index decde80336..859a7bd74f 100644 --- a/src/Libraries/SmartStore.Core/Domain/Messages/EmailAccount.cs +++ b/src/Libraries/SmartStore.Core/Domain/Messages/EmailAccount.cs @@ -54,9 +54,10 @@ public string FriendlyName { get { - if (!String.IsNullOrWhiteSpace(this.DisplayName)) - return this.Email + " (" + this.DisplayName + ")"; - return this.Email; + if (DisplayName.IsEmpty()) + return Email; + + return "{0} ({1})".FormatInvariant(DisplayName, Email); } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201508070937542_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508070937542_ExportFramework.cs index c85249ef25..a3ca9c5035 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508070937542_ExportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508070937542_ExportFramework.cs @@ -98,6 +98,11 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Common.Deployment", "Deployment", "Bereitstellung"); builder.AddOrUpdate("Common.Website", "Website", "Web-Seite"); + builder.AddOrUpdate("Admin.Validation.UsernamePassword", "Please enter username and password", "Bitte geben Sie Benutzername und Passwort ein"); + builder.AddOrUpdate("Admin.Validation.Url", "Please enter a valid URL", "Bitte geben Sie eine gltige URL ein"); + builder.AddOrUpdate("Admin.Validation.Name", "Please enter a name", "Bitte geben Sie einen Namen ein"); + builder.AddOrUpdate("Admin.Validation.EmailAddress", "Please enter a valid email address", "Bitte geben Sie eine gltige E-Mail Adresse ein"); + builder.AddOrUpdate("Admin.Configuration.Export.ProviderSystemName.Validate", "There were no export provider found for system name \"{0}\". A provider is mandatory for an export profile.", @@ -132,10 +137,6 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Specifies the name of the export profile.", "Legt den Namen des Exportprofils fest."); - builder.AddOrUpdate("Admin.Configuration.Export.Name.Validate", - "Please enter the name of the profile.", - "Bitte den Namen des Profils eingeben."); - builder.AddOrUpdate("Admin.Configuration.Export.FileType", "File type", "Dateityp", @@ -325,10 +326,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Legt die auf den Export anzuwendende Whrung fest."); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.FileSystem", "File system", "Dateisystem"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.Email", "Email", "E-Mail"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.Http", "HTTP", "HTTP"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.Ftp", "FTP", "FTP"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.FileSystem", "File system", "Dateisystem"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.Email", "Email", "E-Mail"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.Http", "HTTP", "HTTP"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.Ftp", "FTP", "FTP"); builder.AddOrUpdate("Admin.Configuration.Export.Deployment.Name", @@ -337,10 +338,6 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Specifies the name of the deployment.", "Legt den Namen der Bereitstellung fest."); - builder.AddOrUpdate("Admin.Configuration.Export.Deployment.Name.Validate", - "Please enter the name of the deployment.", - "Bitte den Namen der Bereitstellung eingeben."); - builder.AddOrUpdate("Admin.Configuration.Export.Deployment.IsPublic", "Make public", "ffentlich machen", @@ -349,9 +346,9 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Export.Deployment.DeploymentType", "Type of deployment", - "Typ der Bereitstellung", + "Art der Bereitstellung", "Specifies the deployment type.", - "Legt den Bereitstellungstyp fest."); + "Legt die Art Bereitstellung fest."); builder.AddOrUpdate("Admin.Configuration.Export.Deployment.Username", "User name", diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index d678942acc..b2e2a77d5d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -150,6 +150,17 @@ public virtual Provider LoadProvider(string systemName, int sto return (provider.IsValid() ? provider : null); } + public virtual ExportDeployment GetExportDeploymentById(int id) + { + if (id == 0) + return null; + + var deployment = _exportDeploymentRepository.Table + .Expand(x => x.Profile) + .FirstOrDefault(x => x.Id == id); + + return deployment; + } public virtual void DeleteExportDeployment(ExportDeployment deployment) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs index bb511ebc8d..a02512e322 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs @@ -56,9 +56,15 @@ public interface IExportService /// Export provider Provider LoadProvider(string systemName, int storeId = 0); + /// + /// Get export deployment by identifier + /// + /// Export deployment identifier + /// Export deployment + ExportDeployment GetExportDeploymentById(int id); /// - /// Deletes an export deployment + /// Deleted a export deployment /// /// Export deployment void DeleteExportDeployment(ExportDeployment deployment); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 4a036ed0e5..754b4c2b95 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -17,10 +17,12 @@ using SmartStore.Services.Directory; using SmartStore.Services.Localization; using SmartStore.Services.Media; +using SmartStore.Services.Messages; using SmartStore.Services.Security; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Plugins; +using SmartStore.Web.Framework.UI; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -38,6 +40,7 @@ public class ExportController : AdminControllerBase private readonly ICustomerService _customerService; private readonly ILanguageService _languageService; private readonly ICurrencyService _currencyService; + private readonly IEmailAccountService _emailAccountService; public ExportController( ICommonServices services, @@ -49,7 +52,8 @@ public ExportController( IProductTagService productTagService, ICustomerService customerService, ILanguageService languageService, - ICurrencyService currencyService) + ICurrencyService currencyService, + IEmailAccountService emailAccountService) { _services = services; _exportService = exportService; @@ -61,6 +65,7 @@ public ExportController( _customerService = customerService; _languageService = languageService; _currencyService = currencyService; + _emailAccountService = emailAccountService; } #region Utilities @@ -77,27 +82,6 @@ private string GetThumbnailUrl(Provider provider) return url; } - private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment) - { - var model = new ExportDeploymentModel - { - Id = deployment.Id, - Name = deployment.Name, - Enabled = deployment.Enabled, - IsPublic = deployment.IsPublic, - DeploymentType = deployment.DeploymentType, - DeploymentTypeName = T("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType." + deployment.DeploymentType.ToString()), - Username = deployment.Username, - Password = deployment.Password, - Url = deployment.Url, - FileSystemPath = deployment.FileSystemPath, - EmailAddresses = deployment.EmailAddresses, - EmailSubject = deployment.EmailSubject, - EmailAccountId = deployment.EmailAccountId - }; - return model; - } - private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile, Provider provider) { model.Id = profile.Id; @@ -122,7 +106,7 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile model.Provider.Description = _pluginMediator.GetLocalizedDescription(provider.Metadata); model.Provider.EntityType = provider.Value.EntityType; - model.Provider.EntityTypeName = T("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType." + provider.Value.EntityType.ToString()); + model.Provider.EntityTypeName = provider.Value.EntityType.GetLocalizedEnum(_services.Localization, _services.WorkContext); model.Provider.FileType = provider.Value.FileType.ToUpper(); } } @@ -254,9 +238,69 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile initModelBase(model.OrderFilter); } + } + + private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment) + { + var allEmailAccounts = _emailAccountService.GetAllEmailAccounts(); - // deplyoment - model.Deployments = profile.Deployments.Select(x => PrepareDeploymentModel(x)).ToList(); + var model = new ExportDeploymentModel + { + Id = deployment.Id, + ProfileId = deployment.ProfileId, + Name = deployment.Name, + Enabled = deployment.Enabled, + IsPublic = deployment.IsPublic, + DeploymentType = deployment.DeploymentType, + DeploymentTypeName = deployment.DeploymentType.GetLocalizedEnum(_services.Localization, _services.WorkContext), + Username = deployment.Username, + Password = deployment.Password, + Url = deployment.Url, + FileSystemPath = deployment.FileSystemPath, + EmailAddresses = deployment.EmailAddresses, + EmailSubject = deployment.EmailSubject, + EmailAccountId = deployment.EmailAccountId + }; + + model.AvailableDeploymentTypes = ExportDeploymentType.FileSystem.ToSelectList(false).ToList(); + + model.AvailableEmailAccounts = allEmailAccounts + .Select(x => new SelectListItem { Text = x.FriendlyName, Value = x.Id.ToString() }) + .ToList(); + + 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.IsPublic = model.IsPublic; + deployment.Username = model.Username; + deployment.Password = model.Password; + deployment.Url = model.Url; + deployment.FileSystemPath = model.FileSystemPath; + deployment.EmailAddresses = model.EmailAddresses; + deployment.EmailSubject = model.EmailSubject; + deployment.EmailAccountId = model.EmailAccountId; + } + + 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 @@ -544,16 +588,132 @@ public ActionResult DeploymentList(GridCommand command, int id) { model.Total = profile.Deployments.Count; - model.Data = profile.Deployments.Select(x => PrepareDeploymentModel(x)).ToList(); + model.Data = profile.Deployments.Select(x => + { + var deploymentModel = new ExportDeploymentModel + { + Id = x.Id, + ProfileId = profile.Id, + Name = x.Name, + Enabled = x.Enabled, + DeploymentType = x.DeploymentType, + DeploymentTypeName = x.DeploymentType.GetLocalizedEnum(_services.Localization, _services.WorkContext) + }; + return deploymentModel; + }).ToList(); } } return new JsonResult { Data = model }; } - public ActionResult DeploymentEdit(int id) + public ActionResult CreateDeployment(int id) { - return null; + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var profile = _exportService.GetExportProfileById(id); + if (profile == null) + return RedirectToAction("List"); + + var fileSystemName = ExportDeploymentType.FileSystem.GetLocalizedEnum(_services.Localization, _services.WorkContext); + + var model = PrepareDeploymentModel(new ExportDeployment + { + ProfileId = id, + Enabled = true, + DeploymentType = ExportDeploymentType.FileSystem, + Name = profile.Name + }); + + return View(model); + } + + [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [FormValueRequired("save", "save-continue")] + public ActionResult CreateDeployment(ExportDeploymentModel model, bool continueEditing, ExportDeploymentType deploymentType) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var profile = _exportService.GetExportProfileById(model.ProfileId); + if (profile == null) + return RedirectToAction("List"); + + if (ModelState.IsValid) + { + var deployment = new ExportDeployment(); + + ModelToEntity(model, deployment); + + profile.Deployments.Add(deployment); + + _exportService.UpdateExportProfile(profile); + + return SmartRedirect(continueEditing, profile.Id, deployment.Id); + } + + return CreateDeployment(profile.Id); + } + + public ActionResult EditDeployment(int id) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var deployment = _exportService.GetExportDeploymentById(id); + if (deployment == null) + return RedirectToAction("List"); + + var model = PrepareDeploymentModel(deployment); + + return View(model); + } + + [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [FormValueRequired("save", "save-continue")] + public ActionResult EditDeployment(ExportDeploymentModel model, bool continueEditing) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var deployment = _exportService.GetExportDeploymentById(model.Id); + if (deployment == null) + return RedirectToAction("List"); + + if (ModelState.IsValid) + { + ModelToEntity(model, deployment); + + _exportService.UpdateExportProfile(deployment.Profile); + + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); + + return SmartRedirect(continueEditing, deployment.ProfileId, deployment.Id); + } + + model = PrepareDeploymentModel(deployment); + + return View(model); + } + + [HttpPost] + public ActionResult DeleteDeployment(int id) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var deployment = _exportService.GetExportDeploymentById(id); + if (deployment == null) + return RedirectToAction("List"); + + int profileId = deployment.ProfileId; + + _exportService.DeleteExportDeployment(deployment); + + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + + return SmartRedirect(false, profileId, 0); } #endregion diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs index df0c40302e..d73036f76d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs @@ -1,4 +1,6 @@ -using System.ComponentModel.DataAnnotations; +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; @@ -10,6 +12,8 @@ namespace SmartStore.Admin.Models.DataExchange [Validator(typeof(ExportDeploymentValidator))] public class ExportDeploymentModel : EntityModelBase { + public int ProfileId { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.Name")] public string Name { get; set; } @@ -18,9 +22,32 @@ public class ExportDeploymentModel : EntityModelBase [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.DeploymentType")] public ExportDeploymentType DeploymentType { get; set; } + public List AvailableDeploymentTypes { get; set; } + [SmartResourceDisplayName("Admin.Configuration.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-clone"; + default: + return "fa-question"; + } + } + } + + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.IsPublic")] public bool IsPublic { get; set; } @@ -45,5 +72,6 @@ public class ExportDeploymentModel : EntityModelBase [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.EmailAccountId")] public int EmailAccountId { get; set; } + public List AvailableEmailAccounts { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 77f5ff7b6d..71a6a8e85d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -56,7 +56,6 @@ public partial class ExportProfileModel : EntityModelBase public List Deployments { get; set; } - public class ProviderModel { public string ConfigPartialViewName { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index b7bcbd5add..5df7d83edd 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -510,6 +510,9 @@ + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportDeplomentValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportDeplomentValidator.cs index 4f1539698d..4caa43d099 100644 --- a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportDeplomentValidator.cs +++ b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportDeplomentValidator.cs @@ -1,5 +1,6 @@ using FluentValidation; using SmartStore.Admin.Models.DataExchange; +using SmartStore.Core.Domain.DataExchange; using SmartStore.Services.Localization; namespace SmartStore.Admin.Validators.DataExchange @@ -10,7 +11,32 @@ public ExportDeploymentValidator(ILocalizationService localization) { RuleFor(x => x.Name) .NotEmpty() - .WithMessage(localization.GetResource("Admin.Configuration.Export.Deployment.Name.Validate")); + .WithMessage(localization.GetResource("Admin.Validation.Name")); + + RuleFor(x => x.EmailAddresses) + .NotEmpty() + .When(x => x.DeploymentType == ExportDeploymentType.Email) + .WithMessage(localization.GetResource("Admin.Validation.EmailAddress")); + + RuleFor(x => x.Url) + .NotEmpty() + .When(x => x.DeploymentType == ExportDeploymentType.Http) + .WithMessage(localization.GetResource("Admin.Validation.Url")); + + RuleFor(x => x.Url) + .Matches(RegularExpressions.IsWebUrl) + .When(x => x.DeploymentType == ExportDeploymentType.Http) + .WithMessage(localization.GetResource("Admin.Validation.Url")); + + RuleFor(x => x.Username) + .NotEmpty() + .When(x => x.DeploymentType == ExportDeploymentType.Ftp) + .WithMessage(localization.GetResource("Admin.Validation.UsernamePassword")); + + RuleFor(x => x.Password) + .NotEmpty() + .When(x => x.DeploymentType == ExportDeploymentType.Ftp) + .WithMessage(localization.GetResource("Admin.Validation.UsernamePassword")); } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs index d0a1e14ca1..4fa8d0a556 100644 --- a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs +++ b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs @@ -10,7 +10,7 @@ public ExportProfileValidator(ILocalizationService localization) { RuleFor(x => x.Name) .NotEmpty() - .WithMessage(localization.GetResource("Admin.Configuration.Export.Name.Validate")); + .WithMessage(localization.GetResource("Admin.Validation.Name")); } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/CreateDeployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/CreateDeployment.cshtml new file mode 100644 index 0000000000..79f904409e --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/CreateDeployment.cshtml @@ -0,0 +1,21 @@ +@model ExportDeploymentModel +@using SmartStore.Core.Domain.DataExchange; +@using SmartStore.Admin.Models.DataExchange; +@{ + var title = string.Concat(T("Common.AddNew"), " ", T("Common.Deployment")); + ViewBag.Title = title; +} + +@using (Html.BeginForm()) +{ +
+
+ @title @Html.ActionLink("(" + T("Common.Back") + ")", "Edit", new { id = Model.ProfileId }) +
+
+ + +
+
+ @Html.Partial("_CreateOrUpdateDeployment", Model) +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 7bf4b772b2..e284d1f9db 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -474,17 +474,19 @@ }
- @(Html.Telerik().Grid(Model.Deployments) + @(Html.Telerik().Grid() .Name("export-deployment-grid") .DataKeys(keys => keys.Add(x => x.Id) .RouteKey("Id")) .Columns(columns => { columns.Bound(x => x.Name) - .Width(400) - .Template(x => Html.ActionLink(x.Name, "DeploymentEdit", new { id = x.Id })) - .ClientTemplate("\"><#= Name #>"); - columns.Bound(x => x.DeploymentTypeName); + .Template(x => Html.ActionLink(x.Name, "EditDeployment", "Export", new { id = x.Id }, null)) + .ClientTemplate("\"><#= Name #>"); + columns.Bound(x => x.DeploymentTypeName) + .Template(x => " {1}".FormatInvariant(x.DeploymentTypeIconClass, x.DeploymentTypeName)) + .ClientTemplate(" <#= DeploymentTypeName #>") + .Width(320); columns.Bound(x => x.Enabled) .Width(100) .Template(item => @Html.SymbolForBool(item.Enabled)) @@ -494,8 +496,8 @@ .ToolBar(commands => { commands.Custom() - .Name("export-deployment-insert-button") - .HtmlAttributes(new { @class = "btn btn-primary" }) + .Name("export-deployment-add-button") + .HtmlAttributes(new { @class = "btn btn-primary", href = @Url.Action("CreateDeployment", new { id = Model.Id }) }) .Text(" " + @T("Common.AddNew")); }) .DataBinding(dataBinding => dataBinding.Ajax().Select("DeploymentList", "Export", new { id = Model.Id })) @@ -520,7 +522,7 @@ @Html.SmartLabelFor(x => x.CompletedEmailAddresses) - @Html.EditorFor(x => x.CompletedEmailAddresses, new { @class = "control-xlarge" }) + @Html.EditorFor(x => x.CompletedEmailAddresses) @Html.ValidationMessageFor(x => x.CompletedEmailAddresses) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/EditDeployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/EditDeployment.cshtml new file mode 100644 index 0000000000..356ded774d --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/EditDeployment.cshtml @@ -0,0 +1,26 @@ +@model ExportDeploymentModel +@using SmartStore.Core.Domain.DataExchange; +@using SmartStore.Admin.Models.DataExchange; +@{ + var title = string.Concat(T("Admin.Common.Edit"), " ", T("Common.Deployment")); + ViewBag.Title = title; +} + +@using (Html.BeginForm()) +{ +
+
+ @title - @Model.Name @Html.ActionLink("(" + T("Common.Back") + ")", "Edit", new { id = Model.ProfileId }) +
+
+ + + +
+
+ @Html.Partial("_CreateOrUpdateDeployment", Model) +} + +@Html.DeleteConfirmation("DeleteDeployment", "export-deployment-delete") \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 9c04d96eba..6856f83fbc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -70,7 +70,7 @@
-
public int? CurrencyId { get; set; } + + public int? CustomerId { get; set; } + + public ExportDescriptionMergingType? DescriptionMerging { get; set; } + + public bool DescriptionToPlainText { get; set; } + + public string AppendDescriptionText { get; set; } + + public bool RemoveCriticalCharacters { get; set; } + + public string CriticalCharacters { get; set; } + + public PriceDisplayType? PriceType { get; set; } + + public bool ConvertNetToGrossPrices { get; set; } + + public string Brand { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs b/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs index c07d51555c..7b0ff20fee 100644 --- a/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs +++ b/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using SmartStore.Core.Domain.DataExchange; namespace SmartStore.Core.Plugins { @@ -67,6 +68,11 @@ public class ProviderMetadata /// public bool IsEditable { get; set; } + /// + /// Gets or sets an array of field types that are supported by an export provider and thus should be editable on profile level + /// + public ExportProjectionFieldType[] ExportProjectionFields { get; set; } + /// /// Gets or sets an array of widget system names, which depend on the current provider /// diff --git a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs index 0aefe57b7a..782f8a501b 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs @@ -98,6 +98,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Common.Projection", "Projection", "Projektion"); builder.AddOrUpdate("Common.Deployment", "Deployment", "Bereitstellung"); builder.AddOrUpdate("Common.Website", "Website", "Web-Seite"); + builder.AddOrUpdate("Common.DetailDescription", "Detail description", "Detailbeschreibung"); builder.AddOrUpdate("Admin.Validation.UsernamePassword", "Please enter username and password", "Bitte geben Sie Benutzername und Passwort ein"); builder.AddOrUpdate("Admin.Validation.Url", "Please enter a valid URL", "Bitte geben Sie eine gltige URL ein"); @@ -336,12 +337,82 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Specifies the currency to be applied to the export.", "Legt die auf den Export anzuwendende Whrung fest."); + builder.AddOrUpdate("Admin.Configuration.Export.Projection.CustomerId", + "Customer ID", + "Kunden-ID", + "Specifies the ID of the customer to be applied to the export. Is taken into account for price calculations for example.", + "Legt die ID des Kunden fest, auf den sich der Export beziehen soll. Wird z.B. bei Preisberechnungen bercksichtigt."); + + builder.AddOrUpdate("Admin.Configuration.Export.Projection.DescriptionMerging", + "Product description", + "Artikelbeschreibung", + "Specifies what information to use for the description of the product.", + "Legt fest, welche Informationen zur Beschreibung des Artikel wie verwendet werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Export.Projection.DescriptionToPlainText", + "Remove HTML from description", + "HTML aus der Beschreibung entfernen", + "Specifies whether to remove all HTML from the product description for the export.", + "Legt fest, ob fr den Export alle HTML-Auszeichnungen aus der Artikelbeschreibung entfernt werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Export.Projection.AppendDescriptionText", + "Text to be appended", + "Anzuhngender Text", + "Specifies the text to be attached to the product description. You can enter multiple, semicolon delimited texts. Then one of it is selected randomly.", + "Legt den an die Artikelbeschreibung anzuhngenden Text fest. Es knnen auch mehrere, durch Semikolon getrennte Texte eingegeben werden. Dann wird per Zufall einer ausgewhlt."); + + builder.AddOrUpdate("Admin.Configuration.Export.Projection.RemoveCriticalCharacters", + "Remove critical characters", + "Kritische Zeichen entfernen", + "Specifies whether to remove critical characters (like ) from the detail description.", + "Legt fest, ob kritische Zeichen (wie z.B. ) aus der Detailsbeschreibung entfernt werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Export.Projection.CriticalCharacters", + "Critical characters", + "Kritische Zeichen", + "List with semicolon delimited characters to be removed from the detail description.", + "Liste mit Semikolon getrennten Zeichen, die aus der Detailbeschreibung entfernt werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Export.Projection.PriceType", + "Product price", + "Produktpreis", + "Specifies the product price to be exported.", + "Legt den zu exportierenden Produktpreis fest."); + + builder.AddOrUpdate("Admin.Configuration.Export.Projection.ConvertNetToGrossPrices", + "Convert net into gross prices", + "Netto- in Bruttopreise umrechnen", + "Specifies to convert net into gross prices.", + "Legt fest, dass Netto- in Bruttopreise umgerechnet werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Export.Projection.Brand", + "Manufacturer\\Brand", + "Hersteller\\Marke", + "Specifies the manufacturer or brand to be exported, if a product has no manufacturer assigned.", + "Legt den zu exportierenden Hersteller bzw. die Marke fest, wenn fr ein Produkt kein Hersteller zugeordnet ist."); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.FileSystem", "File system", "Dateisystem"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.Email", "Email", "E-Mail"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.Http", "HTTP", "HTTP"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.Ftp", "FTP", "FTP"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMergingType.None", + "None", "Keine"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMergingType.ShortDescriptionOrNameIfEmpty", + "Short description or name if empty", "Kurzbeschreibung oder Name falls leer"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMergingType.ShortDescription", + "Short description", "Kurzbeschreibung"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMergingType.Description", + "Description", "Detailbeschreibung"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMergingType.NameAndShortDescription", + "Product name + short description", "Produktname + Kurzbeschreibung"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMergingType.NameAndDescription", + "Product name + long description", "Produktname + Detailbeschreibung"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMergingType.ManufacturerAndNameAndShortDescription", + "Manufacturer + Product name + short description", "Hersteller + Produktname + Kurzbeschreibung"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMergingType.ManufacturerAndNameAndDescription", + "Manufacturer + Product name + long description", "Hersteller + Produktname + Detailbeschreibung"); + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.Name", "Name", diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs index 183698d089..eb3f94c593 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs @@ -244,7 +244,7 @@ public static ExpandoObject ToExpando(this Product product, int languageId, IPic dynamic exp = new ExpandoObject(); exp.Id = x.Id; exp.DisplayOrder = x.DisplayOrder; - exp.Picture = x.PictureId == 0 ? null : x.Picture.ToExpando(pictureService, store, mediaSettings.ProductThumbPictureSize, mediaSettings.ProductDetailsPictureSize); + exp.Picture = x.Picture.ToExpando(pictureService, store, mediaSettings.ProductThumbPictureSize, mediaSettings.ProductDetailsPictureSize); return exp as ExpandoObject; }) @@ -259,7 +259,7 @@ public static ExpandoObject ToExpando(this Product product, int languageId, IPic exp.Id = x.Id; exp.DisplayOrder = x.DisplayOrder; exp.IsFeaturedProduct = x.IsFeaturedProduct; - exp.Manufacturer = x.ManufacturerId == 0 ? null : x.Manufacturer.ToExpando(languageId); + exp.Manufacturer = x.Manufacturer.ToExpando(languageId); return exp as ExpandoObject; }) @@ -274,7 +274,7 @@ public static ExpandoObject ToExpando(this Product product, int languageId, IPic exp.Id = x.Id; exp.DisplayOrder = x.DisplayOrder; exp.IsFeaturedProduct = x.IsFeaturedProduct; - exp.Category = x.CategoryId == 0 ? null : x.Category.ToExpando(languageId); + exp.Category = x.Category.ToExpando(languageId); return exp as ExpandoObject; }) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportAttributes.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportAttributes.cs new file mode 100644 index 0000000000..838db79e5a --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportAttributes.cs @@ -0,0 +1,16 @@ +using System; +using SmartStore.Core.Domain.DataExchange; + +namespace SmartStore.Services.DataExchange +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class ExportProjectionFieldAttribute : Attribute + { + public ExportProjectionFieldAttribute(params ExportProjectionFieldType[] types) + { + Types = types; + } + + public ExportProjectionFieldType[] Types { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index 3746621af8..d5a7fdf7ca 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -6,22 +6,28 @@ using System.Linq; using System.Text; using System.Threading; +using System.Web; using Autofac; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Html; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Catalog; +using SmartStore.Services.Customers; +using SmartStore.Services.Directory; using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Seo; using SmartStore.Services.Tasks; +using SmartStore.Services.Tax; using SmartStore.Utilities; namespace SmartStore.Services.DataExchange @@ -37,6 +43,162 @@ public class ExportProfileTask : ITask private IProductService _productService; private IPictureService _pictureService; private MediaSettings _mediaSettings; + private IPriceCalculationService _priceCalculationService; + private ITaxService _taxService; + private ICurrencyService _currencyService; + private ICustomerService _customerService; + + #region Utilities + + private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic expando) + { + try + { + string description = ""; + + // description merging + if (ctx.Projection.DescriptionMerging.HasValue) + { + var type = ctx.Projection.DescriptionMerging ?? ExportDescriptionMergingType.ShortDescriptionOrNameIfEmpty; + + if (type == ExportDescriptionMergingType.ShortDescriptionOrNameIfEmpty) + { + description = expando.FullDescription; + + if (description.IsEmpty()) + description = expando.ShortDescription; + if (description.IsEmpty()) + description = expando.Name; + } + else if (type == ExportDescriptionMergingType.ShortDescription) + { + description = expando.ShortDescription; + } + else if (type == ExportDescriptionMergingType.Description) + { + description = expando.FullDescription; + } + else if (type == ExportDescriptionMergingType.NameAndShortDescription) + { + description = ((string)expando.Name).Grow((string)expando.ShortDescription, " "); + } + else if (type == ExportDescriptionMergingType.NameAndDescription) + { + description = ((string)expando.Name).Grow((string)expando.FullDescription, " "); + } + else if (type == ExportDescriptionMergingType.ManufacturerAndNameAndShortDescription || type == ExportDescriptionMergingType.ManufacturerAndNameAndDescription) + { + string name = (string)expando.Name; + dynamic productManu = ((List)expando.ProductManufacturers).FirstOrDefault(); + + if (productManu != null) + { + dynamic manu = productManu.Manufacturer; + description = ((string)manu.Name).Grow(name, " "); + + if (type == ExportDescriptionMergingType.ManufacturerAndNameAndShortDescription) + description = description.Grow((string)expando.ShortDescription, " "); + else + description = description.Grow((string)expando.FullDescription, " "); + } + } + } + else + { + description = expando.FullDescription; + } + + // append text + if (ctx.Projection.AppendDescriptionText.HasValue() && ((string)expando.ShortDescription).IsEmpty() && ((string)expando.FullDescription).IsEmpty()) + { + string[] appendText = ctx.Projection.AppendDescriptionText.SplitSafe(";"); + if (appendText.Length > 0) + { + var rnd = (new Random()).Next(0, appendText.Length - 1); + + description = description.Grow(appendText.SafeGet(rnd), " "); + } + } + + // remove critical characters + if (description.HasValue() && ctx.Projection.RemoveCriticalCharacters) + { + foreach (var str in ctx.Projection.CriticalCharacters.SplitSafe(";")) + description = description.Replace(str, ""); + } + + // convert to plain text + if (ctx.Projection.DescriptionToPlainText) + { + //Regex reg = new Regex("<[^>]+>", RegexOptions.IgnoreCase); + //description = HttpUtility.HtmlDecode(reg.Replace(description, "")); + + description = HtmlUtils.ConvertHtmlToPlainText(description); + description = HtmlUtils.StripTags(HttpUtility.HtmlDecode(description)); + } + + expando.FullDescription = description; + } + catch { } + } + + private void PrepareProductPrice(ExportProfileTaskContext ctx, dynamic expando, Product product) + { + decimal price = decimal.Zero; + + // price type + if (ctx.Projection.PriceType.HasValue) + { + var type = ctx.Projection.PriceType ?? PriceDisplayType.PreSelectedPrice; + + if (type == PriceDisplayType.LowestPrice) + { + bool displayFromMessage; + price = _priceCalculationService.GetLowestPrice(product, null, out displayFromMessage); + } + else if (type == PriceDisplayType.PreSelectedPrice) + { + price = _priceCalculationService.GetPreselectedPrice(product, null); + } + else if (type == PriceDisplayType.PriceWithoutDiscountsAndAttributes) + { + price = _priceCalculationService.GetFinalPrice(product, null, ctx.ProjectionCustomer, decimal.Zero, false, 1, null, null); + } + } + else + { + price = expando.Price; + } + + // convert net to gross + if (ctx.Projection.ConvertNetToGrossPrices) + { + decimal taxRate; + price = _taxService.GetProductPrice(product, price, true, ctx.ProjectionCustomer, out taxRate); + } + + if (price != decimal.Zero) + { + price = _currencyService.ConvertFromPrimaryStoreCurrency(price, ctx.ProjectionCurrency, ctx.Store); + } + + expando.Price = price; + } + + #endregion + + private void InitDependencies(TaskExecutionContext context) + { + _services = context.Resolve(); + _exportService = context.Resolve(); + _productService = context.Resolve(); + _pictureService = context.Resolve(); + _mediaSettings = context.Resolve(); + _priceCalculationService = context.Resolve(); + _taxService = context.Resolve(); + _currencyService = context.Resolve(); + _customerService = context.Resolve(); + } private IEnumerable GetProducts(ExportProfileTaskContext ctx, int pageIndex) { @@ -85,18 +247,20 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) { dynamic expando = product.ToExpando(ctx.Projection.LanguageId ?? 0, _pictureService, _mediaSettings, ctx.Store); + PrepareProductDescription(ctx, expando); + PrepareProductPrice(ctx, expando, product); + string brand = null; - return expando as ExpandoObject; - } + var manu = (product.ProductManufacturers as List).FirstOrDefault(); + if (manu != null) + brand = manu.Manufacturer.Name; + if (brand.IsEmpty()) + brand = ctx.Projection.Brand; - private void InitDependencies(TaskExecutionContext context) - { - _services = context.Resolve(); - _exportService = context.Resolve(); - _productService = context.Resolve(); - _pictureService = context.Resolve(); - _mediaSettings = context.Resolve(); + expando._Brand = brand; + + return expando as ExpandoObject; } private void Cleanup(ExportProfileTaskContext ctx) @@ -205,6 +369,16 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) ctx.Log = logger; ctx.Export.Log = logger; + if (ctx.Projection.CurrencyId.HasValue) + ctx.ProjectionCurrency = _currencyService.GetCurrencyById(ctx.Projection.CurrencyId.Value); + else + ctx.ProjectionCurrency = _services.WorkContext.WorkingCurrency; + + if (ctx.Projection.CustomerId.HasValue) + ctx.ProjectionCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); + else + ctx.ProjectionCustomer = _services.WorkContext.CurrentCustomer; + // TODO: log number of flown out records if (ctx.Profile.PerStore) @@ -294,7 +468,10 @@ public ExportProfileTaskContext(ExportProfile profile, Provider public ExportProfile Profile { get; private set; } public Provider Provider { get; private set; } public ExportFilter Filter { get; private set; } + public ExportProjection Projection { get; private set; } + public Currency ProjectionCurrency { get; set; } + public Customer ProjectionCustomer { get; set; } public CancellationToken Cancellation { get; private set; } public TraceLogger Log { get; set; } diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 0a70f46c54..985aa13eaf 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -164,6 +164,7 @@ + diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index b003f82cef..f0734e7618 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -74,6 +74,7 @@ using SmartStore.Web.Framework.WebApi; using SmartStore.Web.Framework.WebApi.Configuration; using Module = Autofac.Module; +using SmartStore.Core.Domain.DataExchange; namespace SmartStore.Web.Framework { @@ -770,6 +771,7 @@ protected override void Load(ContainerBuilder builder) var settingPattern = (pluginDescriptor != null ? "Plugins" : "Providers") + ".{0}.{1}"; // e.g. Plugins.MySystemName.DisplayOrder var isConfigurable = typeof(IConfigurable).IsAssignableFrom(type); var isEditable = typeof(IUserEditable).IsAssignableFrom(type); + var exportProjectionFields = GetExportProjectionFields(type); var registration = builder.RegisterType(type).Named(systemName).InstancePerRequest().PropertiesAutowired(PropertyWiringOptions.None); registration.WithMetadata(m => @@ -785,6 +787,7 @@ protected override void Load(ContainerBuilder builder) m.For(em => em.DependentWidgets, dependentWidgets); m.For(em => em.IsConfigurable, isConfigurable); m.For(em => em.IsEditable, isEditable); + m.For(em => em.ExportProjectionFields, exportProjectionFields); }); // register specific provider type @@ -851,6 +854,18 @@ private int GetDisplayOrder(Type type, PluginDescriptor descriptor) return 0; } + private ExportProjectionFieldType[] GetExportProjectionFields(Type type) + { + var attr = type.GetAttribute(false); + + if (attr != null && attr.Types != null) + { + return attr.Types; + } + + return new ExportProjectionFieldType[0]; + } + private Tuple GetFriendlyName(Type type, PluginDescriptor descriptor) { string name = null; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 0655ae1d6f..9557a168be 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -133,6 +133,8 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile if (provider != null) { + model.Provider.ProjectionFields = provider.Metadata.ExportProjectionFields; + try { string partialName; @@ -151,22 +153,42 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile } // projection - model.Projection = new ExportProjectionModel + Action initProjectionBase = x => { - LanguageId = projection.LanguageId, - CurrencyId = projection.CurrencyId + x.AvailableLanguages = allLanguages + .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) + .ToList(); + + x.AvailableCurrencies = allCurrencies + .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) + .ToList(); }; - model.Projection.AvailableLanguages = allLanguages - .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) - .ToList(); + if (model.Provider.EntityType == ExportEntityType.Product) + { + model.ProductProjection = new ExportProductProjectionModel + { + LanguageId = projection.LanguageId, + CurrencyId = projection.CurrencyId, + CustomerId = projection.CustomerId, + DescriptionMerging = projection.DescriptionMerging, + DescriptionToPlainText = projection.DescriptionToPlainText, + AppendDescriptionText = projection.AppendDescriptionText, + RemoveCriticalCharacters = projection.RemoveCriticalCharacters, + CriticalCharacters = projection.CriticalCharacters, + PriceType = projection.PriceType, + ConvertNetToGrossPrices = projection.ConvertNetToGrossPrices, + Brand = projection.Brand + }; - model.Projection.AvailableCurrencies = allCurrencies - .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) - .ToList(); + model.ProductProjection.AvailableDescriptionMergings = ExportDescriptionMergingType.Description.ToSelectList(false); + model.ProductProjection.AvailablePriceTypes = PriceDisplayType.LowestPrice.ToSelectList(false); + + initProjectionBase(model.ProductProjection); + } // filtering - Action initModelBase = x => + Action initFilterBase = x => { x.StoreId = filter.StoreId; x.CreatedFrom = filter.CreatedFrom; @@ -214,7 +236,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) .ToList(); - initModelBase(model.ProductFilter); + initFilterBase(model.ProductFilter); } else if (model.Provider.EntityType == ExportEntityType.Order) { @@ -237,7 +259,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) .ToList(); - initModelBase(model.OrderFilter); + initFilterBase(model.OrderFilter); } } @@ -453,17 +475,36 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) profile.Name = provider.Metadata.SystemName; // projection - var projection = new ExportProjection + ExportProjection projection = null; + Action getProjectionBase = x => { - LanguageId = model.Projection.LanguageId, - CurrencyId = model.Projection.CurrencyId + projection.LanguageId = x.LanguageId; + projection.CurrencyId = x.CurrencyId; + projection.CustomerId = x.CustomerId; }; + if (model.ProductProjection != null && provider.Value.EntityType == ExportEntityType.Product) + { + projection = new ExportProjection + { + DescriptionMerging = model.ProductProjection.DescriptionMerging, + DescriptionToPlainText = model.ProductProjection.DescriptionToPlainText, + AppendDescriptionText = model.ProductProjection.AppendDescriptionText, + RemoveCriticalCharacters = model.ProductProjection.RemoveCriticalCharacters, + CriticalCharacters = model.ProductProjection.CriticalCharacters, + PriceType = model.ProductProjection.PriceType, + ConvertNetToGrossPrices = model.ProductProjection.ConvertNetToGrossPrices, + Brand = model.ProductProjection.Brand + }; + + getProjectionBase(model.ProductProjection); + } + profile.Projection = XmlHelper.Serialize(projection); // filtering ExportFilter filter = null; - Action getFromModelBase = x => + Action getFilterBase = x => { filter.StoreId = x.StoreId ?? 0; filter.CreatedFrom = x.CreatedFrom; @@ -488,7 +529,7 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) ProductType = model.ProductFilter.ProductType }; - getFromModelBase(model.ProductFilter); + getFilterBase(model.ProductFilter); } else if (model.OrderFilter != null && provider.Value.EntityType == ExportEntityType.Order) { @@ -500,7 +541,7 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) CustomerRoleIds = model.OrderFilter.CustomerRoleIds }; - getFromModelBase(model.OrderFilter); + getFilterBase(model.OrderFilter); } profile.Filtering = XmlHelper.Serialize(filter); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index b27e1e5962..5d98b607bc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -55,7 +55,7 @@ public partial class ExportProfileModel : EntityModelBase public ExportProductFilterModel ProductFilter { get; set; } public ExportOrderFilterModel OrderFilter { get; set; } - public ExportProjectionModel Projection { get; set; } + public ExportProductProjectionModel ProductProjection { get; set; } public List Deployments { get; set; } @@ -65,6 +65,8 @@ public class ProviderModel public Type ConfigDataType { get; set; } public object ConfigData { get; set; } + public ExportProjectionFieldType[] ProjectionFields { get; set; } + [SmartResourceDisplayName("Common.Image")] public string ThumbnailUrl { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs index d95ea87ec8..c3eebd5b45 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs @@ -1,10 +1,12 @@ using System.Collections.Generic; using System.Web.Mvc; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.DataExchange; using SmartStore.Web.Framework; namespace SmartStore.Admin.Models.DataExchange { - public class ExportProjectionModel + public abstract class ExportProjectionModelBase { [SmartResourceDisplayName("Admin.Configuration.Export.Projection.LanguageId")] public int? LanguageId { get; set; } @@ -13,5 +15,39 @@ public class ExportProjectionModel [SmartResourceDisplayName("Admin.Configuration.Export.Projection.CurrencyId")] public int? CurrencyId { get; set; } public List AvailableCurrencies { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.CustomerId")] + public int? CustomerId { get; set; } + } + + + public class ExportProductProjectionModel : ExportProjectionModelBase + { + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.DescriptionMerging")] + public ExportDescriptionMergingType? DescriptionMerging { get; set; } + public SelectList AvailableDescriptionMergings { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.DescriptionToPlainText")] + public bool DescriptionToPlainText { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.AppendDescriptionText")] + [AllowHtml] + public string AppendDescriptionText { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.RemoveCriticalCharacters")] + public bool RemoveCriticalCharacters { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.CriticalCharacters")] + public string CriticalCharacters { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.PriceType")] + public PriceDisplayType? PriceType { get; set; } + public SelectList AvailablePriceTypes { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.ConvertNetToGrossPrices")] + public bool ConvertNetToGrossPrices { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.Brand")] + public string Brand { get; set; } } } \ 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 5df7d83edd..9294582bec 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -512,7 +512,9 @@ - + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 2b014776d5..59e0b1af84 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -191,255 +191,12 @@ @helper TabFilter() { - - @if (Model.ProductFilter != null) - { - if (Model.StoreCount > 1) - { - - - - - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - } - else if (Model.OrderFilter != null) - { - if (Model.StoreCount > 1) - { - - - - - } - - - - - - - - - - - - - - - - - - - - - - - - - } -
- @Html.SmartLabelFor(x => x.ProductFilter.StoreId) - - @Html.DropDownListFor(x => x.ProductFilter.StoreId, Model.ProductFilter.AvailableStores, Model.AllString) - @Html.ValidationMessageFor(x => x.ProductFilter.StoreId) -
- @Html.SmartLabelFor(x => x.ProductFilter.CreatedFrom) - - @Html.EditorFor(x => x.ProductFilter.CreatedFrom) - @Html.ValidationMessageFor(x => x.ProductFilter.CreatedFrom) -
- @Html.SmartLabelFor(x => x.ProductFilter.CreatedTo) - - @Html.EditorFor(x => x.ProductFilter.CreatedTo) - @Html.ValidationMessageFor(x => x.ProductFilter.CreatedTo) -
- @Html.SmartLabelFor(x => x.ProductFilter.ProductType) - - @Html.DropDownListFor(x => x.ProductFilter.ProductType, Model.ProductFilter.AvailableProductTypes, Model.AllString) -
- @Html.SmartLabelFor(x => x.ProductFilter.IsPublished) - - @Html.EditorFor(x => x.ProductFilter.IsPublished) - @Html.ValidationMessageFor(x => x.ProductFilter.IsPublished) -
- @Html.SmartLabelFor(x => x.ProductFilter.PriceMinimum) - - @Html.EditorFor(x => x.ProductFilter.PriceMinimum) - @Html.ValidationMessageFor(x => x.ProductFilter.PriceMinimum) -
- @Html.SmartLabelFor(x => x.ProductFilter.PriceMaximum) - - @Html.EditorFor(x => x.ProductFilter.PriceMaximum) - @Html.ValidationMessageFor(x => x.ProductFilter.PriceMaximum) -
- @Html.SmartLabelFor(x => x.ProductFilter.AvailabilityMinimum) - - @Html.EditorFor(x => x.ProductFilter.AvailabilityMinimum) - @Html.ValidationMessageFor(x => x.ProductFilter.AvailabilityMinimum) -
- @Html.SmartLabelFor(x => x.ProductFilter.AvailabilityMaximum) - - @Html.EditorFor(x => x.ProductFilter.AvailabilityMaximum) - @Html.ValidationMessageFor(x => x.ProductFilter.AvailabilityMaximum) -
- @Html.SmartLabelFor(x => x.ProductFilter.WithoutCategories) - - @Html.EditorFor(x => x.ProductFilter.WithoutCategories) - @Html.ValidationMessageFor(x => x.ProductFilter.WithoutCategories) -
- @Html.SmartLabelFor(x => x.ProductFilter.CategoryIds) - - @Html.ListBoxFor(x => x.ProductFilter.CategoryIds, - new MultiSelectList(Model.ProductFilter.AvailableCategories, "Value", "Text"), - new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(x => x.ProductFilter.CategoryIds) -
- @Html.SmartLabelFor(x => x.ProductFilter.WithoutManufacturers) - - @Html.EditorFor(x => x.ProductFilter.WithoutManufacturers) - @Html.ValidationMessageFor(x => x.ProductFilter.WithoutManufacturers) -
- @Html.SmartLabelFor(x => x.ProductFilter.ManufacturerIds) - - @Html.ListBoxFor(x => x.ProductFilter.ManufacturerIds, - new MultiSelectList(Model.ProductFilter.AvailableManufacturers, "Value", "Text"), - new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(x => x.ProductFilter.ManufacturerIds) -
- @Html.SmartLabelFor(x => x.ProductFilter.ProductTagIds) - - @Html.ListBoxFor(x => x.ProductFilter.ProductTagIds, - new MultiSelectList(Model.ProductFilter.AvailableProductTags, "Value", "Text"), - new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(x => x.ProductFilter.ProductTagIds) -
- @Html.SmartLabelFor(x => x.ProductFilter.FeaturedProducts) - - @Html.EditorFor(x => x.ProductFilter.FeaturedProducts) - @Html.ValidationMessageFor(x => x.ProductFilter.FeaturedProducts) -
- @Html.SmartLabelFor(x => x.OrderFilter.StoreId) - - @Html.DropDownListFor(x => x.OrderFilter.StoreId, Model.OrderFilter.AvailableStores, Model.AllString) - @Html.ValidationMessageFor(x => x.OrderFilter.StoreId) -
- @Html.SmartLabelFor(x => x.OrderFilter.CreatedFrom) - - @Html.EditorFor(x => x.OrderFilter.CreatedFrom) - @Html.ValidationMessageFor(x => x.OrderFilter.CreatedFrom) -
- @Html.SmartLabelFor(x => x.OrderFilter.CreatedTo) - - @Html.EditorFor(x => x.OrderFilter.CreatedTo) - @Html.ValidationMessageFor(x => x.OrderFilter.CreatedTo) -
- @Html.SmartLabelFor(x => x.OrderFilter.OrderStatus) - - @Html.ListBoxFor(x => x.OrderFilter.OrderStatus, - new MultiSelectList(Model.OrderFilter.AvailableOrderStates, "Value", "Text"), - new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(x => x.OrderFilter.OrderStatus) -
- @Html.SmartLabelFor(x => x.OrderFilter.PaymentStatus) - - @Html.ListBoxFor(x => x.OrderFilter.PaymentStatus, - new MultiSelectList(Model.OrderFilter.AvailablePaymentStates, "Value", "Text"), - new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(x => x.OrderFilter.PaymentStatus) -
- @Html.SmartLabelFor(x => x.OrderFilter.ShippingStatus) - - @Html.ListBoxFor(x => x.OrderFilter.ShippingStatus, - new MultiSelectList(Model.OrderFilter.AvailableShippingStates, "Value", "Text"), - new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(x => x.OrderFilter.ShippingStatus) -
- @Html.SmartLabelFor(x => x.OrderFilter.CustomerRoleIds) - - @Html.ListBoxFor(x => x.OrderFilter.CustomerRoleIds, - new MultiSelectList(Model.OrderFilter.AvailableCustomerRoles, "Value", "Text"), - new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(x => x.OrderFilter.CustomerRoleIds) -
+ @Html.Partial("_CreateOrUpdate.Filter", Model) } @helper TabProjection() { - - - - - - - - - -
- @Html.SmartLabelFor(x => x.Projection.LanguageId) - - @Html.DropDownListFor(x => x.Projection.LanguageId, Model.Projection.AvailableLanguages, Model.UnspecifiedString) - @Html.ValidationMessageFor(x => x.Projection.LanguageId) -
- @Html.SmartLabelFor(x => x.Projection.CurrencyId) - - @Html.DropDownListFor(x => x.Projection.CurrencyId, Model.Projection.AvailableCurrencies, Model.UnspecifiedString) - @Html.ValidationMessageFor(x => x.Projection.CurrencyId) -
+ @Html.Partial("_CreateOrUpdate.Projection", Model) } @helper TabConfiguration() @@ -543,14 +300,19 @@ jQuery(document).ready(function () { // show/hide product category filter - $('select[name="ProductFilter.WithoutCategories"]').change(function () { + $('#@(Html.FieldIdFor(x => x.ProductFilter.WithoutCategories))').change(function () { $('#FilterCategoryIdsContainer').toggle(_.isEmpty($(this).val())); }).trigger('change'); // show/hide product manufacturer filter - $('select[name="ProductFilter.WithoutManufacturers"]').change(function () { + $('#@(Html.FieldIdFor(x => x.ProductFilter.WithoutManufacturers))').change(function () { $('#FilterManufacturerIdsContainer').toggle(_.isEmpty($(this).val())); }).trigger('change'); + // show/hide critical characters + $('#@(Html.FieldIdFor(x => x.ProductProjection.RemoveCriticalCharacters))').change(function () { + $('#CriticalCharactersContainer').toggle($(this).is(':checked')); + }).trigger('change'); + }); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/EditDeployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/EditDeployment.cshtml index 174cc12041..07d42f416f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/EditDeployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/EditDeployment.cshtml @@ -21,7 +21,7 @@
- @Html.Partial("_CreateOrUpdateDeployment", Model) + @Html.Partial("_CreateOrUpdate.Deployment", Model) } @Html.DeleteConfirmation("DeleteDeployment", "export-deployment-delete") \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdateDeployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml similarity index 100% rename from src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdateDeployment.cshtml rename to src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml new file mode 100644 index 0000000000..f3e7c74565 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml @@ -0,0 +1,229 @@ +@model ExportProfileModel +@using SmartStore.Core.Domain.DataExchange; +@using SmartStore.Admin.Models.DataExchange; + + + @if (Model.ProductFilter != null) + { + if (Model.StoreCount > 1) + { + + + + + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + } + else if (Model.OrderFilter != null) + { + if (Model.StoreCount > 1) + { + + + + + } + + + + + + + + + + + + + + + + + + + + + + + + + } +
+ @Html.SmartLabelFor(x => x.ProductFilter.StoreId) + + @Html.DropDownListFor(x => x.ProductFilter.StoreId, Model.ProductFilter.AvailableStores, Model.AllString) + @Html.ValidationMessageFor(x => x.ProductFilter.StoreId) +
+ @Html.SmartLabelFor(x => x.ProductFilter.CreatedFrom) + + @Html.EditorFor(x => x.ProductFilter.CreatedFrom) + @Html.ValidationMessageFor(x => x.ProductFilter.CreatedFrom) +
+ @Html.SmartLabelFor(x => x.ProductFilter.CreatedTo) + + @Html.EditorFor(x => x.ProductFilter.CreatedTo) + @Html.ValidationMessageFor(x => x.ProductFilter.CreatedTo) +
+ @Html.SmartLabelFor(x => x.ProductFilter.ProductType) + + @Html.DropDownListFor(x => x.ProductFilter.ProductType, Model.ProductFilter.AvailableProductTypes, Model.AllString) +
+ @Html.SmartLabelFor(x => x.ProductFilter.IsPublished) + + @Html.EditorFor(x => x.ProductFilter.IsPublished) + @Html.ValidationMessageFor(x => x.ProductFilter.IsPublished) +
+ @Html.SmartLabelFor(x => x.ProductFilter.PriceMinimum) + + @Html.EditorFor(x => x.ProductFilter.PriceMinimum) + @Html.ValidationMessageFor(x => x.ProductFilter.PriceMinimum) +
+ @Html.SmartLabelFor(x => x.ProductFilter.PriceMaximum) + + @Html.EditorFor(x => x.ProductFilter.PriceMaximum) + @Html.ValidationMessageFor(x => x.ProductFilter.PriceMaximum) +
+ @Html.SmartLabelFor(x => x.ProductFilter.AvailabilityMinimum) + + @Html.EditorFor(x => x.ProductFilter.AvailabilityMinimum) + @Html.ValidationMessageFor(x => x.ProductFilter.AvailabilityMinimum) +
+ @Html.SmartLabelFor(x => x.ProductFilter.AvailabilityMaximum) + + @Html.EditorFor(x => x.ProductFilter.AvailabilityMaximum) + @Html.ValidationMessageFor(x => x.ProductFilter.AvailabilityMaximum) +
+ @Html.SmartLabelFor(x => x.ProductFilter.WithoutCategories) + + @Html.EditorFor(x => x.ProductFilter.WithoutCategories) + @Html.ValidationMessageFor(x => x.ProductFilter.WithoutCategories) +
+ @Html.SmartLabelFor(x => x.ProductFilter.CategoryIds) + + @Html.ListBoxFor(x => x.ProductFilter.CategoryIds, + new MultiSelectList(Model.ProductFilter.AvailableCategories, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(x => x.ProductFilter.CategoryIds) +
+ @Html.SmartLabelFor(x => x.ProductFilter.WithoutManufacturers) + + @Html.EditorFor(x => x.ProductFilter.WithoutManufacturers) + @Html.ValidationMessageFor(x => x.ProductFilter.WithoutManufacturers) +
+ @Html.SmartLabelFor(x => x.ProductFilter.ManufacturerIds) + + @Html.ListBoxFor(x => x.ProductFilter.ManufacturerIds, + new MultiSelectList(Model.ProductFilter.AvailableManufacturers, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(x => x.ProductFilter.ManufacturerIds) +
+ @Html.SmartLabelFor(x => x.ProductFilter.ProductTagIds) + + @Html.ListBoxFor(x => x.ProductFilter.ProductTagIds, + new MultiSelectList(Model.ProductFilter.AvailableProductTags, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(x => x.ProductFilter.ProductTagIds) +
+ @Html.SmartLabelFor(x => x.ProductFilter.FeaturedProducts) + + @Html.EditorFor(x => x.ProductFilter.FeaturedProducts) + @Html.ValidationMessageFor(x => x.ProductFilter.FeaturedProducts) +
+ @Html.SmartLabelFor(x => x.OrderFilter.StoreId) + + @Html.DropDownListFor(x => x.OrderFilter.StoreId, Model.OrderFilter.AvailableStores, Model.AllString) + @Html.ValidationMessageFor(x => x.OrderFilter.StoreId) +
+ @Html.SmartLabelFor(x => x.OrderFilter.CreatedFrom) + + @Html.EditorFor(x => x.OrderFilter.CreatedFrom) + @Html.ValidationMessageFor(x => x.OrderFilter.CreatedFrom) +
+ @Html.SmartLabelFor(x => x.OrderFilter.CreatedTo) + + @Html.EditorFor(x => x.OrderFilter.CreatedTo) + @Html.ValidationMessageFor(x => x.OrderFilter.CreatedTo) +
+ @Html.SmartLabelFor(x => x.OrderFilter.OrderStatus) + + @Html.ListBoxFor(x => x.OrderFilter.OrderStatus, + new MultiSelectList(Model.OrderFilter.AvailableOrderStates, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(x => x.OrderFilter.OrderStatus) +
+ @Html.SmartLabelFor(x => x.OrderFilter.PaymentStatus) + + @Html.ListBoxFor(x => x.OrderFilter.PaymentStatus, + new MultiSelectList(Model.OrderFilter.AvailablePaymentStates, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(x => x.OrderFilter.PaymentStatus) +
+ @Html.SmartLabelFor(x => x.OrderFilter.ShippingStatus) + + @Html.ListBoxFor(x => x.OrderFilter.ShippingStatus, + new MultiSelectList(Model.OrderFilter.AvailableShippingStates, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(x => x.OrderFilter.ShippingStatus) +
+ @Html.SmartLabelFor(x => x.OrderFilter.CustomerRoleIds) + + @Html.ListBoxFor(x => x.OrderFilter.CustomerRoleIds, + new MultiSelectList(Model.OrderFilter.AvailableCustomerRoles, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(x => x.OrderFilter.CustomerRoleIds) +
diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml new file mode 100644 index 0000000000..3754e6c1d4 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml @@ -0,0 +1,114 @@ +@model ExportProfileModel +@using SmartStore.Core.Domain.DataExchange; +@using SmartStore.Admin.Models.DataExchange; + + + @if (Model.ProductProjection != null) + { + + + + + + + + + + + + + + + + + + if (Model.Provider.ProjectionFields.Contains(ExportProjectionFieldType.Brand)) + { + + + + + } + + if (Model.Provider.ProjectionFields.Contains(ExportProjectionFieldType.Description)) + { + + + + + + + + + + + + + + + + + + + + + + + + } + } +
+ @Html.SmartLabelFor(x => x.ProductProjection.LanguageId) + + @Html.DropDownListFor(x => x.ProductProjection.LanguageId, Model.ProductProjection.AvailableLanguages, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.ProductProjection.LanguageId) +
+ @Html.SmartLabelFor(x => x.ProductProjection.CurrencyId) + + @Html.DropDownListFor(x => x.ProductProjection.CurrencyId, Model.ProductProjection.AvailableCurrencies, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.ProductProjection.CurrencyId) +
+ @Html.SmartLabelFor(x => x.ProductProjection.CustomerId) + + @Html.EditorFor(x => x.ProductProjection.CustomerId) + @Html.ValidationMessageFor(x => x.ProductProjection.CustomerId) +
+ @Html.SmartLabelFor(x => x.ProductProjection.PriceType) + + @Html.DropDownListFor(x => x.ProductProjection.PriceType, Model.ProductProjection.AvailablePriceTypes, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.ProductProjection.PriceType) +
+ @Html.SmartLabelFor(x => x.ProductProjection.Brand) + + @Html.EditorFor(x => x.ProductProjection.Brand) + @Html.ValidationMessageFor(x => x.ProductProjection.Brand) +
+
+
@T("Common.DetailDescription")
+
+
+ @Html.SmartLabelFor(x => x.ProductProjection.DescriptionMerging) + + @Html.DropDownListFor(x => x.ProductProjection.DescriptionMerging, Model.ProductProjection.AvailableDescriptionMergings, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.ProductProjection.DescriptionMerging) +
+ @Html.SmartLabelFor(x => x.ProductProjection.AppendDescriptionText) + + @Html.TextBoxFor(x => x.ProductProjection.AppendDescriptionText, new { @class = "input-xlarge" }) + @Html.ValidationMessageFor(x => x.ProductProjection.AppendDescriptionText) +
+ @Html.SmartLabelFor(x => x.ProductProjection.RemoveCriticalCharacters) + + @Html.EditorFor(x => x.ProductProjection.RemoveCriticalCharacters) + @Html.ValidationMessageFor(x => x.ProductProjection.RemoveCriticalCharacters) +
+ @Html.SmartLabelFor(x => x.ProductProjection.CriticalCharacters) + + @Html.EditorFor(x => x.ProductProjection.CriticalCharacters) + @Html.ValidationMessageFor(x => x.ProductProjection.CriticalCharacters) +
+ @Html.SmartLabelFor(x => x.ProductProjection.DescriptionToPlainText) + + @Html.EditorFor(x => x.ProductProjection.DescriptionToPlainText) + @Html.ValidationMessageFor(x => x.ProductProjection.DescriptionToPlainText) +
\ No newline at end of file From 8a970ecb8b3246a6cfe1ca1b011ff94dbe2d771e Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 19 Aug 2015 16:56:58 +0200 Subject: [PATCH 013/874] More export projection --- .../Domain/DataExchange/ExportCore.cs | 11 +- .../Domain/DataExchange/ExportFilter.cs | 17 +- .../Domain/DataExchange/ExportProjection.cs | 12 +- .../Plugins/Providers/ProviderMetadata.cs | 4 +- .../201508091512101_ExportFramework.cs | 66 ++++++- .../DataExchange/ExportAttributes.cs | 6 +- .../DataExchange/ExportExecuteContext.cs | 4 + .../DataExchange/ExportExtensions.cs | 11 +- .../DataExchange/ExportProfileTask.cs | 165 +++++++++++++++--- .../DependencyRegistrar.cs | 10 +- .../Controllers/ExportController.cs | 30 +++- .../Models/DataExchange/ExportFilterModel.cs | 8 +- .../Models/DataExchange/ExportProfileModel.cs | 2 +- .../DataExchange/ExportProjectionModel.cs | 18 +- .../Administration/Views/Export/Edit.cshtml | 5 + .../Export/_CreateOrUpdate.Filter.cshtml | 22 +-- .../Export/_CreateOrUpdate.Projection.cshtml | 74 +++++++- 17 files changed, 384 insertions(+), 81 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs index d7da4e002a..325b8dac0b 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs @@ -19,7 +19,7 @@ public enum ExportDeploymentType Ftp } - public enum ExportDescriptionMergingType + public enum ExportDescriptionMerging { None = 0, ShortDescriptionOrNameIfEmpty, @@ -31,10 +31,15 @@ public enum ExportDescriptionMergingType ManufacturerAndNameAndDescription } - public enum ExportProjectionFieldType + public enum ExportProjectionSupport { Description = 0, Brand, - PictureSize + MainPictureUrl, + UseOwnProductNo, + CategoryPath, + ShippingTime, + ShippingCosts, + Availability } } diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs index 4d7ecd7316..e078979f29 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs @@ -10,11 +10,6 @@ namespace SmartStore.Core.Domain.DataExchange [Serializable] public class ExportFilter { - public ExportFilter() - { - IsPublished = true; - } - /// /// Store identifier; 0 to load all records /// @@ -32,6 +27,16 @@ public ExportFilter() #region Product + /// + /// Minimum product identifier + /// + public int? IdMinimum { get; set; } + + /// + /// Maximum product identifier + /// + public int? IdMaximum { get; set; } + /// /// Minimum price /// @@ -55,7 +60,7 @@ public ExportFilter() /// /// A value indicating whether to load only published or non published products /// - public bool IsPublished { get; set; } + public bool? IsPublished { get; set; } /// /// Category identifiers diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs index 11df38e829..52131b3033 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs @@ -13,6 +13,8 @@ public ExportProjection() PriceType = PriceDisplayType.PreSelectedPrice; } + public int? StoreId { get; set; } + /// /// The language to be applied to the export /// @@ -25,7 +27,7 @@ public ExportProjection() public int? CustomerId { get; set; } - public ExportDescriptionMergingType? DescriptionMerging { get; set; } + public ExportDescriptionMerging? DescriptionMerging { get; set; } public bool DescriptionToPlainText { get; set; } @@ -40,5 +42,13 @@ public ExportProjection() public bool ConvertNetToGrossPrices { get; set; } public string Brand { get; set; } + + public int PictureSize { get; set; } + + public string ShippingTime { get; set; } + + public decimal? ShippingCosts { get; set; } + + public decimal? FreeShippingThreshold { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs b/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs index 7b0ff20fee..4423d1a073 100644 --- a/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs +++ b/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs @@ -69,9 +69,9 @@ public class ProviderMetadata public bool IsEditable { get; set; } /// - /// Gets or sets an array of field types that are supported by an export provider and thus should be editable on profile level + /// Gets or sets an array of values that reflects what export projection is supported by a provider /// - public ExportProjectionFieldType[] ExportProjectionFields { get; set; } + public ExportProjectionSupport[] ExportProjectionSupport { get; set; } /// /// Gets or sets an array of widget system names, which depend on the current provider diff --git a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs index 782f8a501b..22159be80c 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs @@ -87,6 +87,10 @@ public void Seed(SmartObjectContext context) }, new string[] { SystemCustomerRoleNames.Administrators }); } + private void RemoveStringResources(LocaleResourcesBuilder builder) + { + } + public void MigrateLocaleResources(LocaleResourcesBuilder builder) { builder.AddOrUpdate("Common.Enabled", "Enabled", "Aktiviert"); @@ -228,6 +232,18 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Filter by created date.", "Nach dem Erstellungsdatum filtern."); + builder.AddOrUpdate("Admin.Configuration.Export.Filter.IdMinimum", + "Product Id from", + "Produkt-ID von", + "Filter by product identifier.", + "Nach der Produkt-ID filtern."); + + builder.AddOrUpdate("Admin.Configuration.Export.Filter.IdMaximum", + "Product Id to", + "Produkt-ID bis", + "Filter by product identifier.", + "Nach der Produkt-ID filtern."); + builder.AddOrUpdate("Admin.Configuration.Export.Filter.PriceMinimum", "Price from", "Preis von", @@ -325,6 +341,12 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Nach Kundengruppen filtern."); + builder.AddOrUpdate("Admin.Configuration.Export.Projection.StoreId", + "Store", + "Shop", + "Specifies the store to be applied to the export.", + "Legt den auf den Export anzuwendenden Shop fest."); + builder.AddOrUpdate("Admin.Configuration.Export.Projection.LanguageId", "Language", "Sprache", @@ -391,26 +413,51 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Specifies the manufacturer or brand to be exported, if a product has no manufacturer assigned.", "Legt den zu exportierenden Hersteller bzw. die Marke fest, wenn fr ein Produkt kein Hersteller zugeordnet ist."); + builder.AddOrUpdate("Admin.Configuration.Export.Projection.PictureSize", + "Product picture size", + "Produktbildgre", + "Specifies the size (in pixel) of the product image.", + "Legt die Gre (in Pixel) des Produktbildes fest."); + + builder.AddOrUpdate("Admin.Configuration.Export.Projection.ShippingTime", + "Shipping time", + "Lieferzeit", + "Specifies the shipping time for products where it is unspecified.", + "Legt die Lieferzeit fr Produkte fest, wo diese nicht angegeben ist."); + + builder.AddOrUpdate("Admin.Configuration.Export.Projection.ShippingCosts", + "Shipping costs", + "Versandkosten", + "The shipping costs to be exported.", + "Die zu exportierenden Versandkosten."); + + builder.AddOrUpdate("Admin.Configuration.Export.Projection.FreeShippingThreshold", + "Free shipping threshold", + "Kostenloser Versand ab", + "Specifies the amount as from shipping is free.", + "Legt den Betrag fest, ab dem keine Versandkosten anfallen."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.FileSystem", "File system", "Dateisystem"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.Email", "Email", "E-Mail"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.Http", "HTTP", "HTTP"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.Ftp", "FTP", "FTP"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMergingType.None", + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.None", "None", "Keine"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMergingType.ShortDescriptionOrNameIfEmpty", + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.ShortDescriptionOrNameIfEmpty", "Short description or name if empty", "Kurzbeschreibung oder Name falls leer"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMergingType.ShortDescription", + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.ShortDescription", "Short description", "Kurzbeschreibung"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMergingType.Description", + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.Description", "Description", "Detailbeschreibung"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMergingType.NameAndShortDescription", + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.NameAndShortDescription", "Product name + short description", "Produktname + Kurzbeschreibung"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMergingType.NameAndDescription", + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.NameAndDescription", "Product name + long description", "Produktname + Detailbeschreibung"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMergingType.ManufacturerAndNameAndShortDescription", + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.ManufacturerAndNameAndShortDescription", "Manufacturer + Product name + short description", "Hersteller + Produktname + Kurzbeschreibung"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMergingType.ManufacturerAndNameAndDescription", + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.ManufacturerAndNameAndDescription", "Manufacturer + Product name + long description", "Hersteller + Produktname + Detailbeschreibung"); @@ -473,6 +520,9 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "E-Mail Konto", "Specifies the email account through which the data should be sent.", "Legt das E-Mail Konto fest, ber welches die Daten verschickt werden sollen."); + + + RemoveStringResources(builder); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportAttributes.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportAttributes.cs index 838db79e5a..7bbbee9265 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportAttributes.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportAttributes.cs @@ -4,13 +4,13 @@ namespace SmartStore.Services.DataExchange { [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public sealed class ExportProjectionFieldAttribute : Attribute + public sealed class ExportProjectionSupportAttribute : Attribute { - public ExportProjectionFieldAttribute(params ExportProjectionFieldType[] types) + public ExportProjectionSupportAttribute(params ExportProjectionSupport[] types) { Types = types; } - public ExportProjectionFieldType[] Types { get; set; } + public ExportProjectionSupport[] Types { get; set; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs index dab82ef291..6d4a4b1d8a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs @@ -22,6 +22,8 @@ public interface IExportExecuteContext string FileNamePattern { get; } + object ConfigurationData { get; } + Dictionary CustomProperties { get; set; } string GetFilePath(string fileNameSuffix = null); @@ -57,6 +59,8 @@ public bool IsCanceled public string FileNamePattern { get; internal set; } + public object ConfigurationData { get; internal set; } + public Dictionary CustomProperties { get; set; } public string GetFilePath(string fileNameSuffix = null) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs index 0cdb2e10c4..9c357d04ca 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs @@ -1,4 +1,6 @@ -using SmartStore.Core.Plugins; +using System.Linq; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Plugins; namespace SmartStore.Services.DataExchange { @@ -11,5 +13,12 @@ public static bool IsValid(this Provider provider) provider.Value.FileExtension.HasValue() ); } + + public static bool Supports(this Provider provider, ExportProjectionSupport type) + { + if (provider != null) + return provider.Metadata.ExportProjectionSupport.Contains(type); + return false; + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index d5a7fdf7ca..5a6503d8bf 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -23,9 +23,7 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Customers; using SmartStore.Services.Directory; -using SmartStore.Services.Localization; using SmartStore.Services.Media; -using SmartStore.Services.Seo; using SmartStore.Services.Tasks; using SmartStore.Services.Tax; using SmartStore.Utilities; @@ -47,6 +45,8 @@ public class ExportProfileTask : ITask private ITaxService _taxService; private ICurrencyService _currencyService; private ICustomerService _customerService; + private ICategoryService _categoryService; + private IPriceFormatter _priceFormatter; #region Utilities @@ -59,9 +59,9 @@ private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic exp // description merging if (ctx.Projection.DescriptionMerging.HasValue) { - var type = ctx.Projection.DescriptionMerging ?? ExportDescriptionMergingType.ShortDescriptionOrNameIfEmpty; + var type = ctx.Projection.DescriptionMerging ?? ExportDescriptionMerging.ShortDescriptionOrNameIfEmpty; - if (type == ExportDescriptionMergingType.ShortDescriptionOrNameIfEmpty) + if (type == ExportDescriptionMerging.ShortDescriptionOrNameIfEmpty) { description = expando.FullDescription; @@ -70,23 +70,23 @@ private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic exp if (description.IsEmpty()) description = expando.Name; } - else if (type == ExportDescriptionMergingType.ShortDescription) + else if (type == ExportDescriptionMerging.ShortDescription) { description = expando.ShortDescription; } - else if (type == ExportDescriptionMergingType.Description) + else if (type == ExportDescriptionMerging.Description) { description = expando.FullDescription; } - else if (type == ExportDescriptionMergingType.NameAndShortDescription) + else if (type == ExportDescriptionMerging.NameAndShortDescription) { description = ((string)expando.Name).Grow((string)expando.ShortDescription, " "); } - else if (type == ExportDescriptionMergingType.NameAndDescription) + else if (type == ExportDescriptionMerging.NameAndDescription) { description = ((string)expando.Name).Grow((string)expando.FullDescription, " "); } - else if (type == ExportDescriptionMergingType.ManufacturerAndNameAndShortDescription || type == ExportDescriptionMergingType.ManufacturerAndNameAndDescription) + else if (type == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription || type == ExportDescriptionMerging.ManufacturerAndNameAndDescription) { string name = (string)expando.Name; dynamic productManu = ((List)expando.ProductManufacturers).FirstOrDefault(); @@ -96,7 +96,7 @@ private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic exp dynamic manu = productManu.Manufacturer; description = ((string)manu.Name).Grow(name, " "); - if (type == ExportDescriptionMergingType.ManufacturerAndNameAndShortDescription) + if (type == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription) description = description.Grow((string)expando.ShortDescription, " "); else description = description.Grow((string)expando.FullDescription, " "); @@ -185,6 +185,19 @@ private void PrepareProductPrice(ExportProfileTaskContext ctx, dynamic expando, expando.Price = price; } + //public static decimal GetEstimatedShippingCosts(this Product product, decimal calculatedProductPrice, decimal defaultShippingCosts, decimal? freeShippingThreshold) + //{ + // if (product != null) + // { + // if (product.IsFreeShipping) + // return decimal.Zero; + + // if (freeShippingThreshold.HasValue && calculatedProductPrice >= freeShippingThreshold.Value) + // return decimal.Zero; + // } + // return defaultShippingCosts; + //} + #endregion private void InitDependencies(TaskExecutionContext context) @@ -198,6 +211,8 @@ private void InitDependencies(TaskExecutionContext context) _taxService = context.Resolve(); _currencyService = context.Resolve(); _customerService = context.Resolve(); + _categoryService = context.Resolve(); + _priceFormatter = context.Resolve(); } private IEnumerable GetProducts(ExportProfileTaskContext ctx, int pageIndex) @@ -211,10 +226,20 @@ private IEnumerable GetProducts(ExportProfileTaskContext ctx, int pageI OrderBy = ProductSortingEnum.CreatedOn, PageIndex = pageIndex, PageSize = _pageSize, - StoreId = (ctx.Profile.PerStore ? ctx.Store.Id : 0), - VisibleIndividuallyOnly = true + StoreId = (ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId), + VisibleIndividuallyOnly = true, + PriceMin = ctx.Filter.PriceMinimum, + PriceMax = ctx.Filter.PriceMaximum, + IsPublished = ctx.Filter.IsPublished, + WithoutCategories = ctx.Filter.WithoutCategories, + WithoutManufacturers = ctx.Filter.WithoutManufacturers, + FeaturedProducts = ctx.Filter.FeaturedProducts, + ProductType = ctx.Filter.ProductType }; + if (ctx.Filter.CategoryIds != null && ctx.Filter.CategoryIds.Length > 0) + searchContext.CategoryIds = ctx.Filter.CategoryIds.ToList(); + var products = _productService.SearchProducts(searchContext); foreach (var product in products) @@ -247,18 +272,81 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) { dynamic expando = product.ToExpando(ctx.Projection.LanguageId ?? 0, _pictureService, _mediaSettings, ctx.Store); - PrepareProductDescription(ctx, expando); PrepareProductPrice(ctx, expando, product); - string brand = null; + expando._BasePriceInfo = product.GetBasePriceInfo(_services.Localization, _priceFormatter, decimal.Zero, true); + + if (ctx.Provider.Supports(ExportProjectionSupport.Description)) + { + PrepareProductDescription(ctx, expando); + } + + if (ctx.Provider.Supports(ExportProjectionSupport.Brand)) + { + string brand = null; + + var manu = (product.ProductManufacturers as List).FirstOrDefault(); + if (manu != null) + brand = manu.Manufacturer.Name; + if (brand.IsEmpty()) + brand = ctx.Projection.Brand; + + expando._Brand = brand; + } + + if (ctx.Provider.Supports(ExportProjectionSupport.UseOwnProductNo) && product.ManufacturerPartNumber.IsEmpty()) + { + expando.ManufacturerPartNumber = product.Sku; + } + + if (ctx.Provider.Supports(ExportProjectionSupport.CategoryPath)) + { + if (ctx.CategoryPathes == null) + { + ctx.CategoryPathes = new Dictionary(); + } + + if (ctx.Categories == null) + { + var allCategories = _categoryService.GetAllCategories(showHidden: true, applyNavigationFilters: false); + ctx.Categories = allCategories.ToDictionary(x => x.Id); + } + + expando._CategoryPath = _categoryService.GetCategoryPath( + product, + null, + x => ctx.CategoryPathes.ContainsKey(x) ? ctx.CategoryPathes[x] : null, + (id, value) => ctx.CategoryPathes[id] = value, + x => ctx.Categories.ContainsKey(x) ? ctx.Categories[x] : _categoryService.GetCategoryById(x) + ); + } + + if (ctx.Provider.Supports(ExportProjectionSupport.MainPictureUrl)) + { + var picture = product.GetDefaultProductPicture(_pictureService); + + // always use HTTP when getting image URL + if (picture != null) + expando._MainPictureUrl = _pictureService.GetPictureUrl(picture, ctx.Projection.PictureSize, storeLocation: ctx.Store.Url); + else + expando._MainPictureUrl = _pictureService.GetDefaultPictureUrl(ctx.Projection.PictureSize, storeLocation: ctx.Store.Url); + } - var manu = (product.ProductManufacturers as List).FirstOrDefault(); - if (manu != null) - brand = manu.Manufacturer.Name; - if (brand.IsEmpty()) - brand = ctx.Projection.Brand; + if (ctx.Provider.Supports(ExportProjectionSupport.ShippingTime)) + { + dynamic deliveryTime = expando.DeliveryTime; + expando._ShippingTime = (deliveryTime == null ? ctx.Projection.ShippingTime : deliveryTime.Name); + } - expando._Brand = brand; + if (ctx.Provider.Supports(ExportProjectionSupport.ShippingCosts)) + { + expando._FreeShippingThreshold = ctx.Projection.FreeShippingThreshold; + + if (product.IsFreeShipping || (ctx.Projection.FreeShippingThreshold.HasValue && (decimal)expando.Price >= ctx.Projection.FreeShippingThreshold.Value)) + expando._ShippingCosts = decimal.Zero; + else + expando._ShippingCosts = ctx.Projection.ShippingCosts; + } return expando as ExpandoObject; } @@ -273,16 +361,15 @@ private void Cleanup(ExportProfileTaskContext ctx) // TODO: more deployment specific here } - private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) + private void ExportCoreInner(ExportProfileTaskContext ctx) { - ctx.Store = store; - ctx.Export.StoreId = store.Id; - ctx.Export.StoreUrl = store.Url; + ctx.Export.StoreId = ctx.Store.Id; + ctx.Export.StoreUrl = ctx.Store.Url; // be careful with too long file system paths ctx.Export.FileNamePattern = string.Concat( "{0}-", - ctx.Profile.PerStore ? SeoHelper.GetSeName(store.Name, true, false).ToValidFileName("").Truncate(20) : "all-stores", + ctx.Profile.PerStore ? SeoHelper.GetSeName(ctx.Store.Name, true, false).ToValidFileName("").Truncate(20) : "all-stores", "{1}", ctx.Provider.Value.FileExtension.ToLower().EnsureStartsWith(".") ); @@ -300,7 +387,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) logHead.AppendLine("Export provider:\t{0} ({1})".FormatInvariant(ctx.Provider == null ? "".NaIfEmpty() : ctx.Provider.Metadata.FriendlyName, ctx.Profile.ProviderSystemName)); - var storeInfo = (ctx.Profile.PerStore ? "{0} (Id {1})".FormatInvariant(store.Name, store.Id) : "all stores"); + var storeInfo = (ctx.Profile.PerStore ? "{0} (Id {1})".FormatInvariant(ctx.Store.Name, ctx.Store.Id) : "all stores"); logHead.Append("Store:\t\t\t\t" + storeInfo); ctx.Log.Information(logHead.ToString()); @@ -379,6 +466,16 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) else ctx.ProjectionCustomer = _services.WorkContext.CurrentCustomer; + if (ctx.Profile.ProviderConfigData.HasValue()) + { + string partialName; + Type dataType; + if (ctx.Provider.Value.RequiresConfiguration(out partialName, out dataType)) + { + ctx.Export.ConfigurationData = XmlHelper.Deserialize(ctx.Profile.ProviderConfigData, dataType); + } + } + // TODO: log number of flown out records if (ctx.Profile.PerStore) @@ -387,12 +484,19 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) foreach (var store in allStores.Where(x => ctx.Filter.StoreId == 0 || ctx.Filter.StoreId == x.Id)) { - ExportCoreInner(ctx, store); + ctx.Store = store; + + ExportCoreInner(ctx); } } else { - ExportCoreInner(ctx, _services.StoreContext.CurrentStore); + if (ctx.Projection.StoreId.HasValue) + ctx.Store = _services.StoreService.GetStoreById(ctx.Projection.StoreId.Value); + else + ctx.Store = _services.StoreContext.CurrentStore; + + ExportCoreInner(ctx); } } } @@ -410,6 +514,8 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) if (ctx.Segmenter != null) ctx.Segmenter.Dispose(); + ctx.Export.CustomProperties.Clear(); + Cleanup(ctx); } catch { } @@ -479,6 +585,9 @@ public ExportProfileTaskContext(ExportProfile profile, Provider public string Folder { get; private set; } + public Dictionary Categories; + public Dictionary CategoryPathes; + public ExportSegmenter Segmenter { get; set; } public ExportExecuteContext Export { get; set; } diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index f0734e7618..bf12ed92b4 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -771,7 +771,7 @@ protected override void Load(ContainerBuilder builder) var settingPattern = (pluginDescriptor != null ? "Plugins" : "Providers") + ".{0}.{1}"; // e.g. Plugins.MySystemName.DisplayOrder var isConfigurable = typeof(IConfigurable).IsAssignableFrom(type); var isEditable = typeof(IUserEditable).IsAssignableFrom(type); - var exportProjectionFields = GetExportProjectionFields(type); + var exportProjectionSupport = GetExportProjectionSupport(type); var registration = builder.RegisterType(type).Named(systemName).InstancePerRequest().PropertiesAutowired(PropertyWiringOptions.None); registration.WithMetadata(m => @@ -787,7 +787,7 @@ protected override void Load(ContainerBuilder builder) m.For(em => em.DependentWidgets, dependentWidgets); m.For(em => em.IsConfigurable, isConfigurable); m.For(em => em.IsEditable, isEditable); - m.For(em => em.ExportProjectionFields, exportProjectionFields); + m.For(em => em.ExportProjectionSupport, exportProjectionSupport); }); // register specific provider type @@ -854,16 +854,16 @@ private int GetDisplayOrder(Type type, PluginDescriptor descriptor) return 0; } - private ExportProjectionFieldType[] GetExportProjectionFields(Type type) + private ExportProjectionSupport[] GetExportProjectionSupport(Type type) { - var attr = type.GetAttribute(false); + var attr = type.GetAttribute(false); if (attr != null && attr.Types != null) { return attr.Types; } - return new ExportProjectionFieldType[0]; + return new ExportProjectionSupport[0]; } private Tuple GetFriendlyName(Type type, PluginDescriptor descriptor) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 9557a168be..199743c419 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -133,7 +133,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile if (provider != null) { - model.Provider.ProjectionFields = provider.Metadata.ExportProjectionFields; + model.Provider.ProjectionSupport = provider.Metadata.ExportProjectionSupport; try { @@ -155,6 +155,10 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile // projection Action initProjectionBase = x => { + x.AvailableStores = allStores + .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) + .ToList(); + x.AvailableLanguages = allLanguages .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) .ToList(); @@ -168,6 +172,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile { model.ProductProjection = new ExportProductProjectionModel { + StoreId = projection.StoreId, LanguageId = projection.LanguageId, CurrencyId = projection.CurrencyId, CustomerId = projection.CustomerId, @@ -178,10 +183,14 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile CriticalCharacters = projection.CriticalCharacters, PriceType = projection.PriceType, ConvertNetToGrossPrices = projection.ConvertNetToGrossPrices, - Brand = projection.Brand + Brand = projection.Brand, + PictureSize = projection.PictureSize, + ShippingTime = projection.ShippingTime, + ShippingCosts = projection.ShippingCosts, + FreeShippingThreshold = projection.FreeShippingThreshold }; - model.ProductProjection.AvailableDescriptionMergings = ExportDescriptionMergingType.Description.ToSelectList(false); + model.ProductProjection.AvailableDescriptionMergings = ExportDescriptionMerging.Description.ToSelectList(false); model.ProductProjection.AvailablePriceTypes = PriceDisplayType.LowestPrice.ToSelectList(false); initProjectionBase(model.ProductProjection); @@ -219,7 +228,9 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile WithoutManufacturers = filter.WithoutManufacturers, ProductTagIds = filter.ProductTagIds, FeaturedProducts = filter.FeaturedProducts, - ProductType = filter.ProductType + ProductType = filter.ProductType, + IdMinimum = filter.IdMinimum, + IdMaximum = filter.IdMaximum }; model.ProductFilter.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); @@ -478,6 +489,7 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) ExportProjection projection = null; Action getProjectionBase = x => { + projection.StoreId = x.StoreId; projection.LanguageId = x.LanguageId; projection.CurrencyId = x.CurrencyId; projection.CustomerId = x.CustomerId; @@ -494,7 +506,11 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) CriticalCharacters = model.ProductProjection.CriticalCharacters, PriceType = model.ProductProjection.PriceType, ConvertNetToGrossPrices = model.ProductProjection.ConvertNetToGrossPrices, - Brand = model.ProductProjection.Brand + Brand = model.ProductProjection.Brand, + PictureSize = model.ProductProjection.PictureSize, + ShippingTime = model.ProductProjection.ShippingTime, + ShippingCosts = model.ProductProjection.ShippingCosts, + FreeShippingThreshold = model.ProductProjection.FreeShippingThreshold }; getProjectionBase(model.ProductProjection); @@ -526,7 +542,9 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) WithoutManufacturers = model.ProductFilter.WithoutManufacturers, ProductTagIds = model.ProductFilter.ProductTagIds, FeaturedProducts = model.ProductFilter.FeaturedProducts, - ProductType = model.ProductFilter.ProductType + ProductType = model.ProductFilter.ProductType, + IdMinimum = model.ProductFilter.IdMinimum, + IdMaximum = model.ProductFilter.IdMaximum }; getFilterBase(model.ProductFilter); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs index 589dc68f9f..ecfc508539 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs @@ -26,12 +26,18 @@ public abstract class ExportFilterModelBase public class ExportProductFilterModel : ExportFilterModelBase { [SmartResourceDisplayName("Admin.Configuration.Export.Filter.IsPublished")] - public bool IsPublished { get; set; } + public bool? IsPublished { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ProductType")] public ProductType? ProductType { get; set; } public List AvailableProductTypes { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.IdMinimum")] + public int? IdMinimum { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.IdMaximum")] + public int? IdMaximum { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.PriceMinimum")] public decimal? PriceMinimum { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 5d98b607bc..52d92a18cb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -65,7 +65,7 @@ public class ProviderModel public Type ConfigDataType { get; set; } public object ConfigData { get; set; } - public ExportProjectionFieldType[] ProjectionFields { get; set; } + public ExportProjectionSupport[] ProjectionSupport { get; set; } [SmartResourceDisplayName("Common.Image")] public string ThumbnailUrl { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs index c3eebd5b45..9e61bc4741 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs @@ -8,6 +8,10 @@ namespace SmartStore.Admin.Models.DataExchange { public abstract class ExportProjectionModelBase { + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.StoreId")] + public int? StoreId { get; set; } + public List AvailableStores { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.LanguageId")] public int? LanguageId { get; set; } public List AvailableLanguages { get; set; } @@ -24,7 +28,7 @@ public abstract class ExportProjectionModelBase public class ExportProductProjectionModel : ExportProjectionModelBase { [SmartResourceDisplayName("Admin.Configuration.Export.Projection.DescriptionMerging")] - public ExportDescriptionMergingType? DescriptionMerging { get; set; } + public ExportDescriptionMerging? DescriptionMerging { get; set; } public SelectList AvailableDescriptionMergings { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Projection.DescriptionToPlainText")] @@ -49,5 +53,17 @@ public class ExportProductProjectionModel : ExportProjectionModelBase [SmartResourceDisplayName("Admin.Configuration.Export.Projection.Brand")] public string Brand { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.PictureSize")] + public int PictureSize { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.ShippingTime")] + public string ShippingTime { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.ShippingCosts")] + public decimal? ShippingCosts { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.FreeShippingThreshold")] + public decimal? FreeShippingThreshold { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 59e0b1af84..570a62d05c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -309,6 +309,11 @@ $('#FilterManufacturerIdsContainer').toggle(_.isEmpty($(this).val())); }).trigger('change'); + // show/hide store projection + $('#@(Html.FieldIdFor(x => x.PerStore))').change(function () { + $('#ProjectionStoreContainer').toggle(!$(this).is(':checked')); + }).trigger('change'); + // show/hide critical characters $('#@(Html.FieldIdFor(x => x.ProductProjection.RemoveCriticalCharacters))').change(function () { $('#CriticalCharactersContainer').toggle($(this).is(':checked')); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml index f3e7c74565..10439ddeb5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml @@ -19,37 +19,37 @@ } - @Html.SmartLabelFor(x => x.ProductFilter.CreatedFrom) + @Html.SmartLabelFor(x => x.ProductFilter.ProductType) - @Html.EditorFor(x => x.ProductFilter.CreatedFrom) - @Html.ValidationMessageFor(x => x.ProductFilter.CreatedFrom) + @Html.DropDownListFor(x => x.ProductFilter.ProductType, Model.ProductFilter.AvailableProductTypes, Model.AllString) - @Html.SmartLabelFor(x => x.ProductFilter.CreatedTo) + @Html.SmartLabelFor(x => x.ProductFilter.IsPublished) - @Html.EditorFor(x => x.ProductFilter.CreatedTo) - @Html.ValidationMessageFor(x => x.ProductFilter.CreatedTo) + @Html.EditorFor(x => x.ProductFilter.IsPublished) + @Html.ValidationMessageFor(x => x.ProductFilter.IsPublished) - @Html.SmartLabelFor(x => x.ProductFilter.ProductType) + @Html.SmartLabelFor(x => x.ProductFilter.IdMinimum) - @Html.DropDownListFor(x => x.ProductFilter.ProductType, Model.ProductFilter.AvailableProductTypes, Model.AllString) + @Html.EditorFor(x => x.ProductFilter.IdMinimum) + @Html.ValidationMessageFor(x => x.ProductFilter.IdMinimum) - @Html.SmartLabelFor(x => x.ProductFilter.IsPublished) + @Html.SmartLabelFor(x => x.ProductFilter.IdMaximum) - @Html.EditorFor(x => x.ProductFilter.IsPublished) - @Html.ValidationMessageFor(x => x.ProductFilter.IsPublished) + @Html.EditorFor(x => x.ProductFilter.IdMaximum) + @Html.ValidationMessageFor(x => x.ProductFilter.IdMaximum) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml index 3754e6c1d4..7e7215d60b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml @@ -5,6 +5,15 @@ @if (Model.ProductProjection != null) { + + + + - - if (Model.Provider.ProjectionFields.Contains(ExportProjectionFieldType.Brand)) + + + + + + if (Model.Provider.ProjectionSupport.Contains(ExportProjectionSupport.ShippingTime)) + { + + + + + } + + if (Model.Provider.ProjectionSupport.Contains(ExportProjectionSupport.ShippingCosts)) + { + + + + + + + + + } + + if (Model.Provider.ProjectionSupport.Contains(ExportProjectionSupport.MainPictureUrl)) + { + + + + + } + + if (Model.Provider.ProjectionSupport.Contains(ExportProjectionSupport.Brand)) { } - - if (Model.Provider.ProjectionFields.Contains(ExportProjectionFieldType.Description)) + + if (Model.Provider.ProjectionSupport.Contains(ExportProjectionSupport.Description)) { - + From 089c707bc230f0d601016b694379ba5f462a0242 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 21 Aug 2015 11:42:01 +0200 Subject: [PATCH 015/874] IDbContext.DetachAll() cannot be called during export, only after all work is done. It will detach everything even things you do not want to have detached. IScheduleTask.UpdateTask may not work in task executor final block. solution: get fresh task instance and update it. --- .../Domain/DataExchange/ExportFilter.cs | 4 +- .../DataExchange/ExpandoExtensions.cs | 93 ++++++++++--------- .../DataExchange/ExportProfileTask.cs | 79 ++++++++-------- .../Stores/StoreService.cs | 13 +-- .../SmartStore.Services/Tasks/TaskExecutor.cs | 40 ++++---- .../Models/DataExchange/ExportFilterModel.cs | 4 +- 6 files changed, 118 insertions(+), 115 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs index 7675d646dc..5d6ffe6242 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs @@ -75,7 +75,7 @@ public class ExportFilter /// /// Manufacturer identifier /// - public int ManufacturerId { get; set; } + public int? ManufacturerId { get; set; } /// /// A value indicating whether to load products without any manufacturer mapping @@ -85,7 +85,7 @@ public class ExportFilter /// /// Identifiers of product tag /// - public int ProductTagId { get; set; } + public int? ProductTagId { get; set; } /// /// A value indicating whether to load products that are marked as featured (relates only to categories and manufacturers) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs index eb3f94c593..4d8d3ecaf4 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs @@ -226,61 +226,64 @@ public static ExpandoObject ToExpando(this Product product, int languageId) expando.LowestAttributeCombinationPrice = product.LowestAttributeCombinationPrice; expando.IsEsd = product.IsEsd; + expando.DeliveryTime = product.DeliveryTimeId == 0 ? null : product.DeliveryTime.ToExpando(languageId); + expando.QuantityUnit = product.QuantityUnitId == 0 ? null : product.QuantityUnit.ToExpando(languageId); + return expando as ExpandoObject; } - public static ExpandoObject ToExpando(this Product product, int languageId, IPictureService pictureService, MediaSettings mediaSettings, Store store) - { - dynamic expando = product.ToExpando(languageId); + //public static ExpandoObject ToExpando(this Product product, int languageId, IPictureService pictureService, MediaSettings mediaSettings, Store store) + //{ + // dynamic expando = product.ToExpando(languageId); - expando.DeliveryTime = product.DeliveryTimeId == 0 ? null : product.DeliveryTime.ToExpando(languageId); - expando.QuantityUnit = product.QuantityUnitId == 0 ? null : product.QuantityUnit.ToExpando(languageId); + // expando.DeliveryTime = product.DeliveryTimeId == 0 ? null : product.DeliveryTime.ToExpando(languageId); + // expando.QuantityUnit = product.QuantityUnitId == 0 ? null : product.QuantityUnit.ToExpando(languageId); - // pictures - expando.ProductPictures = product.ProductPictures - .OrderBy(x => x.DisplayOrder) - .Select(x => - { - dynamic exp = new ExpandoObject(); - exp.Id = x.Id; - exp.DisplayOrder = x.DisplayOrder; - exp.Picture = x.Picture.ToExpando(pictureService, store, mediaSettings.ProductThumbPictureSize, mediaSettings.ProductDetailsPictureSize); + // // pictures + // expando.ProductPictures = product.ProductPictures + // .OrderBy(x => x.DisplayOrder) + // .Select(x => + // { + // dynamic exp = new ExpandoObject(); + // exp.Id = x.Id; + // exp.DisplayOrder = x.DisplayOrder; + // exp.Picture = x.Picture.ToExpando(pictureService, store, mediaSettings.ProductThumbPictureSize, mediaSettings.ProductDetailsPictureSize); - return exp as ExpandoObject; - }) - .ToList(); + // return exp as ExpandoObject; + // }) + // .ToList(); - // manufacturers - expando.ProductManufacturers = product.ProductManufacturers - .OrderBy(x => x.DisplayOrder) - .Select(x => - { - dynamic exp = new ExpandoObject(); - exp.Id = x.Id; - exp.DisplayOrder = x.DisplayOrder; - exp.IsFeaturedProduct = x.IsFeaturedProduct; - exp.Manufacturer = x.Manufacturer.ToExpando(languageId); + // // manufacturers + // expando.ProductManufacturers = product.ProductManufacturers + // .OrderBy(x => x.DisplayOrder) + // .Select(x => + // { + // dynamic exp = new ExpandoObject(); + // exp.Id = x.Id; + // exp.DisplayOrder = x.DisplayOrder; + // exp.IsFeaturedProduct = x.IsFeaturedProduct; + // exp.Manufacturer = x.Manufacturer.ToExpando(languageId); - return exp as ExpandoObject; - }) - .ToList(); + // return exp as ExpandoObject; + // }) + // .ToList(); - // categories - expando.ProductCategories = product.ProductCategories - .OrderBy(x => x.DisplayOrder) - .Select(x => - { - dynamic exp = new ExpandoObject(); - exp.Id = x.Id; - exp.DisplayOrder = x.DisplayOrder; - exp.IsFeaturedProduct = x.IsFeaturedProduct; - exp.Category = x.Category.ToExpando(languageId); + // // categories + // expando.ProductCategories = product.ProductCategories + // .OrderBy(x => x.DisplayOrder) + // .Select(x => + // { + // dynamic exp = new ExpandoObject(); + // exp.Id = x.Id; + // exp.DisplayOrder = x.DisplayOrder; + // exp.IsFeaturedProduct = x.IsFeaturedProduct; + // exp.Category = x.Category.ToExpando(languageId); - return exp as ExpandoObject; - }) - .ToList(); + // return exp as ExpandoObject; + // }) + // .ToList(); - return expando as ExpandoObject; - } + // return expando as ExpandoObject; + //} } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index c5ea0e5664..aa3a4e6e20 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -24,6 +24,7 @@ using SmartStore.Services.Customers; using SmartStore.Services.Directory; using SmartStore.Services.Helpers; +using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Tasks; using SmartStore.Services.Tax; @@ -187,19 +188,6 @@ private void PrepareProductPrice(ExportProfileTaskContext ctx, dynamic expando, expando.Price = price; } - //public static decimal GetEstimatedShippingCosts(this Product product, decimal calculatedProductPrice, decimal defaultShippingCosts, decimal? freeShippingThreshold) - //{ - // if (product != null) - // { - // if (product.IsFreeShipping) - // return decimal.Zero; - - // if (freeShippingThreshold.HasValue && calculatedProductPrice >= freeShippingThreshold.Value) - // return decimal.Zero; - // } - // return defaultShippingCosts; - //} - #endregion private void InitDependencies(TaskExecutionContext context) @@ -220,7 +208,11 @@ private void InitDependencies(TaskExecutionContext context) private IEnumerable GetProducts(ExportProfileTaskContext ctx, int pageIndex) { - _services.DbContext.DetachAll(); + // do not call DetachAll() here! It will detach everything even things you do not want to have detached. + // note that detached navigation properties will not navigate again. they stay null => exception. + // DetachAll() is actually more useful to be called after all the work is done. + + //_services.DbContext.DetachAll(); if (!ctx.Cancellation.IsCancellationRequested) { @@ -236,10 +228,10 @@ private IEnumerable GetProducts(ExportProfileTaskContext ctx, int pageI IsPublished = ctx.Filter.IsPublished, WithoutCategories = ctx.Filter.WithoutCategories, WithoutManufacturers = ctx.Filter.WithoutManufacturers, - ManufacturerId = ctx.Filter.ManufacturerId, + ManufacturerId = ctx.Filter.ManufacturerId ?? 0, FeaturedProducts = ctx.Filter.FeaturedProducts, ProductType = ctx.Filter.ProductType, - ProductTagId = ctx.Filter.ProductTagId, + ProductTagId = ctx.Filter.ProductTagId ?? 0, IdMin = ctx.Filter.IdMinimum ?? 0, IdMax = ctx.Filter.IdMaximum ?? 0, AvailabilityMinimum = ctx.Filter.AvailabilityMinimum, @@ -286,7 +278,7 @@ private IEnumerable GetProducts(ExportProfileTaskContext ctx, int pageI private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) { - dynamic expando = product.ToExpando(ctx.Projection.LanguageId ?? 0, _pictureService, _mediaSettings, ctx.Store); + dynamic expando = product.ToExpando(ctx.Projection.LanguageId ?? 0); PrepareProductPrice(ctx, expando, product); @@ -300,10 +292,11 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) if (ctx.Provider.Supports(ExportProjectionSupport.Brand)) { string brand = null; + var manu = product.ProductManufacturers.OrderBy(x => x.DisplayOrder).FirstOrDefault(); - var manu = (product.ProductManufacturers as List).FirstOrDefault(); if (manu != null) - brand = manu.Manufacturer.Name; + brand = manu.Manufacturer.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + if (brand.IsEmpty()) brand = ctx.Projection.Brand; @@ -369,9 +362,6 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) private void Cleanup(ExportProfileTaskContext ctx) { - if (!ctx.Profile.Cleanup) - return; - FileSystemHelper.ClearDirectory(ctx.Folder, false, new List { _logName }); // TODO: more deployment specific here @@ -466,7 +456,9 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) { FileSystemHelper.ClearDirectory(ctx.Folder, false); - using (var scope = new DbContextScope(autoDetectChanges: false, validateOnSave: false, forceNoTracking: true)) + var allStores = _services.StoreService.GetAllStores(); + var currentStore = _services.StoreContext.CurrentStore; + using (var logger = new TraceLogger(Path.Combine(ctx.Folder, _logName))) { ctx.Log = logger; @@ -492,27 +484,26 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) } } - // TODO: log number of flown out records - - if (ctx.Profile.PerStore) + using (var scope = new DbContextScope(_services.DbContext, autoDetectChanges: false, proxyCreation: true, validateOnSave: false, forceNoTracking: true)) { - var allStores = _services.StoreService.GetAllStores(); - - foreach (var store in allStores.Where(x => ctx.Filter.StoreId == 0 || ctx.Filter.StoreId == x.Id)) + if (ctx.Profile.PerStore) { - ctx.Store = store; + foreach (var store in allStores.Where(x => x.Id == ctx.Filter.StoreId || ctx.Filter.StoreId == 0)) + { + ctx.Store = store; - ExportCoreInner(ctx); + ExportCoreInner(ctx); + } } - } - else - { - if (ctx.Projection.StoreId.HasValue) - ctx.Store = _services.StoreService.GetStoreById(ctx.Projection.StoreId.Value); else - ctx.Store = _services.StoreContext.CurrentStore; + { + if (ctx.Filter.StoreId == 0) + ctx.Store = allStores.FirstOrDefault(x => x.Id == (ctx.Projection.StoreId ?? currentStore.Id)); + else + ctx.Store = allStores.FirstOrDefault(x => x.Id == ctx.Filter.StoreId); - ExportCoreInner(ctx); + ExportCoreInner(ctx); + } } } } @@ -522,17 +513,27 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) } finally { + try + { + if (ctx.Profile.Cleanup) + Cleanup(ctx); + } + catch { } + try { if (ctx.Cancellation.IsCancellationRequested) ctx.Log.Warning("Export aborted. A cancellation has been requested"); + // TODO: log number of flown out records + if (ctx.Segmenter != null) ctx.Segmenter.Dispose(); ctx.Export.CustomProperties.Clear(); - Cleanup(ctx); + // do not call during processing! + _services.DbContext.DetachAll(); } catch { } } diff --git a/src/Libraries/SmartStore.Services/Stores/StoreService.cs b/src/Libraries/SmartStore.Services/Stores/StoreService.cs index f763985753..16daad6591 100644 --- a/src/Libraries/SmartStore.Services/Stores/StoreService.cs +++ b/src/Libraries/SmartStore.Services/Stores/StoreService.cs @@ -81,9 +81,12 @@ public virtual IList GetAllStores() string key = STORES_ALL_KEY; return _cacheManager.Get(key, () => { - var query = from s in _storeRepository.Table - orderby s.DisplayOrder, s.Name - select s; + var query = _storeRepository.Table + .Expand(x => x.PrimaryStoreCurrency) + .Expand(x => x.PrimaryExchangeRateCurrency) + .OrderBy(x => x.DisplayOrder) + .ThenBy(x => x.Name); + var stores = query.ToList(); return stores; }); @@ -147,9 +150,7 @@ public virtual bool IsSingleStoreMode() { if (!_isSingleStoreMode.HasValue) { - var query = from s in _storeRepository.Table - select s; - _isSingleStoreMode = query.Count() <= 1; + _isSingleStoreMode = (_storeRepository.TableUntracked.Count() <= 1); } return _isSingleStoreMode.Value; diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs index 86e695a704..f418b91bb1 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs @@ -1,15 +1,11 @@ -using SmartStore.Core.Data; +using System; +using System.Threading; +using Autofac; +using SmartStore.Core.Async; +using SmartStore.Core.Data; using SmartStore.Core.Domain.Tasks; using SmartStore.Core.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using SmartStore.Core.Async; -using System.Threading; using SmartStore.Core.Plugins; -using Autofac; namespace SmartStore.Services.Tasks { @@ -46,7 +42,7 @@ public void Execute(ScheduleTask task, bool throwOnError = false) string lastError = null; ITask instance = null; string stateName = null; - + int taskId = task.Id; Type taskType = null; try @@ -103,31 +99,33 @@ public void Execute(ScheduleTask task, bool throwOnError = false) AsyncState.Current.Remove(stateName); } - task.ProgressPercent = null; - task.ProgressMessage = null; - + // "task" may not updatable here. solution: get fresh instance and update it + var task2 = _scheduledTaskService.GetTaskById(taskId); var now = DateTime.UtcNow; - task.LastError = lastError; - task.LastEndUtc = now; + + task2.ProgressPercent = null; + task2.ProgressMessage = null; + task2.LastError = lastError; + task2.LastEndUtc = now; if (faulted) { - if ((!canceled && task.StopOnError) || instance == null) + if ((!canceled && task2.StopOnError) || instance == null) { - task.Enabled = false; + task2.Enabled = false; } } else { - task.LastSuccessUtc = now; + task2.LastSuccessUtc = now; } - if (task.Enabled) + if (task2.Enabled) { - task.NextRunUtc = now.AddSeconds(task.Seconds); + task2.NextRunUtc = now.AddSeconds(task2.Seconds); } - _scheduledTaskService.UpdateTask(task); + _scheduledTaskService.UpdateTask(task2); } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs index 1372ce348a..1d11301385 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs @@ -58,14 +58,14 @@ public class ExportProductFilterModel : ExportFilterModelBase public bool? WithoutCategories { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ManufacturerId")] - public int ManufacturerId { get; set; } + public int? ManufacturerId { get; set; } public List AvailableManufacturers { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Filter.WithoutManufacturers")] public bool? WithoutManufacturers { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ProductTagId")] - public int ProductTagId { get; set; } + public int? ProductTagId { get; set; } public List AvailableProductTags { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Filter.FeaturedProducts")] From aab93f9461090b1849d15beeea375f984e7af56f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 21 Aug 2015 15:30:15 +0200 Subject: [PATCH 016/874] Export test, create zip archive --- .../DataExchange/ExportExecuteContext.cs | 4 + .../DataExchange/ExportProfileTask.cs | 116 ++++++++++++------ .../DataExchange/ExportService.cs | 6 +- .../SmartStore.Services.csproj | 1 + .../Views/Export/CreateDeployment.cshtml | 2 +- 5 files changed, 89 insertions(+), 40 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs index 6d4a4b1d8a..486ba6a807 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs @@ -26,6 +26,8 @@ public interface IExportExecuteContext Dictionary CustomProperties { get; set; } + int SuccessfulExportedRecords { get; set; } + string GetFilePath(string fileNameSuffix = null); } @@ -63,6 +65,8 @@ public bool IsCanceled public Dictionary CustomProperties { get; set; } + public int SuccessfulExportedRecords { get; set; } + public string GetFilePath(string fileNameSuffix = null) { var fileName = FileNamePattern.FormatInvariant( diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index aa3a4e6e20..687f0b9a0d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Dynamic; using System.IO; +using System.IO.Compression; using System.Linq; using System.Text; using System.Threading; @@ -360,11 +361,8 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) return expando as ExpandoObject; } - private void Cleanup(ExportProfileTaskContext ctx) + private void Deploy(ExportProfileTaskContext ctx, ExportDeployment deployment) { - FileSystemHelper.ClearDirectory(ctx.Folder, false, new List { _logName }); - - // TODO: more deployment specific here } private void ExportCoreInner(ExportProfileTaskContext ctx) @@ -439,10 +437,16 @@ private void ExportCoreInner(ExportProfileTaskContext ctx) ctx.Segmenter.Start(() => { - if (!ctx.Provider.Value.Execute(ctx.Export)) - return false; + ctx.Export.SuccessfulExportedRecords = 0; - return !ctx.Cancellation.IsCancellationRequested; + bool goOn = ctx.Provider.Value.Execute(ctx.Export); + + ctx.Log.Information("Provider reports {0} successful exported record(s)".FormatInvariant(ctx.Export.SuccessfulExportedRecords)); + + if (ctx.Cancellation.IsCancellationRequested) + ctx.Log.Warning("Export aborted. A cancellation has been requested"); + + return (goOn && !ctx.Cancellation.IsCancellationRequested); }); } } @@ -452,27 +456,33 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) if (ctx.Profile == null || !ctx.Profile.Enabled) return; - try - { - FileSystemHelper.ClearDirectory(ctx.Folder, false); + var allStores = _services.StoreService.GetAllStores(); + var currentStore = _services.StoreContext.CurrentStore; + + var pathZipArchive = Path.Combine(ctx.FolderRoot, ctx.Profile.FolderName + ".zip"); + var pathLogFile = Path.Combine(ctx.FolderRoot, _logName); - var allStores = _services.StoreService.GetAllStores(); - var currentStore = _services.StoreContext.CurrentStore; + if (ctx.Projection.CurrencyId.HasValue) + ctx.ProjectionCurrency = _currencyService.GetCurrencyById(ctx.Projection.CurrencyId.Value); + else + ctx.ProjectionCurrency = _services.WorkContext.WorkingCurrency; + + if (ctx.Projection.CustomerId.HasValue) + ctx.ProjectionCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); + else + ctx.ProjectionCustomer = _services.WorkContext.CurrentCustomer; - using (var logger = new TraceLogger(Path.Combine(ctx.Folder, _logName))) + + using (var logger = new TraceLogger(pathLogFile)) + { + try { ctx.Log = logger; ctx.Export.Log = logger; - if (ctx.Projection.CurrencyId.HasValue) - ctx.ProjectionCurrency = _currencyService.GetCurrencyById(ctx.Projection.CurrencyId.Value); - else - ctx.ProjectionCurrency = _services.WorkContext.WorkingCurrency; - - if (ctx.Projection.CustomerId.HasValue) - ctx.ProjectionCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); - else - ctx.ProjectionCustomer = _services.WorkContext.CurrentCustomer; + FileSystemHelper.ClearDirectory(ctx.FolderContent, false); + FileSystemHelper.Delete(pathLogFile); + FileSystemHelper.Delete(pathZipArchive); if (ctx.Profile.ProviderConfigData.HasValue()) { @@ -506,33 +516,60 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) } } } + catch (Exception exc) + { + logger.Error(exc); + } + finally + { + try + { + if (ctx.Segmenter != null) + ctx.Segmenter.Dispose(); + + ctx.Export.CustomProperties.Clear(); + ctx.Export.Log = null; + ctx.Log = null; + } + catch { } + } + } + + try + { + // create zip archive + if (ctx.Profile.CreateZipArchive) + { + ZipFile.CreateFromDirectory(ctx.FolderContent, pathZipArchive, CompressionLevel.Fastest, true); + } + + // deployment + foreach (var deployment in ctx.Profile.Deployments.OrderBy(x => x.DeploymentTypeId)) + { + Deploy(ctx, deployment); + } } catch (Exception exc) { - ctx.Log.Error(exc); + using (var logger2 = new TraceLogger(pathLogFile)) + { + logger2.Error(exc); + } } finally { try { if (ctx.Profile.Cleanup) - Cleanup(ctx); + { + FileSystemHelper.ClearDirectory(ctx.FolderContent, false); + } } catch { } try { - if (ctx.Cancellation.IsCancellationRequested) - ctx.Log.Warning("Export aborted. A cancellation has been requested"); - - // TODO: log number of flown out records - - if (ctx.Segmenter != null) - ctx.Segmenter.Dispose(); - - ctx.Export.CustomProperties.Clear(); - - // do not call during processing! + // do not call during processing, should be the final statement _services.DbContext.DetachAll(); } catch { } @@ -583,9 +620,11 @@ public ExportProfileTaskContext(ExportProfile profile, Provider Filter = XmlHelper.Deserialize(profile.Filtering); Projection = XmlHelper.Deserialize(profile.Projection); Cancellation = cancellation; - Folder = FileSystemHelper.TempDir(@"Profile\Export\{0}".FormatInvariant(profile.FolderName)); - Export = new ExportExecuteContext(Cancellation, Folder); + FolderContent = FileSystemHelper.TempDir(@"Profile\Export\{0}\Content".FormatInvariant(profile.FolderName)); + FolderRoot = System.IO.Directory.GetParent(FolderContent).FullName; + + Export = new ExportExecuteContext(Cancellation, FolderContent); } public ExportProfile Profile { get; private set; } @@ -600,7 +639,8 @@ public ExportProfileTaskContext(ExportProfile profile, Provider public TraceLogger Log { get; set; } public Store Store { get; set; } - public string Folder { get; private set; } + public string FolderRoot { get; private set; } + public string FolderContent { get; private set; } public Dictionary Categories; public Dictionary CategoryPathes; diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index fa1fb3bf8f..9363ea1248 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -52,7 +52,8 @@ public virtual ExportProfile InsertExportProfile(Provider provi var folderName = SeoHelper.GetSeName(name, true, false) .Replace("/", "") - .ToValidPath(); + .ToValidPath() + .Truncate(20); var taskType = (new ExportProfileTask()).GetType().AssemblyQualifiedNameWithoutVersion(); @@ -104,6 +105,7 @@ public virtual void DeleteExportProfile(ExportProfile profile) throw new ArgumentNullException("profile"); int scheduleTaskId = profile.SchedulingTaskId; + var folder = CommonHelper.MapPath("~/App_Data/_temp/Profile/Export/" + profile.FolderName); _exportProfileRepository.Delete(profile); @@ -111,6 +113,8 @@ public virtual void DeleteExportProfile(ExportProfile profile) _scheduleTaskService.DeleteTask(scheduleTask); _eventPublisher.EntityDeleted(profile); + + FileSystemHelper.ClearDirectory(folder, true); } public virtual IQueryable GetExportProfiles(bool? enabled = null) diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 985aa13eaf..ef481b01a9 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -111,6 +111,7 @@ + ..\..\packages\System.Linq.Dynamic.1.0.0\lib\net40\System.Linq.Dynamic.dll diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/CreateDeployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/CreateDeployment.cshtml index cd937c8177..bf7866d3df 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/CreateDeployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/CreateDeployment.cshtml @@ -18,5 +18,5 @@ - @Html.Partial("_CreateOrUpdateDeployment", Model) + @Html.Partial("_CreateOrUpdate.Deployment", Model) } \ No newline at end of file From 2ebba455dfc889d6e1cfcbd9f8f348fbf43081df Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 24 Aug 2015 10:36:11 +0200 Subject: [PATCH 017/874] Export framework further implementation --- .../DataExchange/DataExchangeSettings.cs | 17 +++ .../Domain/DataExchange/ExportCore.cs | 4 +- .../Domain/DataExchange/ExportDeployment.cs | 5 + .../Domain/DataExchange/ExportProfile.cs | 8 +- .../Domain/DataExchange/ExportProjection.cs | 4 +- .../SmartStore.Core/SmartStore.Core.csproj | 1 + .../201508091512101_ExportFramework.cs | 49 +++---- .../201508211346171_Merge1.Designer.cs | 29 ++++ .../Migrations/201508211346171_Merge1.cs | 16 +++ .../Migrations/201508211346171_Merge1.resx | 126 +++++++++++++++++ ...1508221015215_ExportFramework1.Designer.cs | 29 ++++ .../201508221015215_ExportFramework1.cs | 77 +++++++++++ .../201508221015215_ExportFramework1.resx | 126 +++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 14 ++ .../DataExchange/ExportProfileTask.cs | 128 +++++++++++++++--- .../DataExchange/ExportService.cs | 7 +- .../Controllers/ExportController.cs | 24 +++- .../DataExchange/ExportDeploymentModel.cs | 3 + .../Models/DataExchange/ExportProfileModel.cs | 8 ++ .../DataExchange/ExportProjectionModel.cs | 5 + .../DataExchange/ExportProfileValidator.cs | 8 +- .../Administration/Views/Export/Edit.cshtml | 48 +++++-- .../Export/_CreateOrUpdate.Deployment.cshtml | 22 ++- .../Export/_CreateOrUpdate.Filter.cshtml | 2 +- .../Export/_CreateOrUpdate.Projection.cshtml | 17 ++- 25 files changed, 702 insertions(+), 75 deletions(-) create mode 100644 src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.resx create mode 100644 src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.resx diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs new file mode 100644 index 0000000000..d5cb3fc4ba --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs @@ -0,0 +1,17 @@ +using SmartStore.Core.Configuration; + +namespace SmartStore.Core.Domain.DataExchange +{ + public class DataExchangeSettings : ISettings + { + public DataExchangeSettings() + { + MaxFileNameLength = 32; + } + + /// + /// The maximum length of file names (in characters) of files created by the export framework + /// + public int MaxFileNameLength { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs index 325b8dac0b..24ed68c33e 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs @@ -40,6 +40,8 @@ public enum ExportProjectionSupport CategoryPath, ShippingTime, ShippingCosts, - Availability + Availability, + AttributeCombinationAsProduct, + OldPrice } } diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs index 0f350e499f..2ad338838b 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs @@ -24,6 +24,11 @@ public class ExportDeployment : BaseEntity /// public bool IsPublic { get; set; } + /// + /// Whether to create a zip archive with the content of the export + /// + public bool CreateZip { get; set; } + /// /// The deployment type identifier /// diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs index ef93eefdf6..f0540e42d7 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs @@ -14,6 +14,7 @@ public ExportProfile() Enabled = true; PerStore = true; Cleanup = true; + EmailAccountId = 0; } /// @@ -77,7 +78,12 @@ public ExportProfile() public bool PerStore { get; set; } /// - /// Email addresses (semicolon separated) where to send a notification message of the completion of the export + /// Identifier of an email account used to send a notification message of the completion of the export + /// + public int EmailAccountId { get; set; } + + /// + /// Email addresses where to send the notification message /// public string CompletedEmailAddresses { get; set; } diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs index 52131b3033..8e28398d45 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs @@ -9,7 +9,7 @@ public class ExportProjection public ExportProjection() { RemoveCriticalCharacters = true; - CriticalCharacters = "¼;½;¾"; + CriticalCharacters = "¼,½,¾"; PriceType = PriceDisplayType.PreSelectedPrice; } @@ -50,5 +50,7 @@ public ExportProjection() public decimal? ShippingCosts { get; set; } public decimal? FreeShippingThreshold { get; set; } + + public bool AttributeCombinationAsProduct { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index e04d7f01e5..1c1ba9a686 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -157,6 +157,7 @@ + diff --git a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs index 819f61f7dc..fa1ed244fd 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs @@ -93,10 +93,6 @@ public void Seed(SmartObjectContext context) }, new string[] { SystemCustomerRoleNames.Administrators }); } - private void RemoveStringResources(LocaleResourcesBuilder builder) - { - } - public void MigrateLocaleResources(LocaleResourcesBuilder builder) { builder.AddOrUpdate("Common.Enabled", "Enabled", "Aktiviert"); @@ -199,14 +195,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Export.CreateZipArchive", "Create ZIP archive", "ZIP-Archiv erstellen", - "Specifies whether to combine and compress the export files in a ZIP archive.", - "Legt fest, ob die Exportdateien in einem ZIP-Archiv zusammengefasst und komprimiert werden sollen."); - - builder.AddOrUpdate("Admin.Configuration.Export.CompletedEmailAddresses", - "Notification (email addresses)", - "Benachrichtigung (E-Mail-Addressen)", - "Specifies the email addresses (semicolon separated) where to send a notification message of the completion of the export.", - "Legt die E-Mail Addressen (Semikolon getrennt) fest, an die eine Benachrichtigung ber die Fertigstellung des Exports verschickt werden soll."); + "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.", + "Legt fest, ob die Exportdateien in einem ZIP-Archiv zusammengefasst werden sollen. Das Archiv verbleibt im temporren Ordner des Exportprofils ohne weitere Vearbeitung."); builder.AddOrUpdate("Admin.Configuration.Export.Cleanup", "Clean up at the end", @@ -386,8 +376,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Export.Projection.AppendDescriptionText", "Text to be appended", "Anzuhngender Text", - "Specifies the text to be attached to the product description. You can enter multiple, semicolon delimited texts. Then one of it is selected randomly.", - "Legt den an die Artikelbeschreibung anzuhngenden Text fest. Es knnen auch mehrere, durch Semikolon getrennte Texte eingegeben werden. Dann wird per Zufall einer ausgewhlt."); + "Specifies the text to be attached to the product description. If there are multiple texts then one of it is selected randomly.", + "Legt den an die Artikelbeschreibung anzuhngenden Text fest. Bei mehreren Texten wird einer per Zufall ausgewhlt."); builder.AddOrUpdate("Admin.Configuration.Export.Projection.RemoveCriticalCharacters", "Remove critical characters", @@ -398,8 +388,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Export.Projection.CriticalCharacters", "Critical characters", "Kritische Zeichen", - "List with semicolon delimited characters to be removed from the detail description.", - "Liste mit Semikolon getrennten Zeichen, die aus der Detailbeschreibung entfernt werden sollen."); + "List with characters to be removed from the detail description.", + "Liste mit Zeichen, die aus der Detailbeschreibung entfernt werden sollen."); builder.AddOrUpdate("Admin.Configuration.Export.Projection.PriceType", "Product price", @@ -474,10 +464,16 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Legt den Namen der Bereitstellung fest."); builder.AddOrUpdate("Admin.Configuration.Export.Deployment.IsPublic", - "Make public", - "ffentlich machen", - "Specifies whether to publish the exported data (e.g. whether they are accessible on the internet).", - "Legt fest, ob die exportierten Daten ffentlich gemacht werden sollen (z.B. ob sie ber das Internet erreichbar sind)."); + "Copy to public folder", + "In ffentlichen Ordner kopieren", + "Specifies whether to copy the exported data into a folder that is accessible through the internet.", + "Legt fest, ob die exportierten Daten in einen bers Internet zugnglichen Ordner kopiert werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.FileSystemPath", + "Directory path", + "Ordnerpfad", + "Specifies the path (relative or absolute) where to deploy the data.", + "Legt den Pfad (relativ oder absolut) zu einem Ordner fest, in den die Daten bereitgestellt werden sollen."); builder.AddOrUpdate("Admin.Configuration.Export.Deployment.DeploymentType", "Type of deployment", @@ -503,17 +499,11 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Specifies the URL on which the data should be deployed.", "Legt die URL fest, unter der die Daten bereitgestellt werden sollen."); - builder.AddOrUpdate("Admin.Configuration.Export.Deployment.FileSystemPath", - "Relative path", - "Relativer Pfad", - "Specifies the relative path for the deployed data.", - "Legt den relativen Pfad fr die bereitgestellten Daten fest."); - builder.AddOrUpdate("Admin.Configuration.Export.Deployment.EmailAddresses", "Email addresses", "E-Mail-Addressen", - "Specifies the email addresses (semicolon separated) where to send the data.", - "Legt die E-Mail Addressen (Semikolon getrennt) fest, an die die Daten verschickt werden soll."); + "Specifies the email addresses where to send the data.", + "Legt die E-Mail Addressen fest, an die die Daten verschickt werden soll."); builder.AddOrUpdate("Admin.Configuration.Export.Deployment.EmailSubject", "Email subject", @@ -526,9 +516,6 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "E-Mail Konto", "Specifies the email account through which the data should be sent.", "Legt das E-Mail Konto fest, ber welches die Daten verschickt werden sollen."); - - - RemoveStringResources(builder); } } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.Designer.cs new file mode 100644 index 0000000000..7884363732 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.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 Merge1 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Merge1)); + + string IMigrationMetadata.Id + { + get { return "201508211346171_Merge1"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.cs b/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.cs new file mode 100644 index 0000000000..146e495730 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.cs @@ -0,0 +1,16 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class Merge1 : DbMigration + { + public override void Up() + { + } + + public override void Down() + { + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.resx b/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.resx new file mode 100644 index 0000000000..3f0be8609e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508211346171_Merge1.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/Migrations/201508221015215_ExportFramework1.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.Designer.cs new file mode 100644 index 0000000000..fc5b31ee8a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.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 ExportFramework1 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ExportFramework1)); + + string IMigrationMetadata.Id + { + get { return "201508221015215_ExportFramework1"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.cs new file mode 100644 index 0000000000..305c4c27f1 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.cs @@ -0,0 +1,77 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class ExportFramework1 : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.ExportDeployment", "CreateZip", c => c.Boolean(nullable: false)); + AddColumn("dbo.ExportProfile", "EmailAccountId", c => c.Int(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.ExportProfile", "EmailAccountId"); + DropColumn("dbo.ExportDeployment", "CreateZip"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Admin.Configuration.Export.EmailAccountId", + "Email notification", + "E-Mail Benachrichtigung", + "Specifies the email account used to send a notification message of the completion of the export.", + "Legt das E-Mail Konto fest, ber welches eine Benachrichtigung ber die Fertigstellung des Exports verschickt werden soll."); + + builder.AddOrUpdate("Admin.Configuration.Export.CompletedEmailAddresses", + "Email addresses", + "E-Mail-Addressen", + "Specifies the email addresses where to send the notification message.", + "Legt die E-Mail Addressen fest, an die die Benachrichtigung geschickt werden soll."); + + builder.AddOrUpdate("Admin.Configuration.Export.CompletedEmail.Subject", + "Export of profile \"{0}\" has been finished", + "Export von Profile \"{0}\" ist abgeschlossen"); + + builder.AddOrUpdate("Admin.Configuration.Export.CompletedEmail.Body", + "This is an automatic notification of store \"{0}\" about a recent data export. You can disable the sending of this message in the details of the export profile.", + "Dies ist eine automatische Benachrichtung von Shop \"{0}\" ber einen erfolgten Datenexport. Sie knnen den Versand dieser Mitteilung in den Details des Exportprofils deaktivieren."); + + builder.AddOrUpdate("Admin.Configuration.Export.FolderName", + "Folder name", + "Ordnername", + "Specifies the name of the folder where the data will be exported.", + "Legt den Namen des Ordners fest, in den die Daten exportiert werden."); + + builder.AddOrUpdate("Admin.Configuration.Export.FolderName.Validate", + "Please enter a valid folder name.", + "Bitte einen gltigen Ordnernamen eingeben."); + + + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.CreateZip", + "Create ZIP archive", + "ZIP-Archiv erstellen", + "Specifies whether to combine the export files in a ZIP archive and only to deploy the archive.", + "Legt fest, ob die Exportdateien in einem ZIP-Archiv zusammengefasst und nur das Archiv bereitgestellt werden soll."); + + builder.AddOrUpdate("Admin.Configuration.Export.Projection.AttributeCombinationAsProduct", + "Export attribute combinations", + "Attributkombinationen exportieren", + "Specifies whether to export all active attribute combinations as a standalone product in addition to each product.", + "Legt fest, ob zustzlich zu jedem Produkt alle seine aktiven Attributkombinationen als eigenstndiges Produkt exportiert werden sollen."); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.resx b/src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.resx new file mode 100644 index 0000000000..827c791dca --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.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 0b8e5ce8ad..e5424dc4d2 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -337,6 +337,14 @@ 201508091512101_ExportFramework.cs + + + 201508211346171_Merge1.cs + + + + 201508221015215_ExportFramework1.cs + @@ -637,6 +645,12 @@ 201508091512101_ExportFramework.cs + + 201508211346171_Merge1.cs + + + 201508221015215_ExportFramework1.cs + diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index 687f0b9a0d..4d8eaccaea 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -18,6 +18,7 @@ using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Email; using SmartStore.Core.Html; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; @@ -27,6 +28,7 @@ using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Media; +using SmartStore.Services.Messages; using SmartStore.Services.Tasks; using SmartStore.Services.Tax; using SmartStore.Utilities; @@ -35,7 +37,6 @@ namespace SmartStore.Services.DataExchange { public class ExportProfileTask : ITask { - private const string _logName = "log.txt"; private const int _maxErrors = 20; private const int _pageSize = 100; @@ -51,6 +52,9 @@ public class ExportProfileTask : ITask private ICategoryService _categoryService; private IPriceFormatter _priceFormatter; private IDateTimeHelper _dateTimeHelper; + private IEmailAccountService _emailAccountService; + private IEmailSender _emailSender; + private DataExchangeSettings _dataExchangeSettings; #region Utilities @@ -115,7 +119,7 @@ private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic exp // append text if (ctx.Projection.AppendDescriptionText.HasValue() && ((string)expando.ShortDescription).IsEmpty() && ((string)expando.FullDescription).IsEmpty()) { - string[] appendText = ctx.Projection.AppendDescriptionText.SplitSafe(";"); + string[] appendText = ctx.Projection.AppendDescriptionText.SplitSafe(","); if (appendText.Length > 0) { var rnd = (new Random()).Next(0, appendText.Length - 1); @@ -127,7 +131,7 @@ private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic exp // remove critical characters if (description.HasValue() && ctx.Projection.RemoveCriticalCharacters) { - foreach (var str in ctx.Projection.CriticalCharacters.SplitSafe(";")) + foreach (var str in ctx.Projection.CriticalCharacters.SplitSafe(",")) description = description.Replace(str, ""); } @@ -189,6 +193,27 @@ private void PrepareProductPrice(ExportProfileTaskContext ctx, dynamic expando, expando.Price = price; } + private void SendCompletionEmail(ExportProfileTaskContext ctx) + { + var emailAccount = _emailAccountService.GetEmailAccountById(ctx.Profile.EmailAccountId); + var smtpContext = new SmtpContext(emailAccount); + + var storeInfo = "{0} ({1})".FormatInvariant(ctx.Store.Name, ctx.Store.Url); + + var message = new EmailMessage(); + + message.To.AddRange(ctx.Profile.CompletedEmailAddresses.SplitSafe(",").Where(x => x.IsEmail()).Select(x => new EmailAddress(x))); + message.From = new EmailAddress(emailAccount.Email, emailAccount.DisplayName); + + message.Subject = _services.Localization.GetResource("Admin.Configuration.Export.CompletedEmail.Subject", ctx.Projection.LanguageId ?? 0) + .FormatInvariant(ctx.Profile.Name); + + message.Body = _services.Localization.GetResource("Admin.Configuration.Export.CompletedEmail.Body", ctx.Projection.LanguageId ?? 0) + .FormatInvariant(storeInfo); + + _emailSender.SendEmail(smtpContext, message); + } + #endregion private void InitDependencies(TaskExecutionContext context) @@ -205,6 +230,9 @@ private void InitDependencies(TaskExecutionContext context) _categoryService = context.Resolve(); _priceFormatter = context.Resolve(); _dateTimeHelper = context.Resolve(); + _emailAccountService = context.Resolve(); + _emailSender = context.Resolve(); + _dataExchangeSettings = context.Resolve(); } private IEnumerable GetProducts(ExportProfileTaskContext ctx, int pageIndex) @@ -358,11 +386,62 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) expando._ShippingCosts = ctx.Projection.ShippingCosts; } + if (ctx.Provider.Supports(ExportProjectionSupport.OldPrice)) + { + if (product.OldPrice != decimal.Zero && product.OldPrice != product.Price && !(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)) + { + if (ctx.Projection.ConvertNetToGrossPrices) + { + decimal taxRate; + expando._OldPrice = _taxService.GetProductPrice(product, product.OldPrice, true, ctx.ProjectionCustomer, out taxRate); + } + else + { + expando._OldPrice = product.OldPrice; + } + } + } + return expando as ExpandoObject; } private void Deploy(ExportProfileTaskContext ctx, ExportDeployment deployment) { + if (deployment.DeploymentType == ExportDeploymentType.FileSystem) + { + string folderDestination = null; + + if (deployment.IsPublic) + { + folderDestination = Path.Combine(HttpRuntime.AppDomainAppPath, "Content\\files\\ExportImport"); + } + else if (deployment.FileSystemPath.IsEmpty()) + { + return; + } + else if (deployment.FileSystemPath.StartsWith("/") || deployment.FileSystemPath.StartsWith("\\") || !Path.IsPathRooted(deployment.FileSystemPath)) + { + folderDestination = CommonHelper.MapPath(deployment.FileSystemPath); + } + else + { + folderDestination = deployment.FileSystemPath; + } + + if (!System.IO.Directory.Exists(folderDestination)) + { + System.IO.Directory.CreateDirectory(folderDestination); + } + + if (deployment.CreateZip) + { + FileSystemHelper.Copy(ctx.ZipPath, Path.Combine(folderDestination, ctx.Profile.FolderName + ".zip")); + } + else + { + FileSystemHelper.CopyDirectory(new DirectoryInfo(ctx.FolderContent), new DirectoryInfo(folderDestination)); + } + } } private void ExportCoreInner(ExportProfileTaskContext ctx) @@ -370,10 +449,9 @@ private void ExportCoreInner(ExportProfileTaskContext ctx) ctx.Export.StoreId = ctx.Store.Id; ctx.Export.StoreUrl = ctx.Store.Url; - // be careful with too long file system paths ctx.Export.FileNamePattern = string.Concat( "{0}-", - ctx.Profile.PerStore ? SeoHelper.GetSeName(ctx.Store.Name, true, false).ToValidFileName("").Truncate(20) : "all-stores", + ctx.Profile.PerStore ? SeoHelper.GetSeName(ctx.Store.Name, true, false).ToValidFileName("").Truncate(_dataExchangeSettings.MaxFileNameLength) : "all-stores", "{1}", ctx.Provider.Value.FileExtension.ToLower().EnsureStartsWith(".") ); @@ -459,9 +537,6 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) var allStores = _services.StoreService.GetAllStores(); var currentStore = _services.StoreContext.CurrentStore; - var pathZipArchive = Path.Combine(ctx.FolderRoot, ctx.Profile.FolderName + ".zip"); - var pathLogFile = Path.Combine(ctx.FolderRoot, _logName); - if (ctx.Projection.CurrencyId.HasValue) ctx.ProjectionCurrency = _currencyService.GetCurrencyById(ctx.Projection.CurrencyId.Value); else @@ -472,18 +547,17 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) else ctx.ProjectionCustomer = _services.WorkContext.CurrentCustomer; + FileSystemHelper.ClearDirectory(ctx.FolderContent, false); + FileSystemHelper.Delete(ctx.LogPath); + FileSystemHelper.Delete(ctx.ZipPath); - using (var logger = new TraceLogger(pathLogFile)) + using (var logger = new TraceLogger(ctx.LogPath)) { try { ctx.Log = logger; ctx.Export.Log = logger; - FileSystemHelper.ClearDirectory(ctx.FolderContent, false); - FileSystemHelper.Delete(pathLogFile); - FileSystemHelper.Delete(pathZipArchive); - if (ctx.Profile.ProviderConfigData.HasValue()) { string partialName; @@ -537,21 +611,24 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) try { - // create zip archive - if (ctx.Profile.CreateZipArchive) + if (ctx.Profile.CreateZipArchive || ctx.Profile.Deployments.Any(x => x.Enabled && x.CreateZip)) { - ZipFile.CreateFromDirectory(ctx.FolderContent, pathZipArchive, CompressionLevel.Fastest, true); + ZipFile.CreateFromDirectory(ctx.FolderContent, ctx.ZipPath, CompressionLevel.Fastest, true); } - // deployment - foreach (var deployment in ctx.Profile.Deployments.OrderBy(x => x.DeploymentTypeId)) + foreach (var deployment in ctx.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) { Deploy(ctx, deployment); } + + if (ctx.Profile.EmailAccountId != 0 && ctx.Profile.CompletedEmailAddresses.HasValue()) + { + SendCompletionEmail(ctx); + } } catch (Exception exc) { - using (var logger2 = new TraceLogger(pathLogFile)) + using (var logger2 = new TraceLogger(ctx.LogPath)) { logger2.Error(exc); } @@ -563,13 +640,14 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) if (ctx.Profile.Cleanup) { FileSystemHelper.ClearDirectory(ctx.FolderContent, false); + // TODO: more cleanup if required } } catch { } try { - // do not call during processing, should be the final statement + // do not call during processing, would generate exceptions. should be the final statement. _services.DbContext.DetachAll(); } catch { } @@ -613,8 +691,6 @@ internal class ExportProfileTaskContext { public ExportProfileTaskContext(ExportProfile profile, Provider provider, CancellationToken cancellation) { - Debug.Assert(profile.FolderName.HasValue(), "Folder name must not be empty."); - Profile = profile; Provider = provider; Filter = XmlHelper.Deserialize(profile.Filtering); @@ -641,6 +717,14 @@ public ExportProfileTaskContext(ExportProfile profile, Provider public string FolderRoot { get; private set; } public string FolderContent { get; private set; } + public string ZipPath + { + get { return Path.Combine(FolderRoot, Profile.FolderName + ".zip"); } + } + public string LogPath + { + get { return Path.Combine(FolderRoot, "log.txt"); } + } public Dictionary Categories; public Dictionary CategoryPathes; diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index 9363ea1248..ab24006a50 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -22,19 +22,22 @@ public partial class ExportService : IExportService private readonly IEventPublisher _eventPublisher; private readonly IScheduleTaskService _scheduleTaskService; private readonly IProviderManager _providerManager; + private readonly DataExchangeSettings _dataExchangeSettings; public ExportService( IRepository exportProfileRepository, IRepository exportDeploymentRepository, IEventPublisher eventPublisher, IScheduleTaskService scheduleTaskService, - IProviderManager providerManager) + IProviderManager providerManager, + DataExchangeSettings dataExchangeSettings) { _exportProfileRepository = exportProfileRepository; _exportDeploymentRepository = exportDeploymentRepository; _eventPublisher = eventPublisher; _scheduleTaskService = scheduleTaskService; _providerManager = providerManager; + _dataExchangeSettings = dataExchangeSettings; } #region Export profiles @@ -53,7 +56,7 @@ public virtual ExportProfile InsertExportProfile(Provider provi var folderName = SeoHelper.GetSeName(name, true, false) .Replace("/", "") .ToValidPath() - .Truncate(20); + .Truncate(_dataExchangeSettings.MaxFileNameLength); var taskType = (new ExportProfileTask()).GetType().AssemblyQualifiedNameWithoutVersion(); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 0e09d94efc..9d5f453c57 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -86,6 +86,7 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile { model.Id = profile.Id; model.Name = profile.Name; + model.FolderName = profile.FolderName; model.Enabled = profile.Enabled; model.SchedulingHours = profile.ScheduleTask.Seconds / 3600; @@ -119,6 +120,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile var allStores = _services.StoreService.GetAllStores(); var allLanguages = _languageService.GetAllLanguages(true); var allCurrencies = _currencyService.GetAllCurrencies(true); + var allEmailAccounts = _emailAccountService.GetAllEmailAccounts(); model.AllString = T("Admin.Common.All"); model.UnspecifiedString = T("Common.Unspecified"); @@ -127,10 +129,17 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile model.Limit = profile.Limit; model.BatchSize = profile.BatchSize; model.PerStore = profile.PerStore; + model.EmailAccountId = profile.EmailAccountId; model.CompletedEmailAddresses = profile.CompletedEmailAddresses; model.CreateZipArchive = profile.CreateZipArchive; model.Cleanup = profile.Cleanup; + model.AvailableEmailAccounts = allEmailAccounts + .Select(x => new SelectListItem { Text = x.FriendlyName, Value = x.Id.ToString() }) + .ToList(); + + model.SerializedCompletedEmailAddresses = string.Join(",", profile.CompletedEmailAddresses.SplitSafe(",").Select(x => x.EncodeJsString())); + if (provider != null) { model.Provider.ProjectionSupport = provider.Metadata.ExportProjectionSupport; @@ -187,12 +196,16 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile PictureSize = projection.PictureSize, ShippingTime = projection.ShippingTime, ShippingCosts = projection.ShippingCosts, - FreeShippingThreshold = projection.FreeShippingThreshold + FreeShippingThreshold = projection.FreeShippingThreshold, + AttributeCombinationAsProduct = projection.AttributeCombinationAsProduct }; model.ProductProjection.AvailableDescriptionMergings = ExportDescriptionMerging.Description.ToSelectList(false); model.ProductProjection.AvailablePriceTypes = PriceDisplayType.LowestPrice.ToSelectList(false); + model.ProductProjection.SerializedAppendDescriptionText = string.Join(",", projection.AppendDescriptionText.SplitSafe(",").Select(x => x.EncodeJsString())); + model.ProductProjection.SerializedCriticalCharacters = string.Join(",", projection.CriticalCharacters.SplitSafe(",").Select(x => x.EncodeJsString())); + initProjectionBase(model.ProductProjection); } @@ -285,6 +298,7 @@ private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment Name = deployment.Name, Enabled = deployment.Enabled, IsPublic = deployment.IsPublic, + CreateZip = deployment.CreateZip, DeploymentType = deployment.DeploymentType, DeploymentTypeName = deployment.DeploymentType.GetLocalizedEnum(_services.Localization, _services.WorkContext), Username = deployment.Username, @@ -298,6 +312,8 @@ private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment model.AvailableDeploymentTypes = ExportDeploymentType.FileSystem.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(); @@ -317,6 +333,7 @@ private void ModelToEntity(ExportDeploymentModel model, ExportDeployment deploym deployment.Enabled = model.Enabled; deployment.DeploymentType = model.DeploymentType; deployment.IsPublic = model.IsPublic; + deployment.CreateZip = model.CreateZip; deployment.Username = model.Username; deployment.Password = model.Password; deployment.Url = model.Url; @@ -469,6 +486,7 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) } profile.Name = model.Name; + profile.FolderName = model.FolderName; profile.Enabled = model.Enabled; profile.ScheduleTask.Seconds = model.SchedulingHours * 3600; profile.Offset = model.Offset; @@ -476,6 +494,7 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) profile.BatchSize = model.BatchSize; profile.PerStore = model.PerStore; profile.CompletedEmailAddresses = model.CompletedEmailAddresses; + profile.EmailAccountId = model.EmailAccountId ?? 0; profile.CreateZipArchive = model.CreateZipArchive; profile.Cleanup = model.Cleanup; @@ -510,7 +529,8 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) PictureSize = model.ProductProjection.PictureSize, ShippingTime = model.ProductProjection.ShippingTime, ShippingCosts = model.ProductProjection.ShippingCosts, - FreeShippingThreshold = model.ProductProjection.FreeShippingThreshold + FreeShippingThreshold = model.ProductProjection.FreeShippingThreshold, + AttributeCombinationAsProduct = model.ProductProjection.AttributeCombinationAsProduct }; getProjectionBase(model.ProductProjection); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs index 9bc0ccb560..719fa0c9f4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs @@ -50,6 +50,8 @@ public string DeploymentTypeIconClass } } + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.CreateZip")] + public bool CreateZip { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.IsPublic")] public bool IsPublic { get; set; } @@ -69,6 +71,7 @@ public string DeploymentTypeIconClass [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.EmailAddresses")] public string EmailAddresses { get; set; } + public string SerializedEmailAddresses { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.EmailSubject")] public string EmailSubject { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 52d92a18cb..4c475cc208 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -19,6 +19,9 @@ public partial class ExportProfileModel : EntityModelBase [SmartResourceDisplayName("Admin.Configuration.Export.Name")] public string Name { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.FolderName")] + public string FolderName { get; set; } + [SmartResourceDisplayName("Common.Enabled")] public bool Enabled { get; set; } @@ -41,8 +44,13 @@ public partial class ExportProfileModel : EntityModelBase [SmartResourceDisplayName("Admin.Configuration.Export.PerStore")] public bool PerStore { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.EmailAccountId")] + public int? EmailAccountId { get; set; } + public List AvailableEmailAccounts { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.CompletedEmailAddresses")] public string CompletedEmailAddresses { get; set; } + public string SerializedCompletedEmailAddresses { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.CreateZipArchive")] public bool CreateZipArchive { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs index 9e61bc4741..ea83709ac7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs @@ -37,12 +37,14 @@ public class ExportProductProjectionModel : ExportProjectionModelBase [SmartResourceDisplayName("Admin.Configuration.Export.Projection.AppendDescriptionText")] [AllowHtml] public string AppendDescriptionText { get; set; } + public string SerializedAppendDescriptionText { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Projection.RemoveCriticalCharacters")] public bool RemoveCriticalCharacters { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Projection.CriticalCharacters")] public string CriticalCharacters { get; set; } + public string SerializedCriticalCharacters { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Projection.PriceType")] public PriceDisplayType? PriceType { get; set; } @@ -65,5 +67,8 @@ public class ExportProductProjectionModel : ExportProjectionModelBase [SmartResourceDisplayName("Admin.Configuration.Export.Projection.FreeShippingThreshold")] public decimal? FreeShippingThreshold { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.AttributeCombinationAsProduct")] + public bool AttributeCombinationAsProduct { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs index 1e45c61a59..429d652155 100644 --- a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs +++ b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs @@ -1,4 +1,6 @@ -using FluentValidation; +using System.IO; +using System.Linq; +using FluentValidation; using SmartStore.Admin.Models.DataExchange; using SmartStore.Services.Localization; @@ -12,6 +14,10 @@ public ExportProfileValidator(ILocalizationService localization) .NotEmpty() .WithMessage(localization.GetResource("Admin.Validation.Name")); + RuleFor(x => x.FolderName) + .Must(x => x.HasValue() && !x.IsCaseInsensitiveEqual("con") && !Path.GetInvalidFileNameChars().Any(y => x.Contains(y))) + .WithMessage(localization.GetResource("Admin.Configuration.Export.FolderName.Validate")); + RuleFor(x => x.Offset) .LessThanOrEqualTo(x => x.Limit) .WithMessage(localization.GetResource("Admin.Configuration.Export.LimitOffsetValidation")); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 54903752fe..cdb981e184 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -128,11 +128,11 @@ @@ -144,6 +144,15 @@ @Html.ValidationMessageFor(x => x.SchedulingHours) + + + +
+ @Html.SmartLabelFor(x => x.ProductProjection.StoreId) + + @Html.DropDownListFor(x => x.ProductProjection.StoreId, Model.ProductProjection.AvailableStores, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.ProductProjection.StoreId) +
@Html.SmartLabelFor(x => x.ProductProjection.LanguageId) @@ -41,8 +50,65 @@ @Html.ValidationMessageFor(x => x.ProductProjection.PriceType)
+ @Html.SmartLabelFor(x => x.ProductProjection.ConvertNetToGrossPrices) + + @Html.EditorFor(x => x.ProductProjection.ConvertNetToGrossPrices) + @Html.ValidationMessageFor(x => x.ProductProjection.ConvertNetToGrossPrices) +
+ @Html.SmartLabelFor(x => x.ProductProjection.ShippingTime) + + @Html.EditorFor(x => x.ProductProjection.ShippingTime) + @Html.ValidationMessageFor(x => x.ProductProjection.ShippingTime) +
+ @Html.SmartLabelFor(x => x.ProductProjection.ShippingCosts) + + @Html.EditorFor(x => x.ProductProjection.ShippingCosts) + @Html.ValidationMessageFor(x => x.ProductProjection.ShippingCosts) +
+ @Html.SmartLabelFor(x => x.ProductProjection.FreeShippingThreshold) + + @Html.EditorFor(x => x.ProductProjection.FreeShippingThreshold) + @Html.ValidationMessageFor(x => x.ProductProjection.FreeShippingThreshold) +
+ @Html.SmartLabelFor(x => x.ProductProjection.PictureSize) + + @Html.EditorFor(x => x.ProductProjection.PictureSize) + @Html.ValidationMessageFor(x => x.ProductProjection.PictureSize) +
@@ -54,8 +120,8 @@
From af31efc33fe092e47fab35df0d19b626ba170843 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 20 Aug 2015 12:59:13 +0200 Subject: [PATCH 014/874] Extended product searching for export filtering --- .../Domain/DataExchange/ExportFilter.cs | 8 +-- .../201508091512101_ExportFramework.cs | 24 ++++--- .../Sql/LatestProductLoadAllPaged.sql | 45 ++++++++++++ .../Catalog/ProductSearchContext.cs | 30 ++++++++ .../Catalog/ProductService.cs | 68 ++++++++++++++++++- .../DataExchange/ExportProfileTask.cs | 18 ++++- .../Controllers/ExportController.cs | 8 +-- .../Models/DataExchange/ExportFilterModel.cs | 8 +-- .../Administration/Views/Export/Edit.cshtml | 2 +- .../Export/_CreateOrUpdate.Filter.cshtml | 18 ++--- 10 files changed, 194 insertions(+), 35 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs index e078979f29..7675d646dc 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs @@ -73,9 +73,9 @@ public class ExportFilter public bool? WithoutCategories { get; set; } /// - /// Manufacturer identifiers + /// Manufacturer identifier /// - public int[] ManufacturerIds { get; set; } + public int ManufacturerId { get; set; } /// /// A value indicating whether to load products without any manufacturer mapping @@ -83,9 +83,9 @@ public class ExportFilter public bool? WithoutManufacturers { get; set; } /// - /// Identifiers of product tags + /// Identifiers of product tag /// - public int[] ProductTagIds { get; set; } + public int ProductTagId { get; set; } /// /// A value indicating whether to load products that are marked as featured (relates only to categories and manufacturers) diff --git a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs index 22159be80c..819f61f7dc 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs @@ -1,6 +1,8 @@ namespace SmartStore.Data.Migrations { using System.Data.Entity.Migrations; + using System.Web.Hosting; + using SmartStore.Core.Data; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Security; using SmartStore.Data.Setup; @@ -55,7 +57,11 @@ public override void Up() .PrimaryKey(t => t.Id) .ForeignKey("dbo.ScheduleTask", t => t.SchedulingTaskId) .Index(t => t.SchedulingTaskId); - + + if (HostingEnvironment.IsHosted && DataSettings.Current.IsSqlServer) + { + this.SqlFileOrResource("LatestProductLoadAllPaged.sql"); + } } public override void Down() @@ -286,10 +292,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Filter by missing category mapping.", "Nach fehlender Warengruppenzuordnung filtern."); - builder.AddOrUpdate("Admin.Configuration.Export.Filter.ManufacturerIds", - "Manufacturers", + builder.AddOrUpdate("Admin.Configuration.Export.Filter.ManufacturerId", + "Manufacturer", "Hersteller", - "Filter by manufacturers.", + "Filter by manufacturer.", "Nach Hersteller filtern."); builder.AddOrUpdate("Admin.Configuration.Export.Filter.WithoutManufacturers", @@ -298,11 +304,11 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Filter by missing manufacturer mapping.", "Nach fehlender Herstellerzuordnung filtern."); - builder.AddOrUpdate("Admin.Configuration.Export.Filter.ProductTagIds", - "Product tags", - "Produkt-Tags", - "Filter by product tags.", - "Nach Produkt-Tags filtern."); + builder.AddOrUpdate("Admin.Configuration.Export.Filter.ProductTagId", + "Product tag", + "Produkt-Tag", + "Filter by product tag.", + "Nach Produkt-Tag filtern."); builder.AddOrUpdate("Admin.Configuration.Export.Filter.FeaturedProducts", "Only featured products", diff --git a/src/Libraries/SmartStore.Data/Sql/LatestProductLoadAllPaged.sql b/src/Libraries/SmartStore.Data/Sql/LatestProductLoadAllPaged.sql index fa7f458209..522b70dc27 100644 --- a/src/Libraries/SmartStore.Data/Sql/LatestProductLoadAllPaged.sql +++ b/src/Libraries/SmartStore.Data/Sql/LatestProductLoadAllPaged.sql @@ -28,6 +28,12 @@ @WithoutManufacturers bit = null, @IsPublished bit = null, @HomePageProducts bit = null, + @IdMin int = 0, + @IdMax int = 0, + @AvailabilityMin int = null, + @AvailabilityMax int = null, + @CreatedFromUtc nvarchar(MAX) = null, + @CreatedToUtc nvarchar(MAX) = null, @FilterableSpecificationAttributeOptionIds nvarchar(MAX) = null OUTPUT, --the specification attribute option identifiers applied to loaded products (all pages). returned as a comma separated list of identifiers @TotalRecords int = null OUTPUT ) @@ -500,6 +506,45 @@ BEGIN WHERE [sm].EntityId = p.Id AND [sm].EntityName = ''Product'' and [sm].StoreId=' + CAST(@StoreId AS nvarchar(max)) + ' ))' END + + --filter by product identifier + IF @IdMin != 0 + BEGIN + SET @sql = @sql + ' + AND p.Id >= ' + CAST(@IdMin AS nvarchar(max)) + END + + IF @IdMax != 0 + BEGIN + SET @sql = @sql + ' + AND p.Id <= ' + CAST(@IdMax AS nvarchar(max)) + END + + --filter by availability + IF @AvailabilityMin is not null + BEGIN + SET @sql = @sql + ' + AND p.StockQuantity >= ' + CAST(@AvailabilityMin AS nvarchar(max)) + END + + IF @AvailabilityMax is not null + BEGIN + SET @sql = @sql + ' + AND p.StockQuantity <= ' + CAST(@AvailabilityMax AS nvarchar(max)) + END + + --filter by creation date + IF @CreatedFromUtc is not null + BEGIN + SET @sql = @sql + ' + AND p.CreatedOnUtc >= ''' + CAST(@CreatedFromUtc AS nvarchar(max)) + '''' + END + + IF @CreatedToUtc is not null + BEGIN + SET @sql = @sql + ' + AND p.CreatedOnUtc <= ''' + CAST(@CreatedToUtc AS nvarchar(max)) + '''' + END --filter by specs IF @SpecAttributesCount > 0 diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductSearchContext.cs b/src/Libraries/SmartStore.Services/Catalog/ProductSearchContext.cs index 2bbaf0dc0f..001e18789b 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductSearchContext.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductSearchContext.cs @@ -31,6 +31,16 @@ public ProductSearchContext() /// Only implemented in LINQ mode at the moment public IList ProductIds { get; set; } + /// + /// Minimum product identifier + /// + public int IdMin { get; set; } + + /// + /// Maximum product identifier + /// + public int IdMax { get; set; } + /// /// A value indicating whether ALL given must be assigned to the resulting products (default is ANY) /// @@ -168,5 +178,25 @@ public ProductSearchContext() /// A value indicating whether to load only products displayed on the homepage /// public bool? HomePageProducts { get; set; } + + /// + /// Search by minimum availability + /// + public int? AvailabilityMinimum { get; set; } + + /// + /// Search by maximum availability + /// + public int? AvailabilityMaximum { get; set; } + + /// + /// Search by created from date + /// + public DateTime? CreatedFromUtc { get; set; } + + /// + /// Search by created to date + /// + public DateTime? CreatedToUtc { get; set; } } } diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs index fd762a1f2b..f1c78600de 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.ServiceModel.Syndication; @@ -578,7 +579,7 @@ public virtual IPagedList SearchProducts(ProductSearchContext ctx) pWithoutManufacturers.DbType = DbType.Boolean; var pIsPublished = _dataProvider.GetParameter(); - pIsPublished.ParameterName = "OverridePublished"; + pIsPublished.ParameterName = "IsPublished"; pIsPublished.Value = (ctx.IsPublished.HasValue ? (object)ctx.IsPublished.Value : DBNull.Value); pIsPublished.DbType = DbType.Boolean; @@ -587,6 +588,37 @@ public virtual IPagedList SearchProducts(ProductSearchContext ctx) pHomePageProducts.Value = (ctx.HomePageProducts.HasValue ? (object)ctx.HomePageProducts.Value : DBNull.Value); pHomePageProducts.DbType = DbType.Boolean; + var pIdMin = _dataProvider.GetParameter(); + pIdMin.ParameterName = "IdMin"; + pIdMin.Value = ctx.IdMin; + pIdMin.DbType = DbType.Int32; + + var pIdMax = _dataProvider.GetParameter(); + pIdMax.ParameterName = "IdMax"; + pIdMax.Value = ctx.IdMin; + pIdMax.DbType = DbType.Int32; + + var pAvailabilityMin = _dataProvider.GetParameter(); + pAvailabilityMin.ParameterName = "AvailabilityMin"; + pAvailabilityMin.Value = ctx.AvailabilityMinimum.HasValue ? (object)ctx.AvailabilityMinimum.Value : DBNull.Value; + pAvailabilityMin.DbType = DbType.Int32; + + var pAvailabilityMax = _dataProvider.GetParameter(); + pAvailabilityMax.ParameterName = "AvailabilityMax"; + pAvailabilityMax.Value = ctx.AvailabilityMaximum.HasValue ? (object)ctx.AvailabilityMaximum.Value : DBNull.Value; + pAvailabilityMax.DbType = DbType.Int32; + + var pCreatedFromUtc = _dataProvider.GetParameter(); + pCreatedFromUtc.ParameterName = "CreatedFromUtc"; + pCreatedFromUtc.Value = ctx.CreatedFromUtc.HasValue ? (object)ctx.CreatedFromUtc.Value.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) : DBNull.Value; + pCreatedFromUtc.DbType = DbType.String; + + var pCreatedToUtc = _dataProvider.GetParameter(); + pCreatedToUtc.ParameterName = "CreatedToUtc"; + pCreatedToUtc.Value = ctx.CreatedToUtc.HasValue ? (object)ctx.CreatedToUtc.Value.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture) : DBNull.Value; + pCreatedToUtc.DbType = DbType.String; + + var pFilterableSpecificationAttributeOptionIds = _dataProvider.GetParameter(); pFilterableSpecificationAttributeOptionIds.ParameterName = "FilterableSpecificationAttributeOptionIds"; pFilterableSpecificationAttributeOptionIds.Direction = ParameterDirection.Output; @@ -629,6 +661,12 @@ public virtual IPagedList SearchProducts(ProductSearchContext ctx) pWithoutManufacturers, pIsPublished, pHomePageProducts, + pIdMin, + pIdMax, + pAvailabilityMin, + pAvailabilityMax, + pCreatedFromUtc, + pCreatedToUtc, pFilterableSpecificationAttributeOptionIds, pTotalRecords); @@ -803,6 +841,34 @@ public virtual IQueryable PrepareProductSearchQuery( { query = query.Where(x => ctx.ProductIds.Contains(x.Id)); } + else + { + if (ctx.IdMin != 0) + query = query.Where(x => x.Id >= ctx.IdMin); + + if (ctx.IdMax != 0) + query = query.Where(x => x.Id <= ctx.IdMax); + } + + if (ctx.AvailabilityMinimum.HasValue) + { + query = query.Where(x => x.StockQuantity >= ctx.AvailabilityMinimum.Value); + } + + if (ctx.AvailabilityMaximum.HasValue) + { + query = query.Where(x => x.StockQuantity <= ctx.AvailabilityMaximum.Value); + } + + if (ctx.CreatedFromUtc.HasValue) + { + query = query.Where(x => x.CreatedOnUtc >= ctx.CreatedFromUtc.Value); + } + + if (ctx.CreatedToUtc.HasValue) + { + query = query.Where(x => x.CreatedOnUtc <= ctx.CreatedToUtc.Value); + } //The function 'CurrentUtcDateTime' is not supported by SQL Server Compact. //That's why we pass the date value diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index 5a6503d8bf..c5ea0e5664 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -23,6 +23,7 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Customers; using SmartStore.Services.Directory; +using SmartStore.Services.Helpers; using SmartStore.Services.Media; using SmartStore.Services.Tasks; using SmartStore.Services.Tax; @@ -47,6 +48,7 @@ public class ExportProfileTask : ITask private ICustomerService _customerService; private ICategoryService _categoryService; private IPriceFormatter _priceFormatter; + private IDateTimeHelper _dateTimeHelper; #region Utilities @@ -213,6 +215,7 @@ private void InitDependencies(TaskExecutionContext context) _customerService = context.Resolve(); _categoryService = context.Resolve(); _priceFormatter = context.Resolve(); + _dateTimeHelper = context.Resolve(); } private IEnumerable GetProducts(ExportProfileTaskContext ctx, int pageIndex) @@ -233,13 +236,26 @@ private IEnumerable GetProducts(ExportProfileTaskContext ctx, int pageI IsPublished = ctx.Filter.IsPublished, WithoutCategories = ctx.Filter.WithoutCategories, WithoutManufacturers = ctx.Filter.WithoutManufacturers, + ManufacturerId = ctx.Filter.ManufacturerId, FeaturedProducts = ctx.Filter.FeaturedProducts, - ProductType = ctx.Filter.ProductType + ProductType = ctx.Filter.ProductType, + ProductTagId = ctx.Filter.ProductTagId, + IdMin = ctx.Filter.IdMinimum ?? 0, + IdMax = ctx.Filter.IdMaximum ?? 0, + AvailabilityMinimum = ctx.Filter.AvailabilityMinimum, + AvailabilityMaximum = ctx.Filter.AvailabilityMaximum }; if (ctx.Filter.CategoryIds != null && ctx.Filter.CategoryIds.Length > 0) searchContext.CategoryIds = ctx.Filter.CategoryIds.ToList(); + if (ctx.Filter.CreatedFrom.HasValue) + searchContext.CreatedFromUtc = _dateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _dateTimeHelper.CurrentTimeZone); + + if (ctx.Filter.CreatedTo.HasValue) + searchContext.CreatedToUtc = _dateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedTo.Value, _dateTimeHelper.CurrentTimeZone); + + var products = _productService.SearchProducts(searchContext); foreach (var product in products) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 199743c419..0e09d94efc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -224,9 +224,9 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile IsPublished = filter.IsPublished, CategoryIds = filter.CategoryIds, WithoutCategories = filter.WithoutCategories, - ManufacturerIds = filter.ManufacturerIds, + ManufacturerId = filter.ManufacturerId, WithoutManufacturers = filter.WithoutManufacturers, - ProductTagIds = filter.ProductTagIds, + ProductTagId = filter.ProductTagId, FeaturedProducts = filter.FeaturedProducts, ProductType = filter.ProductType, IdMinimum = filter.IdMinimum, @@ -538,9 +538,9 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) IsPublished = model.ProductFilter.IsPublished, CategoryIds = model.ProductFilter.CategoryIds, WithoutCategories = model.ProductFilter.WithoutCategories, - ManufacturerIds = model.ProductFilter.ManufacturerIds, + ManufacturerId = model.ProductFilter.ManufacturerId, WithoutManufacturers = model.ProductFilter.WithoutManufacturers, - ProductTagIds = model.ProductFilter.ProductTagIds, + ProductTagId = model.ProductFilter.ProductTagId, FeaturedProducts = model.ProductFilter.FeaturedProducts, ProductType = model.ProductFilter.ProductType, IdMinimum = model.ProductFilter.IdMinimum, diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs index ecfc508539..1372ce348a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs @@ -57,15 +57,15 @@ public class ExportProductFilterModel : ExportFilterModelBase [SmartResourceDisplayName("Admin.Configuration.Export.Filter.WithoutCategories")] public bool? WithoutCategories { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ManufacturerIds")] - public int[] ManufacturerIds { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ManufacturerId")] + public int ManufacturerId { get; set; } public List AvailableManufacturers { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Filter.WithoutManufacturers")] public bool? WithoutManufacturers { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ProductTagIds")] - public int[] ProductTagIds { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.ProductTagId")] + public int ProductTagId { get; set; } public List AvailableProductTags { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Filter.FeaturedProducts")] diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 570a62d05c..54903752fe 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -306,7 +306,7 @@ // show/hide product manufacturer filter $('#@(Html.FieldIdFor(x => x.ProductFilter.WithoutManufacturers))').change(function () { - $('#FilterManufacturerIdsContainer').toggle(_.isEmpty($(this).val())); + $('#FilterManufacturerIdContainer').toggle(_.isEmpty($(this).val())); }).trigger('change'); // show/hide store projection diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml index 10439ddeb5..c2da3fbc05 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml @@ -117,26 +117,22 @@ @Html.ValidationMessageFor(x => x.ProductFilter.WithoutManufacturers)
- @Html.SmartLabelFor(x => x.ProductFilter.ManufacturerIds) + @Html.SmartLabelFor(x => x.ProductFilter.ManufacturerId) - @Html.ListBoxFor(x => x.ProductFilter.ManufacturerIds, - new MultiSelectList(Model.ProductFilter.AvailableManufacturers, "Value", "Text"), - new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(x => x.ProductFilter.ManufacturerIds) + @Html.DropDownListFor(x => x.ProductFilter.ManufacturerId, Model.ProductFilter.AvailableManufacturers, Model.AllString) + @Html.ValidationMessageFor(x => x.ProductFilter.ManufacturerId)
- @Html.SmartLabelFor(x => x.ProductFilter.ProductTagIds) + @Html.SmartLabelFor(x => x.ProductFilter.ProductTagId) - @Html.ListBoxFor(x => x.ProductFilter.ProductTagIds, - new MultiSelectList(Model.ProductFilter.AvailableProductTags, "Value", "Text"), - new { multiple = "multiple", @class = "control-xlarge" }) - @Html.ValidationMessageFor(x => x.ProductFilter.ProductTagIds) + @Html.DropDownListFor(x => x.ProductFilter.ProductTagId, Model.ProductFilter.AvailableProductTags, Model.AllString) + @Html.ValidationMessageFor(x => x.ProductFilter.ProductTagId)
- @Html.SmartLabelFor(x => x.Enabled) + @Html.SmartLabelFor(x => x.FolderName) - @Html.EditorFor(x => x.Enabled) - @Html.ValidationMessageFor(x => x.Enabled) + @Html.EditorFor(x => x.FolderName) + @Html.ValidationMessageFor(x => x.FolderName)
+ @Html.SmartLabelFor(x => x.Enabled) + + @Html.EditorFor(x => x.Enabled) + @Html.ValidationMessageFor(x => x.Enabled) +
} @@ -236,13 +245,13 @@ .RouteKey("Id")) .Columns(columns => { - columns.Bound(x => x.Name) - .Template(x => Html.ActionLink(x.Name, "EditDeployment", "Export", new { id = x.Id }, null)) - .ClientTemplate("\"><#= Name #>"); columns.Bound(x => x.DeploymentTypeName) .Template(x => " {1}".FormatInvariant(x.DeploymentTypeIconClass, x.DeploymentTypeName)) .ClientTemplate(" <#= DeploymentTypeName #>") - .Width(320); + .Width(280); + columns.Bound(x => x.Name) + .Template(x => Html.ActionLink(x.Name, "EditDeployment", "Export", new { id = x.Id }, null)) + .ClientTemplate("\"><#= Name #>"); columns.Bound(x => x.Enabled) .Width(100) .Template(item => @Html.SymbolForBool(item.Enabled)) @@ -265,11 +274,20 @@ + + + + @@ -299,6 +317,16 @@ \ 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 3aca539cd7..1bb68613ac 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml @@ -36,6 +36,15 @@ @Html.ValidationMessageFor(x => x.Enabled) + + + + - + @@ -60,7 +69,7 @@ @Html.SmartLabelFor(x => x.EmailAddresses) @@ -116,12 +125,19 @@ \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml index c2da3fbc05..13cbb8ba7c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml @@ -104,7 +104,7 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml index 7e7215d60b..0e238a881f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml @@ -60,6 +60,19 @@ + if (Model.Provider.ProjectionSupport.Contains(ExportProjectionSupport.AttributeCombinationAsProduct)) + { + + + + + } + if (Model.Provider.ProjectionSupport.Contains(ExportProjectionSupport.ShippingTime)) { @@ -144,7 +157,7 @@ @Html.SmartLabelFor(x => x.ProductProjection.AppendDescriptionText) @@ -162,7 +175,7 @@ @Html.SmartLabelFor(x => x.ProductProjection.CriticalCharacters) From 2b8d01505a027df2594827cf120673c138a678ac Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 25 Aug 2015 22:46:27 +0200 Subject: [PATCH 018/874] Export framework further implementation --- .gitignore | 1 + .../Domain/DataExchange/ExportCore.cs | 2 - .../Domain/DataExchange/ExportDeployment.cs | 5 + .../201508221015215_ExportFramework1.resx | 126 ----- ...508241204123_ExportFramework1.Designer.cs} | 2 +- ...cs => 201508241204123_ExportFramework1.cs} | 8 + .../201508241204123_ExportFramework1.resx | 126 +++++ .../SmartStore.Data/SmartStore.Data.csproj | 10 +- .../Catalog/IManufacturerService.cs | 8 + .../Catalog/IProductService.cs | 7 + .../Catalog/ManufacturerService.cs | 20 + .../Catalog/PriceCalculationContext.cs | 14 +- .../Catalog/ProductAttributeService.cs | 7 +- .../Catalog/ProductService.cs | 16 + .../DataExchange/ExpandoExtensions.cs | 126 ++--- .../DataExchange/ExportDataContext.cs | 76 +++ .../DataExchange/ExportProfileTask.cs | 500 +++++++++++++----- .../DataExchange/ExportSegmenter.cs | 10 +- .../SmartStore.Services.csproj | 2 + .../SmartStore.Services/Tasks/TaskExecutor.cs | 46 +- .../Controllers/ExportController.cs | 2 + .../DataExchange/ExportDeploymentModel.cs | 3 + .../Export/_CreateOrUpdate.Deployment.cshtml | 25 +- .../SmartStore.Web/Exchange/placeholder | 1 + 24 files changed, 763 insertions(+), 380 deletions(-) delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.resx rename src/Libraries/SmartStore.Data/Migrations/{201508221015215_ExportFramework1.Designer.cs => 201508241204123_ExportFramework1.Designer.cs} (92%) rename src/Libraries/SmartStore.Data/Migrations/{201508221015215_ExportFramework1.cs => 201508241204123_ExportFramework1.cs} (87%) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.resx create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportDataContext.cs create mode 100644 src/Presentation/SmartStore.Web/Exchange/placeholder diff --git a/.gitignore b/.gitignore index c95b6dbc19..d610d653ae 100644 --- a/.gitignore +++ b/.gitignore @@ -207,6 +207,7 @@ Kopie von* # Import/Export /src/Presentation/SmartStore.Web/Content/files/[Ee]xport[Ii]mport/*.* +/src/Presentation/SmartStore.Web/[Ee]xchange/*.* # Media /src/Presentation/SmartStore.Web/Media/[Tt]humbs diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs index 24ed68c33e..a36a4626d1 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs @@ -37,10 +37,8 @@ public enum ExportProjectionSupport Brand, MainPictureUrl, UseOwnProductNo, - CategoryPath, ShippingTime, ShippingCosts, - Availability, AttributeCombinationAsProduct, OldPrice } diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs index 2ad338838b..04ce663452 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs @@ -58,6 +58,11 @@ public ExportDeploymentType DeploymentType /// public string Url { get; set; } + /// + /// Whether to use a multipart form to upload files via HTTP + /// + public bool MultipartForm { get; set; } + /// /// The file system path /// diff --git a/src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.resx b/src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.resx deleted file mode 100644 index 827c791dca..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.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/201508221015215_ExportFramework1.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.Designer.cs similarity index 92% rename from src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.Designer.cs rename to src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.Designer.cs index fc5b31ee8a..7c9f6ca9c8 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.Designer.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.Designer.cs @@ -13,7 +13,7 @@ public sealed partial class ExportFramework1 : IMigrationMetadata string IMigrationMetadata.Id { - get { return "201508221015215_ExportFramework1"; } + get { return "201508241204123_ExportFramework1"; } } string IMigrationMetadata.Source diff --git a/src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.cs similarity index 87% rename from src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.cs rename to src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.cs index 305c4c27f1..7d93c0616a 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508221015215_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.cs @@ -9,12 +9,14 @@ public partial class ExportFramework1 : DbMigration, ILocaleResourcesProvider, I public override void Up() { AddColumn("dbo.ExportDeployment", "CreateZip", c => c.Boolean(nullable: false)); + AddColumn("dbo.ExportDeployment", "MultipartForm", c => c.Boolean(nullable: false)); AddColumn("dbo.ExportProfile", "EmailAccountId", c => c.Int(nullable: false)); } public override void Down() { DropColumn("dbo.ExportProfile", "EmailAccountId"); + DropColumn("dbo.ExportDeployment", "MultipartForm"); DropColumn("dbo.ExportDeployment", "CreateZip"); } @@ -72,6 +74,12 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Attributkombinationen exportieren", "Specifies whether to export all active attribute combinations as a standalone product in addition to each product.", "Legt fest, ob zustzlich zu jedem Produkt alle seine aktiven Attributkombinationen als eigenstndiges Produkt exportiert werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.MultipartForm", + "Multipart form", + "Multipart-Form", + "Specifies whether to transmit the export files as multipart form data via HTTP.", + "Legt fest, ob die Exportdateien als Multipart-Form-Data per HTTP bertragen werden sollen."); } } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.resx b/src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.resx new file mode 100644 index 0000000000..b43be88c85 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.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 e5424dc4d2..034dc73d4c 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -341,9 +341,9 @@ 201508211346171_Merge1.cs - - - 201508221015215_ExportFramework1.cs + + + 201508241204123_ExportFramework1.cs @@ -648,8 +648,8 @@ 201508211346171_Merge1.cs - - 201508221015215_ExportFramework1.cs + + 201508241204123_ExportFramework1.cs diff --git a/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs b/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs index 63f8b647a8..43813c2f58 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; @@ -85,6 +86,13 @@ IPagedList GetProductManufacturersByManufacturerId(int manu /// Product manufacturer mapping collection IList GetProductManufacturersByProductId(int productId, bool showHidden = false); + /// + /// Get product manufacturer by product identifiers + /// + /// Product identifiers + /// Product manufacturers + Multimap GetProductManufacturersByProductIds(int[] productIds); + /// /// Gets a product manufacturer mapping /// diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs index 2ae693aa22..b42e8c9954 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs @@ -313,6 +313,13 @@ public partial interface IProductService /// Product pictures IList GetProductPicturesByProductId(int productId); + /// + /// Get product pictures by product identifiers + /// + /// Product identifiers + /// Product pictures + Multimap GetProductPicturesByProductIds(int[] productIds); + /// /// Gets a product picture /// diff --git a/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs b/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs index 2bbf0d4f65..d027ba24db 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; @@ -335,6 +336,25 @@ orderby mGroup.Key return productManufacturers; }); } + + public virtual Multimap GetProductManufacturersByProductIds(int[] productIds) + { + Guard.ArgumentNotNull(() => productIds); + + var query = + from pm in _productManufacturerRepository.TableUntracked.Expand(x => x.Manufacturer).Expand(x => x.Manufacturer.Picture) + join m in _manufacturerRepository.TableUntracked on pm.ManufacturerId equals m.Id + where productIds.Contains(pm.ProductId) + select pm; + + var map = query + .OrderBy(x => x.ProductId) + .ThenBy(x => x.DisplayOrder) + .ToList() + .ToMultimap(x => x.ProductId, x => x); + + return map; + } /// /// Gets a product manufacturer mapping diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs index ee316d42c6..261e82e881 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs @@ -11,7 +11,7 @@ namespace SmartStore.Services.Catalog /// public class PriceCalculationContext { - private List _productIds; + protected List _productIds; private List _productIdsTierPrices; private Func> _funcAttributes; @@ -47,6 +47,18 @@ public PriceCalculationContext(IEnumerable products, _funcProductCategories = productCategories; } + public void Clear() + { + if (_attributes != null) + _attributes.Clear(); + if (_attributeCombinations != null) + _attributeCombinations.Clear(); + if (_tierPrices != null) + _tierPrices.Clear(); + if (_productCategories != null) + _productCategories.Clear(); + } + public LazyMultimap Attributes { get diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs index 47dafa0dfc..570d688fc6 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs @@ -208,9 +208,10 @@ public virtual Multimap GetProductVariantAttribute { Guard.ArgumentNotNull(() => productIds); - var query = from pva in _productVariantAttributeRepository.TableUntracked.Expand(x => x.ProductVariantAttributeValues) - where productIds.Contains(pva.ProductId) - select pva; + var query = + from pva in _productVariantAttributeRepository.TableUntracked.Expand(x => x.ProductAttribute).Expand(x => x.ProductVariantAttributeValues) + where productIds.Contains(pva.ProductId) + select pva; if (controlType.HasValue) { diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs index f1c78600de..8082cace64 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs @@ -1833,6 +1833,22 @@ orderby pp.DisplayOrder return productPictures; } + public virtual Multimap GetProductPicturesByProductIds(int[] productIds) + { + var query = + from pp in _productPictureRepository.TableUntracked + where productIds.Contains(pp.ProductId) + select pp; + + var map = query + .OrderBy(x => x.ProductId) + .ThenBy(x => x.DisplayOrder) + .ToList() + .ToMultimap(x => x.ProductId, x => x); + + return map; + } + /// /// Gets a product picture /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs index 4d8d3ecaf4..67a69adb6b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs @@ -1,4 +1,5 @@ -using System.Dynamic; +using System.Collections.Generic; +using System.Dynamic; using System.Linq; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Directory; @@ -18,6 +19,8 @@ public static ExpandoObject ToExpando(this DeliveryTime deliveryTime, int langua return null; dynamic expando = new ExpandoObject(); + expando._Entity = deliveryTime; + expando.Id = deliveryTime.Id; expando.Name = deliveryTime.GetLocalized(x => x.Name, languageId, true, false); expando.DisplayLocale = deliveryTime.DisplayLocale; @@ -33,6 +36,8 @@ public static ExpandoObject ToExpando(this QuantityUnit quantityUnit, int langua return null; dynamic expando = new ExpandoObject(); + expando._Entity = quantityUnit; + expando.Id = quantityUnit.Id; expando.Name = quantityUnit.GetLocalized(x => x.Name, languageId, true, false); expando.Description = quantityUnit.GetLocalized(x => x.Description, languageId, true, false); @@ -49,12 +54,62 @@ public static ExpandoObject ToExpando(this Picture picture, IPictureService pict return null; dynamic expando = new ExpandoObject(); + expando._Entity = picture; + expando.Id = picture.Id; expando.SeoFileName = picture.SeoFilename; expando.MimeType = picture.MimeType; - expando.ThumbImageUrl = pictureService.GetPictureUrl(picture, thumbPictureSize, false, store.Url); - expando.ImageUrl = pictureService.GetPictureUrl(picture, detailsPictureSize, false, store.Url); - expando.FullSizeImageUrl = pictureService.GetPictureUrl(picture, 0, false, store.Url); + + expando._ThumbImageUrl = pictureService.GetPictureUrl(picture, thumbPictureSize, false, store.Url); + expando._ImageUrl = pictureService.GetPictureUrl(picture, detailsPictureSize, false, store.Url); + expando._FullSizeImageUrl = pictureService.GetPictureUrl(picture, 0, false, store.Url); + + return expando as ExpandoObject; + } + + public static ExpandoObject ToExpando(this ProductVariantAttribute pva, int languageId) + { + if (pva == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = pva; + expando.Id = pva.Id; + expando.TextPrompt = pva.TextPrompt; + expando.IsRequired = pva.IsRequired; + expando.AttributeControlTypeId = pva.AttributeControlTypeId; + expando.DisplayOrder = pva.DisplayOrder; + + dynamic attribute = new ExpandoObject(); + attribute._Entity = pva.ProductAttribute; + attribute.Id = pva.ProductAttribute.Id; + attribute.Alias = pva.ProductAttribute.Alias; + attribute.Name = pva.ProductAttribute.GetLocalized(y => y.Name, languageId, true, false); + attribute.Description = pva.ProductAttribute.GetLocalized(y => y.Description, languageId, true, false); + + attribute.Values = pva.ProductVariantAttributeValues + .OrderBy(x => x.DisplayOrder) + .Select(x => + { + dynamic value = new ExpandoObject(); + value._Entity = x; + value.Id = x.Id; + value.Alias = x.Alias; + value.Name = x.GetLocalized(z => z.Name, languageId, true, false); + value.ColorSquaresRgb = x.ColorSquaresRgb; + value.PriceAdjustment = x.PriceAdjustment; + value.WeightAdjustment = x.WeightAdjustment; + value.IsPreSelected = x.IsPreSelected; + value.DisplayOrder = x.DisplayOrder; + value.ValueTypeId = x.ValueTypeId; + value.LinkedProductId = x.LinkedProductId; + value.Quantity = x.Quantity; + + return value as ExpandoObject; + }) + .ToList(); + + expando.Attribute = attribute as ExpandoObject; return expando as ExpandoObject; } @@ -65,6 +120,8 @@ public static ExpandoObject ToExpando(this Manufacturer manufacturer, int langua return null; dynamic expando = new ExpandoObject(); + expando._Entity = manufacturer; + expando.Id = manufacturer.Id; expando.Name = manufacturer.GetLocalized(x => x.Name, languageId, true, false); expando.SeName = manufacturer.GetSeName(languageId, true, false); @@ -93,6 +150,8 @@ public static ExpandoObject ToExpando(this Category category, int languageId) return null; dynamic expando = new ExpandoObject(); + expando._Entity = category; + expando.Id = category.Id; expando.Name = category.GetLocalized(x => x.Name, languageId, true, false); expando.FullName = category.GetLocalized(x => x.FullName, languageId, true, false); @@ -129,6 +188,8 @@ public static ExpandoObject ToExpando(this Product product, int languageId) return null; dynamic expando = new ExpandoObject(); + expando._Entity = product; + expando.Id = product.Id; expando.Name = product.GetLocalized(x => x.Name, languageId, true, false); expando.SeName = product.GetSeName(languageId, true, false); @@ -226,64 +287,7 @@ public static ExpandoObject ToExpando(this Product product, int languageId) expando.LowestAttributeCombinationPrice = product.LowestAttributeCombinationPrice; expando.IsEsd = product.IsEsd; - expando.DeliveryTime = product.DeliveryTimeId == 0 ? null : product.DeliveryTime.ToExpando(languageId); - expando.QuantityUnit = product.QuantityUnitId == 0 ? null : product.QuantityUnit.ToExpando(languageId); - return expando as ExpandoObject; } - - //public static ExpandoObject ToExpando(this Product product, int languageId, IPictureService pictureService, MediaSettings mediaSettings, Store store) - //{ - // dynamic expando = product.ToExpando(languageId); - - // expando.DeliveryTime = product.DeliveryTimeId == 0 ? null : product.DeliveryTime.ToExpando(languageId); - // expando.QuantityUnit = product.QuantityUnitId == 0 ? null : product.QuantityUnit.ToExpando(languageId); - - // // pictures - // expando.ProductPictures = product.ProductPictures - // .OrderBy(x => x.DisplayOrder) - // .Select(x => - // { - // dynamic exp = new ExpandoObject(); - // exp.Id = x.Id; - // exp.DisplayOrder = x.DisplayOrder; - // exp.Picture = x.Picture.ToExpando(pictureService, store, mediaSettings.ProductThumbPictureSize, mediaSettings.ProductDetailsPictureSize); - - // return exp as ExpandoObject; - // }) - // .ToList(); - - // // manufacturers - // expando.ProductManufacturers = product.ProductManufacturers - // .OrderBy(x => x.DisplayOrder) - // .Select(x => - // { - // dynamic exp = new ExpandoObject(); - // exp.Id = x.Id; - // exp.DisplayOrder = x.DisplayOrder; - // exp.IsFeaturedProduct = x.IsFeaturedProduct; - // exp.Manufacturer = x.Manufacturer.ToExpando(languageId); - - // return exp as ExpandoObject; - // }) - // .ToList(); - - // // categories - // expando.ProductCategories = product.ProductCategories - // .OrderBy(x => x.DisplayOrder) - // .Select(x => - // { - // dynamic exp = new ExpandoObject(); - // exp.Id = x.Id; - // exp.DisplayOrder = x.DisplayOrder; - // exp.IsFeaturedProduct = x.IsFeaturedProduct; - // exp.Category = x.Category.ToExpando(languageId); - - // return exp as ExpandoObject; - // }) - // .ToList(); - - // return expando as ExpandoObject; - //} } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportDataContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportDataContext.cs new file mode 100644 index 0000000000..7a1cf6a452 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportDataContext.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using SmartStore.Collections; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Services.Catalog; + +namespace SmartStore.Services.DataExchange +{ + public class ExportDataContext : PriceCalculationContext + { + private Func> _funcProductManufacturers; + private Func> _funcProductPictures; + + private LazyMultimap _productManufacturers; + private LazyMultimap _productPictures; + + public ExportDataContext(IEnumerable products, + Func> attributes, + Func> attributeCombinations, + Func> tierPrices, + Func> productCategories, + Func> productManufacturers, + Func> productPictures) + : base(products, + attributes, + attributeCombinations, + tierPrices, + productCategories) + { + _funcProductManufacturers = productManufacturers; + _funcProductPictures = productPictures; + } + + public new void Clear() + { + if (_productManufacturers != null) + _productManufacturers.Clear(); + if (_productPictures != null) + _productPictures.Clear(); + + base.Clear(); + } + + public new void Collect(IEnumerable productIds) + { + ProductManufacturers.Collect(productIds); + ProductPictures.Collect(productIds); + + base.Collect(productIds); + } + + public LazyMultimap ProductManufacturers + { + get + { + if (_productManufacturers == null) + { + _productManufacturers = new LazyMultimap(keys => _funcProductManufacturers(keys), _productIds); + } + return _productManufacturers; + } + } + + public LazyMultimap ProductPictures + { + get + { + if (_productPictures == null) + { + _productPictures = new LazyMultimap(keys => _funcProductPictures(keys), _productIds); + } + return _productPictures; + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index 4d8eaccaea..c1f5661f40 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -5,6 +5,8 @@ using System.IO; using System.IO.Compression; using System.Linq; +using System.Net; +using System.Net.Http; using System.Text; using System.Threading; using System.Web; @@ -17,9 +19,11 @@ using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Media; +using SmartStore.Core.Domain.Messages; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Email; using SmartStore.Core.Html; +using SmartStore.Core.IO; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Catalog; @@ -39,7 +43,12 @@ public class ExportProfileTask : ITask { private const int _maxErrors = 20; private const int _pageSize = 100; + // TODO: replaces Content\files\ExportImport... add it to CommonController.RobotsTextFile, CommonController.MaintenanceDeleteFiles, FilePermissionHelper.GetDirectoriesWrite + private const string _publicFolder = "Exchange"; + #region Dependencies + + private DataExchangeSettings _dataExchangeSettings; private ICommonServices _services; private IExportService _exportService; private IProductService _productService; @@ -54,14 +63,45 @@ public class ExportProfileTask : ITask private IDateTimeHelper _dateTimeHelper; private IEmailAccountService _emailAccountService; private IEmailSender _emailSender; - private DataExchangeSettings _dataExchangeSettings; + private IQueuedEmailService _queuedEmailService; + private IProductAttributeService _productAttributeService; + private IDeliveryTimeService _deliveryTimeService; + private IQuantityUnitService _quantityUnitService; + private IManufacturerService _manufacturerService; + + private void InitDependencies(TaskExecutionContext context) + { + _dataExchangeSettings = context.Resolve(); + _services = context.Resolve(); + _exportService = context.Resolve(); + _productService = context.Resolve(); + _pictureService = context.Resolve(); + _mediaSettings = context.Resolve(); + _priceCalculationService = context.Resolve(); + _taxService = context.Resolve(); + _currencyService = context.Resolve(); + _customerService = context.Resolve(); + _categoryService = context.Resolve(); + _priceFormatter = context.Resolve(); + _dateTimeHelper = context.Resolve(); + _emailAccountService = context.Resolve(); + _emailSender = context.Resolve(); + _queuedEmailService = context.Resolve(); + _productAttributeService = context.Resolve(); + _deliveryTimeService = context.Resolve(); + _quantityUnitService = context.Resolve(); + _manufacturerService = context.Resolve(); + } + + #endregion #region Utilities - private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic expando) + private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic expando, Product product) { try { + var languageId = (ctx.Projection.LanguageId ?? 0); string description = ""; // description merging @@ -96,19 +136,17 @@ private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic exp } else if (type == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription || type == ExportDescriptionMerging.ManufacturerAndNameAndDescription) { - string name = (string)expando.Name; - dynamic productManu = ((List)expando.ProductManufacturers).FirstOrDefault(); + var productManus = ctx.DataContext.ProductManufacturers.Load(product.Id); - if (productManu != null) - { - dynamic manu = productManu.Manufacturer; - description = ((string)manu.Name).Grow(name, " "); + if (productManus != null && productManus.Any()) + description = productManus.First().Manufacturer.GetLocalized(x => x.Name, languageId, true, false); - if (type == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription) - description = description.Grow((string)expando.ShortDescription, " "); - else - description = description.Grow((string)expando.FullDescription, " "); - } + description = description.Grow((string)expando.Name, " "); + + if (type == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription) + description = description.Grow((string)expando.ShortDescription, " "); + else + description = description.Grow((string)expando.FullDescription, " "); } } else @@ -153,6 +191,7 @@ private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic exp private void PrepareProductPrice(ExportProfileTaskContext ctx, dynamic expando, Product product) { decimal price = decimal.Zero; + var priceCalculationContext = ctx.DataContext as PriceCalculationContext; // price type if (ctx.Projection.PriceType.HasValue) @@ -162,15 +201,15 @@ private void PrepareProductPrice(ExportProfileTaskContext ctx, dynamic expando, if (type == PriceDisplayType.LowestPrice) { bool displayFromMessage; - price = _priceCalculationService.GetLowestPrice(product, null, out displayFromMessage); + price = _priceCalculationService.GetLowestPrice(product, priceCalculationContext, out displayFromMessage); } else if (type == PriceDisplayType.PreSelectedPrice) { - price = _priceCalculationService.GetPreselectedPrice(product, null); + price = _priceCalculationService.GetPreselectedPrice(product, priceCalculationContext); } else if (type == PriceDisplayType.PriceWithoutDiscountsAndAttributes) { - price = _priceCalculationService.GetFinalPrice(product, null, ctx.ProjectionCustomer, decimal.Zero, false, 1, null, null); + price = _priceCalculationService.GetFinalPrice(product, null, ctx.ProjectionCustomer, decimal.Zero, false, 1, null, priceCalculationContext); } } else @@ -197,11 +236,10 @@ private void SendCompletionEmail(ExportProfileTaskContext ctx) { var emailAccount = _emailAccountService.GetEmailAccountById(ctx.Profile.EmailAccountId); var smtpContext = new SmtpContext(emailAccount); + var message = new EmailMessage(); var storeInfo = "{0} ({1})".FormatInvariant(ctx.Store.Name, ctx.Store.Url); - var message = new EmailMessage(); - message.To.AddRange(ctx.Profile.CompletedEmailAddresses.SplitSafe(",").Where(x => x.IsEmail()).Select(x => new EmailAddress(x))); message.From = new EmailAddress(emailAccount.Email, emailAccount.DisplayName); @@ -216,32 +254,13 @@ private void SendCompletionEmail(ExportProfileTaskContext ctx) #endregion - private void InitDependencies(TaskExecutionContext context) - { - _services = context.Resolve(); - _exportService = context.Resolve(); - _productService = context.Resolve(); - _pictureService = context.Resolve(); - _mediaSettings = context.Resolve(); - _priceCalculationService = context.Resolve(); - _taxService = context.Resolve(); - _currencyService = context.Resolve(); - _customerService = context.Resolve(); - _categoryService = context.Resolve(); - _priceFormatter = context.Resolve(); - _dateTimeHelper = context.Resolve(); - _emailAccountService = context.Resolve(); - _emailSender = context.Resolve(); - _dataExchangeSettings = context.Resolve(); - } - private IEnumerable GetProducts(ExportProfileTaskContext ctx, int pageIndex) { - // do not call DetachAll() here! It will detach everything even things you do not want to have detached. - // note that detached navigation properties will not navigate again. they stay null => exception. - // DetachAll() is actually more useful to be called after all the work is done. - - //_services.DbContext.DetachAll(); + if (ctx.DataContext != null) + { + ctx.DataContext.Clear(); + ctx.DataContext = null; + } if (!ctx.Cancellation.IsCancellationRequested) { @@ -305,26 +324,117 @@ private IEnumerable GetProducts(ExportProfileTaskContext ctx, int pageI } } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product, IEnumerable products) { - dynamic expando = product.ToExpando(ctx.Projection.LanguageId ?? 0); + // load data behind navigation properties for current page in one go + if (ctx.DataContext == null) + { + ctx.DataContext = new ExportDataContext(products, + x => _productAttributeService.GetProductVariantAttributesByProductIds(x, null), + x => _productAttributeService.GetProductVariantAttributeCombinations(x), + x => _productService.GetTierPrices(x, ctx.ProjectionCustomer, ctx.Store.Id), + x => _categoryService.GetProductCategoriesByProductIds(x, true), + x => _manufacturerService.GetProductManufacturersByProductIds(x), + x => _productService.GetProductPicturesByProductIds(x) + ); + } + var languageId = (ctx.Projection.LanguageId ?? 0); + var productPictures = ctx.DataContext.ProductPictures.Load(product.Id); + var productManufacturers = ctx.DataContext.ProductManufacturers.Load(product.Id); + var productCategories = ctx.DataContext.ProductCategories.Load(product.Id); + var productAttributes = ctx.DataContext.Attributes.Load(product.Id); + + dynamic expando = product.ToExpando(languageId); + + // general data PrepareProductPrice(ctx, expando, product); expando._BasePriceInfo = product.GetBasePriceInfo(_services.Localization, _priceFormatter, decimal.Zero, true); + expando._CategoryPath = _categoryService.GetCategoryPath( + product, + null, + x => ctx.CategoryPathes.ContainsKey(x) ? ctx.CategoryPathes[x] : null, + (id, value) => ctx.CategoryPathes[id] = value, + x => ctx.Categories.ContainsKey(x) ? ctx.Categories[x] : _categoryService.GetCategoryById(x) + ); + + + // navigation properties + if (product.DeliveryTimeId.HasValue && ctx.DeliveryTimes.ContainsKey(product.DeliveryTimeId.Value)) + expando.DeliveryTime = ctx.DeliveryTimes[product.DeliveryTimeId.Value].ToExpando(languageId); + else + expando.DeliveryTime = null; + + if (product.QuantityUnitId.HasValue && ctx.QuantityUnits.ContainsKey(product.QuantityUnitId.Value)) + expando.QuantityUnit = ctx.QuantityUnits[product.QuantityUnitId.Value].ToExpando(languageId); + else + expando.QuantityUnit = null; + + expando.ProductPictures = productPictures + .OrderBy(x => x.DisplayOrder) + .Select(x => + { + dynamic exp = new ExpandoObject(); + exp._Entity = x; + exp.Id = x.Id; + exp.DisplayOrder = x.DisplayOrder; + exp.Picture = x.Picture.ToExpando(_pictureService, ctx.Store, _mediaSettings.ProductThumbPictureSize, _mediaSettings.ProductDetailsPictureSize); + + return exp as ExpandoObject; + }) + .ToList(); + + expando.ProductManufacturers = productManufacturers + .OrderBy(x => x.DisplayOrder) + .Select(x => + { + dynamic exp = new ExpandoObject(); + exp._Entity = x; + exp.Id = x.Id; + exp.DisplayOrder = x.DisplayOrder; + exp.IsFeaturedProduct = x.IsFeaturedProduct; + exp.Manufacturer = x.Manufacturer.ToExpando(languageId); + + return exp as ExpandoObject; + }) + .ToList(); + + expando.ProductCategories = productCategories + .OrderBy(x => x.DisplayOrder) + .Select(x => + { + dynamic exp = new ExpandoObject(); + exp._Entity = x; + exp.Id = x.Id; + exp.DisplayOrder = x.DisplayOrder; + exp.IsFeaturedProduct = x.IsFeaturedProduct; + exp.Category = x.Category.ToExpando(languageId); + + return exp as ExpandoObject; + }) + .ToList(); + + expando.ProductAttributes = productAttributes + .OrderBy(x => x.DisplayOrder) + .Select(x => x.ToExpando(languageId)) + .ToList(); + + + // data controlled through ExportProjectionSupport attribute if (ctx.Provider.Supports(ExportProjectionSupport.Description)) { - PrepareProductDescription(ctx, expando); + PrepareProductDescription(ctx, expando, product); } if (ctx.Provider.Supports(ExportProjectionSupport.Brand)) { string brand = null; - var manu = product.ProductManufacturers.OrderBy(x => x.DisplayOrder).FirstOrDefault(); + var productManus = ctx.DataContext.ProductManufacturers.Load(product.Id); - if (manu != null) - brand = manu.Manufacturer.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + if (productManus != null && productManus.Any()) + brand = productManus.First().Manufacturer.GetLocalized(x => x.Name, languageId, true, false); if (brand.IsEmpty()) brand = ctx.Projection.Brand; @@ -337,35 +447,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) expando.ManufacturerPartNumber = product.Sku; } - if (ctx.Provider.Supports(ExportProjectionSupport.CategoryPath)) - { - if (ctx.CategoryPathes == null) - { - ctx.CategoryPathes = new Dictionary(); - } - - if (ctx.Categories == null) - { - var allCategories = _categoryService.GetAllCategories(showHidden: true, applyNavigationFilters: false); - ctx.Categories = allCategories.ToDictionary(x => x.Id); - } - - expando._CategoryPath = _categoryService.GetCategoryPath( - product, - null, - x => ctx.CategoryPathes.ContainsKey(x) ? ctx.CategoryPathes[x] : null, - (id, value) => ctx.CategoryPathes[id] = value, - x => ctx.Categories.ContainsKey(x) ? ctx.Categories[x] : _categoryService.GetCategoryById(x) - ); - } - if (ctx.Provider.Supports(ExportProjectionSupport.MainPictureUrl)) { - var picture = product.GetDefaultProductPicture(_pictureService); - - // always use HTTP when getting image URL - if (picture != null) - expando._MainPictureUrl = _pictureService.GetPictureUrl(picture, ctx.Projection.PictureSize, storeLocation: ctx.Store.Url); + if (productPictures != null && productPictures.Any()) + expando._MainPictureUrl = _pictureService.GetPictureUrl(productPictures.First().Picture, ctx.Projection.PictureSize, storeLocation: ctx.Store.Url); else expando._MainPictureUrl = _pictureService.GetDefaultPictureUrl(ctx.Projection.PictureSize, storeLocation: ctx.Store.Url); } @@ -402,46 +487,150 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) } } + // detach entity + try + { + _services.DbContext.DetachEntity(product); + } + catch { } + return expando as ExpandoObject; } - private void Deploy(ExportProfileTaskContext ctx, ExportDeployment deployment) + private void DeployFileSystem(ExportProfileTaskContext ctx, ExportDeployment deployment) { - if (deployment.DeploymentType == ExportDeploymentType.FileSystem) + string folderDestination = null; + + if (deployment.IsPublic) + { + folderDestination = Path.Combine(HttpRuntime.AppDomainAppPath, _publicFolder); + } + else if (deployment.FileSystemPath.IsEmpty()) + { + return; + } + else if (deployment.FileSystemPath.StartsWith("/") || deployment.FileSystemPath.StartsWith("\\") || !Path.IsPathRooted(deployment.FileSystemPath)) + { + folderDestination = CommonHelper.MapPath(deployment.FileSystemPath); + } + else { - string folderDestination = null; + folderDestination = deployment.FileSystemPath; + } - if (deployment.IsPublic) - { - folderDestination = Path.Combine(HttpRuntime.AppDomainAppPath, "Content\\files\\ExportImport"); - } - else if (deployment.FileSystemPath.IsEmpty()) + if (!System.IO.Directory.Exists(folderDestination)) + { + System.IO.Directory.CreateDirectory(folderDestination); + } + + if (deployment.CreateZip) + { + var path = Path.Combine(folderDestination, ctx.Profile.FolderName + ".zip"); + FileSystemHelper.Copy(ctx.ZipPath, path); + + ctx.Log.Information("Copied ZIP archive " + path); + } + else + { + FileSystemHelper.CopyDirectory(new DirectoryInfo(ctx.FolderContent), new DirectoryInfo(folderDestination)); + + ctx.Log.Information("Copied export data to " + folderDestination); + } + } + + private void DeployEmail(ExportProfileTaskContext ctx, ExportDeployment deployment) + { + var emailAccount = _emailAccountService.GetEmailAccountById(deployment.EmailAccountId); + var smtpContext = new SmtpContext(emailAccount); + var count = 0; + + foreach (var email in deployment.EmailAddresses.SplitSafe(",").Where(x => x.IsEmail())) + { + var queuedEmail = new QueuedEmail { - return; - } - else if (deployment.FileSystemPath.StartsWith("/") || deployment.FileSystemPath.StartsWith("\\") || !Path.IsPathRooted(deployment.FileSystemPath)) + From = emailAccount.Email, + FromName = emailAccount.DisplayName, + To = email, + Subject = deployment.EmailSubject.NaIfEmpty(), + CreatedOnUtc = DateTime.UtcNow, + EmailAccountId = deployment.EmailAccountId + }; + + if (deployment.CreateZip) { - folderDestination = CommonHelper.MapPath(deployment.FileSystemPath); + queuedEmail.Attachments.Add(new QueuedEmailAttachment + { + StorageLocation = EmailAttachmentStorageLocation.Path, + Path = ctx.ZipPath, + Name = ctx.ZipName, + MimeType = "application/zip" + }); } else { - folderDestination = deployment.FileSystemPath; - } + foreach (string path in System.IO.Directory.GetFiles(ctx.FolderContent, "*.*", SearchOption.AllDirectories)) + { + string name = Path.GetFileName(path); - if (!System.IO.Directory.Exists(folderDestination)) - { - System.IO.Directory.CreateDirectory(folderDestination); + queuedEmail.Attachments.Add(new QueuedEmailAttachment + { + StorageLocation = EmailAttachmentStorageLocation.Path, + Path = path, + Name = name, + MimeType = MimeTypes.MapNameToMimeType(name) + }); + } } - if (deployment.CreateZip) - { - FileSystemHelper.Copy(ctx.ZipPath, Path.Combine(folderDestination, ctx.Profile.FolderName + ".zip")); - } - else + _queuedEmailService.InsertQueuedEmail(queuedEmail); + ++count; + } + + ctx.Log.Information("{0} email(s) created and queued.".FormatInvariant(count)); + } + + private void DeployHttp(ExportProfileTaskContext ctx, ExportDeployment deployment) + { + // TODO: make async + var count = 0; + string[] filePaths = System.IO.Directory.GetFiles(ctx.FolderContent, "*.*", SearchOption.AllDirectories); + + if (deployment.MultipartForm) + { + ICredentials credentials = null; + + if (deployment.Username.HasValue()) + credentials = new NetworkCredential(deployment.Username, deployment.Password); + + using (var handler = new HttpClientHandler { Credentials = credentials }) + using (var client = new HttpClient(handler)) + using (var formData = new MultipartFormDataContent()) { - FileSystemHelper.CopyDirectory(new DirectoryInfo(ctx.FolderContent), new DirectoryInfo(folderDestination)); + foreach (var path in filePaths) + { + byte[] fileData = File.ReadAllBytes(path); + formData.Add(new ByteArrayContent(fileData), "file {0}".FormatInvariant(++count), Path.GetFileName(path)); + } + + var response = client.PostAsync(deployment.Url, formData).Result; + if (response.IsSuccessStatusCode) + { + ctx.Log.Information("{0} file(s) successfully uploaded as multipart form data.".FormatInvariant(count)); + } + else if (response.Content != null) + { + ctx.Log.Error("Multipart form data upload failed. Response: " + response.Content.ReadAsStringAsync().Result.NaIfEmpty().Truncate(2000, "...")); + } } } + else + { + } + } + + private void DeployFtp(ExportProfileTaskContext ctx, ExportDeployment deployment) + { + } private void ExportCoreInner(ExportProfileTaskContext ctx) @@ -498,7 +687,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx) ctx.Segmenter = new ExportSegmenter( pageIndex => GetProducts(ctx, pageIndex), - entity => ToExpando(ctx, (Product)entity), + (entity, entities) => ToExpando(ctx, (Product)entity, (IEnumerable)entities), new PagedList(ctx.Profile.Offset, ctx.Profile.Limit, _pageSize, anySingleProduct.TotalCount), ctx.Profile.BatchSize ); @@ -557,6 +746,11 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) { ctx.Log = logger; ctx.Export.Log = logger; + ctx.DeliveryTimes = _deliveryTimeService.GetAllDeliveryTimes().ToDictionary(x => x.Id, x => x); + ctx.QuantityUnits = _quantityUnitService.GetAllQuantityUnits().ToDictionary(x => x.Id, x => x); + + var allCategories = _categoryService.GetAllCategories(showHidden: true, applyNavigationFilters: false); + ctx.Categories = allCategories.ToDictionary(x => x.Id); if (ctx.Profile.ProviderConfigData.HasValue()) { @@ -589,6 +783,42 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) ExportCoreInner(ctx); } } + + if (ctx.Profile.CreateZipArchive || ctx.Profile.Deployments.Any(x => x.Enabled && x.CreateZip)) + { + ZipFile.CreateFromDirectory(ctx.FolderContent, ctx.ZipPath, CompressionLevel.Fastest, true); + } + + foreach (var deployment in ctx.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) + { + try + { + switch (deployment.DeploymentType) + { + case ExportDeploymentType.FileSystem: + DeployFileSystem(ctx, deployment); + break; + case ExportDeploymentType.Email: + DeployEmail(ctx, deployment); + break; + case ExportDeploymentType.Http: + DeployHttp(ctx, deployment); + break; + case ExportDeploymentType.Ftp: + DeployFtp(ctx, deployment); + break; + } + } + catch (Exception exc) + { + logger.Error("Deployment \"{0}\" failed.".FormatInvariant(deployment.Name), exc); + } + } + + if (ctx.Profile.EmailAccountId != 0 && ctx.Profile.CompletedEmailAddresses.HasValue()) + { + SendCompletionEmail(ctx); + } } catch (Exception exc) { @@ -596,6 +826,16 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) } finally { + try + { + if (ctx.Profile.Cleanup) + { + FileSystemHelper.ClearDirectory(ctx.FolderContent, false); + // TODO: more cleanup if required + } + } + catch { } + try { if (ctx.Segmenter != null) @@ -608,50 +848,6 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) catch { } } } - - try - { - if (ctx.Profile.CreateZipArchive || ctx.Profile.Deployments.Any(x => x.Enabled && x.CreateZip)) - { - ZipFile.CreateFromDirectory(ctx.FolderContent, ctx.ZipPath, CompressionLevel.Fastest, true); - } - - foreach (var deployment in ctx.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) - { - Deploy(ctx, deployment); - } - - if (ctx.Profile.EmailAccountId != 0 && ctx.Profile.CompletedEmailAddresses.HasValue()) - { - SendCompletionEmail(ctx); - } - } - catch (Exception exc) - { - using (var logger2 = new TraceLogger(ctx.LogPath)) - { - logger2.Error(exc); - } - } - finally - { - try - { - if (ctx.Profile.Cleanup) - { - FileSystemHelper.ClearDirectory(ctx.FolderContent, false); - // TODO: more cleanup if required - } - } - catch { } - - try - { - // do not call during processing, would generate exceptions. should be the final statement. - _services.DbContext.DetachAll(); - } - catch { } - } } @@ -700,6 +896,8 @@ public ExportProfileTaskContext(ExportProfile profile, Provider FolderContent = FileSystemHelper.TempDir(@"Profile\Export\{0}\Content".FormatInvariant(profile.FolderName)); FolderRoot = System.IO.Directory.GetParent(FolderContent).FullName; + CategoryPathes = new Dictionary(); + Export = new ExportExecuteContext(Cancellation, FolderContent); } @@ -717,17 +915,27 @@ public ExportProfileTaskContext(ExportProfile profile, Provider public string FolderRoot { get; private set; } public string FolderContent { get; private set; } + public string ZipName + { + get { return Profile.FolderName + ".zip"; } + } public string ZipPath { - get { return Path.Combine(FolderRoot, Profile.FolderName + ".zip"); } + get { return Path.Combine(FolderRoot, ZipName); } } public string LogPath { get { return Path.Combine(FolderRoot, "log.txt"); } } - public Dictionary Categories; - public Dictionary CategoryPathes; + // data loaded once per export + public Dictionary Categories { get; set; } + public Dictionary CategoryPathes { get; set; } + public Dictionary DeliveryTimes { get; set; } + public Dictionary QuantityUnits { get; set; } + + // data loaded once per page + public ExportDataContext DataContext { get; set; } public ExportSegmenter Segmenter { get; set; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs index a4a7ea0a86..66bf70f811 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs @@ -27,7 +27,7 @@ public interface IExportSegmenter public class ExportSegmenter : IExportSegmenter, IDisposable { private Func> _loadData; - private Func _convertData; + private Func, ExpandoObject> _convertData; private List _currentSegment; private IPageable _pageable; private int _pageIndexReset; @@ -41,7 +41,11 @@ public class ExportSegmenter : IExportSegmenter, IDisposable private int _itemsPerFileCount; private int _itemsCount; - public ExportSegmenter(Func> loadData, Func convertData, PagedList pageable, int itemsPerFile) + public ExportSegmenter( + Func> loadData, + Func, ExpandoObject> convertData, + PagedList pageable, + int itemsPerFile) { _loadData = loadData; _convertData = convertData; @@ -178,7 +182,7 @@ public ICollection CurrentSegment return _currentSegment.AsReadOnly(); } - var expando = _convertData(item); + var expando = _convertData(item, data); _currentSegment.Add(expando); diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index ef481b01a9..b2b5b497d3 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -115,6 +115,7 @@ ..\..\packages\System.Linq.Dynamic.1.0.0\lib\net40\System.Linq.Dynamic.dll + @@ -162,6 +163,7 @@ + diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs index f418b91bb1..f92641e9b4 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs @@ -42,7 +42,7 @@ public void Execute(ScheduleTask task, bool throwOnError = false) string lastError = null; ITask instance = null; string stateName = null; - int taskId = task.Id; + Type taskType = null; try @@ -99,33 +99,31 @@ public void Execute(ScheduleTask task, bool throwOnError = false) AsyncState.Current.Remove(stateName); } - // "task" may not updatable here. solution: get fresh instance and update it - var task2 = _scheduledTaskService.GetTaskById(taskId); - var now = DateTime.UtcNow; + task.ProgressPercent = null; + task.ProgressMessage = null; - task2.ProgressPercent = null; - task2.ProgressMessage = null; - task2.LastError = lastError; - task2.LastEndUtc = now; + var now = DateTime.UtcNow; + task.LastError = lastError; + task.LastEndUtc = now; - if (faulted) - { - if ((!canceled && task2.StopOnError) || instance == null) - { - task2.Enabled = false; - } - } - else - { - task2.LastSuccessUtc = now; - } + if (faulted) + { + if ((!canceled && task.StopOnError) || instance == null) + { + task.Enabled = false; + } + } + else + { + task.LastSuccessUtc = now; + } - if (task2.Enabled) - { - task2.NextRunUtc = now.AddSeconds(task2.Seconds); - } + if (task.Enabled) + { + task.NextRunUtc = now.AddSeconds(task.Seconds); + } - _scheduledTaskService.UpdateTask(task2); + _scheduledTaskService.UpdateTask(task); } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 9d5f453c57..809289409a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -304,6 +304,7 @@ private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment Username = deployment.Username, Password = deployment.Password, Url = deployment.Url, + MultipartForm = deployment.MultipartForm, FileSystemPath = deployment.FileSystemPath, EmailAddresses = deployment.EmailAddresses, EmailSubject = deployment.EmailSubject, @@ -337,6 +338,7 @@ private void ModelToEntity(ExportDeploymentModel model, ExportDeployment deploym deployment.Username = model.Username; deployment.Password = model.Password; deployment.Url = model.Url; + deployment.MultipartForm = model.MultipartForm; deployment.FileSystemPath = model.FileSystemPath; deployment.EmailAddresses = model.EmailAddresses; deployment.EmailSubject = model.EmailSubject; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs index 719fa0c9f4..4de2383511 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs @@ -66,6 +66,9 @@ public string DeploymentTypeIconClass [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.Url")] public string Url { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.MultipartForm")] + public bool MultipartForm { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.FileSystemPath")] public string FileSystemPath { get; set; } 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 1bb68613ac..2d62e6b096 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml @@ -64,6 +64,15 @@ + + + + - + + - diff --git a/src/Presentation/SmartStore.Web/Exchange/placeholder b/src/Presentation/SmartStore.Web/Exchange/placeholder new file mode 100644 index 0000000000..5f282702bb --- /dev/null +++ b/src/Presentation/SmartStore.Web/Exchange/placeholder @@ -0,0 +1 @@ + \ No newline at end of file From a8af6b86a9cff7a59a9ff5938e706034db4d0eda Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 26 Aug 2015 17:57:51 +0200 Subject: [PATCH 019/874] More export deployment --- .../Domain/DataExchange/ExportCore.cs | 6 + .../Domain/DataExchange/ExportDeployment.cs | 29 +- .../201508091512101_ExportFramework.cs | 8 +- .../201508241204123_ExportFramework1.resx | 126 ------- ...508261545304_ExportFramework1.Designer.cs} | 2 +- ...cs => 201508261545304_ExportFramework1.cs} | 35 +- .../201508261545304_ExportFramework1.resx | 126 +++++++ .../SmartStore.Data/SmartStore.Data.csproj | 10 +- .../DataExchange/ExportProfileTask.cs | 356 +++++++++++------- .../Controllers/ExportController.cs | 13 +- .../DataExchange/ExportDeploymentModel.cs | 11 +- .../DataExchange/ExportDeplomentValidator.cs | 7 +- .../Export/_CreateOrUpdate.Deployment.cshtml | 27 +- 13 files changed, 457 insertions(+), 299 deletions(-) delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.resx rename src/Libraries/SmartStore.Data/Migrations/{201508241204123_ExportFramework1.Designer.cs => 201508261545304_ExportFramework1.Designer.cs} (92%) rename src/Libraries/SmartStore.Data/Migrations/{201508241204123_ExportFramework1.cs => 201508261545304_ExportFramework1.cs} (65%) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.resx diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs index a36a4626d1..dafefd8089 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs @@ -19,6 +19,12 @@ public enum ExportDeploymentType Ftp } + public enum ExportHttpTransmissionType + { + SimplePost = 0, + MultipartFormDataPost + } + public enum ExportDescriptionMerging { None = 0, diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs index 04ce663452..8b9bd9b90e 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs @@ -59,9 +59,24 @@ public ExportDeploymentType DeploymentType public string Url { get; set; } /// - /// Whether to use a multipart form to upload files via HTTP + /// The type identifier of how to transmit via HTTP /// - public bool MultipartForm { get; set; } + public int HttpTransmissionTypeId { get; set; } + + /// + /// The type of how to transmit via HTTP + /// + public ExportHttpTransmissionType HttpTransmissionType + { + get + { + return (ExportHttpTransmissionType)HttpTransmissionTypeId; + } + set + { + HttpTransmissionTypeId = (int)value; + } + } /// /// The file system path @@ -83,7 +98,15 @@ public ExportDeploymentType DeploymentType /// public int EmailAccountId { get; set; } - // TODO: more FTP options + /// + /// Whether to use FTP active or passive mode + /// + public bool PassiveMode { get; set; } + + /// + /// Whether to use SSL + /// + public bool UseSsl { get; set; } public virtual ExportProfile Profile { get; set; } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs index fa1ed244fd..6d1f8ec624 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs @@ -494,10 +494,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Legt das Passwort fest."); builder.AddOrUpdate("Admin.Configuration.Export.Deployment.Url", - "URL", - "URL", - "Specifies the URL on which the data should be deployed.", - "Legt die URL fest, unter der die Daten bereitgestellt werden sollen."); + "URL\\Host", + "URL\\Host", + "Specifies the URL or host name where to send the data.", + "Legt die URL bzw. den Host-Namen fest, an die die Daten bermittelt werden sollen."); builder.AddOrUpdate("Admin.Configuration.Export.Deployment.EmailAddresses", "Email addresses", diff --git a/src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.resx b/src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.resx deleted file mode 100644 index b43be88c85..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.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 - - - H4sIAAAAAAAEAOy923IcObIg+L5m+w8yPc2snZFK6m6zPm1VO0ZSpEQ7EsUmKemcfqEFI0EmWpERWXHhpcfmy/ZhP2l/YQHEJXG/R0RmVj5UKRlwOACHw+HucDj+v//n//31fz6vslePoKxgkf/2+t2bX16/AnlaLGD+8Nvrpr7/H399/T//7//z//j1dLF6fvW9h/sThkM18+q318u6Xv/t7dsqXYJVUr1ZwbQsquK+fpMWq7fJonj7/pdf/v3tu3dvAULxGuF69erXqyav4QqQP9CfJ0WegnXdJNmXYgGyqvuOSq4J1lcXyQpU6yQFv72+XiVlfV0XJXjzIamT16+OMpigblyD7P71qyTPizqpUSf/9q0C13VZ5A/Xa/QhyW5e1gDB3SdZBbrO/20DbjuOX97jcbzdVOxRpU1VFytHhO/+1BHmLV/di7yvB8Ih0p0iEtcveNSEfL+9Pn1eF2X9Aayz4mUF8vr1K77Rv51kJa7AUPmEkBqBwfwNj+LfXnGA/zbwyPs379/88uaXf3t10mR1U4LfctDUZZL926vL5i6D6X+Al5viJ8h/y5ssozuOuo7KmA/o02VZrEFZv1yB+24454vXr96y9d7yFYdqVJ12gOd5/af3r19doMaTuwwMfEERg4zqI8hBmdRgcZnUNShzjAMQygqtc22hP+9hBsxN6tHg//cYED+jdfn61Zfk+TPIH+olWrG/oJV4Bp/Bov/SYf2WQ7SMUaW6bIBzq6c5Bh66flwUGUhyZzTnVTvZoXhOSoDm4B9wHYpow7oYT+jcIBFT5vr5+bPl/Bh4Kamqp6JcjN7QtzLTtxGjkS9IIsA1khtnRbkKndEztMauX6oarNAKXY5OoFMkB7OjxaIEVQWq8WlF2rtu7v4J0nqiwaVpgbZn57VxkTzCByI55YLw9asrkJHyagnX7YYtbCi3A/BZWayuikyyb/Uwt9dFU6Z48RUGwJukfAA12+Nf3242SIttc+iX557Z1T9smOq25tnpzopsAcp52kZ/PkLUeiu/DH2wFijzbPVYU180Ger1TVL9DN1ZkVRHzENIoCQI+hljay0LLFuJyj1+U2S6kblzDx9a62XkJr/e31egDpuLz3AFA1EcJ3W6vIb/AmFoLtFSwTImlFeD9jleNS1W6wwgeeeiGUSY2UElPirTJXwMpskJrtW4K9jKbX+zD1fxt/5+Rzdu/b2OYNvrTowBLMQ03R7QM+B8x2VQCq1FChqkt7Bdc1ZbcLXqDY3koLuo2zLs3X8ZZ+smrq8IzRp2dpAW+aIKE5O4rqanf91u3aYu1l/z07IsylBUF+C5vmryb/XgE0F6ALiBK/M2nFRo7aEF6Fn3NF/4ttqkKdrUfNulySbXrWNw6Xn1CS7Qag2dIPTnA97Cka6REicpw/V2db+g/5IHg0HhPmhr0f/3JiGfEC4fL+8HWCKNuChf3tCIDuJfs6pN4n8M4fYBVGkJ1wbTxbJtQ1OwWmfJy+ciTbIIA7Vq7GuJTKWwTee8+gDuE8STbkLBYZ2Bplf86zpJl57HKp28qN5IER4WnrotimChhhzuCZoEzOPMkiJKPzsjAqjrHqP3VEfaEc9kp0+GpUf5JI5hnpQvdj6JIOn4fhzd7wvSTwxaZ6yWldbkmcrvLV3ot2ec+auGEqxIDajMitR1mkLl0HemlmkI1FfLkdA1gsziD8VTnhXJwktML2DypkdwEMsaOdLR6GMDh1bb3+4nrD0u6mjS+/RXRDWa57VvqxejsYXqSZHX3TH26GPBYsRwzh2nodNnNKZqCl/8eXUBnsLDG27KBPWXshN9UX1bL/BK+yr3DERXVyMpqQcZqG7rsoRFSeCCDsLQTjq9YxG3GsmlaXALFtMP7qaYZGhXYJ29RBmfVTuTjOnkZPQmjtN09DbM8TSRTLDjYvEy+lbWngR6bR0yZz/SKUoIAt39GI2iQ9MdzaJOLL4keZNk2Yvz/qw0jzZGifxcM7J5xJ902htUtgOiSW4a0S0LLB0IA6Mz71jAsCAtplv+ug2N56DcGJapzrPyl7+M4vJuXcMmh5Jl4/q2PhWVbocYaYSXRRkYa2MRlDxW381RyiO13J7zXlfBHgLsb2iPDNCuStYCqjXS6cFlQkJVvoB6Wfi4o7r61RsG0UFq6RiUItS8QZfPadYswOKETDoo8d54vhg/lmNoF29w5Yu+yfeRQti7Nq+RRrFGbbTkn2K03ShJB/AKID6z52C98gvM4apZkQPKo1WrcnRaLkjhKkFS6LJEv7o7dX99/eoan5sibjJiTp5HwtxiuwIVojWJdo1EizMEbncQ7XUhwl6YlsWiSevjJl+glYQWNlKOS3jX1KANJfaQrydJnWTFwxsT6oPEVbe1IVooow1Ej4boe5I1wdjOK7QgkZUE0tojzkxpk23oJrXITCx5S9ffGGkO1QS7zaVukCknNBRz6R7WqrqtjlihK6IldiRkfQBaGBZkNqYxN9MeXxcjSIX6eYeFGtXQGG7PJYmRt9mso50bIhYcBIWjISUxyeEC3Cyb1V1OeR98kX1HE3+XBc8cEQ7VMjzIOF7YW1SHcMDBpMGLSu8eclfqBNsc71712SLdNvWuBbsB33J1NINkQc37NwfvGiXkNgyrAdh3XddpV0UjXL04KBUa8diSCDcXqgtcJiVq82NZNGvUkTj6RbcJnOcL+AgXfidF42zlrmddk+/t1oZ/nPaOFiuYnxSrFRVsM+ZNXcK1YIW25HCTE03O09f8U7EClxF0xR5PPIXhC6gTJDSw7378i6u4sSkZB7d3A2vtvYU4IzvKsuJp8OiCRwieglXeo/W6LB7B4gpJ4/zhulmFzfRFUUfG2KO7QRtGxg06uI/xkHYhHzfFURpsOZAL8bhzZG8MnuLrn834iy7Jm/skxSpIifbR+gIZURupMVqzHxGLjd7IefUR3tcnSRlsgvV4YmgrV+D3Bpbga71EFG+3k2BO6XBulB+dtI4UwnTUYN24hinWjY4WC64PwWM6rzYR+XGiu4OzeuVZu8B7hMFjRLPSLriv9wJOT49Wh+b0eQ1Loqh/SF54nHYojtIaPhIUMdj+U1JdJzgjRqxZZbE53mdCvcHhEEcPJQC04hgQHrBBdgOex9dEz6srkDYlnQrHX3p0iE5e0gy0nQqVcTTGS1DCInj1DTjJ3k8QB66V8wqfPEfLaHiGpr8/yg7W7hYLiFdekvUYT5bYpWHvqHa/sZA8nz4juyZ4LZxXp1UwOVFvTpCB9VDgWIRAIybJkVF0nj+ipYnQ9VEGgWpjXaQ/ox1CYHONYDx6TCCqCzMKa6DfWNpT7w0L5hFH/rl4akeNdxqELTglalHD+xfiEDgryr6PxwBZX4FHV0n6s8DmNE5DHHzOjK1BjPG8pSWyQAajN1ijIEZ/F4cSZ5JajG38SRyMhABg0SGD2pxYkZRVtBZwB4+bl+OmrjfOhYC1haF/wGqZwaqOg7Rb/BlAzIvkOuO/8c7ehbRzgg6m4SdaNJLoO9DXbDFuA51hckKF70Zv43qN8CSZ40DscZI8P/0pm8ctChrXab4IwNS7s05zfEoXhb86scVgBiMzRSfYJm0TGR83ELRrMljgoz0UVPVwGHlSrO5g3p3LRWRC1Oc+uKE6Wq8zGK4x/wDwYTneUmTtmOjof8DFiNg/jUubYacJlScDojBhEu/EIl7UBSDJO4P33+0JuRAHCB9B+YIrO3pP6OxfjlWPkwp0u08Us3vA9wUkVVOCNrOZUp2MkodqaDNuJPaAFv9gUduRloRBkBMtgwcqjnu9CxMBJY68iOX4YJBiasTGeb0sWn9KUrp7OtRRS+2WOGyR0uiXvvS2g74pNr7yTfiLGkqISdKAuoYg0dJAF7pzywIKUTtMuSpghwXyDDDaRP1Yxoj5h0wZY8EUIVaOI+ocXsQgVg+od4tpQqd4ENUABDjPjtOHiNq+04Ca/svAVGOQwnqO4xISPNohdDCa3nMQqo7zYJ59Hg7B1V1uQTQ9ZgFUHeagPPtLLNB72CbIoyOA1f2XV9GMR19BNT5DLc/x3iQP2sHdMnCiPKWKleKUhvGUpt+TEiKlTmZHaruvqaeZIItaqlmyqeo5VTxqp4E7jNZ1iN7jYtP9aniQBRSZkC5XciED5MqG/NmwprM8qNhdFkLZYQ7Mtcu0D0fS3aFYxhlCocALIoRs9u2zGw43XTyyPndK7fDrENesbssQ5TtSTtN+ZmJEanyrsK0U6f6S+kZUdKdW35SrVe588tu5zHySGLVeMo+aXXRVdVI0a3xHexE8MyKmgPet7GaGxEeSVRbKoxtMmHiucSAmM/6mMJhiouFNV9DZ8hs4YVvSArtuThIXwxgeCfMYBOeFtUeiQ9Vxvjqv1tCmvIZkHDJA9VCk0EEXjHq7O+CGUY/isBWr2zJsxSNduMH3XyK1bHLgT3dX4rioEZ9Oeq3H8IRRnJ2pX0axLvQcLsxEetYY3+iLFQDYedocT6rwTarwlwmZqz/VTdFmBOFxe0fkdHi+ssFjAXaHRQTQVZI/TPCOYeS7cfHjF7b45tC2nYLvZQoDgVQkPeB3CJ6+6I0qT2kc+/DRw2QxH0BK7BtrL2wrpqW935xU9UCbHvNlgiYvAIRlB+h74PlMSFf/oLhrxFdLorFeyLB4AOjP49zFB4XFixl/inT5cW/esYh9ciuKEDmE8eSWkzd+4oTBGZ5z5CBepspnpjIrptONvLZS79WgOEZTLRrHFRw35sLU1xhpgiR+wSinaxS+wyrWrpsoj0FIyH7VZMAqC3Qcv8r1Gox+anUMswxHPfbpnQNvP/aXUuOg67MPEwOCSXzt5LHZoBkkvM5NEWfyqEbpJOJTNtzPhtkLFLvlzl/hOE+nz0hQ0S+KTmAjb+IfxjtBUlrH2vMmv72HicINeMibRnTYbdRtGTaCcR7yPimyovwEnklC6slb34m3vV3trAhHrxymw6pRtxXJ0op1EHRenYEET0SvIMzo11buVBsOtbgDsAFWXwIYfiiMIhFwBBPO59aCqb9RrLhL8RpBuGygsR3kw+jygSb3H0JGsNxqed+GraS/dMP8oViH6gojyI+Qm0M2/Y8iSyIJkYP02Ob4rkkjbihOOIQobVeI0iGoaAeDig5xNrsXZ+N1ysVu7uIZl6xcUBSkQDGsjfYqbbid0eKJpiO8wpknuvZJ1E0nYsiTgHn9egpd3iS9Y7mwMeEmSdTaplMPI8onkK3vm+y/QEWSn0ZBdlH44ArR4V3voSv0dsVtdceOtlg6WuSgqtBaNaVeEOrccutZMSJlDf1Ve3W1oLODflX7iJ9e5xh+HewTdVs9jT42cGi1/e2RXdoQNBbpFWDD6+1xWrF4DjxuQ2dFuUrC4306bNdJpk8xFSW7/pSv2kRM/nx0fw8zGMFMJTmBg22ZSHr1edWGgxylzHOR3jcHbGJL4vDR56Sqz9dHiwWydia4ThXTasBd/1w8wNzzyjSu32eWVqLwvyrcklShMPSi/5YCoyLWhVIxZl0EcVV0upCfYe513eRhJX1lQdQd5uBce83bHLpudzC3G41G7DcPI6hcSkDX+wt05JKB3ByohNoMhJrYLJgrrU+fsUKTZEdNvcQqTZv66AqkrW9QMgJdDek0WFUQ5sSulusEITWgWV0WleKGzFAsHYhYKvRaAuLVxZtiDVNNH0m5upNssbyXHIxrN4mDSd5DUiTtHFsi9Isrdu3SFUA2Ro4jrIBqghkQaRflEEJXFWDuXX5KysVlAfO6+gSrWnXoL4FT9F4NJxmDBth1JH0EotVeIwBLxB8HoxaAPKBz3ikqHag6lyQPJSW/EkigvRoyyLAfCOpu1yNDo8jfdAgONr26rTNYVvUkUeFYfZ2kIZPRH6cVxGLrJH+ZoB15SLrBIkPcgdO3PsI8dT3TO6EeRBnxlVuyNN9N1dD70Rv6B1xjXSnJDCmoIj3cuyxyYHzcMtILyP2rfuMze5C9rbZk2jUk3Qr7jXaA2WyAXJGwafPlzns1vUi1neMgxS4yAMqOslBhKZ96cgXE6nc4Dtuzui3Dhmnr5fKJx6g6x0ewB5Ugi5VP/+ap+AwwEc+rwiBo30cQSDfLEgDbBv8UoUEkaEEJU66xwKegvyfBLt54kSHRQjpCY180dix/E224Aye1pWiY203lVrxx6UjM0BLryljF2b6lNwLFsJi9QrI7SgHEzkuhgsxClh4eO1CPgJu6wz6kEUt7H5oaTy6dP+SI1u37tsEReafPadYs0FpnrhHrTr7inLN1K5U0j5cAOU14dj9zthCylJycQrzyyrGDRPYTV6zuHqAvM5gO0srIvMHP786jfN/dleARJgapF+eAexvUOi+z3VM9ERa/VokJCkcbDnwjRKV1uA5rXrPmO1IF35ezCC6Jc0/gHCfuRKy3V48yGqMJ4gdAqM7SlJESXsv6OCsehvg15yWNa1dvKBzbEePedWaSuHE8eOwgj6dC9hilLEXR+nYDuGEnWbnASlKgYDZquxLAQxjBYS9Qt/U5yR+a5CE4ZjPW3Q3X3FbFIsYBnNclvJbZg83Wfmfr8J1IHn91VcaLOj7S9pG6kQXfPA/sHK7FbuMFy5EOOKl9Su5F8doNeV+udsu07Wovm5XdJFg3UGwfmULpds1CBO3Vm04479UkyRT8FwF+0+M5bNnqtubxtfQz01Fda/KN0T6q/XsDrsEUR5lnWfJwvkKDxSm7jXc1YlwUrff3GYkRvF1tXrp2Mq5ARUSbXJjLIKUSUwsoiHc9dNCRnQx1sFSV4DxIWLOsi5DcltDaIEJGehi0b92UQnIkI+m8wqvrMmseYC4IEeOlxKJJZcIniuoUSSjwKpWdCPESCpeKu+Gx0khQKLfD28b0LnQZ/kiqboDxAl8u2dv/UyYO0OZGMOcb8GLAC/BU+bt0ce03FIrt4LHeoztJ1pEp3ceY0vhuSTz3cY9RyujUxN5uADdsLSsXmFgKFMyybVf8+RXXPyhK6rZ23Ht8vSzKGMvRsdkzIrqmbjWaRTePk/aP6HPfKo/qwWkeZWR+JsuwJcrMFKFQuruyEK4XcKj9We5p8dICeL+KVlXws5yKLPte1MQkS0niIWdtAKOo3vCItkOHxb06yqsnj2AetWkz4JTbNRwhbml4yphRg4kWjAY2zG6muhYw7y2KgyKo58ORY0hHUova65pf7xH/bXbYLXrOr8AiQrEMuyXSwrBLjyqSLje63DmNIrdcFYkTw+WEkCLRQqZ4y4kgCXGQDeq2YhmJ88iHNmFbp+mOf1c9mpG2LJ6+5p+QInFJtMVAewkbXx9Jep2bAkvK7bnaOI81OtqFSq0VQKS1zAJgCqTy3l/z38hYtZy33Ihk8ly2UQVdJsCoo9wkwIgOcl3dlulU8y9/GcV5VgIQ7YZ8rNSnCFEn0TAebgoMojVKstM+S2nL/IFbh02KUtvZtZYyoFzBqjLkIOSh1NkU9ZCiJNKDhymXHG6fa9ggbUqEVSDAQT55y6dR9cV5ZOPmScDRWw5IhRptEQt6jt2a91rEurSkQdqGDvFhdWt4PdIlxmlS4PWz3I7uHk6QIKxvslNMJmz5K+Zlwn3TNHWUpqCqpmnwEl+ARkarlZiP/ZK59mLoSPmZeSHrltXZS9YOyZQ9BCupW20SOh9EqLotkgk6+IHmSHI4UmSUq910uZPvI8xx8dw5RzovOTRp1G07uElgbugiDSjrJJUJXd1NCihcnnWdCRJoBMdBoqnbIlTaGomGZwsjCEXUJSfUxaJY3kJyPxvt3kwI6f53CJ4CUeCE2F4JERR4Yk1xj6+Vu25HCPuxIbg9SCGVtYpHK5z2BFMHOyBp78hPXddagPAtIFD6HwS/ui1CoI9l0axHPmUe6a7WlMGNF5tnZoIIFWVzwBI0ih2yj1vEH+tN580aVkvzWxqIk+ZUmVya0wDh0rzrRJBIJzgOcl0jZPZeGu/lGp8qWqZdS+HiQvpQmkyeeImLbm6dJUX71Fr7z0FIqNsiBJro2RXSVvCrzmTA2+KnYF/tDHZWsK+yOT6oRMiLM8020ToSB9tl8oJvpURF1qbRHvMkScExJ01Zgjx9meT5pb6xq2QTNvoBpHCVZK9fXZboF5Fir9/99fWra5xB4bfXf/VeC2wgVug8fU/qKcXKdXNXI5GYnedpdoPRWhHrz87DYhrDaeunaOwGN4bmhryiPdUImUanGWkndaYZYdfYpCNDDTksZffGGOGIdhGIVYQkOwNgbJqqWx6bwOqWx6Z2h3/81J6Eh0Zn0n6tj9sKESujNYEU7yZfgMXRatSR0K8A/wAlQGwXIb36EqQ/i6Y+qhEf3TU1mNKUFhqv/nM1weumneoR65rT0f09zCBak9Hsg/XoNCD3g7BRg182LgGSXyeIt1jdyZulECY6qn68icRdnkQNF2gzXiKDpPoJFqopGXWEJ4+P478kixs6fV7DkjgSvhR5vZy4zf8Cyfj0pNdX+8jRB3AHg6+tUKiOUrJvfiqyxQT8ITY8EWNSDR8n+c9JDGCuzUlEDN3m+cmUzQ3vC03X5PldMoFy0e2mRAEcUuGNve4bZBCUXRrXmzLJqyTFPzeqweRNT7JkVI1fgarJxif7SbLGjvRpCS42OtFor5u7QUefdsiXTZkukwpM6cC/TKDvpW/+NdWx56VrDtvnSOCsm/a0ZHKv8QeQgXqvXt7SWMJXAB+9UXeLrY4tPiXVBXjqvDgXBb5nkzKvEoYfdbKnNNIjT7KMbnnAzbGnrFyIlJACucbkaeMG2xZkIYNsiaJr3oGCvW/oW4Ws9E+wqgvFg4kywNvuEHfTWTWUcJysAXV9E+QjvCeGnHEQMsDbbxVY/ID1UhiMGVoYlEUV18GRWjjnm4a/SUo4of9ckdBZvtyrZxdtiihVz3CxomebInnPqHLXnl2BBQArsKCF2KnyLVAaysgURmBhMOYazs+vo76rkw72pSLZ2RLpQ/FUsU+v1lbSWIDkpR0HoBB6PFRQ+JpcEHq8ttyiqaQC8xCwom5rOAYN9PC2Ui3UTTzOO1ubkxjbHXZTw7DJ9h+FtaKHdtUXOpkyhoJg1XFWcnmtdLm24BuIJsN2WObqtnp6ha5QZgeNgYx5ESb6weJIAqWnpr22u6lhUHT7j8K61EO7ChRO0ZpCabcakFynCxI44ULmIFjUbfV+M0oqyJ77sJNOMQLlRg5VOK/6zpJsaUkEb1SP8KRo1hM5ta8QMdYQO4im8NoNrU2T4OYa5NiOnWJkbVPTDOsLMrao/KmjtXNebbiDuA/n9reO5IBy35Kt3E7CBm47Grkk1Y7nVlVHHJECVLkrq+CDtmSqh0E3UA5vIdkY44hIwbdDouj5l+3jb6Fo/t4k3eiDjI4c1pclTEcP4R0aGj1id4rRTDKSsSOMe99GqyOOTTK2tbFpN0uY67ThrR+KpzwrkkWE56zOqx5ZNDX+M1ogeQV6vI4mEJbWPwB8WDqYLoa7dE2+yABSs5LxAxlaAX9C8uTF4m+lonRUVUWKo5IXvbIiP/WIqCWpND+TVhXu33U4aJQe10gOIq0V0nZWDd0aoGQd6wo1XeshwvXKC/IWRJBeiVEc9Ep1W1G0wQvqzY7psiv2VzGLTbDHVtq0Bklgd7AvXW6Sg3+vxSY5WA/KOi3Bd1iDGr6LdNW+JXgMHMdJluQpCDTLqtFvmU3lQBtJMmgD2WTRLrKwNh2cIDa0wPFPnCIHBNmMJuJxUx/G4/OkRhdRM8QQHeSfuq0oOshNmaQ/EcUnCvQml3TjWneEZ4Bv+PgHkMFHUL54Vp9c97EOruMXvSL2zjXkTx0ISkPcbmSA2EEGQBkEyEIFpXqiUcYQSgenu3lFEjpFiaSTHaJP4y83LoaY60C1XuWrxdNeQeyYX4Hf8ZuR/g4CBs1hHWjWQYyEYtEWQSxzKc7h0xVIqiI/K8qWm6Z3g3T8C4jbO8oBQVgH9Jen7Ka2uwE7ft6Y6zq5v78opkhRc7RYwdx8tffPv0R5EISRbXHyym3RFTtfi5qiicKWlkFI7E4pWKCxWRDl7ARtQmHxFDymw86mW/8RdrbLpOz0G8cTwvY8z6MiPcUxIiyjeSDjhIXMk2IJLUlQIh7CYRGjeSzjKB27IIo3zK2wLlgpdUvD0zaGEkxiaahho94xFhqS7SVKIHO/vT2yuvNdoRXJMa8KxtzlKIe+HZLrNVpr/R33QRb4nEgldZIVD2+0eA/b4+jyXE74r2s2EYjnXoGTV53BDA0swpvtiK+fvubdsC+poyX/JFjeCfi9VrmW1WVL3qqC+BiyVS1n36yGTzxGq0dnSwIdFke6aFGFWQ/aoXoLTh3ag9x0FXgjPx41bpjNGBJMTibFu+5zL3X+fCeC1AiTi3ItT9uuAg+lALpXF3VDDxwjCL/oYu8g8P4IoknHvYrz4umXnHDWHLBsQyy1m+Qh3CxDSA5Ly3tp/TnSI2WmvVq7K99uprKSbLd0sWofZWBi8Ob3pIRJvknXflKs7mCeBGrEZuQHXla3df2zicDKhrv3NdQdgsZp5EuSN/dJiiexvESzbYw6i9Oso2/acLuJdCQWth9wEQ/Zp7jXppIKEMq5RiNbosU/WNRWhzhHVQUfcrSKIOGj84XuDPpdnGPhPkoQ+/Ydz5z6kwt8Cdb1JlxFruQFO9N28ngJicz0Z5xjH+Lr/NrUX+8J0va815mo6qRqFHPodnvN5nfL4hA0AduqKseadf2YJycWbasdqxa1fAbr62KlV7HvYFkcTiOmq/oMm6kf46yJbyy+anjQB0cX6B2aaI7WG/Bco0+r9fjp8XFqnt8bWIZfVqfWS16XRRYjLmSm0yt+CTmIV1eZ6i1IeZ7zGY/UH2Rbx3WEemerxVB5xCTPo9YtIK1xq5S8Rhpo66scC25IYngeIm4mh13EYhc5ymBisJ0iSOqZXNyjpX4J1ZS6NK+x1SWC9sDt6rYUdAuPo9mhRWSIjiyyorz+vUlKUF093I0+qNa3tPgnWgl0cH/02NH2uukEDZ1XCBPayEEaIYlSgA7JYSKiIYZa+xnmP3Gg75w57Fy1re3Rsyz1TjtlzWtHuoGg7Fzh3tvPgOOw1+gkW5z4zxi3LPqgaMx0nq7rUC9D1IsBxtBzPFDpqh9Y95YF3axrOYSwchVgMX2nmyYkNrxQqOmhzk63jxTCjOhzcx5/qdp/DvJC3dZEsQpcq99K3SnOSI1eV9lpjuGDFaRrkCJeiTMIw7FqUWnv1EY6ZPxcPBTDoeZsDkh+7PUqOy4WL1O8tVrkNVov/XnUBaifivLnFMEBSDC9kCV80pQlyNOXcM86wXn6nC6T/AGQx0J9UWv2D2Uj8oA7PMJbbS0qvM4ELMavGmu4b5DizJhHxoErhsRA6cfCggZtpJtueTzshp/WLsqXNz2Sw46qbsuwo/5llL2tnxjDwyV/GSWbhH228L/6biefC4wgnKw2BstZUa7QpFN308Zq7zNcQcRiN0WrrQYH2+AFVi23yQG0Rdee+TES2XaaL9DMRtew3MybLwm5mRto5XRYDqJZ3VZL/lANq8Uyj+Hk4Zex99EtwQoQt99d5mNyk/rVGwbNgRvVbRFCjW5jROJTCz+/ppX3cczUMbn/OEl/nuckSvO6uaOOcL391QqMhyURdYJHc4HHyrAzRyZqBetJs56YYAUL1Vghpjda1ZjEN20AtR5HFL81fd3kBqyQOh0UXCRDdxAk6rbm0c6+Q/CEurqcvmVfi83efYSm4aEoXyLwMo/qwMcHPp6MjzvhHoGNOUwHLj5w8WRcTKYCpkFhy8VqVeRveEwHNla3Fcd9hEb0sSya9fRsiVqeYRUaXAMjRWGP6So4SrMrkBb41Wp3jy0OHUDo3gxIDitu7BU3p8NWGwPnHOR7vtgcgoXfHjaGrQ08qgxbk0MIprYCLMjC/laGrMLizVD/sAD3eQFeZ43uHHekVmPlT/ic5A9N8jDaTnZ/DzPoZwcNdavNz8NSUrd1tFiUoAp+0+EDyECEOx6e7Kl+D7odnXwb6dnjdoCidhC+UNw8BIgwzyxIKsR//UtwAdFQDKYD66vbMkh+2ztdrpL/parBCtHhiSgJUzd/hUk3dlzUeC6Gjrk/wBXIK7+DUGGlDMgOi+WwWPZpsZwVZbMKDBsgOKo3AqrDWtFwLUWnjw0cWm5/z3TgT2YwOKFNsYbpLoYeCPwrDTpQQwnqnwY0MJ0UfES0GB4k912wLJ7DatWs1hjRPZiHYq3UmyIWJsSf/wSp9pXAcQL/ceKr6b3rOAdWEmyDnledMXv8ctTUy6KMiPAKaQ5rSKVc8MU5kgyl2ViRKIAWK7csPJ0PQAkmufavhnWN2dosHZve09DKvm+ATD2nIIO2AOIEQZ8+Fz7B96jWA36bm8JykP7qtigy4Sa3JjzT+L7rSDJ0JMHCkVlxrjPA3Arw9OGOEkxywqOGjfqSH92QTKjIyrW9jS5KWjLGECcY/iBS1G3N7LQweGrej9OqVw4DhzjP1TqBDz4OjG53rt70OA6s6807Y0XFGM2UkRrGeRx2bYuNeVnXwe1OFlFAfOqwDDlUh9XovRpHkuTHaXqKpizrjjWB7hqybRdC13+khDKR1vs0QRbtJKQpfg8kPFFh1Cv91yBf4Ls/SZa9hOI6quskXWJj590ZdM7Itqn9Pqj2n4y1rWXlBXiqPgO81AOPXgaRKcd4kJzqtuQUCz6LIUtSJw7/MkoqkzjyJKraMWYg88kSpD+LJij9efukzRsB1WHRaBbNLJGQO/lGxvUSrtcYvrtuFQvveXWTPJ8+A4oavqgQov5KY7R3rrbjRZBY+pba08dLDc0LFXLYW4kM2zgDbesIL1FYVwx6fULeSkQhfEjNb9qqeYqN/OL7SKL9kM9+JAloL7mml1n8oYazsPOSWVf4Ahvm68vkBbPAJ1jhEE9/oaVAeJBa6rZ4koUKLTITWxpsxo9Vus4UPHQrVt6sM9s6wjqzrhh1ncVbYIeVpWHilzQD7DPLnqsBI7oEJSwW4VFqdZIRfFUYousaTTtekjH8ErGcr5HuM53nsIZJts2SjO6ilRS7ZWuoRRcDaJRXLLRrcIZy/59aLPMmm7M89xLLJBEx/Bd5i4iQxSvSg+AgwG8EhAfxrG4rznVkv0u1EjwkKfV8+TyG9udqeqrcHkph1M+kVPoIK+t2A76RN2ooQZBqQANVvYyshj6loXdKKhbRQZCo27rsc4S+CxMBA5738zhp7V2NZVFVaFlk4VzGozrw2bbymYNWExIAfwh8N+yVD5/BI8iC7xgti7IerqtNHUt3hsDjNW4w5tZDooiRA6KUtwgMyxFRQf/8VxxCICYHZQnKKdqa8KkO0w0/8HvTPlUUdrGDvOn1Kan0GSB9SOd3dUF1ZUF7VSHeFYVr0D1q45ErMr+HD03ZWqwdnoPIV7c1TzzpHqZapGIrfF4kSZ7fUBgOHOvNsbuaKJekUfDhHFyvav85sI1m7ZMbWJEus5g8/GnWLMB5fg1Rk8ngb/OPALtMqgrfHEMFdZRAgx7h6LS4gbX2Nbw4rUwSxP8F1El3g0+n7sdr7AOgQsUnaG+auYp87+AK5EgkH1U/4KI9ZAnC1mL5R5GPT4e2qR9lsu70f6HvNvWRqf3EzFvYyI8LvMFFkC8lLEoilYPOk/HAbhJdVlSfuVDvyc2K2pHbyzbDdRDMrsiYxq71dkWeV2dZ8lANLfpf5tC3FG1XRzRBk5u9IBrSs8JOwRewugNlL1ez4u71K6Kt//b6F2G6GFh8W4Z4AZBBDIZK7/SV2lcPOljJ7Zp2QuiPR1VVpJAQpp+n53VR4njxe4gTOqdLsGgw21Q/uaGd5otXramqrrKxatuZw9YwA424D9EarhF1EQv99vr/EgZo1cxwJrxphuk514pIxo7U+HgcyY8KzTrMa1FNg3kK10lm3SEOg6WqhydraIsv+QDWWEbntTXxbTrR1cO8jGrKuzS0zOmlJtr9+pbiMhvmQw1lRXtm37OJnifECmrG28C6856kHQnnaRn8HT8dv37N21CYVziuBgfQniRVmixEcY4W8CIa16qHMhnPqmfNpgtdldlY9e8NaMCC22tuz3TMqq4iY1cptAvPapqTcO2H4inPCpzci23hlzdv5udZ81Am4Frz9Nl04mxLmPaWvv1sw7JMBQPDerIp24RMtNJ93jrJahrJtDwqnS+bLvDX4ufmVGqx0SzmJvZowkwnbOlWJeysWTFbxc36Ec0meiVzatMXqtps7N3FdVxC8rTrbfevkqfl4DJGZiFdOFjRhoRtFcjnZ1n9ECbgU/08Wam1bZWtYcwuasyWafgnl8dgTO6ZZrGN7WdMdggzMCY7T5b2Vv+6+iyM2R/mGmUlDyhjxs3Zsj0bCnjtJSOxoaK4nFSdmICFVHTdBan2AVatGny0RpOCj0u60UBQKRlJV0nGVD28C1Np25AwmCXjBpGmW+kuhOmrjEeWoQV7sR+BJv2PLvsI1jotyCKr5UKZqPuVbf9kLiERzjyJXmLNinQTyDgrEtn0o68/tx53i/gGPoLyhQRgGjQsBlijw3kobyxqGa/R/Rxt+9T1ZjotTEpnK66i6s3NWYM6MGxJBhYQKmg4zEc/UzbjsI1ujaWgHMV0XKqcL5su0ImxtoJLLQ1ZHn5kHt1hW1Y1hhkYdAetWZzb9T4hRlFptGhlwDLGpOFcuFKKfw7rVteRCfhKR+ddsHI7pmaGwfCEQSwpK2qkoC/PGZuU8J+ura2Ri8YRTScgjfNp0xW63lZxtuWOLqszET/v8O6uG8dMDLyDuzxJYgvzhy+gXhaL2yuAsEF84eAE+wa0zkdzVRkTs7Vc+NeiPZkdRQojeiOva6TUoWl7RIwCbnv0yk7LoKV0oQGdyCJtwZ4S8y9o7QgmWMraObIyJdsqsy3i7kq6kRk5OBkbDrfb7RmQx2rPevHUckUfJmAeBU13iW1Y0WOaZna1xGUhFrcsZF4nJeOzk7Q/EzKVlNY27TMV5zsQ75MDbJ4RUnGACCo9FB9SFDgciouYJYxlx7Q+Yz+GGb600Ddg7CYLH50KHHp7Uoiry4Ma3c23Ta4JU3/5Cjp6DNfq3MkiNCPbxBRkn1+BMo1iAollmi+r/ZDKeDOLvGpTB7aXDpF5QXq+ycyn4iFtLRm/yiq4MK2+QQnnbpILbhvnWg1lAva1mkObftAJPGfh4eOseLgsqtrMtwKkjFd7IBf+FBHvFE8quz8BHyrnZGd4r3u0/HZgHB2X8MAqDhxeQndjQgG9hA9VDL4dfKgawUSsqJofm+b7OnN75K/AIwRPn0C2vm+yHOu8TIHJOW+orvHTCzU9nPWm1tV++250W8fVbgObzpFvOc8OPv224mzsj5+/PK/ByqwICJAypu6BXJhYRLxTioCy+xNwpXJOdkIRwL3v946BcXRcwgOrONBDEZCil/ChisG3gw9VI5iIFVXzY9N8X2c+RaDIMrMUZKCk23qROd0aZxHulOSTdn2KvVg2Bzsh8XDPj/LqCUcBFEJCRZYpKDgVn7UgrtxGI5ZphhIG3g5ek3R8Im6TzIWViofgZ+W170VNXFVpUS5uKY7RsYeijooHaXBXTlQ1peBKObtvB28ahjIRnxrmzpZn21rzcS4oV7DCmey6kfQufzxQ9dmYvpqUf7kaTvyrb01zMoQBRjhBtCOSHDz6+eFMxDh9xtl/k+yoqZeYF9tMidwEaXKwWdSWJ2RTV3RLzmbTgZ06dHQa0gRS0mmObfoz+3HkWVE2K+J2NTK4CCrj5gHKhXUlqJ34NEpAjroTE3CWmri7w0YkXbwlH7GwSkbqEtg7chKHfCZWkvdiKl6SE3h3mOmW/L99WlI72RSgko2cOYhGKmEfqm9bt2eq+j8V40nmw6bpTa1tEGIt11gImXbIY4ivFrOK+baU7yRdn1bgMfNhzXdboH5R/GLWkqgBx1fBKOQq7pPy9ZawoGIMk+pw4vzYNE8qzMaK7XvolpHEMmAZK3ZPG9mzoRSxfQBxJP1N14sJGElHXZvm2Zozc5TRHmDBInLRbvo85H2fjOt20mLo80x9w0+ffIKoN+XLkLzKmKlMWkuXqYyu4JPPTd6gJvXY9nGp1VAmYFqrObTpx+ypyqQjaSWfEz+1y3gq7m1bk7CuVGZvKd8yg5iLaZl5s+kEqTDv5k7CdPQ8ysEpt3fXuD8e7+6woKLnU+3w4lzsErOZ0p0IkCMw3C4mNFH2flK228HkJR/hfX2S4IAI1OVlUoHFD1gvNxykYhdDPRlb9lVcuNLUjEosyrg/3t12y15NwHuW02DFiVIMszMmo0QMLGTiF2ktHVP6ao36BiXsqVoF88tQq6FMyNPaObTpR19nu3j4G73E3BiZqToZN7Ot7o4iaj+YuZhaOp82nWEqzqu2XhQ1sLGRNnBKlRWDOKusFN7dYU1Fz6dSVsW52H4b6Qo8oeVzWSAEVb9+jL53XSUZG0rgXRhS29xOeeltRjIBt9rM30548GUDsVMEjDWtxZ7U/nFpyG/BBCU9wgkrya05PZFYMFXOTNdrjxzW3dle5B2fYL3K52H7N5e+363N3LOKiS8YaB3Tufri5A3IcvkpuHp7WFA6hAk5UTpHNu0PCOYNL+gz9tpFrHDQEYMNeMwhSe+CtGZVT6bSnVU0tuWp9RZErlyBuilz/OoVsLkZIQeXqwMUpJvmLG1ix3Rm3Rgm0ZZ187QTevKm05ZyT1Uh+qW9mALQSTMpSLMnSdnuZcdNvsiA9mxGU0eupbDgbpqKuin1+SE1htE2C4ueTaKGGKfCphebWjOqyNxIjNuGssboTLib+4dxGHPw607uIsIoTGEVqgqjc+ouxlqYBjEHm+5g5AWbG8/EoVJoY+ZE73SJO8mY2hFMwJXaOdohlrxegxTed7f/j+q6hHdNDWxZVF9bw7Lyih4sbOjBDrK03YimY3G7Od4FlpeP5Osa/3mr4Ejl7u6OS6pgaNA4KRse3ZH5mK2W5fxLJWC4U2gs/rxh5VmUYtjSjUTL4H6yXU/e2bYcbbes15p86c+/4sLHPvuOZcM3/uuvxTP3Kuy35ZvkQZMfToTVrBuPFcJgVqtgqDhexrMO5/ekhEleD9NyUqzuYE4Ab9GKgY+gfLmBK01eWzc8GsJpUHjQ1LpDsruX9MBH88J6dnQ6ueA6pzY9o+vNvfh147M06CxQbAXH77B95zCs7VgaO2jpWYzq7w0qQZzxLYdBq4LGsxVLg+mQZH0wA59zM5B1dDs4XjanNj2j620b7/vuAB5iPwJD75OA3x6pvkeivB+K2VNni2AeBte644Qx7hrLz+F9c51vh0WwNc42fnDfk6wZRihwqyNrapE5rBKCJ8JS0fdHvWxMq3ZrV4/VgOdbSVb84bCqeBRzLy4n08BW//fxom2VJj+zur4XOvntdbJaZ+BD8ZRnRaLObCAHj8xdHHKZ27Dv5+i8Je/LhNwlp7TVaQBTczYOu4GgRKNOAfPiiJLD5OAyDhsgXXhMgd7x+RPCZ3Pv0fqhTMCi+qmy6QBdbwsY1OSIECBHYMtddC8oez8pE+6uC4EKx29/WnrEVPU0+7H60oPFzqxsb5eY1XIs023xplm06QlTcYvY2Z2Rp2NhH+aNqVuquzIL7+2D+Bws9TOY1Ti7u/mamgMOK57kqgexqKYrPtfatlD2mkc4x1ows4C9TJ73ulxdlFgxgqukfDl9TpdJ/gCukhqcNCVqIn1RRxGbakpjhnElp+BgYytSw6zr+zjS2bpPE3Cm9SzYSWslmu1gUPKHG2cyVeKzJIt+Zl6UdmZqJpQS3IH7mPqzsd1xkv48z1Ff0p/XzV2VlrCNtTXeJjZVlLGgoo4LUxqb3akrxrajmYC3befTxZm1dVxtMsIM9Sbm6V30KViOZUaG3kHr7ijNJO+2K7lYDi5j3gHShV0V6B0PDuZnVf04JuBQ/TztzKnB0f09zNAXcGtKDiRASnmyB3LiSQHz5I/4KbswBSepCGvT9tzZz8jrlW7Kp7qK8oFS381Z09RMD33PrSmaib8TOiIa7yNaMV8Q6ycP4PasLFZGvtPUkbtEaXA3B6i6oenZzqI3kzgnjcS36QVdb1uY76ZwZb1NjVEZj2pmdrYT+zI904lkt+nDptaMJkQNH9HkfC4ebqnfeDI1doSyjtyYGEDczAl1KzIljuv8FpoVxvFMYlsY585KN2SrbgX7GkWlDHgkht1Nt6NuBBPz5k6qjidLkP4sGj7cXviszpJriUCaNVda1ymHrm3rMqYWxrh13O06vAk43nW+rVYBX3nG9OVpUyJSPFwmLySt/3kOMV7TQyqaWvJk5mwFt3zmusbsXxKJotdadWYCrrSaAat+UPW2hgv7N3MEtrHlERUCG970eqzKsnkJt5qWxvxC2XV0M7C/ab5tusTXnW01fC7SJIP/Aou+j7efk/yhwd4AFQOqq8g4XoB24XVNUxLuHnq+dVxtHscEfGyeN5tO9HVm5FgL085k0jmacr4m3FbceprJetsRq+2U3ChFdWpUA5R9lN0qKeuvd/8EaY2LwDOa75R0LMnzoiZY/vatAidZidmg+u11XTaiyMSor0HdIT19XhdljUaQFUToV69ftSAUJ/EwEqaUIUVkvIcZUGPsAIzortMlWDQZuEmqnzJsdLkRGX1fV4aMvfRsRAYasDhdJTBDpkySLlUklAIa0fe3PmUYN5dn7fto6Jl5Xkn/07RoFIxClRuRdVrGF1AvC+kIGQAzOkP4t7QFY9i/a6NWrdii1SAzsw6slNPUlxmRnCQ1eChKKF3DXeGLeTQwrZtSiqMrsiWIDhMDYU0e/MAVLIFq2UrAzKipjI1SnEyyTLuBayeChbGYjxb+S5I39wkhl25x0GBG1CacTsj6TVfKfINyY0DSBYhICTfE1Jg6gllAtQpImZno/ZNfanHHQpgRog0fXxV8RKqPtGcMgDWxiWYhXwwciBHlcVY8XBaVFFdfZkTSq/YyJBvbyoQEWxngusbG7RWoiN4nRSiBMyK/AE+VSvT3ZeY1WWTZUV49KZbiUGqFSIXCmgmwsqzjgDYm0tQTUK5gVSGltA0UlPaKg7HQLNG2nCfZUVMvsWbdJslWN6CDNzZGgoVUHDwU2qG5KdYwVeIhpTaIEhUGu158LItmrewFKTUiIp5SGY7O7Wy5837DMQmt20ouWSWAZjH7Ed6jjbBcGLFLAO2x6xDa0U8lMIZCOzQXRS0d3VBoRCM+ui6nl/Rxdos9T6VZbV6btkSiohj7LrZxtNSrpvJhMo/OGvvGPh0m7x//xJudeiZ/BUCjqKnenzCNQfPagHQ82mclvBqzb8aWeOR5AiWlyLsAdog0WXw1DWiTMfs17NCabRM2uJ2RSpP5OXS+CwgwNDaki5FhplL3GHXoQm5NdvdNjeoSuQCpMAyGO6U2nfiSECGh7EtXbibMEhmphKZ3chWOATCr8PJ7SFKNXnWXzMFkvAGrdZbIGVIGZ+vAeNEh5mGsZYwGJwdiVixADhDHalckD2M2ffsrOlLbd3N7yoDmW6lBMxSae9Nf85D2ZnNvxsQuIKkQC/wA8GEp3cEZAFt0HyBSISoFZ/Mwdqq2ab1ILnYY+Y4O55WzHRs6bWSQIZZNziJUiKE9KnL2oEfXBr8a1+5qncAHKfH6Mov5JaTQChYWxMre/wxqZFeaplgOaR44HxwlpYAYy+aKVrk1q6L0jMq1LCZBZU4oYk4c27DBbeceog6h5V2WBA0YO4sZaqHx5rMQ5iksi6pCFTMNSh7GYvTS1W+z6tGvWqW1tEVmhSV51jq3N+Vmeit9LHL3CnX4qji6vGVPFqkKsnNMFpo/Jd4c3KsrDVEBw8jEs1Th+NkKcR8fQCHmTlX5c36WOtaU25wS3w7Huyq6SWBNgxOrqGnGHGobySbBLCGafjY8qCY9GL49U9BNA60en7qSjHaqI20NATUNSEhIHWrHpN4te/ispR0LazUwpoqBbtbUYpHK2I09cB+J25gQAFumoyu5sgZNgjFZkG5HQl3dlHkQlz2Cvh2OokWKKiDVw5NXkNFOOCnXEE2BVUIpFboIVOq1FDOVZPlvtOPhEt9EohKX2EbEGoNKvc6j4yIBRj0GHlRGE1oN05BDQDUuu/SnQLdH63UGweKmoPspEkULrx6VrpqMWFR4jYZWWqwSuqmnIArlNvxpQ7cB2mV8faWYNBtwjrvkhoblsUEakkkrWIxQVi8C4aRoZYqYdJyxBDz7GrJavGteTZaJYfnjyBYcYUYmIxEbyxWLNoMM3Zi2SvqIsMZhCVU0dLKU+krMY0sxvmGzviCA2o/KrDF4kmsapYE+ItEpDlI49WBk4DISccF/GvJIMU6idjIts9GISoZS1zEygLKqhsXsyWhsREJSLfZIdDWvUSm420DNazWMkNMsWTbo8/YKVHWJmB4sqJhTkYoWtdSDNVeW0VSIX9WQ06IF2baxGXE4WenY19shRldCSSmgZmgyeCm9uPBcHbmkOEemUJ9rT0MbHkQ9Ag5SRg8qDltDCh7RRETgQqXVpGABzeNgpzaYLCw6mU9fy3U+foj+shzVUYknQoTSOBAEYKk3YhP8r3NGiLgkVFESOYQgxzDLkIwb7hBoqMKBWgyHrRGBPhzCiYjU3RTYXI3VUEmANY+Kr6Kj0+ZegwW5BMQyOaSaAA96ye4dbO68S4imr6AeoLaejHyKmxMaGuqbkBCSuuERTMj+WomWeCKQejQCrIxI1EUXDWFEVBMQ46RYkZOQzX0bOT0EOP04ePDglSdFKqGPktT+pssVeITg6RPI1vdNlmPpyBRorBhTTaPBYUAQTFXLZjTSTdWMB7n7u1DatSkCqUcnwMroRd3O0hBKRDXy2sQN9vy+uSQmp4cApx8HDx7MRVKkEvooSe2zNoss0zIKC6BZAjSc1FnQXpjTrSIGxciMsbnqd9te2ZMPnQbR95yCVA1/uHhoIAKNSuYWkVHSkwTfi5roD+SFAfr6o5wcKnD9eBS1wsWuBreCboop8KEed6XylrvUKSGgvoZmnNqKUm4T74Tq6KjHr9m2urHGs/aMRFRAWphmRqI5mnqTE0l3x1Zr+tlVVA/Zqr48PlB7i1gbK2jT5Mjm4nD1WEtdCZR6XCKwjG70hWgNkSTIpqAIifI1k4QDMwyDhVYSpQ89NlGFQzcFWW7pu90KmtAwhhFQoApqJGYy0EgkNGDuqsdkju6qvJYzWhireWzHEocnWlxyYggUDRIadMYBndig4GzWOjWWGKKDQqfiEDmBPWjTJo01e3SlcOrByMBltOkzKmjoIkU1she3bVMnTzkIU/d1UtSaBlPJTln6idtN5ix1OJi8gnpY2nq6cDA+x4VFbJi8DU1s2GiU7JKFWJJRko3bPD42GfcIBGQTcBuZ2XcJEn+Lkl48iGH9bCCVq9DsLuMxTUMCTViJCGTTeU0IiTMhpgkX6ZPM3F6iHi+TCix+wHpJJY0RSWOqoh6coaaMbFSCHA3VTIhV7BTLuSjL/XO7yd+jpqG8gnmg0no6+jlIJn0bElIq5ygWJb/RE2pNTraW43iZymMSlm1odLmHUzgZRD8FYpBTG0ilxOvySZkkHoVpTBJIck5p1VEtvHpIumoySilSZmmIpm1hZD1W1rZxhZoruQ3Wc9k44vWdK8+4TXIgpiQhB6EeFwuoir00X4Hk8Iy5MOmca7ebTG5qIrCA5jEw8DqSmFU0OUpZ7JyKyt6mcx8Ha/Qo8IAmG5iDD7KneVwjOxWYBHsGaS6F1AkIWQW5VGDTAGqljhTpyEJ703kzCylhLc6KLBjJ8fRpYnbisyve0lnWZfJIDa4TIcpactkkZIXUyic1crUxyWScj09F3bpUAzsMUrc6A+k31RoVGtb4J5SwDuPSeCsCKTaN74KNOTPfEeIANWECMvi4sXPTkkie+9SCZIaKxsHq62uuWinzupoJbGhz5OtXmmS2t6oUtJLl7YFGsz7dsUmFgj6xr05AeHRAplDbMUX0daLPUOy6eLTYfLlbT+EJl5m2I9ZzqmCqgOQBfQJVksdZOWUMmJEENHRw5gAal1pGtf2PRRVNWmnbbAvWKIwUsMWkobQ+ubZ5Eqy7MF1mB12XzPu3Te0gqph38jHnZFIlStcR9o03r+lgUARRhcY008QwXZAmSmPezBtrijxWiP+y8FgLXnSel+v71rXqq3Vd78FrFdUxqK1VTHmYEelPMiffKgod5kKPx5lMWnQOc9SnkHafKH0P1JNm5JIApdNyW3CU/bYC3k7vnElU314nq3UGNo+OqqnDQZqHxFYIphCHbuQMtMNjI8y9Cxl9FJDqAckryOhDP4eioZAC4QRXRDYta7Z4EchmKJpt3Jksk27VlJ+//WnWfpRVjKtCVVOz2jRHDE4tTE1MJzL6EHBM0s1ENO7dY8Mpmkt1h8GrsViRWnzv2YXymsYnO4XDDz0hDoCrpHw5fU6XSf4ArhJkqg1PSUlcz8ZKGj+vqa48sVVhyphsxivdcjaPacUlJfnDmoYstOUgmUoxqMYiHJtcire7tMfAxjrqkZqqSnOsKF8i02UVMTU08hGxqn3NFmWq4j5YzXYVlajTbF3Du2VGfVsBqR6YvII0axv1spoubZsc4QT69vCe2q0mcEgE0gyFh5WShXriTUcWAdfIQULCU2vmS8KuIk1dSXkZ0n7FaZCPLMDYB+Vuz8pipSOdDlyniylryRU/7hk8rZqnRj0t6W4KB8JRwNZj29SJTDQK8cgko14IvBVeC5QJeDW4Tigra8lFPf1GolbYq/HK5JvwYGJU8umYTQpnNzAdi3lRarKAWumThreSVxQlAba2ddVDtkUhjVpTvduoC8e1bU9GdsnzkxHCvtk3GW/Pc1jDJNPcQNFVUI9cW08eCy68KKkNB9ehH/cylPTFzFvxtUszMZV17QeuQmFDYttLU5YtSqhunFTfJK30+5/mDK1yaPWQ1ZWUuVn5Z0s1BNVgHzmpnWlHstyJTDuQcecZY8f59W2LAAcVJzAH5VD261v8xOYq6T78+haBpGBdN0n2pViArOoL+sffNzW7L6+u10mKxfn/uH796nmV5dVvr5d1vf7b27cVQV29WcG0LKrivn6TFqu3yaJ4+/6XX/797bt3b1ctjrcpY9f9yvV2aAmtLTTbXClOt74AZ7Cs6g9JndwlFaL4yWIlgF2vkrL+evdPkNYkuvqZkyW/DlTuG5Q/GiqzQXFFrCP1NfHvzs2HWyWusje4e8KzojyyDWXP0GAxBBk3oFjAjAIhuUYLKSn7ZdQN5XyBSFNkzSrf/M3zorr2ZfukKI+E+myPC/+fRdN+scdwmid3GeD6Mny0x3NeXTZ3GU40xFBm+GqP6aQEyCb5B1yzqKjP9rg2s4tZiSe5WGqP+VuFs8Px1N98deCHpKqeipJnh+GrQ5/KjOsO/mBf/0uT1XCNFtpZUa5YTFyRPU78Nuz1S1WD1WVSL1mkfJkD15LnPzeOI4Z5uTJHrNcNkW0SnEOJaz/bV1157uPLRKy/vuVkGC8x3woik9vLeFnsIKkv+5ePw8X0pfzJZ2sZraw/joAOF6pnRYZzJwh46O9OG8YjRPXapSJilZVPvwF0j4yj2cTPjPMzIJY6iZAalOSJeU569J+daInXMMT3QzgaDt/dZwYpQvfwAXO8fGbocnvsX+/vK8AJov6bPZbPcAU5JN0nexzHSZ0ur5FBweKhPjvQDDEqXuUcpYavUwlXpfpR4KCyun9iWr7HKIE81JyjMl3CR6DQdoZSB8wZSPKGV5/6j1uzzXQiAWCBELbL0Jg8Nhl99W3dY44ymHBc2X1ykNkgLfIFh2X4aI+n9dvTSNovM+xCdbH+mp+WZVFyo6ILHOYJ2bdXTf6t5gwb+ruDLE4qxLeIeQV8bIkbxtN8IcXXf3fsX5OmSJjJe0iVOfZRnBHqs4uZ+QkuFoDbvTdfnfbuByy10c6TEmcqt3Gzhe54u0M1Od6hcGukMRtNHiKNdTHzFtJYX31bpfEHMBzZ8z4GqsABH6zWWfLSPrHFYWSLnHF2BzESlF2Jy2r8AO6TJqv55Th83iL+Bk2vrtV1ki7DfZBSlF4cb4VnHNanGucRcUVOmzD2MGMOFReEUOjiKeM9SK5+ozOJ4/XM2esqGnqupl24wPkCV0BUuzZft2bhba7ghKw11SUji+WlrjrOiurb+9hA3t/MlDj5mvuqoouXK3Pvp4AyEN8xzBP8zLAMZV/mYo6TDD4iqzMFbhIgFx1zw1cHk+UZtV8J8o367LKRXuD3B9lNlHxywXFTJqhlQZllChy4br1IarD4Kho/bMnWSBpqv4q2sYdt59MdKcKiRO3zxkb/1WF5lAV38tN+ccMgbm+brw5ehYLzKRRutcVe9N/ssVyBdfbCd2T46IxH7BJT4CAWTzhpeOLkz025FU0+OCh4sjMyj+Ox42LB8Wz7xdWLKhNSbImLTwxtKm3IEusVGz674ZJ0jfo8t7cbdWXxJcmbJMtehE5SJVsj5OmhBp5NUph8jia11ceR8+2mIk69hxdAlERMgT2+T+StaRpR+8Vh9ypK3v1Gvux2MEbrvL6u+PnafHYzAFqPCpJqC8SNMMkqYbAykK1Zt1145hdQL4tAW5BB5bFyDfVHUtHoRpUn+SogF6skzZoFWND3zM75Ix4lkEc7WP6VL+omqHJ37H2O6ZYiykYkYC4WJukhwYXtti6qkZ9uDZiL1yaHq2ZFHK1Hq/Z9MdZ/I5Y7YE+e9dgl5Q4HjqTGFajqEpJICQWhdHAOZkSTZUqnulC4PWLOmKsgSPIZsPsIQ2eU48jHTQd4PGyJA7/2AxEYlC7wwEdu2CiRDqVOsbMluAYZsp74I3CuaHv5PDJjx+DkCQO6ccOSgO7+s+syUGAUCl2OedpTVf6Ep//qZD+k4tay+eqOqTtkFw7LZeVTHrFcL8n9AMUeJJa6RjTS1+7EwEZddkQ15k9wAW6WzeouFyxFrsge53dYwTv+FHr46LBO8FWAasmLOOrznGfZ8T1JO+dAv+yTd0QQ5f4CfGKxLbsYwhW5WHklGhl5z5xcvpPuCwoY5wV5ni/gI1xIfHdSgD0RnYF2gVK7W6wgslRWK+H4jC1x5y+wQvJI1EIlxU70ffqaf0K2+6WwbfJlLq68to5auMohHCxUUCf/AV6e2oQ6jG3KlLhhVPKDUOiG9wbW/LZHfXYJ1c2Kp8HXQp6ZESJ3ZRAOLazXZfEIFldJjSTsdcOd4UmKHaRBURvQyyHce39T1Ekmp48UwmsM6kaUQM4HYzfFUcppX2yJ4/UN3KE+r5xwkYMudOjpz4brIP7g4mvKm/skrZsSlGhHqy+QYslLCxWMfSsfETexONsvLrb15rln1rDefHfoT1dHpjTwZS6Hw783sARf6yVOT9snUGNPiWUQzi1s1A05fqbcYf02SG6hhZ9iReNoseCw8WvZCO0UgDrkqeYiUIfv7hFMwj1h6ruD+p9n7fKkklAzRoCk3MnX2y6nr/cK/HIId2qcPq9hSWI1PyQvlZwyPIx7KyRVEMEgvamthHLQbpKKT23OqDZisYM0ZWoKlx+FUqde43PMo4cSAFE3FUvdDk2Hijc4j4NwesoVu6zLIS8LvzCpAhf51VU6eUkz8BnkD3wksBzCt4VLUMJCmEcVjEcrRMEgaARJLINwoTw+DJNep+KKXHCeIT7oD9l4pGyZi8W1gHglJ1lf+2SJE7Xw1pcKyikyMnk+fUaWlhgZuSlwwXdaCbQlnxzC05LnE2T2PRT4bJSLUmOLnHQyZJud549oxaLK/WmooJTJgZzi+9Ofco8yV+Ts0CP1jx4TmCV3MBPQq6H8WpIPQg7hdMqrwS6WOlgDxVM79i6xHj+5snInKwnevxB/x1lR9v07Bsg2FSwlNaDDYUeS/iywCwEnSRIOO/hCR3tbnfFYMLztkyOr2ySukO74Xj7vcgjXFtojfF0LPIQj3cCiqyqEQEqKnVYd3neOm5fjpq6FW3JCqTPmH7BaZrCqNeh5EAfKtLImA4jdL5EdJnrG5BAOxw/IGiJVYcpfdGBKnILCeVTOOL5mCwmazVdnb+iJEC3IFDjsQfh50iST9I4t8cNIrmJ/QJswfjRSjN/VQvq1eJovrNrj4Rw4rHMvnuY4DkXGaDIA59gmBguQsY8W0DnayaI9HaCTJUY/M8SZYboXiNQ40X4NKulLjZJxGIGdxtKfrldH63UGeVtBCmCP/weAD0tuofffHKgjsfPcLbsfcMEj6T450Esynk/O4xn2CL140YB5tKUTLEqgOQ/io4YJAJKlh+tY/3E3gwM0Yx3eORYzHbJl7oFDOB2DeEGcLXPR/CvQbWcSR4VY6oH5C0iqpgTtC49S7AyERwuyEFuh0AMv/qHFTQO4hpaRw0vR2ycUOoesgRLH3MmdRAoQzzYwDfRNDBDeoyhaV1PC30zRgG1NGM+wcYfduO8j+twDedRVx4nkwf8Pi1fpeyw9e+DKnDzsqthGrmhbgib72jLxw5e5+As7PUa0bJgSe4ydviLgo787n45WJ0WzxtcVFsIdXbHcQXdQYPXD1s8COfonyr+KX1kIx4iD9owNaQuyiAO6cGukXucyh4EZa3vPu4fYU1fdVrGH4+VELJuvLspn3PxXx0WNjHUlVkmxi5szPHNiP9eq6D5Z+SFMTo+rDYxVnYqJpQ6YIQlCElBuPrv08gGIqXA3Xz3DAqubor3mI8dvgnXv/1fZcYhQ6OhuvsLvv/I46YLZQ1xHdnztTgDgNrpZ9vtWh5p65N7+dwievghaolC4NQpXJz0Db4u0SHxui6hqjqNsdc3JkptxRS4nJq45/JRiBxTyxGZMgUuEyyEfmVjud50qzjphcPlfrpp61US8GhtTifPfaGb26XWuiPDktRKEAZ4+LZZxWKtvWuX+8MNGDeSq6V8nEk1kC3CnMAC5x40pcLCdYYYfWhkyr3Cms1DqopN3IZhy1JJiFydcmzAEa7NsXhreGaeG82ntUnklQgbh1QKb+kjVCgfl01I/AVLbTgPmN0tEGOimZwBwcOw+12Ui5lemPm+PSKbOVANlMYXJRwhrq2+r7xHVLcpP4JnkS+E940yR87a+JVnz51U7I/nDWWw+bnEjhu1XPVU+ST9v5Hl1BhKss/Y7i3Cjgi/ef26lr4pGYVgaoT/T6rFsP+PS/ZdcBGHKDgzsxcDxODeQZefg1XA1IPbBIU0F1eGcCuZwQKfHdThG271jtMPx0b4cH80VV9Otlu69lSjKWZtZxV8tU9XffoVMIoqdxXA7ejGGl/7ugI1k7OEwdd9cLkhk6/sm+y9QkXvr/E0JrtAZ70WhRjuUxV8yBu4+r/Dvr/f/TQgiY9fMf/cJJmNRTMXrfbOCpUt9dzAU1t3TyFyXNp9djI4+ORNvbWy+/zEFu6MMPs6Khz6dnTtfamuPxJNtc6LAYwoczkfQEC6LSkyvS32ffZaYna6TdDleMoGbphbVqPtn275iF90U2uP9kVTdaFiU9PfZ5/ECPFX+q01be9zVJioqbMl86xcTRZYonv4++7xfFln2vcBq1BVI8dMxHuvWiGKk9YraPcqrJ1ELYEu2zkQJjPzvsAQoa1NraeITo2yJ01WlSC8oxXijKt4rTH2ds6JcJZJUzHypO+brhH/+mi1xcGRFT0EcO/HV0f09zKDEo8oUOODDaYp4F133bXr30XnVxu/0j7pxFOMKHaJmFEFEftFCnxOkocrtKq5oTmsId+Vz8QBz6TU9sdQNc5/eSolcANiavaqbncBT+X6O3bcqZc1xdqozWFa1yPrUZ7eZF1Ftvk67RyFxvE7yF0GjbT+64JHG0vnF59WI39G3R5ingowWCh36KKQaO3FNLday3Tt+X+u/OmN6L8X03gXTP+Aam/tJJt7N5YoctIJlkQNZLmymwGH99El8ufWz+TyFlJ9LrydrIPhGb7uSfNR6Vc1tPUwnx5tVF+ErOfncFLnilOf14MsczkGeis+grpEhWRXi8hNLHTAvSwB0uCXlDnPUIOMKplLMfJnHrcHvifxR7a5kpuPZ6MeqoYfSM8kj9pnRMKnE4vIQTiYE2yqjYgf8xOfO84cckbpNM82fXLFFLnHt0zzLO8ZjtnOtNlpfDVxsNCqftaavP5YjPp5hEEG1uLsrwSNMxHXLluzaFjUTc/dnjWF83WPxPMeVVx2Hmz8n+UODE89zWOjv04bRHBcLzqhtv7hGHrZuWemDXkORA84umKGreyJ6IuUQDrJg88yVuhElkMMcJQ8cUdovTu6Mrc4b9seMAh4ronTn3BO96AqT4T0WDxmurrqtyn/f45MmI2kIpFvBUOhwfJnD3xtwDSRWOVfk4HrLkofzFeoPTqAhDl1S7BB0WXPuZ/Jh/jX4h9bJ2ruh13VJIi6qoilD7Q4ZRp91boVmF/S2vvficmJL3DFKrhFzRS4HwWdlsbrMmgf+9Um2xOkgvmhSYV1Rn7dmFfRRVGGc32PxjHmTV90FDo9hmZD3sTmFt/3ksHs1GbfFtF/m2Qu2XZM/WHK6drZP4/9jWmRzXdEfoj4DL38NeDyDYVWVx9kXcIuyANipbab+Ed/vRc2vPa5oj3VzTPhw7vPku93URMI5rw1Q7KSmLHZxKJpJqxglRTDZ8D82oKqRdo5XlkQfYIvnPKzcds1qR4/66SPYONcKMKaAqwXy6tvq51O/F+37WrQikN0rjB1V6pabmH2XL5s6mL2PMyfzLY1Ab0vGDj+fa58H5QpWOCShvfMUuOdz2Hz2fyOKbV2Dca8dDOnbWDtt+Lo1HHT6XONLS9lRUy9RozAlgQkxuEmH2YOz3NCNw2Vxr/vHCKvvqXK+wDS5h7yeJCt3x96JeFMjEjD7tr7iib0pfgLOaUB/d8R2lKagqlQ4mVIHjRzHNSGtUyUvZOVbs9rPirJZhQfSDGg81rGm7jiL9qZYw5RHMXyca/GL17td73WfX8qzhVxuxY22nUvcRPiSsEWEtUHw+C4OReVxVgdpkUcxfJxtdWASyF6XZAqcbwxILws4Ohdb6cn7Fbuv9pjwCzUcmu6TiwOsqmWZWejv7thUMykrd8dOsm5L8bYlB5nlJrOScGHlK6cmFVEfy6JZS+XUULLL1zIuhr2Hlyz95zmEFF6aUvWJKTgILEueOeTbHEltIyIggtpG8PiKQ0XlbfV3bf/Fsm3i7j07ryFzEbZg2ul0XyuKeuMsE9KYLCMAU+CIT0wjRX12OYqUvAfk8QZQXMuru/Le+RCEnI9Cqcvxc3uMpUAtKXacF3ztsBHwckXu/ZWjFUsdXIjt21VyxEKhM972irPSP6kCcue4k6YsQZ6+iLcI5BAuLbT1rhI+noAtce8ze2CpWjMilINln9QyiUN9duXr5q7GKZvP8zRDHZOxNw/h2QK+4KxvYYBwb4GknR6eG9SNRQ4Z2KJ2bHJI1xY7kaAZGw/h2YJmLDyEZwuorrj25BCe8gnJeYh1zCQ7A0BKMgvwGG1LiWkBHqNtKZktwJ2CN3AV4Wpp/9WRP+Rc58Np/WqToNsUufZOzHpPf3e5JnPf5AuwkD0zype5YH1KysVlAfO6+gFKgOaWD+xTgDjsc0uQ/iya+qiuS3jX1EBp5+khA1qs/nOVGZpqQdx3b1XIp6zcIRQpcp7OQfNeKzTytXOAJbYLkNBFhi8SByeIRWQqhh7SJXilXIihXpuvbphEFXTz1RGTZMx+I/ySVD/BQk9NFYxbn08eH9+LPW6/umE6fV7DkgS9fCnyeikiFQB88f8XSCRU5sv9OPgDLEFafwB3kL/dowJycUEN1bq8s5+KTOKOUkGFtCTjIDWUV0vHSf5TNLWkAN74xcUqBfDDf36iRo3LvLD2WQdVmIdyL+zndwnvHuUL3fcFopNIM1fLIRxWWoN0xhL+iyzTmzLJqyTFP4WNTQMX3prIpHrI8BavQNXwecVNsC7ScY0zLGjoKYcIaUE2IjWUUyTEoOVpBqQBc7m8UqbLpAJKL6wUwMWugvK7F0yBu8evyxEo9ff1Ze5YscGFEx43dbtHa7101pVcDnzi5H3fhWMZ2oy6AqsE5sLNCgWIfRufkuoCPHXG+kWBI15TSb43DdjWHAf1Bve3CtlOn2BVhz+ELkHpk1PYDs04h0eDB5JfM9R3RweF9HDCcavYtbxPH+E9sZ8iMpcEpQ9z2aEZh7n6tnks9HcHCVqBxQ9YL6VMJhS64ZWkiqE+/wEYNw6vBvDnZFf4O3WM4hbJu2FqKHful53B8WUO1oHEW+vupT2v+h6Q64iJoDNJAdzHjgzTtcxWkpW7aD4pXEOsagg6JVfkgVNyY4kvc9CLQY61flH1pb67YpN0kClw8BCCqhJuyQ8fXbhpQ3ai/IncJAHYY6k6SIwI4UeeKbM0dUcMQ8INKkKHNkXT65OXEZ/r/nuTECqzqDZfHRSPHNaXJUzlJ8NiqQdm6bmvWOpCSVV/ffuq7qdvH6UHzh7Hyr0N1G6v0kErQHzbkJJBAeKgMhiPSUOPRzXHot7HoR+KpzwrkoUkfxlX5LJR9VWVao8EwCVSNgV5BXocQrC+WOzQdyQ/fwD4sORfIaS+O4RANvkiA3iX4YIfqe/O8vUE33iVSdi2YLu25wuSICt4e8ZofLdned0xt+fwrfRCyH504Zfw6KYYnsiVRdLTxXusKdIe2jh+IxGjj9vICss4nBo31rsdAieVum+uWI5RCX7tRYJsKHLzO8msefr79Lbczq0gfIrUJmINWTU9Fs9Xt+RVt1uS35RJ+hMNTHZoyZc5YMVhiDI9hSlwPFkE8iNQvszpnBA+glL+WrBQ+AdYPeHOChpTwCqa0mUxtCk5B++/ezhA5GvT2X/s7miYTYepmzK/Ar+TDJiByguFyktt0dYfiY0iXXKLzUBxFal4fq8rkFRFflaU7Wzxvnau0AUvmXZAbHfexSEUeuNVR3JoAd3nTZ58Xix14dTk/v5CTBpNf3fw+ixWMJeG+LElLpSmlq/86qACZDdDe2bb9Avy5wkSvjE2fhab1+ZvQrHdkvsyKTvVRbzpSpe4+udkGNkSF2VnQ2PZkbSsfDajPeJ5zRi+6X5UiIVBCRbkoEI+bhZijt32IO96vrleg3SITNzMfVg6YR1qn9zCbvjGEYsxl598JF/X0oBsA6zjbZwzmKHFJwTF8mVOIvTpa96RQfW+AFPs7BXfnec3dJMVqFBoMPsoF07oRlI0pH2w4//JkybtByeOwYPRuG+HUmjtGjd08pc8LB1hZ0d4/LdxaeW5Z3reefmelBApscNKOClWdzBPwncNM37/eXRCOtIO8rPhNgv8wSEit+YfLm2/OJwwJnlzn6T4dl6JzFrpbWAVjGM4mCQOzCkMB+QP/OXx/ps9lh9wwSPpPjncn5IchH1yD9ZJKkCIIDsuFgo98OIfWtw0gIMWXlXwIUfWLyQccc4/wCgrdz/DwylKhetLXJm74Y2DJHmsfJlL4JnsKaDN1/1xjSAJmv6Uey+4Ikdr7mtTf70nKLrEjqJRJ4Js++43ypYXb5/bRYdDV0lpZMnKHcIbwHONPomPjlHf3S5O/N7AUnZfov/usZxPirwui0zm5VXB7L9BEHfNRVhsk6+yowwmgtgknyY1KD1zMm+X4CY3Q0eR3u2d02giXIFuVDnO90EhgmVgu8bNqG5RXv/eJCWorh7uuAMWvtDRDDpa/LOpavF4XSh0MGqI8aFCLJa67GWXJbgGGUgltyqYoin2GhVGshxkGyNT4HLjI/+Jj/ek+otQuIfBXzcQWfqYH8OE4YDGQ/Jp6m6/uho7Qzp+ClZ1CN+XzXEM7eLXmescoX3BIOjYAP/0OSWQ15vbVayMACg5dwH54MD0VXaaJ3cZv1HQ3x2wgbQpgdAn6rODw058VOiT84tCxUMxuLS4HYEtmnMn/FSvsuNi8cJ3kf7uogflNWLs3u92AeqnovzJq0NyGCchghbcC1ktfZJ8UQrLYZxbOX1Ol0n+AEh6Mn1jKtCtEW1dp0LvlvVj83q0XlV1W4Wc7uUHvxcfxFvpHlfS8WL/XKBCIYcpU+SqOZwV5SqpayFaRix1UY7DHg5SLtDmLoPVkt89qM9zCtZtCrhTjrrAyQBP8wWaUW5euKKtEWGETbpPEZS0DsxXV1NWH0eatc3zODZfXTGJ0pH+Pp71MpeRugQrQNxNSLcMNFRpVD7Gqr7+OOxDGuUOLNpPU26okiR7zgn2doThjpP053lOjiLpLMNhrKdA6sGE1pjGYcdo1y4i+mHiXmDYuXvAdODODVghxSf0oEyG0YNV7dBsqw2Bnzu/TPgwos3XPT52PUET9VCUL3G4icfmY4EaURy4aOu4qA+kjcJEHLKAuN4DC+0OC30EOShhGin+g8fmk/LYiGLbjcj/AC/tS+AMps1XJ0wCkoNNIeXjozS7AmkRmqt7QOPBuZq6286ycf0e45y2nlfnC96t23/bGjb8VkZhwwGNBxtq6v6x2PA6azh3fftljsB01bOV+ucq55Kl/aOXgbK0R+MjS9V1x2FixZv0Xm/Rx3pnScZ8atabyzcCkqopQRscF+gUoVH5eEP09bfV+mgf80Kq3RMW2qzAYoucDlNhIZym4k97bMt08/8BoiarYIcyj82fITUoDjy55zx5VpTNKt4ph4DOgystcIx0skG1Kb6DIZbOdT5BCMSjGj46HCwWa5jyeIaPe3xeclmS1wG6BMuhbkkal5dXUo9gu0/xzspipeJuvsyFM1U42RKntf1PkNbCkm4/ul0hFC8Pul4bTCRXBhNHb0OnsB+/tM8I8wiFYi/cw8tPSvQUxB5LDGLWoILPRWD4EoXIy4WnqT2S4blpUnolVSye7dhe+l65ex7TXWZOjDkag2KgMCaVYxhpRxvBJAg3VKQ3JTTXJGY771+tE/gQqP33WLzO91VVt9YEjaRR4OsaLJL2y5wxy6Gx37M5WIgOHSkEikXm5V4xYNhW1j5OU/Icaedd5hlAUryLyybe6UlLjVT6FjtfNv8FDPzeLA7MS7KMz/HDlDjoiHWdpEu8Bt6dQfFMVVLsg/u9Hvf7ENx/0uP+kxr3TKLuAjxVn0FdgzKe806O00Pw2SIaSf5JWxcdejo4x7UvWfLjHaRNpwbsSGzNyRKkP4smVpIgAZ2POmvGsa2b/zYnySJvhmHbqQvllONWQ7n0+yZ5Pn0GAhmYAgeqJs99CLPg0mSLnLavrU4ZFk/x2hbBEiFxlBxnDBEzabIooXXBhycDmFKOHZIwha3e2d6jS5sSv+1wmbzgVmO9qyvD6ve2riWmcdYd3zyPTVZujz3W66Q750LnyRaX2yKw2WRy/SXNgCwFN1PgiO8SlLBYCDsEW+RyOlwnGanNOUWYAhczB02f/I1apmQO11KsWM3zHNYwyaQLnC/b43VOEqzAf5G8fnggofsKj89nR7HAMc5aP40WGO8XPq7ERrLgyK8p8WUeWBUI3XFJri8xBVvD9VcAnz/0uSxD9zYal9fOpkcwDq9f9mkF3vGGwOa7B7b3Cmzv91pHPymLqkLWSBaFo3hsPrawEcX+ctVsO2lgJJRfBNSEkU+oqc/gEWTCpkZ9d3FdFmXdB3ly7kq6xCH0sskyKUKmwEFFXHcHuhzRNp/niuzCzzEKGUKHjy7G8z0oS1AKuJiCOU9c4ieLOyvxa9s4dyMXmzt8domwIzlAPyUVbxzSBVsjotAHnPQwMN9ci8Qn1Zyq5rae/PyBruNvzlxCLUDq9MYnoZyu9rbyyc7pq+ROS+A0YxQ+EyyvN87UtnGu4gTT311cRmnWLMB5fg1R7YQzxMVSp4OOpKpwMC4qqOXHHSKAi77QVuYVhv6rg6sR1nxOh+7TlJFqX0CddNHLnHLGlrhhVL7lIxS64ZXQjPo8f/zbFciReDqqfsDFA6h5RZAtczgWJDX+UeTc0Onvrth+lMm6U61kSJliV9z4vXPJTAmFrniPCyz8+QXNlzkd46JNmn/EYvPVcS3jR33F5Uy+Tr9dHVVVkULyEq2wZ6GtlMSM3h6t1xnEnE4pLJqNSl+P3556aJUSgvbIhbj7Kxu4vS6aUvoMjNVuN6CWbXuYmENfArt5k5R4eXt2syeWYzd/fSud7xCWGPxvTgyxqaViB4UbzYrMPfat5oWhk2Gc0KGZmhFIgBv6E22ry2JxewWquoRYSTrBozRJCJvagi3L1GkBbYSFua1ANuEaiMAsFl0OFB8t9aZmmt71dktd29A9FiJCi6+CtDC9f9BMWxFr4PQPCCNMvKRzYRPd4Zltoo9hlqHPg/vWYrb5Kqopd5lrFmcgTTlkMaedQx3GmM4dQ4r8AuL5fHVeXTRZ9tvr+ySrgOPog5nnEpQrWOFkQG2Svls6IaKWh0w1hbwXHDwNbsFe+uYCZ09AHoHPDB0O3FMoXLPJG2tWUdVQSRtH1pCj38adZl9Y4Ao8JeXisoB5XXUBsbffKrD4Aetl5wzWRZQYK4sxJEIVC74wNhQ4AyyuCHxi7vA2blImMsQTOL2q7qLhCHViqDgc0kA+4rHFlDg87m1kIPP4w9WcznDv/r1JHvS6jQxcTORFCjcwNlqMiDhUdfFySVh3Loq/BGGKvkP1vlbsEE9gDkoeZHDmdl+Gv6v+A57Q5AF8KRYgqzb1rtMlWCVkRNU6SYkFvwBnsKxwaHhyl1SgBXn9CvX9ESKp1x+5dQ6X37OTrE2R1QN8SXJ4D6r6pvgJ8t9ev//l3fvXr44ymFQ4biC7f/3qeZXl1d9Ssm6TPC9qMvTfXi/rev23t28r0mL1ZgXTsqiK+/pNWqzeJoviLcL1p7fv3r0Fi9VbvnqH1grLL//eY6mqRUbPOHVw2kcSP6+Lsv4A1lkhua3w638Agdv6Wb4C969UsuTXt3zFXyX8hbvx22uIqUtWJHmggUSYJvhud46hAOnw61dY5OCLoYPYeatFj/687y7+U63wRP3beb4Az7+9/l+k1t9enf/n7VDx316RXfBvr3559b+dm28PbNuW88ekTJcJ0oW+JM/tNYzfXr/75RdnpEPuoRbvHaydUZxX5LHONARHGyD1D7gOQbJhuP5aKzVNjriQ1lLmJoL/mSV4XYrBMQIPDafOcfGSUDU9SmecX5qshmu09eEXY0MmBqfLaCVg+0BN3KHzyWciU4GgHzLSjNH1TTYYW36lTxotJHEnf/ZEDI8iB8+KDAnmUVD3mzwdduPAo1OJcay0LBrsT7xJqp/umxxfP2ivQxIDcQkOl+RI9d9WyfN/dxa6ZYFXL9F44qAjE4p0y3v4gHW6KGi/3t9XoA7ZtEgUTAiC46ROl9fwXyAEySVidbzkQ5jRVzBKNItitSbXI922CdfJGxSYI4SOXOoMUIYykOSNgypkvR10axTgFfpH2Q3+4iFOienlgtWGRa5BWuSLKoSZSQJVoVsWwpnwwP9mRvDXSNuMResX4Lm+avLbrvqwM7zz2BkQ/6y/5qdlWZQha6zrErk90WLB9ylqiJkpYETcXmfDFp+TqiYXy337MiAgv07z4N4gFPH78s6TMk2aIoEt7Y/HyGi2USl4zgv7vPoEF0h+hfAj+vMB70xo80yJa0gqJCy1E4JpuPIVNFrrreXvTUI+fcvhvvh7LLYWDx8FFUhtjdpm3rvLFu2V83FwU2d3fvsXfijgPmmyegTd5u8NaHotb8jUuSecSI3N3TBjKgdZZZ1HHLMYzcBeRoLJEeQjiM+8vLNnWtes1fKgTEA0ljuYJ+WLlw1oFDnvPbS2L2i3lGuOToitV+KH4inPimSxJ4uvH06bCrZtqMnh7w2ABOU9BFol3H2dfatA3yjl1/VyiYtognwUPb5jwuJxeL67syLjUC98eD3LXPdeyE6f6+4xvBjYzqsL8BR21nJTJqg/lIpoa4QNN8ZvKSRBphh7B93DWtB3ybxD+egGeyKUNjee/Pdg/CBXdNcJRurqk7FZOjdF9K7eFGN09Aqssxe33jqgHaPHJyexMR6naWyUNidwPjpje/k2gmxnc3yofRV2mjbeEDc3d/zWN0Zi7E7gMaWFlGdrhxkgzIsU0U1Iuqd7sk90jw/oFP+//MVdrWz9AWZrhcVtw2ufikq/yH36e1mUQediVtEgXh2zCQfxQdw6pq+rIOsB2yGtvwYJN8KBqNYI665LattesduThceMadSj/2eSeYO5MHO+iH56NTTTXlc0tfDeJ9Kma4K9cjnCWLoxkPawbUcs0OfAg+YvMIerZkV2t6MV2UL6bRekcJVkONwT/apI3Oa7vyKWw55aNOvOcVnJ80Qttdj7O6+xKIUT0tn6wc0RW/ZSpo02Pm7yBVohaD0ObxG00SZ7Ing243NX1ei6QYoa88yDP6ewj1mE4eLeTIi/ifHstSf8dNmnG/WIem4rBnFSS0/vTnDVg7rSH3GGcGGfe2IsiT2k7GjPkakDYK8oCQuNxdmQX5K7AUrx7+vzRWxOvfgVMOhPcAFuls3qLqdMJx9E39F83mVBE0Ai66tlWGRnrNPbqB4OvfM4qkjeL0GMWwjbCS+TEnWBZMwfEq+HYew4HYlf+AgXju6ZcLFj5zsaRe7oVVkvnEeLFcyHh+ojIOwZp3t4OGyqcS64r/knZGleBm4uPY5Y4olNfhh30xJyIEaYFioLYtzOHmVZ8TS4A8AjBE9BG+LRel0Wj2BxleAExtfNKmSWLoo6Kr4eGXnshxtsYP9ioeyOLm6KozRInxCSXPqjuv7ZRF8iSd7cJ2ndlKBEO0x9gTSozYKO1cpHxDKxcZ5XH+F9fZKUQVpWjyN8f+4eKP1aL0F5yaT08+tZ/+DpsNXrxaPPKdpRgw2xGqZ46z9aLLgmg7p/Xg0BRREiYQKvheZZuwh7dEEjQ0RvV8nXewGfl3HZITl9XsOSWMQfkhcVRpdwH/JQGUEYzt2fkuo6wXdyYswqi8nj0hpXPyQKEA0MnxMdPZQArPgAHY8DpwERfmo5UvjR8I5gmDzpkDCPAIbIOxrf5fAEYASMzBOAIb5DfCYR5a78GZrU/oAjSClbtJlTkqzHdrLEyTh8HEu2oWebl65DKHBaBZGQex87wF5IcvwWXf6IlhhC1p82BWl6dZH+jOQmxDYRwXf0mEBUF2YUzgA3kLSPXrsXzKON93Px1I4VbzTde4NBOjy8fyF29FlR9v07BsgsCkF7nKQ/ScpynBIm8GADm2gY33lLQ2QiDMZlkEpBtq/uKDLGxLT42gPHGPjIwMGiQwUNF3F9VFHE5hj4uHk5buq6CLqV1eH6AatlBqs6HGG3ljOA+BIJZsbn4XVVGOnbBBVMw/zMNILoW8fXbDFuA52ZcULFDkVv43qN8CSZ90CszkaoNobHhm/gCkQI4qNxn+aLiJh7V9NpXoOyCubFTnoxWMHIDNRJuEnbRPbCDQTt2g2S+WjzBFU9HH6dFCt8GwNvJaMyK+p/f9pYdZn0Q0bxA8CH5XjLlzVToqP/ARcjYv80Lm2GXSm2zBkQxxU4sU4M4pyu9q+vUyhiXgbbllNX6cjhIyhf8Ky6u17Y2iGOF/rquc/1WLp2SD+Okwp022uwo2DA9QUkVVMCcq1eHwbp3d1xYwiHZvAPtikPj2Qbz9O+0iRxi4UeBXThQqDEAVwx3DQMQkyGmPiul0Xr+Uk24e0RI9qGWKX9iJ8wRhX43K3uaRTuF/9WgThBW+oQsOiKQ9+UvwSxdKt1akmMu0ytJhIBU3fGVZ0UzRpHRi+CZk3EYpOA0CUkkBwfk10wjFE3ePDW7eBXtxY88ufn9lfw+IQz4cgjV8RW3BI5yuW4qJEGFj1GypigzWOh9GwXJ1DqEI7EhBvGObO5hCTAxCMYva8YouDjaLXQ7JhMcFZ1U7Q3EnjMXj7cDsdX9hTBRtWxiyJEOvxVkj/ET54ZMaYwrk9qCyO3dsJnsW8R5zIKknux3yF4+iLR2SwksH0Ueyu69kQZ6kYTM4GQVX6rP3tEjINClUuIQf0nn5DDQyKgORIBdQeFe7ak5r2hN55ONuJe4+wE66z9PUoq2Y/Mw3U/1AydOZ60V03/UojMtvXaHa7XYHQHVfcQ65AbISiGrA/ni4Fs81z1TcEmiAjwwW+QXiojyr2mikJMZ8+IjbynsMJQCsTeKflBFD59rsvE82EJe8lGHcDtiUgzOsQ8MjafFFlRfgLPJAVBbOTbl7TZVZXaM1/tzLqUwktm0f6mZlAHzqszZHI3m6s7ESKOR+ZA+uLZgQtjcCFNUfc+sLX/MNy4h2w4ysFV9HMLiu6Hk5u4JzeH0xZj73bvtOVwGLHbhxH2YRQd33cJ9vdkU+pH5aEiDzXDlJJ194pg7DV/XvWZJ4ICiXaVTfeMP/XPhbgHB5oTEPtkdTWmh/ZAapXUOAAvfo46CcxY1eO6TjaPUcXqZ/QETpGuXh/d38MMBqvH5I5ukO409o5/XrVnCH0q95AIEE3mZm2Xw1gIP1Zot8/4xLnF3CFwTz8XDzCPFFWL8fW3wC1QuhzPdOS89cgiyiMIsWv64wf/7ogY7PtjvS33zLcfu/IZLKs62pkiZtJoyKR7sOdLWqt1kkd6zkR6/GijZfcVg9YIQo+z9j7CPPXwP3DVQ3pyQiU8CEzrSNbTu5jI3kdB9g+4viwqnCjHL5JNol0tixzIE8D5pdnsE2XFYW3//c/eqGlXwZ5IT6Mj+J2HI5j45qpuaw3SawmiGLf1bp6KzwAT67wqjFdx3rvy3c2yBMAe/59c8aMlAkqYcriDMlZ+T4K0561KX+3t97Re9OzDJX+Utb8Nh0DRHqh+yBH12kxqQZ7xiV7lifCSjT1300rVnjD3eFrujPvq3V0JHmEiW1yhroMZBbo1nx5nxQPWZ/eERT8n+UODExU68+imZhCTSo+WBSlt54ixeWzT4WC39bFGSW7e4TrR5KewW9Kb/OGxUN4kD3GCVLfzHv0h3CL+wfwUJm4vXvZEzo6yF/c0OmkycuFIf2LsfixIzhWvwQjG6lmWPJyvUNfxFTgjbdyjk6/q/bpRO65O0waIX9c4p/UVqIqm3BsVfHb9pqenkcl98gP1yKW3B/xUH5xDvFhdZs0DVOWitTxULppUzfYBR0cX4KnaoxcQZ+fQiBo4eQUrDiqcgSYOpigSeDuV2z+SnbId+uvBwOjRWt+jKbLsKK+e9iYEDw/II2id1Iruz/MTif27QN+LOuzpkrHvYCGi7QnXzL7Nx+OeNmqtk3lxTvSjbNHR8jyRbfVjA6oaabF4jWzDAeN2aiDjH3vSZ1x7IgvMtuBf/uKuMEd6+ilKMDBC0vE9xqFgeysNNzgcuA/XJfwzUqyueupCFCdQrmCFz12vQIol7f4yf8g+NMpiGvIOhCG2nuvTZ0zVJDtq6iWma0qUgb2a99lvVsWLhO1n65y6chMRbSc5I2P/innrpvgJ4piRBN1RmoKqioeUxH+gCQ5K0mS96M6Kslnt0Vn+TbGGqfvy6qqFZRaZe3FbvWJqpy9c7sLFmCmuTpL1QbhjTxYIGZA7i3bVdnyB4HnELQfmj2tjd/W+Q49UVRetJA5yRuF0uUEI8MUb3IvAFww6LIop98RG8qgFGuu7KYH2Sfh8LItm7SmBurrjhoR6vVIT+YTiott1gtZyDIGC155MqTqIlVE8nLspnsiy3BMZtRPiYd/YbQ6neku8/WBaMpaIFzgJvqj5VfQpe3fDfmCTFIQmOQjsDJeiIDjHQdglblIT319q+J54DisGri7HdURU7b3DyPnThyxiTVmCPH2Jdkm8R3iVbI5vDZnZ/+q9KtnjrjBqf0/q2MIMGe01WgvZeZ5mqKuW5PiznyTuG8O3Bqdo7AY3NrwZMNEImUanGWknG6YZYdfYpCNDDTksVvfGGDGGBD/Ee0OSnQEwNk3VLY9NYHXLY1O7wx/nahnhk9EZcXiReNRWiOgYrQmkqTdI21mM/CTJFXhKysVlgTa46gcoAWKtwLySS5D+LJr6qK5LeNfUILbVKDRQ/ecqUl6kTglQRPi5xp7FSaM3aP3rKGMkEXHYjMFPx5QAyZETNP+snuI17QgLHSEUNhG4S9EUU2F8gXnEq59goSJdcE9PHh/jZGvCyE6f17AkltOXIq+XI+D9L5DEGTvNlx8gEm71B0D4MCRec0DTZXj8VGSLSHMlIo/ICBTy4yT/Gc2U4vBGW2I03vOT2Cj7HFmR0Z7fJZE2pE5CE6UgZj5XHJiDFsS/2kfU8XuHScq+yz4K+mjspmrgClSNmE7XUxqt8YXp+MQREUfsNbI7B50oftcvG1y3ArHdqZcJjBVszmXAikLTDiW2VXCCy6Z1GY7i6xo7MfH2HmjQ5sIVWCUwp4LUPaLCPyX4hdvOuL0osFs+ZTISRTwq6a3CbxXS7T9BNF17k3hx9jc6SVX31rtqYQFZE+Qw+QjvibK/h6zTD8198jY1g+bvWwUWP2C99GQhrnpwV5gMDNHdLVMy654waK/OULOM9nKPe7sKPCEHhj2pww+LRnbznVd9V8k9rITXXjyFBjLN1hENhis02DV+0z2apjZgjHdn5BrkWK+O1cMWXbzufQFVRd2aDXxQZDMjRDeb7ckht3iYPUoqM4wnahzLjOra5bxvlv69SbqJ8JfV33JYk4f3xj5uHBoa/XRxitFMMpKxT0N7e6ndr8cmGdva2LQb7cgu/lHdh+Ipz4pkEZyM6LzqUUVRjD4jFs8r0ONU6oRWuy+S+j8AfFh6qYU2LRw3+SIDH5I6ieMg7J56xxcx4/Co27Z/gRN87NO2P8sGfUGlSQm7gNlH0hXD041brjzS3s398vLMHnXcEjUoeJVgOE6yBL8rEKK+VaNH1cQ0wuIeBtD+D48n5fj6Izwohw9yVvvzDvGMkvymTNKfMH+IeAZIYt7GVUrIQR6IddL4AWTwEZQ2b0Ruy5tg/QLYIy/GMCSv+x1tzQjnU6LfeHTvgYPuUTdlfgV+x9nq9mXaw69LRZq42fWfGH6nK5BURX5WlC2vxFHSO44DxP61sPd9kNqGIDjdM+KSJYe+eHJ/f1HEinLXvezNJ+d1fxGBFhQxboVtS7SJwwZZkFCfk6Tcq00yXFpeJmWnOAR5nlq/kN8xK1037PnrzSSHH6/OLv9nPvYY77IEWp2gROyPnfuj2dMxds+dE3PdzF+vEQ37sLhhHvdE5s28LOS0/bpmo3GtbRUttrD1i69HnMEMkTswK3CbXLsjX2h+7ZGzxusIuicLQD7EWLy3NZnmZ2GUPWGRnZmFTqjcJA97THnHN61difc9KSHSdQYeRlbkHcyTfZJ4PxsXmlqFYdZQ/4Sxz4s9Sd7cJym+BFQis0pxvS+0FW+12epJB9KvsbD/gIvxkH8aN+4hqQChvP8xoFMz+AfblM8DClUFH3K0AiFhStMr8e88XEv9YQnOJehxlYSpHWL79+Yejn9z7wdbO6Qf59X/396VNceNI+m/MtGP+9De7tl9c2+ELFvdivChkeTu2H1h0FWQxDGrWEOybGt+/YLghSMTBwnwsl+6rUICeeBDEkciMf7Vis0t/On3YPfZx4KcrWk+nMsPD6xJpmSIFHnIl3Ujn9Ox+Gr/39klehezjbcegBE/b+zQ+MvwXbAR6xy1hdHZ5ulP3CM5owPm/3VO8nFBhNzk71jmWTp++xOd+E+Ak1+N3eI6krc2hC/SJPb+SIHHVeOoaOCxbrq+j7eNjkZUHOwFQVthhT79+HyItTqayNIsv/vXOc5Jcfv4ybec9cph/0/aTfwBr/dDjzrMagJG1wVt6Y6kZDcy+t3DZ8YLpM2fHFV2xmj8l/ZtcvxcnYZBk7LlBBvdJySvtx425VhnO0bylxa6etxw+CF1XdvH0ntUFILHs2D7I4CqDzYCZw/7zsCRda6s/0e3eVekb44V/ajPxh3ZnXPiKJ/VLiL/boi3XbK32WPWbcIFWZU5tvNHeUhfZftnX6mv6KqTArfdzntPyq9Z/jnAzvchzp/Z0GlTfA/x31ArIyN1WJNvvlEtj4+E5ZMaKx/cmIOYDo8s161/L55wwAtlugz1Ytvuca8hc9Q37uJtVjXgYBT7aK6rLD/QLuMiTDw1P/gdFGB4+nhhfvsvz7zOquRsb4572pljP4Fuc7BmW3AjDqjWcdx3vm4jyKTOeeVhv0R8IgfCFty0qY10JtPJ90TGtV9t2hSSkGHvdzm3Gg4sr+Ld5+sjO8bjc5tuBDY+AvPn3aCYPfx9inuTfMTOPTnQD/xmToiCfDuqV3apaEpa+NENBw4zvKQ2fczy5x99vN0+btzejy7ebhczuya7rR3m+1gwUBXqx2h9dylt2D/+oLnqsEiDcBPUi116S3bZZpLiLnxZOsVpluXR9vW+3yzzGFT4Mf8BqCn3OdKzfk9ySKM+wnuHvBVm77XaJ8Q2ArLBr/z6eWwYerbDVQNHwNgvXklc0IVrk1ppG91t9AS/DPEE7CkXqtnXyvn6bv22QmDoE5xQM+oGQ6+TAzkW29l++wGjaWF0leXnwxa3cTmVvGZTn313lfXYuLnbfXZKdrPlEXLYF2I5mtv8mhsB5vjzhas8OwyHoVh73IWabLgYfN1x8ZjnT/8kO0PqrQGRI9VtIT87HdVFoXjUTPS6aKazr57rdxY9Nda9+DGmvSlcAZuK05/eZluJNeA0qrgMWCbJDaz80NDyLdUlQrL6dSOwDDtVNs7ufx3QqHNQssPh4+EUJ49bmRND1h+4dw9/c4c1VkUzzz7sgc2+oWGMDkt4NrH93s49h4zxV7sdezWt2RtUU7fqOFi+0mycRQ65P2AF7qnSU9Qm3EGvx041OADLk+O+iuaJ0/R5TDsXZRnvnqo5xC9XiXIS5Wbsvq1fPbb1d8e2rB3Je/K1eEuqgbvBnRxYOa97OuCDjFaT30q4VBEuYg1GzVJfvHzNu6n/dr90MN4JeP1IQrsZvs3mMafE5RPZfc7Om0sqEeQYdpHpUtiDIRV9EzTmo83r4j7+9uYb4TQd0gxtpI1WHPdxnTAlTPBD9OFDc0u5QBTlBmyzqE14T/c62mX8yJbhdwQ6PCizO+dVtuib+LkyyLYetJO1cx89agseXhha/AGarPVG4HD5vEuJmO90UA9UzdyQPMnwp1stj8HKOGWtjXrz8K6M89Li6bLJwt08hFBdH5MyidOB7+KJtRd/J4ld1k7+zTICMd4bGW9guKhF9yn2iC7paiI7H/ej8pEhUZh+BPqvIQKxS/p4HL8fyf4u7R4MmZ91koYS8ldvQg6+aeDwdUzZ+KgXkhsZqzftFdJfxnyKulZ+nX7Vab9izLOioHPz9EcP+ulBhw/dVqIwqCZvyReSjpsA3j1leWn7DLRdiBcl9/mu9PWpOTnzfW4WMm7EKrMUtRCQTGyQkShSSZ6T3Fd7sySMsRHsKq8elKwSR41ZPtWZw/6IC9NtVE+nXHekzla0Dc8T5Jh8FdczuX3y76UvF3gjm8WAb8T+ddCaVXiT1cfyuEvPe3J9vEtos3G3oht2tnQTF0UVTEcLytHbyG1jXvS8T0o1t9yglrxFubwjZdwEH/p5nblqcFxOe7hNf7bzGEpzS+gsK78o/kr2j2TUWWbdwv9lRz861s39lcenZtaAyGbfVvWypdAHw7V8lVUuduS4zJMsH5lJmalTvafnbnFrr08/Kiz6a8wHuG0jUr8EIH3LC6A3fzkEZm4LDK7quK1jXn7HNU5fNUT+11bDERsgrp3ZsFprX/Liu0nQ1wzRkyyshS5u6KflKdtfVqoOG5xiQ9ZDtGY4qFcBjm6WVRoYeY+kV8VxsHY1w+Rqrrc72n2YAX3bNmHbqw2rQb0q8Bq2sTO2J3nx3SToa4boyRuSH5KiCi6pk8LwGWyGdKvcnvWg5fgO6mOQsaNXVJvwcg2t02kY9JrqIcfx2F53Hcyje3sBI3r5vdt85W9GPW6Nz5F05JTZsFE87ZQGFaCVf5AMdeWAPXpLviTk6x8kPT2c0+PAD7CHrS+bqBcv3VErPLhD2uqjRPkrLhqLBwgYfpVmj+2l4a32ZaOfVSoCq306arObrBgQQNnXDDFIq/sg30tfets49A2OqhOuSyLnlrG8zlPXDOLBszT9M6uO/mZNwjiB56aKXhyLr0NO0/m6wT+j2+2BeTP3+/MNdT95cw1VirP+naBBz5bV84D/JQWLlvbQ1PvMsSUM6xdFke0S1rMNhzffTlleLQIeErrSuNs9kf252povPkvIf3Pc/61ajfz2k0DUiHRH0oefxYJ357RMTmmyo3L89tMviqp9g4IMQotSidjkfyhNsqiW6mA1Ti+zY1HmMTWVOl6T4y45xSmkjkRsObwrg3fNyiWvyak6qzqWoLY2DBsJKSwrGWH2HRfJ75hM8vIFhwkbqFBGacauWESgDuP79RfZKC8/HOv4/L9Vgf7VRafLuNjFe3X4UNjv9dL04gMC8YVBsObe917Axullw7ORbzag/eNMzmRfZ3DoMgpEVzqovc6+HtOszzzGOrX/UezM//z550AYAyUXZEIogqCt038CoMF62TC+WgjUIj5lCO7TeCLBgwgFk3g0TngMY6H8GGqq4OiyYidnf5kbW5wbQ1UZ17HfhUNzhcFsPo2rORv82v39+kXnqPk/irm2nO/b7rdJsCYKLAoiFQVBF2ihMMiS9LGalPUvcy8CTmAcjtKZUC9+L3CCLLQcOPXbP7PAqQsX8+iX2Lwe32boAgH5pvof1+5T4DhHrPtn9iZdkNvFiZq8CgluxE9IEd1l53ynWes1dcW1XvfjJM5FCS6FZAmMrU7lCcClD6ZFeOoDLmeH2X2cV6HjXtzFhmHm1OVzwEwTdD0bzJrP66p8GTRlU8o248lcpmdLdGQdwgxubP6J+Pz4mnAqPgRe2iC6SdHV/qNJNVntd8jRY8v0XZzAoDhC+WZ8GK+Vix+bexMhoj2ffCH58311ox/tW55I6FShwGkN6OANfeECVTUMNpy+a5xsc2Oim8Uh9/WWNTGXhIbwtKFpuayRy6x8MbhazY7ljKCafs/Sbddq5k3L6u2Gh5jtnOUTblzybIXmxIK1b2AK2qxhE7OBo4AKXImxHRrW6aAigeVBoObc/348kDNbvsKisLeaL9wCwDb9l87dvc38tZMuq98S2lpSZfWpb49bnNSILYixwlLRJPCDUw/gYnUUYQKPRRNMgESL1AsIY5tcCwvBpOlYB+h1pJ+/CxS6oGA2+OFJO6bCXUnXKVSuL1RVEoGiLwtjvLwitsSS1WNKUMcBS7NBqc3Y4RFEhvWjlA+XtdT9tnYAgElmFt71GtCOH8OLA4P7EJ0QEoJw80XDtVlHukdfTVPrtoboHrofJ/nIyHmeIFEC+5lW4QlQpc1qhbDUZ76ZCVmGCbKDg9gsrlz6eAZY4dnOpkbVqySt7qXCmd4GYcoUxOvi99aLBiteovHnR0OT8DaClVjox0vK0iuI0pVt5uPVauTy8ZoNV/XrSHdlzvKhFGxCFLVPYqHg6gj4Hu1/nARckOSiPCBBEJjBBgsDM1AtG778Q2ezYK1N+rQOfLXSCjL0P64eR50qq8BOmxNqHdhppRVk6H9cPXY6VVaBnSqZ1TpwU0kqHtayH1aPF6bGarBSJz6LVKFdeyogSmoZFQnan8Oc59v2oge0NIrYcKvI58OLnLGazyps3GGUa4v9qRROgy5dPnKdfCJhGATKJpkCjdb52TF8WiVkXwBYTaf6GBB0Hf8dgtQZIHMD1JALfur9TicPuqhtLtMAmRB8k254DQXc4sC2Ig+4RNBN5fFGA29eT/fmW/VAZZxenMunqsUdK5Q+yYv2eDoNpJyfOsLVe0Ctei6AnA2LV1l+PrBNWd/Aw88XO55CS9yvq8dFr8t6QMBe8J0aBfWzwQoMmp+3gQPgaeRFAyFi//09z84nFAUcidJ5zc+TfIgYQ1WEQNDBDBMQPFaMermW4EIAud17LCReZnA69n05rcdh5AuYfGBij+m6kBCafPbi2K+Tzl+YWLOBiD2QEiBITjd3YTyFRppf1h4bV6thw0g0+My9v4pF87SgmXKaa4+a2We4bXqmj0X8SP5IqDT5cwTnlVpoGi5eclAekWAzibgEtWz4zp6JC8QaMFbcfcR3ADF7nzIXvpiE8374WETckhHVSalKEDAUbjLs9IqsCTDLz2EyD2wmzFfiBpy5s5T8njyUl3G+j27O1aOPBdn/lZRPiA7Du9FwLamVQmis/zGcJ7Hup5Gg6HSxwgTYFbNDRJjrwAoN7dJAvgaSHJRngkmPEwI8oc153tNWXBbWPvJDYaGzoSVBbbI50mCcCT0677TpfVaS5c+zKylVCepf142hXpHlz7NvyVeK9puMNlC0zmkV+5OA4II4YPnq9y4hrVaxkwnhzOtH0DAdXw5cJnNDQ7EidMu8if3Y+6hL/pK1QirJ+gK+KzEZgDo9lv8Za0VlO0aw3EN7LTBylA0HsSBYnlHrjvUEIuttia7CvIe0bWrK6RLZ/Dim/0ky+oyzlfKcH6vHdEiAWONA82FOZGlqI5RsYA7M67OK2W93gWd6r/IjPRbqXuac22ZMkMs4r6csr87HfUq0xxPNsQpHCZx48aVOMJElkqYkcmHIEy3MFmEApOhmw7MXcTkQWsVXagkwm/JbNQhds3+uFGwt/wx+CcCa8ER+EK7mPphv+N+dyC55aG4fXpRlnnw6l2QFGNPKD8mFUa4dfXr11gBFWPQPp+rPyEYv3vWY8WAHhFCuUaOrhZQtYZidqOEQGutBdcpaLSbABhbqXO21DQ2c79I3jwbb7A5bp8HcmG+nDvfxozmH2VJmEb3MkCx86UZmC5xKDjOEGdOPAeAyZNzR9ynQmd8DxGw7fE6UUfLZgfZnnCfxsex862V2+JQcGWGEPxnP34fhiYR7MEKB07agWTYIWlryMFe8UAMFRZtOURspeLGXjL/VrM0XD9Tpv7tjMTr3Ut0Cnv840xLaSx+PCY5RgYjHgliwSfeIG2jR0OPFXhr+1usTbSC5We+3ZpfXQs68JakQAl0+9TbkYtE46bajB1guZq9RVuLPOD13INVrGAYX0yKXqWsjZkMYEsOD8BQGyrW2DniWG5gb1rNPJ22mDOueGK5u9hfdxYdTSl5nX49pFuNXhDsCYf+l+3HhaIDVmx8JovFnw8J9QnKq344IediN8VaMyDb/OsNEkK9YJ70gC/frJpKt9/rYMOOlWwCslr+knAdEEy4b3fCzkIViH47aBBIvH0iK5G7RzOsDlqqSDVOhOxcEM68AM06BfqBEz3Rx+OjWcldJWlb5boNfb5jKTUma6YVUiLdzbcKoq713m/caRZnl1cQrOcT585tvu6f4+Ehu45JcnnPKYvesmd43BOLUvv3R3skxEcR4uvqXQBNySK8wIKn1sPNgaAcsAxrsjx+YmAETguXnew0+3n2+PlJZdp/vzp+KXZ7UseKruHWFCC++FY/RrP4OFqaZywbB4nC3/BXe8kA34Tx+DObmns1f7FLgwV2/25yBMNeJLl6c73/dxB5nr89q9jgvHh6SlP5CoonePOkYig31v6498UGvig2zuROqsCd6gk6ccCgovNWnkDY241F1WsVch+r3hQL6HQVq/Eiiqzw7TIcSkbk0bxKLVo8PSSEbjnxnLAUg99kPeCwEHn1XzDhtLZMv1ORvs8eI+3fVUfhkQ6ITZ41S2UQz2I4rJk2ouYvOZmGAxitlNYsRRVwE1FaxATQfqqb0W65wmt1lXT6R3efsLEfQKj/j2FIoBZCppZOgDVZLL1rIGFmDPcOAEdHQCpdy1RnzYO7OOdX48SZ+ZrmYr49J1a6nlMz4dEtmLCW0lAvXnWdZ0ceKJ9cTi8FHm9ldr5Gvfg6WSRVUSita2JTxwwDiF5QuGeTlurPh8222i9Pk32Tfyhi9jY+PZ2VVw/V+R8B3d//jJAhUxBaFUUuDoA42VRi0qTrZMG0lnBFfvhcA4eK/5bn/Nub8q5nrN6d6t+RLQr7+QdLTwzk9VkmIqy7pG2e7nh2m6O9lNe26zyKsvhF2TRsg+roy55BMVQ7gpBkgsrgBMwpzrUaTbJPZ9IgT7zlxKBSY4goaKrTPZ0OU31AByBgLBJNQeb5QlTR7vMwObJ1o6dS4KlP7MZ61EJLC/74hb4Wa2ondxDCq/n2Tya88SL3ICOQurH+cJtDJHUmevBRsnmXgp5VtNhy9J18LR3fEVZnaHfGs+SaF3zfkjlBTO7GbGEbVv7WXWDoCuQsnvLIyAEme3BFsnmXgp5Vtvnl3lqZ/ZmVyfGyCK23XfVK9ydd7Mn9hVq4UbshF6S3vxHMOfFU/XByLr5qdKY5E7tX252lu2Y3BmK8VH2KuBWGrF3EZuweDtq5m3a+y37BYt+fytHsxOaCWf6tlIJzWd4PFHUPT3lt5w3JDVYOK1iB5u82f7clVkhfl67iMP8WFes5X1bojZUP/5tspy0sqXJo1R8w1BdevKsnd7okc4t9+2n/KKALiTynYkOKRIM7UZg9JdT0GYduV4zw7EgPDqoX9OSX3cfEZ4CcWQ+xECgM3MQuawk0shriJFEZu5Ez2bw5xkl6UZbx7QvoSoYP5g6QGQfpUXwrvvghi15faq6pX0KiWGaJM+d0uO8MDQygGASpQGLg1sQLvSPmUQfaTyiF+EomJoTFdgyqDsQoolrGWq6Q2olnKYsscZ6llZB4xSYEBrC8CR0xXauBwGZfkkQXnKBz6IohDX2oyUbIrzznkxbsS0ERtoV0PaLhIBJr+iBqi6F3MHhC07p/qveUkJ4hjBal0vSYQmkQQ3q9QeQvFIFOBws7aGtQoFDp7t1TWBm8qvouP54eY9ZXGE4lUOjl4SmtZDEKYuYsUpoEqr/LU8SpTgMNWJrJkq+GnZ2Tk0N1fVRh0JVD7XaFJgWpAwf6tLQHFbwtNE8fm1Wz0yywTgJNHicbEs6TDhkL3C12tQGNeKgc5iiQGhv2ZocKrL4LY9KUGDn0MocKhL4I49KUmDlUQHbkrc7atVNQPoQHcQDKQM0hpkKI/7lA490UQt6rU7B25vUnVKXKFoC/kyi34IBzwtq1dTZ0SAXU3dbHO5dQUJh1IfkiKgi6c251TVR+FBNRNoTIubum09hinF+fyqVrc188GolLoyeGlr66GQTp2PRoZ7FwZxJcVF5HVgGe099kp2WF8mkINo4bChhPGRNt+Q2HT/u95dj5hTJpCDaeGwsCpuSiiMGl+h9pviiynsR+rK61dtD46jxXJdBNZkdIgxe/JA5025nuDFDAZJAVMaSmFhrOem103Ih8BrgztTquFKaN8n5WQJ+XKUCZ1sYHJLflKFb7JkmNZ4B0GUkGMQUKL+Rey7OqLsDmX1QKrJUR6TCzWcbLqt1tClwPHagFIQBcslcNmFEiM6mVs7kmhi6sok8BqylR2aznsrWFsVYfR69Z3yMPPtis9/RPOqr205KDttDUGSWctl4tEtn3K3k3FOpAVanqLldvx0T4Wh/HXVtLIpa03TF57IQ37VtLjR677KRYC2UnijBTkOSBbszTkDr3W1DBIxyXtV0ThyiC+XLFxHZ+BO5bN7/C6PbPYE+2TqgKrqLYIXkG1pTaidyCDNeiKUUVsUXpP6xPWkZ/ApaFUDvaKSGLa8MBSL6r7HxgluB2CETvsNN6TwymNwZEKk5l2HntKy6MCjQQqie7owJpz+2HAGSsUuu+LLdvfyZHQgaxzjioJuDRQqEx7on1SRnVXtC8D90X7YgOTjznOhCuDmHDFJk36pIKqJn0ZqElfbBofJC4olP8iyeMTNGmWysERIZLYMXyd0Hl9ATsGlUTDlqOy2W0w+CSARrP34OSJ5HRewEAUCXT7RBKpcUxw6XiAUcGVwuOCI7BnVWe30rGrKQwsayKjgz2c4uQR6tK+CHaobakRt8zWum+ITAGjViKy2PV+S8qS5AbkYoTYjjhEazKymvdHtbZKA5pdJXNljk13MUIrMeymuGiWDmDPAaGEdx8QYkdxLOSwE8DuZEhMGQEfC4k06JmQSGZUuxpBezyORCaAVRZpTCjMs6Kgjac4V5UERJ5CZTQ15LtRn23jq+m/SmQR0paA64+20LT0iL9pgh2EUnDZwROYWCGnIZqDELsTkHY3XqOISqLb1o8uTqc0Ifv7rKFPiPlwsq2Mg06hsJOhITdLIB6z44EBCJ355P6WFNQFswBqi8CE5rQSj39QKHSHng2RRUfIB5aGc1c9uc2JaFSRWm9JdREqdsfB9sfCQsZ+1x0ym41Ny/3NqKdzlQLI8IAJA5BqZAKoLWJCuhuT6rYIX4pFhnQEFtNHnJVQik0UbVmp96lU+yokWMSDSOXSw6ZutehLhSF3VQAJtI/EIHauAhR2L1LLdxq415qA0HoqgD5kXmgBvA3AmtAH+b8QVbY2R39RIeqaxowB0HpXBGhDvZTBNaO5aOFuEjDUP7pCjKKhxlWS7wUwVbCYf6Gm9moDa8bupsIos0RiJL/WKCKtppeB2wV1D2tuDWCmwQwSDB0CD1uQ8JW8qrUQxIhx6FEXj67aB6HElZKC35kaSPy8WA+Mna+rS0Xe1W/n+Gb14Wt90MVHVf7FqN9Fumv6XaHx3+PyEpBVRFeo7mrqVojRXRMjq2puU03z/ZBuydTfD+QGDFgTNItaGNY893H+SMARYVPNU58vzDzt2t4JO3KlkMiBfI9SFtIwLqiRK/l3qTOZBbg4VWhNAlYIiRPghpjQiO7K1+CPbSTe7UI/tSKdRhngrlmtheYO2VBcDVe7+472rglVXaUN4UiRa3O8+h7dqKyaea6lkAabbE1kAuFCn2bKBdL5n3ZBVwVZZe0VwME9L11n5FmgEMDreFYLQgXaEFgexEzmQQKSBxsoE5tEPMiI2pMM0hx/6Kfv9pU124TgRcp6o1B7PVLTinTEAzSGXfMMYkB8qmZfWfN1ApRF1FuKyfhLqFHXLmAekDCAKaCLtbUJtPdl3VVvTut0Sssk/tWVTh5ZPexa9XAVJdPhioqE3ntpQtW7E8fuUFbjPHFiTY9Lt/LrLkdu3IM1IQMoZSEMgTtBnNhLPy7BDK+SNKWOtGtZYwSJNJwJrDA0Quk212HfNK61QhtyCEiZNIQGsPQY7maA0gT0Dy0BttBXwNWSUygwfbD0CGJNTWqEuhWbVAfupmmzQ2jNoRKFMIGc4YLVRLNXuKvaJnfQqqoShVBVTkHBavY/jl9cZWmqVVMkCKEin5+iXjNB+SeGqVanyIjqFmHleJKRQio1xPQeXT0kcccAFeXYLT5oSjeNsauoUQ7Jx1GraMixoW0JCiQDW9XmD/FuSnwiZFfR/IFTFNYpuEgTgqF7VpNpR/R5mE2YjD6NaSym15OjaS4T6fLgaKejdhVDoMkmN9BdHenlkOPH3XRdph+tnQCqEEZRUhKxqtyvftRlwfRmfSWyYAoL1wF6jeGQ/4EqR3x+IkRfnsYgspAFqRcZzG6k1lYr+u3Zpkltt9Y0owWdpzfZSOSb1Y1Yjs6j6BOPW5aqyGJXBaTzv6UiJOhiteD8W0MV1fkmiSKEWwqnHpQvLOrjK/BADriCJoJhdCAHlJ1MaEWbbcyTaRq7W9qlph7dr4swR5cBDbeBTOJXcSV7W1/P0wZKL7/mTFol8n8SHV7VNmdedEPXZ09xQfZ/JeUTx0FV3FTFmzpCXTkrIKuKZvwbbghh3Pbt42aAK3hSBKyJjnirdIueTPOR73dr+4i1/PqFuczTZWs0OESOJIBD5DNN9vXAJJLuKgJ5IbVTIS19iImRJg8ma8QmsaUfsxjHhbmSX3TMY5o25SY+JiQKv0rLSUnv2igaT+G9fEbRqG8X11Ik9CQ4WFP5vmqznw5dCLURScYln0y4pjWfkMfV4PBAyjCuDshP24xkXdrZERvcFp2N0vrv79BhE3Ia3Yh/jQga4Ti5cUWgvpnErw3wZ5CkkQ8nEG5GvyEpsAcD6UYGThxicMxuCs2CEaX1v26c2gzaxNAWgd2Giv4NZJX5mm/NMhvzAARpEk8jWbNBcA1oRoMfs13sDGLRqpjEW9M2kozbN1r1zF0hrG1tYlMtdASoSXm0US44dTA3oaYe4lvgS4MYQxOnglMblUK0AdRYhkk0yd9tb1laN6HZdfd1AdMicT5vUZfU90FMbP6I29QONkTXZk7x/eBBNhWawA0EPXXMTKF7wnitJh4A0+mxaWOt8CbSTiet6xqV12ptN29cpBlZit8IZWttUn07kxjFpl0hTbKucTj18fCJkKWjnNwbhk2gEN3Fh1NK+hRsuOISpWbqMjjJW1iVu+dahLhYSGWE0ryRNDxyVnmGhlXHX5gZo77m26US+f9ahVdV2dZs9krNH220SrCP9tCdWS9mcTLIpk3RfVeukrSsIgi1e/Eu1afZmzdJpG9aIfZw4z9jGf2SQ5w/v/m2e4qPj+Q2ptPz7uEpYHPRWEnngsWXsBr3C79yJe6B8Y9z1Ztd4NtbI43A/rDWXqRel9rI41fa4xtjnRCnOIbHv1hDti95+TOTximbqvh3zfOaqHviyjhlQyhDTtmUp7vqk2X0Va4B6rfvYkWaE3GVyP9RuPK2V10TfbZr4LUGa2+hoQ7hJ9CnuO66aw6egS++mBVd5dlBZw8deQiDwI+DNU5F+9bXaFPcZw6G4IjXbgbuibFIeW4M8oc4uc6lgc+hNY5N+8YZ1g7WhmeT6EAB0oXAwzRqw6+ARcAbZUD4lG1djXmwZ9VqO5leStO3pW7/Wb55NiToTnw9LLo+JmUSp5qAWl0FXMlhwbTwG2l3dfyd4d2z8cZoA4ZVVma7oHUDqattCY5Etnu4bmAKIP5VOHP+H5gaV3Bk8h/o9bu6CeODdkPMoXfMwR2y7Ij9OOBm3aS8pBRV38XL7FiUeZxUj6HQaVinX5vw6j6LsPqanSb/vMzmHpA1C1pyoi9Z8WtQ86tUHrtJKHAyuljTUnVc30Wbj3u3yxZswlNfwO7FuBbDIxZ46aze4NA8YTbOsH3iMb25Ojq98NaJzWZQm3uczbb3hffcVAONbDE8noDn7Fgbunfqxhm2z+6mN1dHpxfeOnvcDGrL7+5ZfxGV9/qAD4Cvtif4yiLvGNafB9PbhB7Mziels7AkR65Xyin/3bwm4T/CgyZmxknIsFYnnuPZT3RGG9l8cC0RBju09qj6yxd1I5XhaS+TvCt7+aJ+BbT5gf5JF6x0vfcu25O0YL++fHF7prUPpP7rNSmSx76Jl7TNI2HvFfeNtjTXx4esXfVJErUkbXH76AYp431cxhd5mVRPE9DiHZ11svdt2f5M9XzhJ7K/Pn44l6dzSVUmh0+psKZ8+ULP/+ULReaX9X2DwocKVMyEqkA+HF+dk3TfyX0Vp4W0jsaauKTW/53Q3+u+pEOzJI/PXUvvs6NlQ435XpMTOe7pkLsnh1P1znvx4XgXfyFDZPtYkLfkMd4931RJ0tl2E9aIuSNEs798ncSPeXwomjb6+vRPiuH94dv//D/XUOKaK4oIAA== - - - dbo - - \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.Designer.cs similarity index 92% rename from src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.Designer.cs rename to src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.Designer.cs index 7c9f6ca9c8..b34e5095fe 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.Designer.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.Designer.cs @@ -13,7 +13,7 @@ public sealed partial class ExportFramework1 : IMigrationMetadata string IMigrationMetadata.Id { - get { return "201508241204123_ExportFramework1"; } + get { return "201508261545304_ExportFramework1"; } } string IMigrationMetadata.Source diff --git a/src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.cs similarity index 65% rename from src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.cs rename to src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.cs index 7d93c0616a..4fbee6ee31 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508241204123_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.cs @@ -9,14 +9,20 @@ public partial class ExportFramework1 : DbMigration, ILocaleResourcesProvider, I public override void Up() { AddColumn("dbo.ExportDeployment", "CreateZip", c => c.Boolean(nullable: false)); - AddColumn("dbo.ExportDeployment", "MultipartForm", c => c.Boolean(nullable: false)); + AddColumn("dbo.ExportDeployment", "HttpTransmissionTypeId", c => c.Int(nullable: false)); + AddColumn("dbo.ExportDeployment", "HttpTransmissionType", c => c.Int(nullable: false)); + AddColumn("dbo.ExportDeployment", "PassiveMode", c => c.Boolean(nullable: false)); + AddColumn("dbo.ExportDeployment", "UseSsl", c => c.Boolean(nullable: false)); AddColumn("dbo.ExportProfile", "EmailAccountId", c => c.Int(nullable: false)); } public override void Down() { DropColumn("dbo.ExportProfile", "EmailAccountId"); - DropColumn("dbo.ExportDeployment", "MultipartForm"); + DropColumn("dbo.ExportDeployment", "UseSsl"); + DropColumn("dbo.ExportDeployment", "PassiveMode"); + DropColumn("dbo.ExportDeployment", "HttpTransmissionType"); + DropColumn("dbo.ExportDeployment", "HttpTransmissionTypeId"); DropColumn("dbo.ExportDeployment", "CreateZip"); } @@ -75,11 +81,26 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Specifies whether to export all active attribute combinations as a standalone product in addition to each product.", "Legt fest, ob zustzlich zu jedem Produkt alle seine aktiven Attributkombinationen als eigenstndiges Produkt exportiert werden sollen."); - builder.AddOrUpdate("Admin.Configuration.Export.Deployment.MultipartForm", - "Multipart form", - "Multipart-Form", - "Specifies whether to transmit the export files as multipart form data via HTTP.", - "Legt fest, ob die Exportdateien als Multipart-Form-Data per HTTP bertragen werden sollen."); + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.HttpTransmissionType", + "HTTP transmission type", + "HTTP bertragungsart", + "Specifies how to transmit the export files via HTTP.", + "Legt fest, aus welcher Art die Exportdateien per HTTP bertragen werden sollen."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportHttpTransmissionType.SimplePost", "Simple POST", "Einfacher POST"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportHttpTransmissionType.MultipartFormDataPost", "Multipart form data POST", "Multipart-Form-Data POST"); + + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.PassiveMode", + "Passive mode", + "Passiver Modus", + "Specifies whether to exchange data in active or passive mode.", + "Legt fest, ob Daten im aktiven oder passiven Modus ausgetauscht werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.UseSsl", + "Use SSL", + "SSL verwenden", + "Specifies whether to use a SSL (Secure Sockets Layer) connection.", + "Legt fest, ob einen SSL (Secure Sockets Layer) Verbindung genutzt werden soll."); } } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.resx b/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.resx new file mode 100644 index 0000000000..749d5146fc --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.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 034dc73d4c..e9ab2681aa 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -341,9 +341,9 @@ 201508211346171_Merge1.cs - - - 201508241204123_ExportFramework1.cs + + + 201508261545304_ExportFramework1.cs @@ -648,8 +648,8 @@ 201508211346171_Merge1.cs - - 201508241204123_ExportFramework1.cs + + 201508261545304_ExportFramework1.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index c1f5661f40..173b9b0e36 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Dynamic; using System.IO; using System.IO.Compression; @@ -252,6 +251,206 @@ private void SendCompletionEmail(ExportProfileTaskContext ctx) _emailSender.SendEmail(smtpContext, message); } + private void DeployFileSystem(ExportProfileTaskContext ctx, ExportDeployment deployment) + { + string folderDestination = null; + + if (deployment.IsPublic) + { + folderDestination = Path.Combine(HttpRuntime.AppDomainAppPath, _publicFolder); + } + else if (deployment.FileSystemPath.IsEmpty()) + { + return; + } + else if (deployment.FileSystemPath.StartsWith("/") || deployment.FileSystemPath.StartsWith("\\") || !Path.IsPathRooted(deployment.FileSystemPath)) + { + folderDestination = CommonHelper.MapPath(deployment.FileSystemPath); + } + else + { + folderDestination = deployment.FileSystemPath; + } + + if (!System.IO.Directory.Exists(folderDestination)) + { + System.IO.Directory.CreateDirectory(folderDestination); + } + + if (deployment.CreateZip) + { + var path = Path.Combine(folderDestination, ctx.Profile.FolderName + ".zip"); + FileSystemHelper.Copy(ctx.ZipPath, path); + + ctx.Log.Information("Copied ZIP archive " + path); + } + else + { + FileSystemHelper.CopyDirectory(new DirectoryInfo(ctx.FolderContent), new DirectoryInfo(folderDestination)); + + ctx.Log.Information("Copied export data to " + folderDestination); + } + } + + private void DeployEmail(ExportProfileTaskContext ctx, ExportDeployment deployment) + { + var emailAccount = _emailAccountService.GetEmailAccountById(deployment.EmailAccountId); + var smtpContext = new SmtpContext(emailAccount); + var count = 0; + + foreach (var email in deployment.EmailAddresses.SplitSafe(",").Where(x => x.IsEmail())) + { + var queuedEmail = new QueuedEmail + { + From = emailAccount.Email, + FromName = emailAccount.DisplayName, + To = email, + Subject = deployment.EmailSubject.NaIfEmpty(), + CreatedOnUtc = DateTime.UtcNow, + EmailAccountId = deployment.EmailAccountId + }; + + foreach (string path in ctx.GetDeploymentFiles(deployment)) + { + string name = Path.GetFileName(path); + + queuedEmail.Attachments.Add(new QueuedEmailAttachment + { + StorageLocation = EmailAttachmentStorageLocation.Path, + Path = path, + Name = name, + MimeType = MimeTypes.MapNameToMimeType(name) + }); + } + + _queuedEmailService.InsertQueuedEmail(queuedEmail); + ++count; + } + + ctx.Log.Information("{0} email(s) created and queued.".FormatInvariant(count)); + } + + private async void DeployHttp(ExportProfileTaskContext ctx, ExportDeployment deployment) + { + var succeeded = 0; + var url = deployment.Url; + + if (!url.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !url.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) + url = "http://" + url; + + if (deployment.HttpTransmissionType == ExportHttpTransmissionType.MultipartFormDataPost) + { + var count = 0; + ICredentials credentials = null; + + if (deployment.Username.HasValue()) + credentials = new NetworkCredential(deployment.Username, deployment.Password); + + using (var handler = new HttpClientHandler { Credentials = credentials }) + using (var client = new HttpClient(handler)) + using (var formData = new MultipartFormDataContent()) + { + foreach (var path in ctx.GetDeploymentFiles(deployment)) + { + byte[] fileData = File.ReadAllBytes(path); + formData.Add(new ByteArrayContent(fileData), "file {0}".FormatInvariant(++count), Path.GetFileName(path)); + } + + var response = await client.PostAsync(url, formData); + + if (response.IsSuccessStatusCode) + { + succeeded = count; + } + else if (response.Content != null) + { + var content = await response.Content.ReadAsStringAsync(); + + var msg = "Multipart form data upload failed. {0} ({1}). Response: {2}".FormatInvariant( + response.StatusCode.ToString(), (int)response.StatusCode, content.NaIfEmpty().Truncate(2000, "...")); + + ctx.Log.Error(msg); + } + } + } + else + { + using (var webClient = new WebClient()) + { + if (deployment.Username.HasValue()) + webClient.Credentials = new NetworkCredential(deployment.Username, deployment.Password); + + foreach (var path in ctx.GetDeploymentFiles(deployment)) + { + await webClient.UploadFileTaskAsync(url, path); + } + } + } + + ctx.Log.Information("{0} file(s) successfully uploaded via HTTP.".FormatInvariant(succeeded)); + } + + private async void DeployFtp(ExportProfileTaskContext ctx, ExportDeployment deployment) + { + var bytesRead = 0; + var succeededFiles = 0; + var url = deployment.Url; + var buffLength = 32768; + byte[] buff = new byte[buffLength]; + var deploymentFiles = ctx.GetDeploymentFiles(deployment).ToList(); + var lastIndex = (deploymentFiles.Count - 1); + + if (!url.StartsWith("ftp://", StringComparison.InvariantCultureIgnoreCase)) + url = "ftp://" + url; + + foreach (var path in deploymentFiles) + { + var fileUrl = url.EnsureEndsWith("/") + Path.GetFileName(path); + + var request = (FtpWebRequest)WebRequest.Create(fileUrl); + request.Method = WebRequestMethods.Ftp.UploadFile; + request.KeepAlive = (deploymentFiles.IndexOf(path) != lastIndex); + request.UseBinary = true; + request.Proxy = null; + request.UsePassive = deployment.PassiveMode; + request.EnableSsl = deployment.UseSsl; + + if (deployment.Username.HasValue()) + request.Credentials = new NetworkCredential(deployment.Username, deployment.Password); + + request.ContentLength = (new FileInfo(path)).Length; + + var requestStream = await request.GetRequestStreamAsync(); + + using (var stream = new FileStream(path, FileMode.Open)) + { + while ((bytesRead = stream.Read(buff, 0, buffLength)) != 0) + { + await requestStream.WriteAsync(buff, 0, bytesRead); + } + } + + requestStream.Close(); + + var response = (FtpWebResponse)await request.GetResponseAsync(); + var statusCode = (int)response.StatusCode; + + if (statusCode >= 200 && statusCode <= 299) + { + ++succeededFiles; + } + else + { + var msg = "The FTP transfer might fail. {0} ({1}), {2}. File {3}".FormatInvariant( + response.StatusCode.ToString(), statusCode, response.StatusDescription.NaIfEmpty(), path); + + ctx.Log.Error(msg); + } + } + + ctx.Log.Information("{0} file(s) successfully uploaded via FTP.".FormatInvariant(succeededFiles)); + } + #endregion private IEnumerable GetProducts(ExportProfileTaskContext ctx, int pageIndex) @@ -497,142 +696,6 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product, I return expando as ExpandoObject; } - private void DeployFileSystem(ExportProfileTaskContext ctx, ExportDeployment deployment) - { - string folderDestination = null; - - if (deployment.IsPublic) - { - folderDestination = Path.Combine(HttpRuntime.AppDomainAppPath, _publicFolder); - } - else if (deployment.FileSystemPath.IsEmpty()) - { - return; - } - else if (deployment.FileSystemPath.StartsWith("/") || deployment.FileSystemPath.StartsWith("\\") || !Path.IsPathRooted(deployment.FileSystemPath)) - { - folderDestination = CommonHelper.MapPath(deployment.FileSystemPath); - } - else - { - folderDestination = deployment.FileSystemPath; - } - - if (!System.IO.Directory.Exists(folderDestination)) - { - System.IO.Directory.CreateDirectory(folderDestination); - } - - if (deployment.CreateZip) - { - var path = Path.Combine(folderDestination, ctx.Profile.FolderName + ".zip"); - FileSystemHelper.Copy(ctx.ZipPath, path); - - ctx.Log.Information("Copied ZIP archive " + path); - } - else - { - FileSystemHelper.CopyDirectory(new DirectoryInfo(ctx.FolderContent), new DirectoryInfo(folderDestination)); - - ctx.Log.Information("Copied export data to " + folderDestination); - } - } - - private void DeployEmail(ExportProfileTaskContext ctx, ExportDeployment deployment) - { - var emailAccount = _emailAccountService.GetEmailAccountById(deployment.EmailAccountId); - var smtpContext = new SmtpContext(emailAccount); - var count = 0; - - foreach (var email in deployment.EmailAddresses.SplitSafe(",").Where(x => x.IsEmail())) - { - var queuedEmail = new QueuedEmail - { - From = emailAccount.Email, - FromName = emailAccount.DisplayName, - To = email, - Subject = deployment.EmailSubject.NaIfEmpty(), - CreatedOnUtc = DateTime.UtcNow, - EmailAccountId = deployment.EmailAccountId - }; - - if (deployment.CreateZip) - { - queuedEmail.Attachments.Add(new QueuedEmailAttachment - { - StorageLocation = EmailAttachmentStorageLocation.Path, - Path = ctx.ZipPath, - Name = ctx.ZipName, - MimeType = "application/zip" - }); - } - else - { - foreach (string path in System.IO.Directory.GetFiles(ctx.FolderContent, "*.*", SearchOption.AllDirectories)) - { - string name = Path.GetFileName(path); - - queuedEmail.Attachments.Add(new QueuedEmailAttachment - { - StorageLocation = EmailAttachmentStorageLocation.Path, - Path = path, - Name = name, - MimeType = MimeTypes.MapNameToMimeType(name) - }); - } - } - - _queuedEmailService.InsertQueuedEmail(queuedEmail); - ++count; - } - - ctx.Log.Information("{0} email(s) created and queued.".FormatInvariant(count)); - } - - private void DeployHttp(ExportProfileTaskContext ctx, ExportDeployment deployment) - { - // TODO: make async - var count = 0; - string[] filePaths = System.IO.Directory.GetFiles(ctx.FolderContent, "*.*", SearchOption.AllDirectories); - - if (deployment.MultipartForm) - { - ICredentials credentials = null; - - if (deployment.Username.HasValue()) - credentials = new NetworkCredential(deployment.Username, deployment.Password); - - using (var handler = new HttpClientHandler { Credentials = credentials }) - using (var client = new HttpClient(handler)) - using (var formData = new MultipartFormDataContent()) - { - foreach (var path in filePaths) - { - byte[] fileData = File.ReadAllBytes(path); - formData.Add(new ByteArrayContent(fileData), "file {0}".FormatInvariant(++count), Path.GetFileName(path)); - } - - var response = client.PostAsync(deployment.Url, formData).Result; - if (response.IsSuccessStatusCode) - { - ctx.Log.Information("{0} file(s) successfully uploaded as multipart form data.".FormatInvariant(count)); - } - else if (response.Content != null) - { - ctx.Log.Error("Multipart form data upload failed. Response: " + response.Content.ReadAsStringAsync().Result.NaIfEmpty().Truncate(2000, "...")); - } - } - } - else - { - } - } - - private void DeployFtp(ExportProfileTaskContext ctx, ExportDeployment deployment) - { - - } - private void ExportCoreInner(ExportProfileTaskContext ctx) { ctx.Export.StoreId = ctx.Store.Id; @@ -645,6 +708,12 @@ private void ExportCoreInner(ExportProfileTaskContext ctx) ctx.Provider.Value.FileExtension.ToLower().EnsureStartsWith(".") ); + if (ctx.Segmenter != null) + { + ctx.Segmenter.Dispose(); + ctx.Segmenter = null; + } + { var logHead = new StringBuilder(); logHead.AppendLine(); @@ -841,6 +910,11 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) if (ctx.Segmenter != null) ctx.Segmenter.Dispose(); + ctx.DeliveryTimes.Clear(); + ctx.QuantityUnits.Clear(); + ctx.CategoryPathes.Clear(); + ctx.Categories.Clear(); + ctx.Export.CustomProperties.Clear(); ctx.Export.Log = null; ctx.Log = null; @@ -928,6 +1002,14 @@ public string LogPath get { return Path.Combine(FolderRoot, "log.txt"); } } + public string[] GetDeploymentFiles(ExportDeployment deployment) + { + if (deployment.CreateZip) + return new string[] { ZipPath }; + + return System.IO.Directory.GetFiles(FolderContent, "*.*", SearchOption.AllDirectories); + } + // data loaded once per export public Dictionary Categories { get; set; } public Dictionary CategoryPathes { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 809289409a..86f1741b7e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -304,14 +304,17 @@ private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment Username = deployment.Username, Password = deployment.Password, Url = deployment.Url, - MultipartForm = deployment.MultipartForm, + HttpTransmissionType = deployment.HttpTransmissionType, FileSystemPath = deployment.FileSystemPath, EmailAddresses = deployment.EmailAddresses, EmailSubject = deployment.EmailSubject, - EmailAccountId = deployment.EmailAccountId + EmailAccountId = deployment.EmailAccountId, + PassiveMode = deployment.PassiveMode, + UseSsl = deployment.UseSsl }; 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())); @@ -338,11 +341,13 @@ private void ModelToEntity(ExportDeploymentModel model, ExportDeployment deploym deployment.Username = model.Username; deployment.Password = model.Password; deployment.Url = model.Url; - deployment.MultipartForm = model.MultipartForm; + deployment.HttpTransmissionType = model.HttpTransmissionType; deployment.FileSystemPath = model.FileSystemPath; deployment.EmailAddresses = model.EmailAddresses; deployment.EmailSubject = model.EmailSubject; - deployment.EmailAccountId = model.EmailAccountId; + deployment.EmailAccountId = model.EmailAccountId; + deployment.PassiveMode = model.PassiveMode; + deployment.UseSsl = model.UseSsl; } private ActionResult SmartRedirect(bool continueEditing, int profileId, int deploymentId) diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs index 4de2383511..c9924400c8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs @@ -66,8 +66,9 @@ public string DeploymentTypeIconClass [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.Url")] public string Url { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.MultipartForm")] - public bool MultipartForm { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.HttpTransmissionType")] + public ExportHttpTransmissionType HttpTransmissionType { get; set; } + public List AvailableHttpTransmissionTypes { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.FileSystemPath")] public string FileSystemPath { get; set; } @@ -82,5 +83,11 @@ public string DeploymentTypeIconClass [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.EmailAccountId")] public int EmailAccountId { get; set; } public List AvailableEmailAccounts { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.PassiveMode")] + public bool PassiveMode { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Deployment.UseSsl")] + public bool UseSsl { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportDeplomentValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportDeplomentValidator.cs index 4caa43d099..e23171230b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportDeplomentValidator.cs +++ b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportDeplomentValidator.cs @@ -20,12 +20,7 @@ public ExportDeploymentValidator(ILocalizationService localization) RuleFor(x => x.Url) .NotEmpty() - .When(x => x.DeploymentType == ExportDeploymentType.Http) - .WithMessage(localization.GetResource("Admin.Validation.Url")); - - RuleFor(x => x.Url) - .Matches(RegularExpressions.IsWebUrl) - .When(x => x.DeploymentType == ExportDeploymentType.Http) + .When(x => x.DeploymentType == ExportDeploymentType.Http || x.DeploymentType == ExportDeploymentType.Ftp) .WithMessage(localization.GetResource("Admin.Validation.Url")); RuleFor(x => x.Username) 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 2d62e6b096..6bfaa071d9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml @@ -92,7 +92,7 @@ - + @@ -103,11 +103,11 @@ @@ -129,6 +129,25 @@ @Html.ValidationMessageFor(x => x.Password) + + + + + + + + +
+ @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.EditorFor(x => x.CompletedEmailAddresses) + @Html.HiddenFor(model => model.CompletedEmailAddresses) @Html.ValidationMessageFor(x => x.CompletedEmailAddresses)
+ @Html.SmartLabelFor(x => x.CreateZip) + + @Html.EditorFor(x => x.CreateZip) + @Html.ValidationMessageFor(x => x.CreateZip) +
@Html.SmartLabelFor(x => x.IsPublic) @@ -45,7 +54,7 @@ @Html.ValidationMessageFor(x => x.IsPublic)
@Html.SmartLabelFor(x => x.FileSystemPath) - @Html.EditorFor(x => x.EmailAddresses) + @Html.HiddenFor(x => x.EmailAddresses) @Html.ValidationMessageFor(x => x.EmailAddresses)
@Html.ListBoxFor(x => x.ProductFilter.CategoryIds, new MultiSelectList(Model.ProductFilter.AvailableCategories, "Value", "Text"), - new { multiple = "multiple", @class = "control-xlarge" }) + new { multiple = "multiple" }) @Html.ValidationMessageFor(x => x.ProductFilter.CategoryIds)
+ @Html.SmartLabelFor(x => x.ProductProjection.AttributeCombinationAsProduct) + + @Html.EditorFor(x => x.ProductProjection.AttributeCombinationAsProduct) + @Html.ValidationMessageFor(x => x.ProductProjection.AttributeCombinationAsProduct) +
- @Html.TextBoxFor(x => x.ProductProjection.AppendDescriptionText, new { @class = "input-xlarge" }) + @Html.HiddenFor(x => x.ProductProjection.AppendDescriptionText) @Html.ValidationMessageFor(x => x.ProductProjection.AppendDescriptionText)
- @Html.EditorFor(x => x.ProductProjection.CriticalCharacters) + @Html.HiddenFor(x => x.ProductProjection.CriticalCharacters) @Html.ValidationMessageFor(x => x.ProductProjection.CriticalCharacters)
+ @Html.SmartLabelFor(x => x.EmailAccountId) + + @Html.DropDownListFor(x => x.EmailAccountId, Model.AvailableEmailAccounts) + @Html.ValidationMessageFor(x => x.EmailAccountId) +
@Html.SmartLabelFor(x => x.EmailAddresses) @@ -82,23 +91,23 @@ @Html.ValidationMessageFor(x => x.EmailSubject)
- @Html.SmartLabelFor(x => x.EmailAccountId) + @Html.SmartLabelFor(x => x.Url) - @Html.DropDownListFor(x => x.EmailAccountId, Model.AvailableEmailAccounts) - @Html.ValidationMessageFor(x => x.EmailAccountId) + @Html.EditorFor(x => x.Url) + @Html.ValidationMessageFor(x => x.Url)
- @Html.SmartLabelFor(x => x.Url) + @Html.SmartLabelFor(x => x.MultipartForm) - @Html.EditorFor(x => x.Url) - @Html.ValidationMessageFor(x => x.Url) + @Html.EditorFor(x => x.MultipartForm) + @Html.ValidationMessageFor(x => x.MultipartForm)
@Html.SmartLabelFor(x => x.Url)
- @Html.SmartLabelFor(x => x.MultipartForm) + @Html.SmartLabelFor(x => x.HttpTransmissionType) - @Html.EditorFor(x => x.MultipartForm) - @Html.ValidationMessageFor(x => x.MultipartForm) + @Html.DropDownListFor(x => x.HttpTransmissionType, Model.AvailableHttpTransmissionTypes) + @Html.ValidationMessageFor(x => x.HttpTransmissionType)
+ @Html.SmartLabelFor(x => x.PassiveMode) + + @Html.EditorFor(x => x.PassiveMode) + @Html.ValidationMessageFor(x => x.PassiveMode) +
+ @Html.SmartLabelFor(x => x.UseSsl) + + @Html.EditorFor(x => x.UseSsl) + @Html.ValidationMessageFor(x => x.UseSsl) +
+ +@if (Model.EntityType == ExportEntityType.Product) +{ +
+ @(Html.Telerik().Grid() + .Name("export-product-grid") + .DataKeys(keys => keys.Add(x => x.Id) + .RouteKey("Id")) + .Columns(columns => + { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .Width(50) + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); + columns.Bound(x => x.Id) + .Width(80) + .Centered(); + columns.Bound(x => x.Name) + .ClientTemplate(@Html.LabeledProductName("Id", "Name")); + columns.Bound(x => x.Sku); + columns.Bound(x => x.Price) + .Format("{0:0.00}") + .RightAlign(); + columns.Bound(x => x.StockQuantity) + .Centered(); + columns.Bound(x => x.Published) + .Width(100) + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Bound(x => x.AdminComment); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) + .ClientEvents(events => events.OnDataBound("OnDataBound")) + .EnableCustomBinding(true)) +
+} +else if (Model.EntityType == ExportEntityType.Product) +{ +
+ @(Html.Telerik().Grid() + .Name("export-order-grid") + .DataKeys(keys => keys.Add(x => x.Id) + .RouteKey("Id")) + .Columns(columns => + { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .Width(50) + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); + columns.Bound(x => x.Id) + .Width(80) + .Centered(); + columns.Bound(x => x.OrderNumber) + .ClientTemplate(@Html.LabeledOrderNumber()); + columns.Bound(x => x.OrderStatus); + columns.Bound(x => x.PaymentStatus); + columns.Bound(x => x.ShippingStatus); + columns.Bound(x => x.CustomerEmail); + columns.Bound(x => x.StoreName); + columns.Bound(x => x.CreatedOn); + columns.Bound(x => x.OrderTotal); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) + .ClientEvents(events => events.OnDataBound("OnDataBound")) + .EnableCustomBinding(true)) +
+} +else +{ +
Unsupported entity type!
+} \ No newline at end of file From b0f7c86db136151b8ea2c23e737580ece7cbb6ba Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 1 Sep 2015 18:36:59 +0200 Subject: [PATCH 027/874] Added wildcard test for leading zeros --- src/Tests/SmartStore.Core.Tests/WildcardTests.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Tests/SmartStore.Core.Tests/WildcardTests.cs b/src/Tests/SmartStore.Core.Tests/WildcardTests.cs index b9cc5415f4..ecf1ece45f 100644 --- a/src/Tests/SmartStore.Core.Tests/WildcardTests.cs +++ b/src/Tests/SmartStore.Core.Tests/WildcardTests.cs @@ -36,6 +36,13 @@ public void Can_match_number_range() Assert.IsTrue(w1.IsMatch("3267")); Assert.IsFalse(w1.IsMatch("500")); Assert.IsFalse(w1.IsMatch("4000")); + + w1 = new Wildcard("01000-01010"); + Console.WriteLine(w1.Pattern); + Assert.IsTrue(w1.IsMatch("01000")); + Assert.IsTrue(w1.IsMatch("01008")); + Assert.IsFalse(w1.IsMatch("02200")); + Assert.IsFalse(w1.IsMatch("1005")); } [Test] From f732c82b98b5c7c103af5bb34799a2f5caaa5b34 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 1 Sep 2015 19:20:45 +0200 Subject: [PATCH 028/874] Added support for leading zeroes in "Wildcard" number ranges, which is called by the ShippingByWeight plugin to match zip code ranges. NOTE: Both min and max part MUST have the same amount of leading zeroes ("0100-0999" is valid, whereas "0010-0999" is invalid) --- src/Libraries/SmartStore.Core/Utilities/Wildcard.cs | 13 ++++++++----- src/Tests/SmartStore.Core.Tests/WildcardTests.cs | 10 +++++----- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Utilities/Wildcard.cs b/src/Libraries/SmartStore.Core/Utilities/Wildcard.cs index fc628d0fdd..690e2624d5 100644 --- a/src/Libraries/SmartStore.Core/Utilities/Wildcard.cs +++ b/src/Libraries/SmartStore.Core/Utilities/Wildcard.cs @@ -1,5 +1,6 @@ using System; using System.Text; +using System.Linq; using System.Text.RegularExpressions; using System.Threading; @@ -86,10 +87,11 @@ private static string WildcardToRegex(string pattern) foreach (Match match in collection) { string[] split = match.Value.Split(new char[] { '-' }); + int leadingZeroesCount = split[0].TakeWhile(x => x == '0').Count(); int min = Int32.Parse(split[0]); int max = Int32.Parse(split[1]); - pattern = pattern.Replace(match.Value, ConvertNumberRange(min, max)); + pattern = pattern.Replace(match.Value, ConvertNumberRange(min, max, leadingZeroesCount)); } return pattern; @@ -101,21 +103,22 @@ private static string WildcardToRegex(string pattern) /// The minimum value. /// The maximum value. /// The regular expression pattern for the number range term. - private static string ConvertNumberRange(int min, int max) + private static string ConvertNumberRange(int min, int max, int leadingZeroesCount) { if (max < min) { throw new InvalidOperationException("The minimum value could not be greater than the maximum value."); } - string pattern = string.Empty; + string prefix = new String('0', leadingZeroesCount); // for leading zeroes + string pattern = string.Empty; if (min > -1 && max - min < 10) { // special treatment: the below function has issues with too small ranges for (var i = min; i <= max; i++) { - pattern += i.ToString() + (i < max ? "|" : ""); + pattern += prefix + i.ToString() + (i < max ? "|" : ""); } } else @@ -131,7 +134,7 @@ private static string ConvertNumberRange(int min, int max) if (tempMax >= currentValue) { - pattern += ParseRange(currentValue, tempMax, radix); + pattern += prefix + ParseRange(currentValue, tempMax, radix); if (!(!m_isForward && radix == 1)) { pattern += "|"; diff --git a/src/Tests/SmartStore.Core.Tests/WildcardTests.cs b/src/Tests/SmartStore.Core.Tests/WildcardTests.cs index ecf1ece45f..8e12f793c9 100644 --- a/src/Tests/SmartStore.Core.Tests/WildcardTests.cs +++ b/src/Tests/SmartStore.Core.Tests/WildcardTests.cs @@ -37,12 +37,12 @@ public void Can_match_number_range() Assert.IsFalse(w1.IsMatch("500")); Assert.IsFalse(w1.IsMatch("4000")); - w1 = new Wildcard("01000-01010"); + w1 = new Wildcard("0001000-0005000"); Console.WriteLine(w1.Pattern); - Assert.IsTrue(w1.IsMatch("01000")); - Assert.IsTrue(w1.IsMatch("01008")); - Assert.IsFalse(w1.IsMatch("02200")); - Assert.IsFalse(w1.IsMatch("1005")); + Assert.IsTrue(w1.IsMatch("0001000")); + Assert.IsTrue(w1.IsMatch("0002008")); + Assert.IsFalse(w1.IsMatch("1000")); + Assert.IsFalse(w1.IsMatch("5000")); } [Test] From 2917779e92f7a11ac3f1158b6675dbc99ddf9522 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 1 Sep 2015 20:27:07 +0200 Subject: [PATCH 029/874] Added SmartStore.Core.Localization.LocalizationHelper.IsValidCultureCode() --- .../Infrastructure/RegularExpressions.cs | 2 +- .../Localization/LocalizationHelper.cs | 22 +++++++++++++ .../SmartStore.Core/SmartStore.Core.csproj | 1 + .../Localization/LocalizedUrlHelper.cs | 31 ++----------------- 4 files changed, 26 insertions(+), 30 deletions(-) create mode 100644 src/Libraries/SmartStore.Core/Localization/LocalizationHelper.cs diff --git a/src/Libraries/SmartStore.Core/Infrastructure/RegularExpressions.cs b/src/Libraries/SmartStore.Core/Infrastructure/RegularExpressions.cs index cc621f6629..a9143bed56 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/RegularExpressions.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/RegularExpressions.cs @@ -30,7 +30,7 @@ public static class RegularExpressions public static readonly Regex IsGuid = new Regex(@"\{?[a-fA-F0-9]{8}(?:-(?:[a-fA-F0-9]){4}){3}-[a-fA-F0-9]{12}\}?", RegexOptions.Compiled); public static readonly Regex IsBase64Guid = new Regex(@"[a-zA-Z0-9+/=]{22,24}", RegexOptions.Compiled); - public static readonly Regex IsCultureCode = new Regex(@"^([a-z]{2})|([a-z]{2}-[A-Z]{2})$", RegexOptions.Singleline | RegexOptions.Compiled); + public static readonly Regex IsCultureCode = new Regex(@"^[a-z]{2}(-[A-Z]{2})?$", RegexOptions.Singleline | RegexOptions.Compiled); public static readonly Regex IsYearRange = new Regex(@"^(\d{4})-(\d{4})$", RegexOptions.Compiled); diff --git a/src/Libraries/SmartStore.Core/Localization/LocalizationHelper.cs b/src/Libraries/SmartStore.Core/Localization/LocalizationHelper.cs new file mode 100644 index 0000000000..d4c940ee36 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Localization/LocalizationHelper.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace SmartStore.Core.Localization +{ + public static class LocalizationHelper + { + private readonly static HashSet _cultureCodes = + new HashSet( + CultureInfo.GetCultures(CultureTypes.NeutralCultures | CultureTypes.SpecificCultures | CultureTypes.UserCustomCulture) + .Select(x => x.IetfLanguageTag) + .Where(x => !string.IsNullOrWhiteSpace(x))); + + public static bool IsValidCultureCode(string cultureCode) + { + return _cultureCodes.Contains(cultureCode); + } + + } +} diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index c160b84ee1..67757356e1 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -178,6 +178,7 @@ + diff --git a/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedUrlHelper.cs b/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedUrlHelper.cs index 0a7fbd2fcc..8cf645774a 100644 --- a/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedUrlHelper.cs +++ b/src/Presentation/SmartStore.Web.Framework/Localization/LocalizedUrlHelper.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Web; using System.Threading.Tasks; +using SmartStore.Core.Localization; namespace SmartStore.Web.Framework.Localization { @@ -60,13 +61,7 @@ public bool IsLocalizedUrl(out string seoCode) firstPart = firstPart.Substring(0, firstSlash); } - //int length = firstPart.Length; - //if ((length == 2 || length == 5) && RegularExpressions.IsCultureCode.IsMatch(firstPart)) - //{ - // seoCode = firstPart; - // return true; - //} - if (IsValidCulture(firstPart)) + if (LocalizationHelper.IsValidCultureCode(firstPart)) { seoCode = firstPart; return true; @@ -75,28 +70,6 @@ public bool IsLocalizedUrl(out string seoCode) return false; } - private bool IsValidCulture(string cultureName) - { - var segments = cultureName.Split('-'); - - if (segments.Length == 0) - { - return false; - } - - if (segments.Length > 2) - { - return false; - } - - if (segments.Any(s => s.Length != 2)) - { - return false; - } - - return true; - } - public string StripSeoCode() { string seoCode; From 2c70c1ae0205e59c39f6677aeb493546e0a16cd2 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 2 Sep 2015 11:54:03 +0200 Subject: [PATCH 030/874] Export selected versus all entities --- .../201508261545304_ExportFramework1.cs | 8 +++ .../DataExchange/ExportProfileTask.cs | 21 ++++-- .../Orders/IOrderService.cs | 3 +- .../Orders/OrderService.cs | 5 +- .../Controllers/ExportController.cs | 12 +++- .../Administration/Views/Export/List.cshtml | 16 ++++- .../Views/Export/Preview.cshtml | 68 +++++++++++++++++-- 7 files changed, 117 insertions(+), 16 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.cs index 8fb6c629e7..460fb03f92 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.cs @@ -38,6 +38,14 @@ public void Seed(SmartObjectContext context) public void MigrateLocaleResources(LocaleResourcesBuilder builder) { + builder.AddOrUpdate("Admin.Common.ExportSelected", + "Export selected", + "Ausgewhlte exportieren"); + + builder.AddOrUpdate("Admin.Common.ExportAll", + "Export all", + "Alle exportieren"); + builder.AddOrUpdate("Admin.Configuration.Export.EmailAccountId", "Email notification", "E-Mail Benachrichtigung", diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index c3a105acb8..9e1097a11b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -113,6 +113,7 @@ private ProductSearchContext GetProductSearchContext(ExportProfileTaskContext ct OrderBy = ProductSortingEnum.CreatedOn, PageIndex = pageIndex, PageSize = pageSize, + ProductIds = ctx.SelectedEntityIds, StoreId = (ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId), VisibleIndividuallyOnly = true, PriceMin = ctx.Filter.PriceMinimum, @@ -597,7 +598,8 @@ private List GetOrders(ExportProfileTaskContext ctx, int pageIndex, int p null, pageIndex, pageSize, - null + null, + ctx.SelectedEntityIds ); totalCount = pagedList.TotalCount; @@ -1133,10 +1135,15 @@ public void Execute(TaskExecutionContext context) { InitDependencies(context); - var profileId = context.ScheduleTask.Alias.ToInt(); - var profile = _exportService.GetExportProfileById(profileId); + var id = context.ScheduleTask.Alias.ToInt(); + var profile = _exportService.GetExportProfileById(id); + + var selectedIdsCacheKey = "ExportTaskSelectedIds" + id.ToString(); + var selectedIds = HttpRuntime.Cache[selectedIdsCacheKey] as string; + + var ctx = new ExportProfileTaskContext(profile, _exportService.LoadProvider(profile.ProviderSystemName), context.CancellationToken, selectedIds); - var ctx = new ExportProfileTaskContext(profile, _exportService.LoadProvider(profile.ProviderSystemName), context.CancellationToken); + HttpRuntime.Cache.Remove(selectedIdsCacheKey); ExportCoreOuter(ctx); } @@ -1153,7 +1160,8 @@ public void Preview(ExportProfile profile, IComponentContext context, int pageIn var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(5.0)); - var ctx = new ExportProfileTaskContext(profile, _exportService.LoadProvider(profile.ProviderSystemName), cancellation.Token, pageIndex, pageSize, totalRecords, previewData); + var ctx = new ExportProfileTaskContext(profile, _exportService.LoadProvider(profile.ProviderSystemName), cancellation.Token, + null, pageIndex, pageSize, totalRecords, previewData); ExportCoreOuter(ctx); } @@ -1192,6 +1200,7 @@ internal class ExportProfileTaskContext public ExportProfileTaskContext(ExportProfile profile, Provider provider, CancellationToken cancellation, + string selectedIds = null, int pageIndex = 0, int pageSize = 100, int? totalRecords = null, @@ -1202,6 +1211,7 @@ public ExportProfileTaskContext(ExportProfile profile, Filter = XmlHelper.Deserialize(profile.Filtering); Projection = XmlHelper.Deserialize(profile.Projection); Cancellation = cancellation; + SelectedEntityIds = selectedIds.SplitSafe(",").Select(x => x.ToInt()).ToList(); PageIndex = pageIndex; PageSize = pageSize; TotalRecords = totalRecords; @@ -1217,6 +1227,7 @@ public ExportProfileTaskContext(ExportProfile profile, Export = new ExportExecuteContext(Cancellation, FolderContent); } + public List SelectedEntityIds { get; private set; } public int PageIndex { get; private set; } public int PageSize { get; private set; } public int? TotalRecords { get; set; } diff --git a/src/Libraries/SmartStore.Services/Orders/IOrderService.cs b/src/Libraries/SmartStore.Services/Orders/IOrderService.cs index 69d7c65aa4..5033b926d5 100644 --- a/src/Libraries/SmartStore.Services/Orders/IOrderService.cs +++ b/src/Libraries/SmartStore.Services/Orders/IOrderService.cs @@ -86,10 +86,11 @@ public partial interface IOrderService /// Page index /// Page size /// Billing name. Leave empty to load all records. + /// Filter by order identifiers /// Order collection IPagedList SearchOrders(int storeId, int customerId, DateTime? startTime, DateTime? endTime, int[] orderStatusIds, int[] paymentStatusIds, int[] shippingStatusIds, - string billingEmail, string orderGuid, string orderNumber, int pageIndex, int pageSize, string billingName = null); + string billingEmail, string orderGuid, string orderNumber, int pageIndex, int pageSize, string billingName = null, IList orderIds = null); /// /// Gets all orders by affiliate identifier diff --git a/src/Libraries/SmartStore.Services/Orders/OrderService.cs b/src/Libraries/SmartStore.Services/Orders/OrderService.cs index d4c83b1b97..21f6a254c8 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderService.cs @@ -202,7 +202,7 @@ from o in _orderRepository.Table /// Order collection public virtual IPagedList SearchOrders(int storeId, int customerId, DateTime? startTime, DateTime? endTime, int[] orderStatusIds, int[] paymentStatusIds, int[] shippingStatusIds, - string billingEmail, string orderGuid, string orderNumber, int pageIndex, int pageSize, string billingName = null) + string billingEmail, string orderGuid, string orderNumber, int pageIndex, int pageSize, string billingName = null, IList orderIds = null) { var query = _orderRepository.Table; @@ -241,6 +241,9 @@ public virtual IPagedList SearchOrders(int storeId, int customerId, DateT if (shippingStatusIds != null && shippingStatusIds.Count() > 0) query = query.Where(x => shippingStatusIds.Contains(x.ShippingStatusId)); + if (orderIds != null && orderIds.Count > 0) + query = query.Where(x => orderIds.Contains(x.Id)); + query = query.Where(o => !o.Deleted); query = query.OrderByDescending(o => o.CreatedOnUtc); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 48a04f601b..7e4c000978 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Web; +using System.Web.Caching; using System.Web.Mvc; using Autofac; using SmartStore.Admin.Models.DataExchange; @@ -757,7 +759,8 @@ public ActionResult PreviewList(GridCommand command, int id, int totalRecords) return new EmptyResult(); } - public ActionResult Execute(int id) + [HttpPost] + public ActionResult Execute(int id, string selectedIds, bool exportAll) { if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); @@ -766,6 +769,13 @@ public ActionResult Execute(int id) if (profile == null) return RedirectToAction("List"); + var selectedIdsCacheKey = "ExportTaskSelectedIds" + id.ToString(); + + if (selectedIds.HasValue()) + HttpRuntime.Cache.Add(selectedIdsCacheKey, selectedIds, null, DateTime.UtcNow.AddMinutes(5), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); + else + HttpRuntime.Cache.Remove(selectedIdsCacheKey); + var returnUrl = Url.Action("List", "Export", new { area = "admin" }); return RedirectToAction("RunJob", "ScheduleTask", new { area = "admin", id = profile.SchedulingTaskId, returnUrl = returnUrl }); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index b06a36209c..dcdce640da 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -15,6 +15,11 @@ + + + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml index d79f020bd4..d3f4d7cf89 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml @@ -2,6 +2,11 @@ @using SmartStore.Core.Domain.DataExchange; @using SmartStore.Admin.Models.DataExchange; +
+ + @Html.Raw(T("Admin.Configuration.Export.Filter.Note")) +
+ @if (Model.ProductFilter != null) { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml index b620559f2c..d7aba58bff 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml @@ -2,6 +2,11 @@ @using SmartStore.Core.Domain.DataExchange; @using SmartStore.Admin.Models.DataExchange; +
+ + @Html.Raw(T("Admin.Configuration.Export.Projection.Note")) +
+
@if (Model.ProductProjection != null) { From e1f16f665080ad6e71394843140202f8824e9231 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 2 Sep 2015 16:28:30 +0200 Subject: [PATCH 033/874] #768 Make LocalizedEditor compatible with multistore configurator --- .../Settings/StoreDependingSettingHelper.cs | 72 ++++++++++++++++++- .../UI/PageAssetsBuilder.cs | 6 +- 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs b/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs index d2efb7c9d1..802fea0798 100644 --- a/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs +++ b/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs @@ -1,7 +1,12 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.Linq; using System.Web.Mvc; using Fasterflect; +using SmartStore.Core.Infrastructure; using SmartStore.Services.Configuration; +using SmartStore.Services.Localization; +using SmartStore.Web.Framework.Localization; namespace SmartStore.Web.Framework.Settings { @@ -90,11 +95,52 @@ public void GetOverrideKeys(object settings, object model, int storeId, ISetting _viewData[ViewDataKey] = data; } } + + 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 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 = "Locales[" + index.ToString() + "]." + name; + + var leService = EngineContext.Current.Resolve(); + var resultStr = leService.GetLocalizedValue(localized.LanguageId, 0, settingName, name); + + if (!String.IsNullOrEmpty(resultStr)) + data.OverrideSettingKeys.Add(key); + } + + if (isRootModel) + { + data.ActiveStoreScopeConfiguration = storeId; + data.RootSettingClass = settingName; + + _viewData[ViewDataKey] = data; + } + } + public void UpdateSettings(object settings, FormCollection form, int storeId, ISettingService settingService) { var settingName = settings.GetType().Name; var properties = settings.GetType().GetProperties(); - + foreach (var prop in properties) { var name = prop.Name; @@ -111,5 +157,27 @@ public void UpdateSettings(object settings, FormCollection form, int storeId, IS } } } + + public void UpdateLocalizedSettings(object settings, FormCollection form, int storeId, ISettingService settingService, ILocalizedModelLocal localized) + { + var settingName = settings.GetType().Name; + var properties = localized.GetType().GetProperties(); + + foreach (var prop in properties) + { + var name = prop.Name; + var key = settingName + "." + name; + + if (storeId == 0 || IsOverrideChecked(key, form)) + { + dynamic value = settings.TryGetPropertyValue(name); + settingService.SetSetting(key, value == null ? "" : value, storeId, false); + } + else if (storeId > 0) + { + settingService.DeleteSetting(key, storeId); + } + } + } } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/PageAssetsBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/PageAssetsBuilder.cs index 6111b3c259..4256ce6aea 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/PageAssetsBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/PageAssetsBuilder.cs @@ -109,7 +109,7 @@ public void AddTitleParts(IEnumerable parts, bool append = false) AddPartsCore(_titleParts, parts, append); } - public string GenerateTitle(bool addDefaultTitle) + public virtual string GenerateTitle(bool addDefaultTitle) { string result = ""; var specificTitle = string.Join(_seoSettings.PageTitleSeparator, _titleParts.AsEnumerable().Reverse().ToArray()); @@ -154,7 +154,7 @@ public void AddMetaDescriptionParts(IEnumerable parts, bool append = fal AddPartsCore(_metaDescriptionParts, parts, append); } - public string GenerateMetaDescription() + public virtual string GenerateMetaDescription() { var metaDescription = string.Join(", ", _metaDescriptionParts.AsEnumerable().Reverse().ToArray()); var result = !String.IsNullOrEmpty(metaDescription) ? metaDescription : _seoSettings.DefaultMetaDescription; @@ -167,7 +167,7 @@ public void AddMetaKeywordParts(IEnumerable parts, bool append = false) AddPartsCore(_metaKeywordParts, parts, append); } - public string GenerateMetaKeywords() + public virtual string GenerateMetaKeywords() { var metaKeyword = string.Join(", ", _metaKeywordParts.AsEnumerable().Reverse().ToArray()); var result = !String.IsNullOrEmpty(metaKeyword) ? metaKeyword : _seoSettings.DefaultMetaKeywords; From dc6ad9a28a5583bc88a9f2f9ccf085fe989db993 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 2 Sep 2015 18:53:26 +0200 Subject: [PATCH 034/874] File name pattern for export files --- .../DataExchange/DataExchangeSettings.cs | 2 +- .../Domain/DataExchange/ExportProfile.cs | 5 + .../Mapping/DataExchange/ExportProfileMap.cs | 1 + .../201508261545304_ExportFramework1.resx | 126 ------------------ ...509021536425_ExportFramework1.Designer.cs} | 2 +- ...cs => 201509021536425_ExportFramework1.cs} | 14 +- .../201509021536425_ExportFramework1.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 10 +- .../DataExchange/ExportExecuteContext.cs | 40 +++--- .../DataExchange/ExportProfileTask.cs | 14 +- .../DataExchange/ExportService.cs | 8 +- .../Controllers/ExportController.cs | 5 +- .../Models/DataExchange/ExportProfileModel.cs | 3 + .../DataExchange/ExportProfileValidator.cs | 6 +- .../Administration/Views/Export/Edit.cshtml | 27 ++-- 15 files changed, 213 insertions(+), 176 deletions(-) delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.resx rename src/Libraries/SmartStore.Data/Migrations/{201508261545304_ExportFramework1.Designer.cs => 201509021536425_ExportFramework1.Designer.cs} (92%) rename src/Libraries/SmartStore.Data/Migrations/{201508261545304_ExportFramework1.cs => 201509021536425_ExportFramework1.cs} (91%) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.resx diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs index d5cb3fc4ba..742e379df5 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs @@ -6,7 +6,7 @@ public class DataExchangeSettings : ISettings { public DataExchangeSettings() { - MaxFileNameLength = 32; + MaxFileNameLength = 50; } /// diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs index f0540e42d7..cabc876a29 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs @@ -27,6 +27,11 @@ public ExportProfile() /// public string FolderName { get; set; } + /// + /// The pattern for file names + /// + public string FileNamePattern { get; set; } + /// /// The system name of the export provider /// diff --git a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs index 7d62b17bd6..2ae4dd4c9d 100644 --- a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs @@ -17,6 +17,7 @@ public ExportProfileMap() this.Property(x => x.Projection).IsMaxLength(); this.Property(x => x.ProviderConfigData).IsMaxLength(); this.Property(x => x.CompletedEmailAddresses).HasMaxLength(400); + this.Property(x => x.FileNamePattern).HasMaxLength(400); this.HasRequired(x => x.ScheduleTask) .WithMany() diff --git a/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.resx b/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.resx deleted file mode 100644 index 749d5146fc..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.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/201508261545304_ExportFramework1.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.Designer.cs similarity index 92% rename from src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.Designer.cs rename to src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.Designer.cs index b34e5095fe..0bb342e80d 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.Designer.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.Designer.cs @@ -13,7 +13,7 @@ public sealed partial class ExportFramework1 : IMigrationMetadata string IMigrationMetadata.Id { - get { return "201508261545304_ExportFramework1"; } + get { return "201509021536425_ExportFramework1"; } } string IMigrationMetadata.Source diff --git a/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs similarity index 91% rename from src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.cs rename to src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs index bb7f5a6800..0f4af5f155 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508261545304_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs @@ -13,12 +13,14 @@ public override void Up() AddColumn("dbo.ExportDeployment", "HttpTransmissionType", c => c.Int(nullable: false)); AddColumn("dbo.ExportDeployment", "PassiveMode", c => c.Boolean(nullable: false)); AddColumn("dbo.ExportDeployment", "UseSsl", c => c.Boolean(nullable: false)); + AddColumn("dbo.ExportProfile", "FileNamePattern", c => c.String(maxLength: 400)); AddColumn("dbo.ExportProfile", "EmailAccountId", c => c.Int(nullable: false)); } public override void Down() { DropColumn("dbo.ExportProfile", "EmailAccountId"); + DropColumn("dbo.ExportProfile", "FileNamePattern"); DropColumn("dbo.ExportDeployment", "UseSsl"); DropColumn("dbo.ExportDeployment", "PassiveMode"); DropColumn("dbo.ExportDeployment", "HttpTransmissionType"); @@ -42,6 +44,12 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Common.ExportAll", "Export all", "Alle exportieren"); builder.AddOrUpdate("Admin.Common.NoDescriptionAvailable", "No description available", "Keine Beschreibung vorhanden"); + builder.AddOrUpdate("Admin.Configuration.Export.FileNamePattern", + "Pattern for file names", + "Muster fr Dateinamen", + "Specifies the pattern for creating file names.", + "Legt das Muster fest, nach dem Dateinamen erzeugt werden."); + builder.AddOrUpdate("Admin.Configuration.Export.EmailAccountId", "Email notification", "E-Mail Benachrichtigung", @@ -68,9 +76,9 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Specifies the name of the folder where the data will be exported.", "Legt den Namen des Ordners fest, in den die Daten exportiert werden."); - builder.AddOrUpdate("Admin.Configuration.Export.FolderName.Validate", - "Please enter a valid folder name.", - "Bitte einen gltigen Ordnernamen eingeben."); + builder.AddOrUpdate("Admin.Configuration.Export.FolderAndFileName.Validate", + "Please enter a valid folder and file name.", + "Bitte einen gltigen Ordner- und Dateinamen eingeben."); builder.AddOrUpdate("Admin.Configuration.Export.Deployment.CreateZip", diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.resx b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.resx new file mode 100644 index 0000000000..e07999aa4a --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.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 e9ab2681aa..11ab25c6cc 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -341,9 +341,9 @@ 201508211346171_Merge1.cs - - - 201508261545304_ExportFramework1.cs + + + 201509021536425_ExportFramework1.cs @@ -648,8 +648,8 @@ 201508211346171_Merge1.cs - - 201508261545304_ExportFramework1.cs + + 201509021536425_ExportFramework1.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs index e8cb5e817d..c259535ae6 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs @@ -17,17 +17,17 @@ public interface IExportExecuteContext ExpandoObject Store { get; } - string Folder { get; } - - string FileNamePattern { get; } - object ConfigurationData { get; } Dictionary CustomProperties { get; set; } int SuccessfulExportedRecords { get; set; } - string GetFilePath(string fileNameSuffix = null); + int MaxFileNameLength { get; } + + string Folder { get; } + string FileName { get; } + string FilePath { get; } } @@ -54,24 +54,32 @@ public bool IsCanceled public ExpandoObject Store { get; internal set; } - public string Folder { get; private set; } - - public string FileNamePattern { get; internal set; } - public object ConfigurationData { get; internal set; } public Dictionary CustomProperties { get; set; } public int SuccessfulExportedRecords { get; set; } - public string GetFilePath(string fileNameSuffix = null) - { - var fileName = FileNamePattern.FormatInvariant( - (Data.FileIndex + 1).ToString("D5"), - SeoHelper.GetSeName(fileNameSuffix.EmptyNull(), true, false) - ); + public int MaxFileNameLength { get; internal set; } - return Path.Combine(Folder, fileName); + public string Folder { get; private set; } + public string FileNamePattern { get; internal set; } + public string FileExtension { get; internal set; } + public string FileName + { + get + { + var finallyResolvedPattern = FileNamePattern + .Replace("%Misc.FileNumber%", (Data.FileIndex + 1).ToString("D5")) + .ToValidFileName("") + .Truncate(MaxFileNameLength); + + return string.Concat(finallyResolvedPattern, FileExtension); + } + } + public string FilePath + { + get { return Path.Combine(Folder, FileName); } } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index 9e1097a11b..0e8bc9b257 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -902,12 +902,14 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.Store = ctx.Store.ToExpando(ctx.Projection.LanguageId ?? 0); - ctx.Export.FileNamePattern = string.Concat( - "{0}-", - ctx.Profile.PerStore ? SeoHelper.GetSeName(ctx.Store.Name, true, false).ToValidFileName("").Truncate(_dataExchangeSettings.MaxFileNameLength) : "all-stores", - "{1}", - ctx.Provider.Value.FileExtension.ToLower().EnsureStartsWith(".") - ); + ctx.Export.MaxFileNameLength = _dataExchangeSettings.MaxFileNameLength; + + ctx.Export.FileExtension = ctx.Provider.Value.FileExtension.ToLower().EnsureStartsWith("."); + + ctx.Export.FileNamePattern = ctx.Profile.FileNamePattern + .Replace("%ExportProfile.Id%", ctx.Profile.Id.ToString()) + .Replace("%Store.Name%", ctx.Profile.PerStore ? SeoHelper.GetSeName(ctx.Store.Name, true, false) : "allstores"); + var totalCount = GetTotalRecords(ctx); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index ab24006a50..b64406fd68 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -53,10 +53,7 @@ public virtual ExportProfile InsertExportProfile(Provider provi if (name.IsEmpty()) name = systemName; - var folderName = SeoHelper.GetSeName(name, true, false) - .Replace("/", "") - .ToValidPath() - .Truncate(_dataExchangeSettings.MaxFileNameLength); + var seoName = SeoHelper.GetSeName(name, true, false).Replace("/", "").Replace("-", ""); var taskType = (new ExportProfileTask()).GetType().AssemblyQualifiedNameWithoutVersion(); @@ -75,7 +72,8 @@ public virtual ExportProfile InsertExportProfile(Provider provi var profile = new ExportProfile { Name = name, - FolderName = folderName, + FolderName = seoName.ToValidPath().Truncate(_dataExchangeSettings.MaxFileNameLength), + FileNamePattern = "%Misc.FileNumber%-%ExportProfile.Id%-{0}-%Store.Name%".FormatInvariant(seoName), ProviderSystemName = systemName, SchedulingTaskId = task.Id, Filtering = XmlHelper.Serialize(new ExportFilter()), diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index f855e5ac26..ca6d7c0795 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -102,6 +102,7 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile model.Id = profile.Id; model.Name = profile.Name; model.FolderName = profile.FolderName; + model.FileNamePattern = profile.FileNamePattern; model.Enabled = profile.Enabled; model.SchedulingHours = profile.ScheduleTask.Seconds / 3600; @@ -452,9 +453,6 @@ public ActionResult Create() return item; }).ToList(); - model.Provider.AvailableExportProviders.Add(new SelectListItem { Text = "Tester", Value = "Provider.Tester" }); - model.Provider.ProviderDescriptions.Add("Provider.Tester", "Hello world!"); - return PartialView(model); } @@ -523,6 +521,7 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) } profile.Name = model.Name; + profile.FileNamePattern = model.FileNamePattern; profile.FolderName = model.FolderName; profile.Enabled = model.Enabled; profile.ScheduleTask.Seconds = model.SchedulingHours * 3600; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 63f6aca708..70ca552416 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -22,6 +22,9 @@ public partial class ExportProfileModel : EntityModelBase [SmartResourceDisplayName("Admin.Configuration.Export.FolderName")] public string FolderName { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Export.FileNamePattern")] + public string FileNamePattern { get; set; } + [SmartResourceDisplayName("Common.Enabled")] public bool Enabled { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs index 429d652155..8cccfb4386 100644 --- a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs +++ b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ExportProfileValidator.cs @@ -16,7 +16,11 @@ public ExportProfileValidator(ILocalizationService localization) RuleFor(x => x.FolderName) .Must(x => x.HasValue() && !x.IsCaseInsensitiveEqual("con") && !Path.GetInvalidFileNameChars().Any(y => x.Contains(y))) - .WithMessage(localization.GetResource("Admin.Configuration.Export.FolderName.Validate")); + .WithMessage(localization.GetResource("Admin.Configuration.Export.FolderAndFileName.Validate")); + + RuleFor(x => x.FileNamePattern) + .NotEmpty() + .WithMessage(localization.GetResource("Admin.Configuration.Export.FolderAndFileName.Validate")); RuleFor(x => x.Offset) .LessThanOrEqualTo(x => x.Limit) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index b5eaa5c5b8..8bc2ebfb2c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -283,6 +283,24 @@ @Html.ValidationMessageFor(x => x.FolderName) + + + + + + + + - - - -
+ @Html.SmartLabelFor(x => x.FileNamePattern) + + @Html.TextBoxFor(x => x.FileNamePattern, new { @class = "input-large" }) + @Html.ValidationMessageFor(x => x.FileNamePattern) +
+ @Html.SmartLabelFor(x => x.CreateZipArchive) + + @Html.EditorFor(x => x.CreateZipArchive) + @Html.ValidationMessageFor(x => x.CreateZipArchive) +
@Html.SmartLabelFor(x => x.EmailAccountId) @@ -301,15 +319,6 @@ @Html.ValidationMessageFor(x => x.CompletedEmailAddresses)
- @Html.SmartLabelFor(x => x.CreateZipArchive) - - @Html.EditorFor(x => x.CreateZipArchive) - @Html.ValidationMessageFor(x => x.CreateZipArchive) -
@Html.SmartLabelFor(x => x.Cleanup) From 0e3a480d5cebe37e10480881d5de8ade8c5aac51 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 2 Sep 2015 20:47:18 +0200 Subject: [PATCH 035/874] TODO: TaskScheduler.Seconds does not exist anymore --- .../SmartStore.Services/DataExchange/ExportService.cs | 2 +- .../SmartStore.Web.Framework/DependencyRegistrar.cs | 1 - .../Administration/Controllers/ExportController.cs | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index b64406fd68..aa50e48b68 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -60,7 +60,7 @@ public virtual ExportProfile InsertExportProfile(Provider provi var task = new ScheduleTask { Name = string.Concat(name, " Export Task"), - Seconds = _defaultSchedulingHours * 3600, + //Seconds = _defaultSchedulingHours * 3600, // TODO cron Type = taskType, Enabled = false, StopOnError = false, diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index 58a206da76..515eb4db21 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -42,7 +42,6 @@ using SmartStore.Services.Directory; using SmartStore.Services.Discounts; using SmartStore.Services.Events; -using SmartStore.Services.DataExchange; using SmartStore.Services.ExportImport; using SmartStore.Services.Filter; using SmartStore.Services.Forums; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index ca6d7c0795..1429a58bd1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -104,7 +104,7 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile model.FolderName = profile.FolderName; model.FileNamePattern = profile.FileNamePattern; model.Enabled = profile.Enabled; - model.SchedulingHours = profile.ScheduleTask.Seconds / 3600; + //model.SchedulingHours = profile.ScheduleTask.Seconds / 3600; // TODO cron model.Provider = new ExportProfileModel.ProviderModel { @@ -524,7 +524,7 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) profile.FileNamePattern = model.FileNamePattern; profile.FolderName = model.FolderName; profile.Enabled = model.Enabled; - profile.ScheduleTask.Seconds = model.SchedulingHours * 3600; + //profile.ScheduleTask.Seconds = model.SchedulingHours * 3600; // TODO cron profile.Offset = model.Offset; profile.Limit = model.Limit; profile.BatchSize = model.BatchSize; From 4b477741d908435ead1e3ae9081f0e5364d53a01 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 3 Sep 2015 11:04:02 +0200 Subject: [PATCH 036/874] ScheduleTask.Seconds becomes ScheduleTask.CronExpression --- .../Migrations/201508091512101_ExportFramework.cs | 6 ------ .../Migrations/201509021536425_ExportFramework1.cs | 2 ++ .../SmartStore.Services/DataExchange/ExportService.cs | 4 +--- .../Administration/Controllers/ExportController.cs | 4 ++-- .../Models/DataExchange/ExportProfileModel.cs | 5 +++-- .../Administration/Views/Export/Edit.cshtml | 11 +++++------ .../Administration/Views/Export/List.cshtml | 4 +--- 7 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs index 5f355fe54c..98fb1a99a9 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs @@ -146,12 +146,6 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "The file type of the exported data.", "Der Dateityp der exportierten Daten."); - builder.AddOrUpdate("Admin.Configuration.Export.SchedulingHours", - "Hours (interval)", - "Stunden (Intervall)", - "Specifies the interval in hours to which the export should execute automatically.", - "Legt das Intervall in Stunden fest, zu dem der Export automatisch erfolgen soll."); - builder.AddOrUpdate("Admin.Configuration.Export.LastExecution", "Last execution", "Letzte Ausfhrung", diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs index 0f4af5f155..91f8cabeee 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs @@ -44,6 +44,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Common.ExportAll", "Export all", "Alle exportieren"); builder.AddOrUpdate("Admin.Common.NoDescriptionAvailable", "No description available", "Keine Beschreibung vorhanden"); + builder.AddOrUpdate("Admin.System.ScheduleTask", "Scheduled task", "Geplante Aufgabe"); + builder.AddOrUpdate("Admin.Configuration.Export.FileNamePattern", "Pattern for file names", "Muster fr Dateinamen", diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index aa50e48b68..6af5f668bf 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -15,8 +15,6 @@ namespace SmartStore.Services.DataExchange { public partial class ExportService : IExportService { - private const int _defaultSchedulingHours = 6; - private readonly IRepository _exportProfileRepository; private readonly IRepository _exportDeploymentRepository; private readonly IEventPublisher _eventPublisher; @@ -60,7 +58,7 @@ public virtual ExportProfile InsertExportProfile(Provider provi var task = new ScheduleTask { Name = string.Concat(name, " Export Task"), - //Seconds = _defaultSchedulingHours * 3600, // TODO cron + CronExpression = "0 */6 * * *", // every six hours Type = taskType, Enabled = false, StopOnError = false, diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 1429a58bd1..000fc12706 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -104,7 +104,8 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile model.FolderName = profile.FolderName; model.FileNamePattern = profile.FileNamePattern; model.Enabled = profile.Enabled; - //model.SchedulingHours = profile.ScheduleTask.Seconds / 3600; // TODO cron + model.ScheduleTaskId = profile.SchedulingTaskId; + model.ScheduleTaskName = profile.ScheduleTask.Name.NaIfEmpty(); model.Provider = new ExportProfileModel.ProviderModel { @@ -524,7 +525,6 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) profile.FileNamePattern = model.FileNamePattern; profile.FolderName = model.FolderName; profile.Enabled = model.Enabled; - //profile.ScheduleTask.Seconds = model.SchedulingHours * 3600; // TODO cron profile.Offset = model.Offset; profile.Limit = model.Limit; profile.BatchSize = model.BatchSize; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 70ca552416..093641c7c2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -28,8 +28,9 @@ public partial class ExportProfileModel : EntityModelBase [SmartResourceDisplayName("Common.Enabled")] public bool Enabled { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Export.SchedulingHours")] - public int SchedulingHours { get; set; } + [SmartResourceDisplayName("Admin.System.ScheduleTask")] + public int ScheduleTaskId { get; set; } + public string ScheduleTaskName { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.LastExecution")] [AllowHtml] diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 8bc2ebfb2c..278ed9fca7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -128,20 +128,19 @@
- @Html.SmartLabelFor(x => x.SchedulingHours) + @Html.SmartLabelFor(x => x.Enabled) - @Html.EditorFor(x => x.SchedulingHours) - @Html.ValidationMessageFor(x => x.SchedulingHours) + @Html.EditorFor(x => x.Enabled) + @Html.ValidationMessageFor(x => x.Enabled)
- @Html.SmartLabelFor(x => x.Enabled) + @Html.SmartLabelFor(x => x.ScheduleTaskId) - @Html.EditorFor(x => x.Enabled) - @Html.ValidationMessageFor(x => x.Enabled) + @Model.ScheduleTaskName
diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 10e872cf2c..bad4693935 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -54,11 +54,9 @@ columns.Bound(x => x.Provider.EntityTypeName); columns.Bound(x => x.Provider.FileExtension) .Centered(); - columns.Bound(x => x.SchedulingHours) - .Centered(); columns.Bound(x => x.LastExecution) .ClientTemplate( - "" + "" ) .ReadOnly(); }) From 7473cfa365e1d0e69338529346c34197adfe6462 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 3 Sep 2015 11:11:11 +0200 Subject: [PATCH 037/874] Debitoor: Customer VAT number not transmitted anymore because it appears on the Debitoor invoice --- changelog.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index f20361d198..c5ce89da7a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ -# Release Notes +# Release Notes ## SmartStore.NET 2.5 @@ -45,7 +45,8 @@ * #62 free shipping info on product detail page * Display base price in CompactProductBox * Automatically redirect to referrer after login -* #249 Make UI editor for 'SeoSettings.ExtraRobotsDisallows' +* #249 Make UI editor for 'SeoSettings.ExtraRobotsDisallows' +* Debitoor: Customer VAT number not transmitted anymore because it appears on the Debitoor invoice. ### Bugfixes * #523 Redirecting to payment provider performed by core instead of plugin From 6994b707f18663326bc9b5dd977c13332787202a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 3 Sep 2015 11:57:58 +0200 Subject: [PATCH 038/874] Added new folder "Exchange" to core, used for file-based data exchange between .NET and the internet --- .../DataExchange/ExportProfileTask.cs | 1 - .../Security/FilePermissionHelper.cs | 1 + .../Controllers/CommonController.cs | 58 +++++++++++-------- .../Controllers/CommonController.cs | 1 + 4 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index 0e8bc9b257..11d1d06f34 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -46,7 +46,6 @@ namespace SmartStore.Services.DataExchange public class ExportProfileTask : ITask { private const int _maxErrors = 20; - // TODO: replaces Content\files\ExportImport... add it to CommonController.RobotsTextFile, CommonController.MaintenanceDeleteFiles, FilePermissionHelper.GetDirectoriesWrite private const string _publicFolder = "Exchange"; #region Dependencies diff --git a/src/Presentation/SmartStore.Web.Framework/Security/FilePermissionHelper.cs b/src/Presentation/SmartStore.Web.Framework/Security/FilePermissionHelper.cs index e601c80016..74e63cfc0b 100644 --- a/src/Presentation/SmartStore.Web.Framework/Security/FilePermissionHelper.cs +++ b/src/Presentation/SmartStore.Web.Framework/Security/FilePermissionHelper.cs @@ -158,6 +158,7 @@ public static IEnumerable GetDirectoriesWrite(IWebHelper webHelper) dirsToCheck.Add(Path.Combine(rootDir, "bin")); dirsToCheck.Add(Path.Combine(rootDir, "content")); dirsToCheck.Add(Path.Combine(rootDir, "content\\files\\exportimport")); + dirsToCheck.Add(Path.Combine(rootDir, "Exchange")); dirsToCheck.Add(Path.Combine(rootDir, "media")); dirsToCheck.Add(Path.Combine(rootDir, "media\\thumbs")); dirsToCheck.Add(Path.Combine(rootDir, "media\\uploaded")); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs index b2e0ec9a03..d64752d555 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs @@ -849,31 +849,43 @@ public ActionResult MaintenanceDeleteFiles(MaintenanceModel model) model.DeleteExportedFiles.NumberOfDeletedFiles = 0; - string path = string.Format("{0}Content\\files\\exportimport\\", this.Request.PhysicalApplicationPath); - foreach (var fullPath in System.IO.Directory.GetFiles(path)) - { - try - { - var fileName = Path.GetFileName(fullPath); - if (fileName.Equals("index.htm", StringComparison.InvariantCultureIgnoreCase)) - continue; - if (fileName.Equals("placeholder", StringComparison.InvariantCultureIgnoreCase)) - continue; + var appPath = this.Request.PhysicalApplicationPath; - var info = new FileInfo(fullPath); - if ((!startDateValue.HasValue || startDateValue.Value < info.CreationTimeUtc)&& - (!endDateValue.HasValue || info.CreationTimeUtc < endDateValue.Value)) - { - System.IO.File.Delete(fullPath); - model.DeleteExportedFiles.NumberOfDeletedFiles++; - } - } - catch (Exception exc) - { - NotifyError(exc, false); - } - } + string[] paths = new string[] + { + string.Concat(appPath, "Content\\files\\exportimport\\"), + string.Concat(appPath, "Exchange\\"), + }; + + foreach (var path in paths) + { + foreach (var fullPath in System.IO.Directory.GetFiles(path)) + { + try + { + var fileName = Path.GetFileName(fullPath); + if (fileName.Equals("index.htm", StringComparison.InvariantCultureIgnoreCase)) + continue; + + if (fileName.Equals("placeholder", StringComparison.InvariantCultureIgnoreCase)) + continue; + + var info = new FileInfo(fullPath); + + if ((!startDateValue.HasValue || startDateValue.Value < info.CreationTimeUtc) && + (!endDateValue.HasValue || info.CreationTimeUtc < endDateValue.Value)) + { + if (FileSystemHelper.Delete(fullPath)) + ++model.DeleteExportedFiles.NumberOfDeletedFiles; + } + } + catch (Exception exc) + { + NotifyError(exc, false); + } + } + } return View(model); } diff --git a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs index 8b915e8d5f..4c411f9238 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs @@ -803,6 +803,7 @@ public ActionResult RobotsTextFile() "/bin/", "/Content/files/", "/Content/files/ExportImport/", + "/Exchange/", "/Country/GetStatesByCountryId", "/Install", "/Product/SetReviewHelpfulness", From 5213bbce3b7edf097c1df8cbda4a5aa23a59a66e Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 3 Sep 2015 13:18:07 +0200 Subject: [PATCH 039/874] Resolves #774 Installation error due to duplicate pending migration changes --- .../201509031112324_Merge3.Designer.cs | 29 ++++ .../Migrations/201509031112324_Merge3.cs | 16 +++ .../Migrations/201509031112324_Merge3.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 7 + 4 files changed, 178 insertions(+) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.resx diff --git a/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.Designer.cs new file mode 100644 index 0000000000..b8acae3dd9 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.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 Merge3 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Merge3)); + + string IMigrationMetadata.Id + { + get { return "201509031112324_Merge3"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.cs b/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.cs new file mode 100644 index 0000000000..24ce9a2d5f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.cs @@ -0,0 +1,16 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class Merge3 : DbMigration + { + public override void Up() + { + } + + public override void Down() + { + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.resx b/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.resx new file mode 100644 index 0000000000..e84ddc5393 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509031112324_Merge3.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 129f579b6d..6a464ac05e 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -344,6 +344,10 @@ 201508142203054_CronExpressions.cs + + + 201509031112324_Merge3.cs + @@ -649,6 +653,9 @@ 201508142203054_CronExpressions.cs + + 201509031112324_Merge3.cs + From 307ceac8fc37dca16b3842a6fcc499b9b5bd09ce Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 3 Sep 2015 16:34:15 +0200 Subject: [PATCH 040/874] Product export: Option to append attribute values of an attribute combination to the product name if attribute combinations are exported as products --- .../Domain/DataExchange/ExportCore.cs | 6 +++ .../Domain/DataExchange/ExportProjection.cs | 2 + .../201509021536425_ExportFramework1.cs | 15 ++++++- .../Catalog/IProductAttributeParser.cs | 9 ++++ .../Catalog/ProductAttributeParser.cs | 42 +++++++++++++++++++ .../DataExchange/ExportProfileTask.cs | 8 ++++ .../Controllers/ExportController.cs | 7 +++- .../DataExchange/ExportProjectionModel.cs | 4 ++ .../Administration/Views/Export/Edit.cshtml | 41 +++++++++++------- .../Export/_CreateOrUpdate.Projection.cshtml | 9 ++++ 10 files changed, 124 insertions(+), 19 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs index dafefd8089..cf95283fb1 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs @@ -37,6 +37,12 @@ public enum ExportDescriptionMerging ManufacturerAndNameAndDescription } + public enum ExportAttributeValueMerging + { + None = 0, + AppendAllValuesToName + } + public enum ExportProjectionSupport { Description = 0, diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs index 8e28398d45..f0a9e9b2b9 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs @@ -52,5 +52,7 @@ public ExportProjection() public decimal? FreeShippingThreshold { get; set; } public bool AttributeCombinationAsProduct { get; set; } + + public ExportAttributeValueMerging? AttributeCombinationValueMerging { get; set; } } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs index 91f8cabeee..0c0acc5f1b 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs @@ -79,8 +79,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Legt den Namen des Ordners fest, in den die Daten exportiert werden."); builder.AddOrUpdate("Admin.Configuration.Export.FolderAndFileName.Validate", - "Please enter a valid folder and file name.", - "Bitte einen gltigen Ordner- und Dateinamen eingeben."); + "Please enter a valid folder and file name. Example for file names: %Misc.FileNumber%-%ExportProfile.Id%-gmc-%Store.Name%", + "Bitte einen gltigen Ordner- und Dateinamen eingeben. Beispiel fr Dateinamen: %Misc.FileNumber%-%ExportProfile.Id%-gmc-%Store.Name%"); builder.AddOrUpdate("Admin.Configuration.Export.Deployment.CreateZip", @@ -95,6 +95,17 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Specifies whether to export a standalone product for each active attribute combination.", "Legt fest, ob fr jede aktive Attributkombination ein eigenstndiges Produkt exportiert werden soll."); + builder.AddOrUpdate("Admin.Configuration.Export.Projection.AttributeCombinationValueMerging", + "Attribute values", + "Attributwerte", + "Specifies if and how to further process the attribute values.", + "Legt fest, ob und wie die Werte der Attribute weiter verarbeitet werden sollen."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportAttributeValueMerging.None", + "None", "Keine"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportAttributeValueMerging.AppendAllValuesToName", + "Append all values to the product name", "Alle Werte an den Produktnamen anhngen"); + builder.AddOrUpdate("Admin.Configuration.Export.Deployment.HttpTransmissionType", "HTTP transmission type", "HTTP bertragungsart", diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs index 4ac7e9b945..64f4c859cb 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs @@ -32,6 +32,15 @@ public partial interface IProductAttributeParser /// Product variant attribute values IEnumerable ParseProductVariantAttributeValues(string attributesXml); + /// + /// Get list of localized product variant attribute values + /// + /// XML formatted attributes + /// Product variant attributes + /// Language identifier + /// List of localized product variant attribute values + IList ParseProductVariantAttributeValues(string attributesXml, 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 3d454b3822..754dc54209 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs @@ -7,6 +7,7 @@ using Newtonsoft.Json; using SmartStore.Collections; using SmartStore.Core.Domain.Catalog; +using SmartStore.Services.Localization; namespace SmartStore.Services.Catalog { @@ -141,6 +142,47 @@ where id.HasValue() return values; } + public IList ParseProductVariantAttributeValues(string attributesXml, IEnumerable attributes, int languageId = 0) + { + var values = new List(); + + if (attributesXml.IsEmpty()) + 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)) + { + var pvaValuesStr = combinedAttributes[pva.Id]; + var ids = pvaValuesStr.Where(x => x.HasValue()).Select(x => x.ToInt()); + + allValueIds.AddRange(ids); + } + } + + foreach (int id in allValueIds.Distinct()) + { + foreach (var attribute in attributes) + { + var attributeValue = attribute.ProductVariantAttributeValues.FirstOrDefault(x => x.Id == id); + if (attributeValue != null) + { + var value = attributeValue.GetLocalized(x => x.Name, languageId, true, false); + + if (!values.Any(x => x.IsCaseInsensitiveEqual(value))) + values.Add(value); + break; + } + } + } + + return values; + } + + /// /// Gets selected product variant attribute value /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index 11d1d06f34..416a482db1 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -67,6 +67,7 @@ public class ExportProfileTask : ITask private IEmailSender _emailSender; private IQueuedEmailService _queuedEmailService; private IProductAttributeService _productAttributeService; + private IProductAttributeParser _productAttributeParser; private IDeliveryTimeService _deliveryTimeService; private IQuantityUnitService _quantityUnitService; private IManufacturerService _manufacturerService; @@ -93,6 +94,7 @@ private void InitDependencies(TaskExecutionContext context) _emailSender = context.Resolve(); _queuedEmailService = context.Resolve(); _productAttributeService = context.Resolve(); + _productAttributeParser = context.Resolve(); _deliveryTimeService = context.Resolve(); _quantityUnitService = context.Resolve(); _manufacturerService = context.Resolve(); @@ -746,6 +748,12 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod exp.BasePriceAmount = product.BasePriceAmount; exp.BasePriceBaseAmount = product.BasePriceBaseAmount; + if (combination != null && ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) + { + var values = _productAttributeParser.ParseProductVariantAttributeValues(combination.AttributesXml, productAttributes, languageId); + exp.Name = ((string)exp.Name).Grow(string.Join(", ", values), " "); + } + exp._BasePriceInfo = product.GetBasePriceInfo(_services.Localization, _priceFormatter, decimal.Zero, true); // navigation properties diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 000fc12706..1137b8a7b2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -214,11 +214,13 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile ShippingTime = projection.ShippingTime, ShippingCosts = projection.ShippingCosts, FreeShippingThreshold = projection.FreeShippingThreshold, - AttributeCombinationAsProduct = projection.AttributeCombinationAsProduct + AttributeCombinationAsProduct = projection.AttributeCombinationAsProduct, + AttributeCombinationValueMerging = projection.AttributeCombinationValueMerging }; model.ProductProjection.AvailableDescriptionMergings = ExportDescriptionMerging.Description.ToSelectList(false); model.ProductProjection.AvailablePriceTypes = PriceDisplayType.LowestPrice.ToSelectList(false); + model.ProductProjection.AvailableAttributeCombinationValueMerging = ExportAttributeValueMerging.AppendAllValuesToName.ToSelectList(false); model.ProductProjection.SerializedAppendDescriptionText = string.Join(",", projection.AppendDescriptionText.SplitSafe(",").Select(x => x.EncodeJsString())); model.ProductProjection.SerializedCriticalCharacters = string.Join(",", projection.CriticalCharacters.SplitSafe(",").Select(x => x.EncodeJsString())); @@ -566,7 +568,8 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) ShippingTime = model.ProductProjection.ShippingTime, ShippingCosts = model.ProductProjection.ShippingCosts, FreeShippingThreshold = model.ProductProjection.FreeShippingThreshold, - AttributeCombinationAsProduct = model.ProductProjection.AttributeCombinationAsProduct + AttributeCombinationAsProduct = model.ProductProjection.AttributeCombinationAsProduct, + AttributeCombinationValueMerging = model.ProductProjection.AttributeCombinationValueMerging }; getProjectionBase(model.ProductProjection); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs index ea83709ac7..00eba55706 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs @@ -70,5 +70,9 @@ public class ExportProductProjectionModel : ExportProjectionModelBase [SmartResourceDisplayName("Admin.Configuration.Export.Projection.AttributeCombinationAsProduct")] public bool AttributeCombinationAsProduct { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.AttributeCombinationValueMerging")] + public ExportAttributeValueMerging? AttributeCombinationValueMerging { get; set; } + public SelectList AvailableAttributeCombinationValueMerging { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 278ed9fca7..d5bba598a8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -337,14 +337,38 @@ $('#@(Html.FieldIdFor(x => x.CompletedEmailAddresses))').select2({ tags: [@(Html.Raw(Model.SerializedCompletedEmailAddresses))] }); + // show/hide store projection + $('#@(Html.FieldIdFor(x => x.PerStore))').change(function () { + $('#ProjectionStoreContainer').toggle(!$(this).is(':checked')); + }).trigger('change'); + + // show/hide completion email addresses + $('#@(Html.FieldIdFor(x => x.EmailAccountId))').change(function () { + $('#CompletedEmailAddressesContainer').toggle(!_.isEmpty($(this).val())); + }).trigger('change'); + + @if (Model.ProductProjection != null) { $('#@(Html.FieldIdFor(x => x.ProductProjection.AppendDescriptionText))').select2({ tags: [@(Html.Raw(Model.ProductProjection.SerializedAppendDescriptionText))] }); $('#@(Html.FieldIdFor(x => x.ProductProjection.CriticalCharacters))').select2({ tags: [@(Html.Raw(Model.ProductProjection.SerializedCriticalCharacters))] }); + + // show/hide attribute combination value appending + $('#@(Html.FieldIdFor(x => x.ProductProjection.AttributeCombinationAsProduct))').change(function () { + $('#AttributeCombinationAddValuesToNameContainer').toggle($(this).is(':checked')); + }).trigger('change'); + + // show/hide critical characters + $('#@(Html.FieldIdFor(x => x.ProductProjection.RemoveCriticalCharacters))').change(function () { + $('#CriticalCharactersContainer').toggle($(this).is(':checked')); + }).trigger('change'); } + @if (Model.ProductFilter != null) + { + // show/hide product category filter $('#@(Html.FieldIdFor(x => x.ProductFilter.WithoutCategories))').change(function () { $('#FilterCategoryIdsContainer').toggle(_.isEmpty($(this).val())); @@ -354,20 +378,7 @@ $('#@(Html.FieldIdFor(x => x.ProductFilter.WithoutManufacturers))').change(function () { $('#FilterManufacturerIdContainer').toggle(_.isEmpty($(this).val())); }).trigger('change'); - - // show/hide store projection - $('#@(Html.FieldIdFor(x => x.PerStore))').change(function () { - $('#ProjectionStoreContainer').toggle(!$(this).is(':checked')); - }).trigger('change'); - - // show/hide critical characters - $('#@(Html.FieldIdFor(x => x.ProductProjection.RemoveCriticalCharacters))').change(function () { - $('#CriticalCharactersContainer').toggle($(this).is(':checked')); - }).trigger('change'); - - // show/hide completion email addresses - $('#@(Html.FieldIdFor(x => x.EmailAccountId))').change(function () { - $('#CompletedEmailAddressesContainer').toggle(!_.isEmpty($(this).val())); - }).trigger('change'); + + } }); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml index d7aba58bff..911909e299 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml @@ -58,6 +58,15 @@ @Html.ValidationMessageFor(x => x.ProductProjection.AttributeCombinationAsProduct) + + + @Html.SmartLabelFor(x => x.ProductProjection.AttributeCombinationValueMerging) + + + @Html.DropDownListFor(x => x.ProductProjection.AttributeCombinationValueMerging, Model.ProductProjection.AvailableAttributeCombinationValueMerging, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.ProductProjection.AttributeCombinationValueMerging) + + } From 022541efe0727bbec6a06d22cdfc36739ab63799 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 3 Sep 2015 19:16:34 +0200 Subject: [PATCH 041/874] Minor improvements --- .../Domain/DataExchange/ExportProjection.cs | 31 +++++++++++++++++-- .../201508091512101_ExportFramework.cs | 2 +- .../201509021536425_ExportFramework1.cs | 2 +- .../DataExchange/ExportProfileTask.cs | 19 ++++++------ .../Controllers/ExportController.cs | 8 ++--- .../DataExchange/ExportProjectionModel.cs | 4 +-- .../Export/_CreateOrUpdate.Projection.cshtml | 12 +++---- 7 files changed, 52 insertions(+), 26 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs index f0a9e9b2b9..4e180f3a3e 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs @@ -1,4 +1,5 @@ using System; +using System.Xml.Serialization; using SmartStore.Core.Domain.Catalog; namespace SmartStore.Core.Domain.DataExchange @@ -27,7 +28,20 @@ public ExportProjection() public int? CustomerId { get; set; } - public ExportDescriptionMerging? DescriptionMerging { get; set; } + public int DescriptionMergingId { get; set; } + + [XmlIgnore] + public ExportDescriptionMerging DescriptionMerging + { + get + { + return (ExportDescriptionMerging)DescriptionMergingId; + } + set + { + DescriptionMergingId = (int)value; + } + } public bool DescriptionToPlainText { get; set; } @@ -53,6 +67,19 @@ public ExportProjection() public bool AttributeCombinationAsProduct { get; set; } - public ExportAttributeValueMerging? AttributeCombinationValueMerging { get; set; } + public int AttributeCombinationValueMergingId { get; set; } + + [XmlIgnore] + public ExportAttributeValueMerging AttributeCombinationValueMerging + { + get + { + return (ExportAttributeValueMerging)AttributeCombinationValueMergingId; + } + set + { + AttributeCombinationValueMergingId = (int)value; + } + } } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs index 98fb1a99a9..490a986592 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs @@ -429,7 +429,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDeploymentType.Ftp", "FTP", "FTP"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.None", - "None", "Keine"); + "Not specified", "Nicht spezifiziert"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.ShortDescriptionOrNameIfEmpty", "Short description or name if empty", "Kurzbeschreibung oder Name falls leer"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportDescriptionMerging.ShortDescription", diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs index 0c0acc5f1b..f2f654d2ec 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs @@ -102,7 +102,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Legt fest, ob und wie die Werte der Attribute weiter verarbeitet werden sollen."); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportAttributeValueMerging.None", - "None", "Keine"); + "Not specified", "Nicht spezifiziert"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportAttributeValueMerging.AppendAllValuesToName", "Append all values to the product name", "Alle Werte an den Produktnamen anhngen"); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index 416a482db1..34e4c04c5f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -152,11 +152,9 @@ private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic exp string description = ""; // description merging - if (ctx.Projection.DescriptionMerging.HasValue) + if (ctx.Projection.DescriptionMerging != ExportDescriptionMerging.None) { - var type = ctx.Projection.DescriptionMerging ?? ExportDescriptionMerging.ShortDescriptionOrNameIfEmpty; - - if (type == ExportDescriptionMerging.ShortDescriptionOrNameIfEmpty) + if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ShortDescriptionOrNameIfEmpty) { description = expando.FullDescription; @@ -165,23 +163,24 @@ private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic exp if (description.IsEmpty()) description = expando.Name; } - else if (type == ExportDescriptionMerging.ShortDescription) + else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ShortDescription) { description = expando.ShortDescription; } - else if (type == ExportDescriptionMerging.Description) + else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.Description) { description = expando.FullDescription; } - else if (type == ExportDescriptionMerging.NameAndShortDescription) + else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.NameAndShortDescription) { description = ((string)expando.Name).Grow((string)expando.ShortDescription, " "); } - else if (type == ExportDescriptionMerging.NameAndDescription) + else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.NameAndDescription) { description = ((string)expando.Name).Grow((string)expando.FullDescription, " "); } - else if (type == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription || type == ExportDescriptionMerging.ManufacturerAndNameAndDescription) + else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription || + ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ManufacturerAndNameAndDescription) { var productManus = ctx.ProductDataContext.ProductManufacturers.Load(product.Id); @@ -190,7 +189,7 @@ private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic exp description = description.Grow((string)expando.Name, " "); - if (type == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription) + if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription) description = description.Grow((string)expando.ShortDescription, " "); else description = description.Grow((string)expando.FullDescription, " "); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 1137b8a7b2..c0b93ce289 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -202,7 +202,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile LanguageId = projection.LanguageId, CurrencyId = projection.CurrencyId, CustomerId = projection.CustomerId, - DescriptionMerging = projection.DescriptionMerging, + DescriptionMergingId = projection.DescriptionMergingId, DescriptionToPlainText = projection.DescriptionToPlainText, AppendDescriptionText = projection.AppendDescriptionText, RemoveCriticalCharacters = projection.RemoveCriticalCharacters, @@ -215,7 +215,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile ShippingCosts = projection.ShippingCosts, FreeShippingThreshold = projection.FreeShippingThreshold, AttributeCombinationAsProduct = projection.AttributeCombinationAsProduct, - AttributeCombinationValueMerging = projection.AttributeCombinationValueMerging + AttributeCombinationValueMergingId = projection.AttributeCombinationValueMergingId }; model.ProductProjection.AvailableDescriptionMergings = ExportDescriptionMerging.Description.ToSelectList(false); @@ -556,7 +556,7 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) { projection = new ExportProjection { - DescriptionMerging = model.ProductProjection.DescriptionMerging, + DescriptionMergingId = model.ProductProjection.DescriptionMergingId, DescriptionToPlainText = model.ProductProjection.DescriptionToPlainText, AppendDescriptionText = model.ProductProjection.AppendDescriptionText, RemoveCriticalCharacters = model.ProductProjection.RemoveCriticalCharacters, @@ -569,7 +569,7 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) ShippingCosts = model.ProductProjection.ShippingCosts, FreeShippingThreshold = model.ProductProjection.FreeShippingThreshold, AttributeCombinationAsProduct = model.ProductProjection.AttributeCombinationAsProduct, - AttributeCombinationValueMerging = model.ProductProjection.AttributeCombinationValueMerging + AttributeCombinationValueMergingId = model.ProductProjection.AttributeCombinationValueMergingId }; getProjectionBase(model.ProductProjection); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs index 00eba55706..ab9d47c9f9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs @@ -28,7 +28,7 @@ public abstract class ExportProjectionModelBase public class ExportProductProjectionModel : ExportProjectionModelBase { [SmartResourceDisplayName("Admin.Configuration.Export.Projection.DescriptionMerging")] - public ExportDescriptionMerging? DescriptionMerging { get; set; } + public int DescriptionMergingId { get; set; } public SelectList AvailableDescriptionMergings { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Projection.DescriptionToPlainText")] @@ -72,7 +72,7 @@ public class ExportProductProjectionModel : ExportProjectionModelBase public bool AttributeCombinationAsProduct { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.Projection.AttributeCombinationValueMerging")] - public ExportAttributeValueMerging? AttributeCombinationValueMerging { get; set; } + public int AttributeCombinationValueMergingId { get; set; } public SelectList AvailableAttributeCombinationValueMerging { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml index 911909e299..70f1b69988 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml @@ -60,11 +60,11 @@ - @Html.SmartLabelFor(x => x.ProductProjection.AttributeCombinationValueMerging) + @Html.SmartLabelFor(x => x.ProductProjection.AttributeCombinationValueMergingId) - @Html.DropDownListFor(x => x.ProductProjection.AttributeCombinationValueMerging, Model.ProductProjection.AvailableAttributeCombinationValueMerging, Model.UnspecifiedString) - @Html.ValidationMessageFor(x => x.ProductProjection.AttributeCombinationValueMerging) + @Html.DropDownListFor(x => x.ProductProjection.AttributeCombinationValueMergingId, Model.ProductProjection.AvailableAttributeCombinationValueMerging) + @Html.ValidationMessageFor(x => x.ProductProjection.AttributeCombinationValueMergingId) } @@ -160,11 +160,11 @@ - @Html.SmartLabelFor(x => x.ProductProjection.DescriptionMerging) + @Html.SmartLabelFor(x => x.ProductProjection.DescriptionMergingId) - @Html.DropDownListFor(x => x.ProductProjection.DescriptionMerging, Model.ProductProjection.AvailableDescriptionMergings, Model.UnspecifiedString) - @Html.ValidationMessageFor(x => x.ProductProjection.DescriptionMerging) + @Html.DropDownListFor(x => x.ProductProjection.DescriptionMergingId, Model.ProductProjection.AvailableDescriptionMergings) + @Html.ValidationMessageFor(x => x.ProductProjection.DescriptionMergingId) From 188321238d7b9f023bdd2a6d4993aa9d1676818f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 4 Sep 2015 16:54:55 +0200 Subject: [PATCH 042/874] Option to change the order status for exported orders --- .../Domain/DataExchange/ExportCore.cs | 7 + .../Domain/DataExchange/ExportProjection.cs | 27 ++ .../201509021536425_ExportFramework1.cs | 10 + .../DataExchange/ExportProfileTask.cs | 156 ++++++---- .../DataExchange/ExportSegmenter.cs | 16 +- .../Controllers/ExportController.cs | 273 ++++++++---------- .../Models/DataExchange/ExportFilterModel.cs | 21 +- .../Models/DataExchange/ExportProfileModel.cs | 5 +- .../DataExchange/ExportProjectionModel.cs | 21 +- .../Administration/Views/Export/Edit.cshtml | 14 +- .../Export/_CreateOrUpdate.Projection.cshtml | 122 ++++---- 11 files changed, 380 insertions(+), 292 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs index cf95283fb1..55f38cd94d 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs @@ -54,4 +54,11 @@ public enum ExportProjectionSupport AttributeCombinationAsProduct, OldPrice } + + public enum ExportOrderStatusChange + { + None = 0, + Processing, + Complete + } } diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs index 4e180f3a3e..77b04fffae 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs @@ -14,6 +14,8 @@ public ExportProjection() PriceType = PriceDisplayType.PreSelectedPrice; } + #region All entity types + public int? StoreId { get; set; } /// @@ -28,6 +30,10 @@ public ExportProjection() public int? CustomerId { get; set; } + #endregion + + #region Product + public int DescriptionMergingId { get; set; } [XmlIgnore] @@ -81,5 +87,26 @@ public ExportAttributeValueMerging AttributeCombinationValueMerging AttributeCombinationValueMergingId = (int)value; } } + + #endregion + + #region Order + + public int OrderStatusChangeId { get; set; } + + [XmlIgnore] + public ExportOrderStatusChange OrderStatusChange + { + get + { + return (ExportOrderStatusChange)OrderStatusChangeId; + } + set + { + OrderStatusChangeId = (int)value; + } + } + + #endregion } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs index f2f654d2ec..9e6f73a2b9 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs @@ -147,6 +147,16 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Export.Deployment.Note", "Click Insert to add one or multiple publishing profiles to specify how to further proceed with the export files.", "Legen Sie ber Hinzufgen ein oder mehrere Verffentlichungsprofile an, um festzulegen wie mit den Exportdateien weiter zu verfahren ist."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportOrderStatusChange.None", "None", "Keine"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportOrderStatusChange.Processing", "Processing", "Wird bearbeitet"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportOrderStatusChange.Complete", "Complete", "Komplett"); + + builder.AddOrUpdate("Admin.Configuration.Export.Projection.OrderStatusChange", + "Change order status to", + "Auftragsstatus ndern in", + "Specifies if and how to change the status of the exported orders.", + "Legt fest, ob und wie der Status der exportierten Auftrge gendert werden soll."); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index 34e4c04c5f..c67653bb36 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -74,6 +74,7 @@ public class ExportProfileTask : ITask private IOrderService _orderService; private IAddressService _addressesService; private ICountryService _countryService; + private IRepository _orderRepository; private void InitDependencies(TaskExecutionContext context) { @@ -101,6 +102,7 @@ private void InitDependencies(TaskExecutionContext context) _orderService = context.Resolve(); _addressesService = context.Resolve(); _countryService = context.Resolve(); + _orderRepository = context.Resolve>(); } #endregion @@ -114,7 +116,7 @@ private ProductSearchContext GetProductSearchContext(ExportProfileTaskContext ct OrderBy = ProductSortingEnum.CreatedOn, PageIndex = pageIndex, PageSize = pageSize, - ProductIds = ctx.SelectedEntityIds, + ProductIds = ctx.EntityIdsSelected, StoreId = (ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId), VisibleIndividuallyOnly = true, PriceMin = ctx.Filter.PriceMinimum, @@ -144,6 +146,30 @@ private ProductSearchContext GetProductSearchContext(ExportProfileTaskContext ct return searchContext; } + private int GetTotalRecords(ExportProfileTaskContext ctx) + { + int result = 0; + + if (ctx.TotalRecords.HasValue) + { + return ctx.TotalRecords.Value; + } + else if (ctx.Provider.Value.EntityType == ExportEntityType.Product) + { + var searchContext = GetProductSearchContext(ctx, ctx.Profile.Offset, 1); + + var anySingleProduct = _productService.SearchProducts(searchContext); + + result = anySingleProduct.TotalCount; + } + else if (ctx.Provider.Value.EntityType == ExportEntityType.Order) + { + var unused = GetOrders(ctx, 0, 1, out result); + } + + return result; + } + private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic expando, Product product) { try @@ -507,29 +533,7 @@ private async void DeployFtp(ExportProfileTaskContext ctx, ExportDeployment depl #endregion - private int GetTotalRecords(ExportProfileTaskContext ctx) - { - int result = 0; - - if (ctx.TotalRecords.HasValue) - { - return ctx.TotalRecords.Value; - } - else if (ctx.Provider.Value.EntityType == ExportEntityType.Product) - { - var searchContext = GetProductSearchContext(ctx, ctx.Profile.Offset, 1); - - var anySingleProduct = _productService.SearchProducts(searchContext); - - result = anySingleProduct.TotalCount; - } - else if (ctx.Provider.Value.EntityType == ExportEntityType.Order) - { - var unused = GetOrders(ctx, 0, 1, out result); - } - - return result; - } + #region Segmenter callbacks private List GetProducts(ExportProfileTaskContext ctx, int pageIndex) { @@ -599,11 +603,19 @@ private List GetOrders(ExportProfileTaskContext ctx, int pageIndex, int p pageIndex, pageSize, null, - ctx.SelectedEntityIds + ctx.EntityIdsSelected ); totalCount = pagedList.TotalCount; + if (ctx.Projection.OrderStatusChange != ExportOrderStatusChange.None) + { + ctx.EntityIdsLoaded = ctx.EntityIdsLoaded + .Union(pagedList.Select(x => x.Id)) + .Distinct() + .ToList(); + } + var result = pagedList as List; if (pageSize > 1) @@ -842,7 +854,7 @@ private List ToExpando(ExportProfileTaskContext ctx, Order order, expando.Customer = customers .FirstOrDefault(x => x.Id == order.CustomerId) .ToExpando(); - + expando.BillingAddress = addresses .FirstOrDefault(x => x.Id == order.BillingAddressId) .ToExpando(languageId); @@ -858,7 +870,7 @@ private List ToExpando(ExportProfileTaskContext ctx, Order order, expando.Store = null; expando.OrderItems = orderItems - .Select(e => + .Select(e => { dynamic exp = e.ToExpando(languageId); @@ -883,6 +895,36 @@ private List ToExpando(ExportProfileTaskContext ctx, Order order, return result; } + #endregion + + private List Init(ExportProfileTaskContext ctx) + { + if (ctx.Projection.CurrencyId.HasValue) + ctx.ProjectionCurrency = _currencyService.GetCurrencyById(ctx.Projection.CurrencyId.Value); + else + ctx.ProjectionCurrency = _services.WorkContext.WorkingCurrency; + + if (ctx.Projection.CustomerId.HasValue) + ctx.ProjectionCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); + else + ctx.ProjectionCustomer = _services.WorkContext.CurrentCustomer; + + ctx.Stores = _services.StoreService.GetAllStores().ToDictionary(x => x.Id, x => x); + + if (!ctx.IsPreview && ctx.Profile.PerStore) + { + return new List(ctx.Stores.Values.Where(x => x.Id == ctx.Filter.StoreId || ctx.Filter.StoreId == 0)); + } + else + { + int? storeId = (ctx.Filter.StoreId == 0 ? ctx.Projection.StoreId : ctx.Filter.StoreId); + + ctx.Store = ctx.Stores.Values.FirstOrDefault(x => x.Id == (storeId ?? _services.StoreContext.CurrentStore.Id)); + + return new List { ctx.Store }; + } + } + private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) { ctx.Store = store; @@ -1093,12 +1135,15 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) try { + (ctx.Export.Data as IExportExecuter).Dispose(); + ctx.Countries.Clear(); ctx.Stores.Clear(); ctx.QuantityUnits.Clear(); ctx.DeliveryTimes.Clear(); ctx.CategoryPathes.Clear(); ctx.Categories.Clear(); + ctx.EntityIdsSelected.Clear(); ctx.ProductDataContext = null; ctx.OrderDataContext = null; @@ -1109,33 +1154,40 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) catch { } } } - } - private List Init(ExportProfileTaskContext ctx) - { - if (ctx.Projection.CurrencyId.HasValue) - ctx.ProjectionCurrency = _currencyService.GetCurrencyById(ctx.Projection.CurrencyId.Value); - else - ctx.ProjectionCurrency = _services.WorkContext.WorkingCurrency; + // post process order entities + if (!ctx.IsPreview && ctx.EntityIdsLoaded.Count > 0 && ctx.Projection.OrderStatusChange != ExportOrderStatusChange.None) + { + using (var logger = new TraceLogger(ctx.LogPath)) + { + try + { + int? orderStatusId = null; - if (ctx.Projection.CustomerId.HasValue) - ctx.ProjectionCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); - else - ctx.ProjectionCustomer = _services.WorkContext.CurrentCustomer; + if (ctx.Projection.OrderStatusChange == ExportOrderStatusChange.Processing) + orderStatusId = (int)OrderStatus.Processing; + else if (ctx.Projection.OrderStatusChange == ExportOrderStatusChange.Complete) + orderStatusId = (int)OrderStatus.Complete; - ctx.Stores = _services.StoreService.GetAllStores().ToDictionary(x => x.Id, x => x); + using (var scope = new DbContextScope(_services.DbContext, false, null, false, false, false, false)) + { + foreach (var chunk in ctx.EntityIdsLoaded.Chunk()) + { + var entities = _orderRepository.Table.Where(x => chunk.Contains(x.Id)).ToList(); - if (!ctx.IsPreview && ctx.Profile.PerStore) - { - return new List(ctx.Stores.Values.Where(x => x.Id == ctx.Filter.StoreId || ctx.Filter.StoreId == 0)); - } - else - { - int? storeId = (ctx.Filter.StoreId == 0 ? ctx.Projection.StoreId : ctx.Filter.StoreId); + entities.ForEach(x => x.OrderStatusId = (orderStatusId ?? x.OrderStatusId)); - ctx.Store = ctx.Stores.Values.FirstOrDefault(x => x.Id == (storeId ?? _services.StoreContext.CurrentStore.Id)); + _services.DbContext.SaveChanges(); + } + } - return new List { ctx.Store }; + logger.Information("Updated order status for {0} order(s).".FormatInvariant(ctx.EntityIdsLoaded.Count())); + } + catch (Exception exc) + { + logger.Error(exc); + } + } } } @@ -1219,7 +1271,7 @@ public ExportProfileTaskContext(ExportProfile profile, Filter = XmlHelper.Deserialize(profile.Filtering); Projection = XmlHelper.Deserialize(profile.Projection); Cancellation = cancellation; - SelectedEntityIds = selectedIds.SplitSafe(",").Select(x => x.ToInt()).ToList(); + EntityIdsSelected = selectedIds.SplitSafe(",").Select(x => x.ToInt()).ToList(); PageIndex = pageIndex; PageSize = pageSize; TotalRecords = totalRecords; @@ -1233,9 +1285,13 @@ public ExportProfileTaskContext(ExportProfile profile, Countries = new Dictionary(); Export = new ExportExecuteContext(Cancellation, FolderContent); + + EntityIdsLoaded = new List(); } - public List SelectedEntityIds { get; private set; } + public List EntityIdsSelected { get; private set; } + public List EntityIdsLoaded { get; set; } + public int PageIndex { get; private set; } public int PageSize { get; private set; } public int? TotalRecords { get; set; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs index e91a60e2a3..2d6b3cd857 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs @@ -26,6 +26,7 @@ public interface IExportSegmenter internal interface IExportExecuter { void Start(Func callback); + void Dispose(); } @@ -33,6 +34,7 @@ public class ExportSegmenter : IExportSegmenter, IExportExecuter, IDisposable { private Func> _loadData; private Func, List> _convertData; + private List _currentData; private List _currentSegment; private IPageable _pageable; private int _pageIndexReset; @@ -68,6 +70,12 @@ protected void ClearSegment() _currentSegment.Clear(); _currentSegment = null; } + + if (_currentData != null) + { + _currentData.Clear(); + _currentData = null; + } } public void Dispose() @@ -150,7 +158,7 @@ public bool ReadNextSegment() return false; } - if (_fileRecordsSkip > 0) + if (_fileRecordsSkip > 0) // read rest of last segment for new file { return true; } @@ -174,9 +182,9 @@ public ICollection CurrentSegment _currentSegment = new List(); var tmpCount = 0; - var data = _loadData(_pageable.PageIndex); + _currentData = _loadData(_pageable.PageIndex); - foreach (var record in data.Skip(_fileRecordsSkip)) + foreach (var record in _currentData.Skip(_fileRecordsSkip)) { if (_recordsPerFile > 0 && _recordsPerFileCount >= _recordsPerFile) { @@ -188,7 +196,7 @@ public ICollection CurrentSegment return _currentSegment.AsReadOnly(); } - foreach (var obj in _convertData(record, data)) + foreach (var obj in _convertData(record, _currentData)) { _currentSegment.Add(obj); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index c0b93ce289..7df243acf8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -179,67 +179,87 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile } // projection - Action initProjectionBase = x => + model.Projection = new ExportProjectionModel { - x.AvailableStores = allStores - .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) - .ToList(); + StoreId = projection.StoreId, + LanguageId = projection.LanguageId, + CurrencyId = projection.CurrencyId, + CustomerId = projection.CustomerId, + DescriptionMergingId = projection.DescriptionMergingId, + DescriptionToPlainText = projection.DescriptionToPlainText, + AppendDescriptionText = projection.AppendDescriptionText, + RemoveCriticalCharacters = projection.RemoveCriticalCharacters, + CriticalCharacters = projection.CriticalCharacters, + PriceType = projection.PriceType, + ConvertNetToGrossPrices = projection.ConvertNetToGrossPrices, + Brand = projection.Brand, + PictureSize = projection.PictureSize, + ShippingTime = projection.ShippingTime, + ShippingCosts = projection.ShippingCosts, + FreeShippingThreshold = projection.FreeShippingThreshold, + AttributeCombinationAsProduct = projection.AttributeCombinationAsProduct, + AttributeCombinationValueMergingId = projection.AttributeCombinationValueMergingId, + OrderStatusChangeId = projection.OrderStatusChangeId + }; - x.AvailableLanguages = allLanguages - .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) - .ToList(); + model.Projection.AvailableStores = allStores + .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) + .ToList(); - x.AvailableCurrencies = allCurrencies - .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) - .ToList(); - }; + model.Projection.AvailableLanguages = allLanguages + .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) + .ToList(); - if (model.Provider.EntityType == ExportEntityType.Product) - { - model.ProductProjection = new ExportProductProjectionModel - { - StoreId = projection.StoreId, - LanguageId = projection.LanguageId, - CurrencyId = projection.CurrencyId, - CustomerId = projection.CustomerId, - DescriptionMergingId = projection.DescriptionMergingId, - DescriptionToPlainText = projection.DescriptionToPlainText, - AppendDescriptionText = projection.AppendDescriptionText, - RemoveCriticalCharacters = projection.RemoveCriticalCharacters, - CriticalCharacters = projection.CriticalCharacters, - PriceType = projection.PriceType, - ConvertNetToGrossPrices = projection.ConvertNetToGrossPrices, - Brand = projection.Brand, - PictureSize = projection.PictureSize, - ShippingTime = projection.ShippingTime, - ShippingCosts = projection.ShippingCosts, - FreeShippingThreshold = projection.FreeShippingThreshold, - AttributeCombinationAsProduct = projection.AttributeCombinationAsProduct, - AttributeCombinationValueMergingId = projection.AttributeCombinationValueMergingId - }; + model.Projection.AvailableCurrencies = allCurrencies + .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) + .ToList(); - model.ProductProjection.AvailableDescriptionMergings = ExportDescriptionMerging.Description.ToSelectList(false); - model.ProductProjection.AvailablePriceTypes = PriceDisplayType.LowestPrice.ToSelectList(false); - model.ProductProjection.AvailableAttributeCombinationValueMerging = ExportAttributeValueMerging.AppendAllValuesToName.ToSelectList(false); - model.ProductProjection.SerializedAppendDescriptionText = string.Join(",", projection.AppendDescriptionText.SplitSafe(",").Select(x => x.EncodeJsString())); - model.ProductProjection.SerializedCriticalCharacters = string.Join(",", projection.CriticalCharacters.SplitSafe(",").Select(x => x.EncodeJsString())); + if (model.Provider.EntityType == ExportEntityType.Product) + { + model.Projection.AvailableDescriptionMergings = ExportDescriptionMerging.Description.ToSelectList(false); + model.Projection.AvailablePriceTypes = PriceDisplayType.LowestPrice.ToSelectList(false); + model.Projection.AvailableAttributeCombinationValueMerging = ExportAttributeValueMerging.AppendAllValuesToName.ToSelectList(false); - initProjectionBase(model.ProductProjection); + model.Projection.SerializedAppendDescriptionText = string.Join(",", projection.AppendDescriptionText.SplitSafe(",").Select(x => x.EncodeJsString())); + model.Projection.SerializedCriticalCharacters = string.Join(",", projection.CriticalCharacters.SplitSafe(",").Select(x => x.EncodeJsString())); + } + else if (model.Provider.EntityType == ExportEntityType.Order) + { + model.Projection.AvailableOrderStatusChange = ExportOrderStatusChange.Processing.ToSelectList(false); } // filtering - Action initFilterBase = x => + model.Filter = new ExportFilterModel { - x.StoreId = filter.StoreId; - x.CreatedFrom = filter.CreatedFrom; - x.CreatedTo = filter.CreatedTo; - - x.AvailableStores = allStores - .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) - .ToList(); + StoreId = filter.StoreId, + CreatedFrom = filter.CreatedFrom, + CreatedTo = filter.CreatedTo, + PriceMinimum = filter.PriceMinimum, + PriceMaximum = filter.PriceMaximum, + AvailabilityMinimum = filter.AvailabilityMinimum, + AvailabilityMaximum = filter.AvailabilityMaximum, + IsPublished = filter.IsPublished, + CategoryIds = filter.CategoryIds, + WithoutCategories = filter.WithoutCategories, + ManufacturerId = filter.ManufacturerId, + WithoutManufacturers = filter.WithoutManufacturers, + ProductTagId = filter.ProductTagId, + FeaturedProducts = filter.FeaturedProducts, + ProductType = filter.ProductType, + IdMinimum = filter.IdMinimum, + IdMaximum = filter.IdMaximum, + OrderStatusIds = filter.OrderStatusIds, + PaymentStatusIds = filter.PaymentStatusIds, + ShippingStatusIds = filter.ShippingStatusIds, + CustomerRoleIds = filter.CustomerRoleIds }; + model.Filter.AvailableStores = allStores + .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) + .ToList(); + + if (model.Provider.EntityType == ExportEntityType.Product) { var allCategories = _categoryService.GetAllCategories(showHidden: true); @@ -247,62 +267,32 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile var allManufacturers = _manufacturerService.GetAllManufacturers(true); var allProductTags = _productTagService.GetAllProductTags(); - model.ProductFilter = new ExportProductFilterModel - { - PriceMinimum = filter.PriceMinimum, - PriceMaximum = filter.PriceMaximum, - AvailabilityMinimum = filter.AvailabilityMinimum, - AvailabilityMaximum = filter.AvailabilityMaximum, - IsPublished = filter.IsPublished, - CategoryIds = filter.CategoryIds, - WithoutCategories = filter.WithoutCategories, - ManufacturerId = filter.ManufacturerId, - WithoutManufacturers = filter.WithoutManufacturers, - ProductTagId = filter.ProductTagId, - FeaturedProducts = filter.FeaturedProducts, - ProductType = filter.ProductType, - IdMinimum = filter.IdMinimum, - IdMaximum = filter.IdMaximum - }; + model.Filter.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); - model.ProductFilter.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); - - model.ProductFilter.AvailableCategories = allCategories + model.Filter.AvailableCategories = allCategories .Select(x => new SelectListItem { Text = x.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = x.Id.ToString() }) .ToList(); - model.ProductFilter.AvailableManufacturers = allManufacturers + model.Filter.AvailableManufacturers = allManufacturers .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) .ToList(); - model.ProductFilter.AvailableProductTags = allProductTags + model.Filter.AvailableProductTags = allProductTags .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) .ToList(); - - initFilterBase(model.ProductFilter); } else if (model.Provider.EntityType == ExportEntityType.Order) { var allCustomerRoles = _customerService.GetAllCustomerRoles(true); - model.OrderFilter = new ExportOrderFilterModel - { - OrderStatusIds = filter.OrderStatusIds, - PaymentStatusIds = filter.PaymentStatusIds, - ShippingStatusIds = filter.ShippingStatusIds, - CustomerRoleIds = filter.CustomerRoleIds - }; - - model.OrderFilter.AvailableOrderStates = OrderStatus.Pending.ToSelectList(false).ToList(); - model.OrderFilter.AvailablePaymentStates = PaymentStatus.Pending.ToSelectList(false).ToList(); - model.OrderFilter.AvailableShippingStates = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); + model.Filter.AvailableOrderStates = OrderStatus.Pending.ToSelectList(false).ToList(); + model.Filter.AvailablePaymentStates = PaymentStatus.Pending.ToSelectList(false).ToList(); + model.Filter.AvailableShippingStates = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); - model.OrderFilter.AvailableCustomerRoles = allCustomerRoles + model.Filter.AvailableCustomerRoles = allCustomerRoles .OrderBy(x => x.Name) .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) .ToList(); - - initFilterBase(model.OrderFilter); } } @@ -543,84 +533,57 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) profile.Name = provider.Metadata.SystemName; // projection - ExportProjection projection = null; - Action getProjectionBase = x => + var projection = new ExportProjection { - projection.StoreId = x.StoreId; - projection.LanguageId = x.LanguageId; - projection.CurrencyId = x.CurrencyId; - projection.CustomerId = x.CustomerId; + StoreId = model.Projection.StoreId, + LanguageId = model.Projection.LanguageId, + CurrencyId = model.Projection.CurrencyId, + CustomerId = model.Projection.CustomerId, + DescriptionMergingId = model.Projection.DescriptionMergingId, + DescriptionToPlainText = model.Projection.DescriptionToPlainText, + AppendDescriptionText = model.Projection.AppendDescriptionText, + RemoveCriticalCharacters = model.Projection.RemoveCriticalCharacters, + CriticalCharacters = model.Projection.CriticalCharacters, + PriceType = model.Projection.PriceType, + ConvertNetToGrossPrices = model.Projection.ConvertNetToGrossPrices, + Brand = model.Projection.Brand, + PictureSize = model.Projection.PictureSize, + ShippingTime = model.Projection.ShippingTime, + ShippingCosts = model.Projection.ShippingCosts, + FreeShippingThreshold = model.Projection.FreeShippingThreshold, + AttributeCombinationAsProduct = model.Projection.AttributeCombinationAsProduct, + AttributeCombinationValueMergingId = model.Projection.AttributeCombinationValueMergingId, + OrderStatusChangeId = model.Projection.OrderStatusChangeId }; - if (model.ProductProjection != null && provider.Value.EntityType == ExportEntityType.Product) - { - projection = new ExportProjection - { - DescriptionMergingId = model.ProductProjection.DescriptionMergingId, - DescriptionToPlainText = model.ProductProjection.DescriptionToPlainText, - AppendDescriptionText = model.ProductProjection.AppendDescriptionText, - RemoveCriticalCharacters = model.ProductProjection.RemoveCriticalCharacters, - CriticalCharacters = model.ProductProjection.CriticalCharacters, - PriceType = model.ProductProjection.PriceType, - ConvertNetToGrossPrices = model.ProductProjection.ConvertNetToGrossPrices, - Brand = model.ProductProjection.Brand, - PictureSize = model.ProductProjection.PictureSize, - ShippingTime = model.ProductProjection.ShippingTime, - ShippingCosts = model.ProductProjection.ShippingCosts, - FreeShippingThreshold = model.ProductProjection.FreeShippingThreshold, - AttributeCombinationAsProduct = model.ProductProjection.AttributeCombinationAsProduct, - AttributeCombinationValueMergingId = model.ProductProjection.AttributeCombinationValueMergingId - }; - - getProjectionBase(model.ProductProjection); - } - profile.Projection = XmlHelper.Serialize(projection); // filtering - ExportFilter filter = null; - Action getFilterBase = x => + var filter = new ExportFilter { - filter.StoreId = x.StoreId ?? 0; - filter.CreatedFrom = x.CreatedFrom; - filter.CreatedTo = x.CreatedTo; + StoreId = model.Filter.StoreId ?? 0, + CreatedFrom = model.Filter.CreatedFrom, + CreatedTo = model.Filter.CreatedTo, + PriceMinimum = model.Filter.PriceMinimum, + PriceMaximum = model.Filter.PriceMaximum, + AvailabilityMinimum = model.Filter.AvailabilityMinimum, + AvailabilityMaximum = model.Filter.AvailabilityMaximum, + IsPublished = model.Filter.IsPublished, + CategoryIds = model.Filter.CategoryIds, + WithoutCategories = model.Filter.WithoutCategories, + ManufacturerId = model.Filter.ManufacturerId, + WithoutManufacturers = model.Filter.WithoutManufacturers, + ProductTagId = model.Filter.ProductTagId, + FeaturedProducts = model.Filter.FeaturedProducts, + ProductType = model.Filter.ProductType, + IdMinimum = model.Filter.IdMinimum, + IdMaximum = model.Filter.IdMaximum, + OrderStatusIds = model.Filter.OrderStatusIds, + PaymentStatusIds = model.Filter.PaymentStatusIds, + ShippingStatusIds = model.Filter.ShippingStatusIds, + CustomerRoleIds = model.Filter.CustomerRoleIds }; - if (model.ProductFilter != null && provider.Value.EntityType == ExportEntityType.Product) - { - filter = new ExportFilter - { - PriceMinimum = model.ProductFilter.PriceMinimum, - PriceMaximum = model.ProductFilter.PriceMaximum, - AvailabilityMinimum = model.ProductFilter.AvailabilityMinimum, - AvailabilityMaximum = model.ProductFilter.AvailabilityMaximum, - IsPublished = model.ProductFilter.IsPublished, - CategoryIds = model.ProductFilter.CategoryIds, - WithoutCategories = model.ProductFilter.WithoutCategories, - ManufacturerId = model.ProductFilter.ManufacturerId, - WithoutManufacturers = model.ProductFilter.WithoutManufacturers, - ProductTagId = model.ProductFilter.ProductTagId, - FeaturedProducts = model.ProductFilter.FeaturedProducts, - ProductType = model.ProductFilter.ProductType, - IdMinimum = model.ProductFilter.IdMinimum, - IdMaximum = model.ProductFilter.IdMaximum - }; - - getFilterBase(model.ProductFilter); - } - else if (model.OrderFilter != null && provider.Value.EntityType == ExportEntityType.Order) - { - filter = new ExportFilter - { - OrderStatusIds = model.OrderFilter.OrderStatusIds, - PaymentStatusIds = model.OrderFilter.PaymentStatusIds, - ShippingStatusIds = model.OrderFilter.ShippingStatusIds, - CustomerRoleIds = model.OrderFilter.CustomerRoleIds - }; - - getFilterBase(model.OrderFilter); - } - profile.Filtering = XmlHelper.Serialize(filter); // provider configuration diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs index 6a92a4b5ff..a9a2faf821 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportFilterModel.cs @@ -2,15 +2,14 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Domain.Payments; -using SmartStore.Core.Domain.Shipping; using SmartStore.Web.Framework; namespace SmartStore.Admin.Models.DataExchange { - public abstract class ExportFilterModelBase + public class ExportFilterModel { + #region All entity types + [SmartResourceDisplayName("Admin.Configuration.Export.Filter.StoreId")] public int? StoreId { get; set; } public List AvailableStores { get; set; } @@ -20,11 +19,11 @@ public abstract class ExportFilterModelBase [SmartResourceDisplayName("Admin.Configuration.Export.Filter.CreatedTo")] public DateTime? CreatedTo { get; set; } - } + #endregion + + #region Product - public class ExportProductFilterModel : ExportFilterModelBase - { [SmartResourceDisplayName("Admin.Configuration.Export.Filter.IsPublished")] public bool? IsPublished { get; set; } @@ -70,11 +69,11 @@ public class ExportProductFilterModel : ExportFilterModelBase [SmartResourceDisplayName("Admin.Configuration.Export.Filter.FeaturedProducts")] public bool? FeaturedProducts { get; set; } - } + #endregion + + #region Order - public class ExportOrderFilterModel : ExportFilterModelBase - { [SmartResourceDisplayName("Admin.Configuration.Export.Filter.OrderStatusIds")] public int[] OrderStatusIds { get; set; } public List AvailableOrderStates { get; set; } @@ -90,5 +89,7 @@ public class ExportOrderFilterModel : ExportFilterModelBase [SmartResourceDisplayName("Admin.Configuration.Export.Filter.CustomerRoleIds")] public int[] CustomerRoleIds { get; set; } public List AvailableCustomerRoles { get; set; } + + #endregion } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 093641c7c2..2b007f0312 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -64,10 +64,9 @@ public partial class ExportProfileModel : EntityModelBase public ProviderModel Provider { get; set; } - public ExportProductFilterModel ProductFilter { get; set; } - public ExportOrderFilterModel OrderFilter { get; set; } + public ExportFilterModel Filter { get; set; } - public ExportProductProjectionModel ProductProjection { get; set; } + public ExportProjectionModel Projection { get; set; } public List Deployments { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs index ab9d47c9f9..4c27c98eb1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs @@ -1,13 +1,14 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.DataExchange; using SmartStore.Web.Framework; namespace SmartStore.Admin.Models.DataExchange { - public abstract class ExportProjectionModelBase + public class ExportProjectionModel { + #region All entity types + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.StoreId")] public int? StoreId { get; set; } public List AvailableStores { get; set; } @@ -22,11 +23,11 @@ public abstract class ExportProjectionModelBase [SmartResourceDisplayName("Admin.Configuration.Export.Projection.CustomerId")] public int? CustomerId { get; set; } - } + #endregion + + #region Product - public class ExportProductProjectionModel : ExportProjectionModelBase - { [SmartResourceDisplayName("Admin.Configuration.Export.Projection.DescriptionMerging")] public int DescriptionMergingId { get; set; } public SelectList AvailableDescriptionMergings { get; set; } @@ -74,5 +75,15 @@ public class ExportProductProjectionModel : ExportProjectionModelBase [SmartResourceDisplayName("Admin.Configuration.Export.Projection.AttributeCombinationValueMerging")] public int AttributeCombinationValueMergingId { get; set; } public SelectList AvailableAttributeCombinationValueMerging { get; set; } + + #endregion + + #region Order + + [SmartResourceDisplayName("Admin.Configuration.Export.Projection.OrderStatusChange")] + public int OrderStatusChangeId { get; set; } + public SelectList AvailableOrderStatusChange { get; set; } + + #endregion } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index d5bba598a8..2a452b2915 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -347,24 +347,18 @@ $('#CompletedEmailAddressesContainer').toggle(!_.isEmpty($(this).val())); }).trigger('change'); - - @if (Model.ProductProjection != null) - { - - $('#@(Html.FieldIdFor(x => x.ProductProjection.AppendDescriptionText))').select2({ tags: [@(Html.Raw(Model.ProductProjection.SerializedAppendDescriptionText))] }); - $('#@(Html.FieldIdFor(x => x.ProductProjection.CriticalCharacters))').select2({ tags: [@(Html.Raw(Model.ProductProjection.SerializedCriticalCharacters))] }); + $('#@(Html.FieldIdFor(x => x.Projection.AppendDescriptionText))').select2({ tags: [@(Html.Raw(Model.Projection.SerializedAppendDescriptionText))] }); + $('#@(Html.FieldIdFor(x => x.Projection.CriticalCharacters))').select2({ tags: [@(Html.Raw(Model.Projection.SerializedCriticalCharacters))] }); // show/hide attribute combination value appending - $('#@(Html.FieldIdFor(x => x.ProductProjection.AttributeCombinationAsProduct))').change(function () { + $('#@(Html.FieldIdFor(x => x.Projection.AttributeCombinationAsProduct))').change(function () { $('#AttributeCombinationAddValuesToNameContainer').toggle($(this).is(':checked')); }).trigger('change'); // show/hide critical characters - $('#@(Html.FieldIdFor(x => x.ProductProjection.RemoveCriticalCharacters))').change(function () { + $('#@(Html.FieldIdFor(x => x.Projection.RemoveCriticalCharacters))').change(function () { $('#CriticalCharactersContainer').toggle($(this).is(':checked')); }).trigger('change'); - - } @if (Model.ProductFilter != null) { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml index 70f1b69988..b8052f5a79 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml @@ -8,42 +8,42 @@ - @if (Model.ProductProjection != null) + @if (Model.Provider.EntityType == ExportEntityType.Product) { @@ -51,40 +51,40 @@ { } @@ -92,11 +92,11 @@ { } @@ -105,20 +105,20 @@ { } @@ -127,11 +127,11 @@ { } @@ -140,11 +140,11 @@ { } @@ -160,49 +160,61 @@ } } + else if (Model.Provider.EntityType == ExportEntityType.Order) + { + + + + + }
- @Html.SmartLabelFor(x => x.ProductProjection.StoreId) + @Html.SmartLabelFor(x => x.Projection.StoreId) - @Html.DropDownListFor(x => x.ProductProjection.StoreId, Model.ProductProjection.AvailableStores, Model.UnspecifiedString) - @Html.ValidationMessageFor(x => x.ProductProjection.StoreId) + @Html.DropDownListFor(x => x.Projection.StoreId, Model.Projection.AvailableStores, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.Projection.StoreId)
- @Html.SmartLabelFor(x => x.ProductProjection.LanguageId) + @Html.SmartLabelFor(x => x.Projection.LanguageId) - @Html.DropDownListFor(x => x.ProductProjection.LanguageId, Model.ProductProjection.AvailableLanguages, Model.UnspecifiedString) - @Html.ValidationMessageFor(x => x.ProductProjection.LanguageId) + @Html.DropDownListFor(x => x.Projection.LanguageId, Model.Projection.AvailableLanguages, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.Projection.LanguageId)
- @Html.SmartLabelFor(x => x.ProductProjection.CurrencyId) + @Html.SmartLabelFor(x => x.Projection.CurrencyId) - @Html.DropDownListFor(x => x.ProductProjection.CurrencyId, Model.ProductProjection.AvailableCurrencies, Model.UnspecifiedString) - @Html.ValidationMessageFor(x => x.ProductProjection.CurrencyId) + @Html.DropDownListFor(x => x.Projection.CurrencyId, Model.Projection.AvailableCurrencies, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.Projection.CurrencyId)
- @Html.SmartLabelFor(x => x.ProductProjection.CustomerId) + @Html.SmartLabelFor(x => x.Projection.CustomerId) - @Html.EditorFor(x => x.ProductProjection.CustomerId) - @Html.ValidationMessageFor(x => x.ProductProjection.CustomerId) + @Html.EditorFor(x => x.Projection.CustomerId) + @Html.ValidationMessageFor(x => x.Projection.CustomerId)
- @Html.SmartLabelFor(x => x.ProductProjection.AttributeCombinationAsProduct) + @Html.SmartLabelFor(x => x.Projection.AttributeCombinationAsProduct) - @Html.EditorFor(x => x.ProductProjection.AttributeCombinationAsProduct) - @Html.ValidationMessageFor(x => x.ProductProjection.AttributeCombinationAsProduct) + @Html.EditorFor(x => x.Projection.AttributeCombinationAsProduct) + @Html.ValidationMessageFor(x => x.Projection.AttributeCombinationAsProduct)
- @Html.SmartLabelFor(x => x.ProductProjection.AttributeCombinationValueMergingId) + @Html.SmartLabelFor(x => x.Projection.AttributeCombinationValueMergingId) - @Html.DropDownListFor(x => x.ProductProjection.AttributeCombinationValueMergingId, Model.ProductProjection.AvailableAttributeCombinationValueMerging) - @Html.ValidationMessageFor(x => x.ProductProjection.AttributeCombinationValueMergingId) + @Html.DropDownListFor(x => x.Projection.AttributeCombinationValueMergingId, Model.Projection.AvailableAttributeCombinationValueMerging) + @Html.ValidationMessageFor(x => x.Projection.AttributeCombinationValueMergingId)
- @Html.SmartLabelFor(x => x.ProductProjection.PriceType) + @Html.SmartLabelFor(x => x.Projection.PriceType) - @Html.DropDownListFor(x => x.ProductProjection.PriceType, Model.ProductProjection.AvailablePriceTypes, Model.UnspecifiedString) - @Html.ValidationMessageFor(x => x.ProductProjection.PriceType) + @Html.DropDownListFor(x => x.Projection.PriceType, Model.Projection.AvailablePriceTypes, Model.UnspecifiedString) + @Html.ValidationMessageFor(x => x.Projection.PriceType)
- @Html.SmartLabelFor(x => x.ProductProjection.ConvertNetToGrossPrices) + @Html.SmartLabelFor(x => x.Projection.ConvertNetToGrossPrices) - @Html.EditorFor(x => x.ProductProjection.ConvertNetToGrossPrices) - @Html.ValidationMessageFor(x => x.ProductProjection.ConvertNetToGrossPrices) + @Html.EditorFor(x => x.Projection.ConvertNetToGrossPrices) + @Html.ValidationMessageFor(x => x.Projection.ConvertNetToGrossPrices)
- @Html.SmartLabelFor(x => x.ProductProjection.ShippingTime) + @Html.SmartLabelFor(x => x.Projection.ShippingTime) - @Html.EditorFor(x => x.ProductProjection.ShippingTime) - @Html.ValidationMessageFor(x => x.ProductProjection.ShippingTime) + @Html.EditorFor(x => x.Projection.ShippingTime) + @Html.ValidationMessageFor(x => x.Projection.ShippingTime)
- @Html.SmartLabelFor(x => x.ProductProjection.ShippingCosts) + @Html.SmartLabelFor(x => x.Projection.ShippingCosts) - @Html.EditorFor(x => x.ProductProjection.ShippingCosts) - @Html.ValidationMessageFor(x => x.ProductProjection.ShippingCosts) + @Html.EditorFor(x => x.Projection.ShippingCosts) + @Html.ValidationMessageFor(x => x.Projection.ShippingCosts)
- @Html.SmartLabelFor(x => x.ProductProjection.FreeShippingThreshold) + @Html.SmartLabelFor(x => x.Projection.FreeShippingThreshold) - @Html.EditorFor(x => x.ProductProjection.FreeShippingThreshold) - @Html.ValidationMessageFor(x => x.ProductProjection.FreeShippingThreshold) + @Html.EditorFor(x => x.Projection.FreeShippingThreshold) + @Html.ValidationMessageFor(x => x.Projection.FreeShippingThreshold)
- @Html.SmartLabelFor(x => x.ProductProjection.PictureSize) + @Html.SmartLabelFor(x => x.Projection.PictureSize) - @Html.EditorFor(x => x.ProductProjection.PictureSize) - @Html.ValidationMessageFor(x => x.ProductProjection.PictureSize) + @Html.EditorFor(x => x.Projection.PictureSize) + @Html.ValidationMessageFor(x => x.Projection.PictureSize)
- @Html.SmartLabelFor(x => x.ProductProjection.Brand) + @Html.SmartLabelFor(x => x.Projection.Brand) - @Html.EditorFor(x => x.ProductProjection.Brand) - @Html.ValidationMessageFor(x => x.ProductProjection.Brand) + @Html.EditorFor(x => x.Projection.Brand) + @Html.ValidationMessageFor(x => x.Projection.Brand)
- @Html.SmartLabelFor(x => x.ProductProjection.DescriptionMergingId) + @Html.SmartLabelFor(x => x.Projection.DescriptionMergingId) - @Html.DropDownListFor(x => x.ProductProjection.DescriptionMergingId, Model.ProductProjection.AvailableDescriptionMergings) - @Html.ValidationMessageFor(x => x.ProductProjection.DescriptionMergingId) + @Html.DropDownListFor(x => x.Projection.DescriptionMergingId, Model.Projection.AvailableDescriptionMergings) + @Html.ValidationMessageFor(x => x.Projection.DescriptionMergingId)
- @Html.SmartLabelFor(x => x.ProductProjection.AppendDescriptionText) + @Html.SmartLabelFor(x => x.Projection.AppendDescriptionText) - @Html.HiddenFor(x => x.ProductProjection.AppendDescriptionText) - @Html.ValidationMessageFor(x => x.ProductProjection.AppendDescriptionText) + @Html.HiddenFor(x => x.Projection.AppendDescriptionText) + @Html.ValidationMessageFor(x => x.Projection.AppendDescriptionText)
- @Html.SmartLabelFor(x => x.ProductProjection.RemoveCriticalCharacters) + @Html.SmartLabelFor(x => x.Projection.RemoveCriticalCharacters) - @Html.EditorFor(x => x.ProductProjection.RemoveCriticalCharacters) - @Html.ValidationMessageFor(x => x.ProductProjection.RemoveCriticalCharacters) + @Html.EditorFor(x => x.Projection.RemoveCriticalCharacters) + @Html.ValidationMessageFor(x => x.Projection.RemoveCriticalCharacters)
- @Html.SmartLabelFor(x => x.ProductProjection.CriticalCharacters) + @Html.SmartLabelFor(x => x.Projection.CriticalCharacters) - @Html.HiddenFor(x => x.ProductProjection.CriticalCharacters) - @Html.ValidationMessageFor(x => x.ProductProjection.CriticalCharacters) + @Html.HiddenFor(x => x.Projection.CriticalCharacters) + @Html.ValidationMessageFor(x => x.Projection.CriticalCharacters)
- @Html.SmartLabelFor(x => x.ProductProjection.DescriptionToPlainText) + @Html.SmartLabelFor(x => x.Projection.DescriptionToPlainText) - @Html.EditorFor(x => x.ProductProjection.DescriptionToPlainText) - @Html.ValidationMessageFor(x => x.ProductProjection.DescriptionToPlainText) + @Html.EditorFor(x => x.Projection.DescriptionToPlainText) + @Html.ValidationMessageFor(x => x.Projection.DescriptionToPlainText)
+ @Html.SmartLabelFor(x => x.Projection.OrderStatusChangeId) + + @Html.DropDownListFor(x => x.Projection.OrderStatusChangeId, Model.Projection.AvailableOrderStatusChange) + @Html.ValidationMessageFor(x => x.Projection.OrderStatusChangeId) +
\ No newline at end of file From 4a28aa144e1cd67ecb58f706e488cc099842cb26 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 5 Sep 2015 11:19:14 +0200 Subject: [PATCH 043/874] Minor improvements --- .../DataExchange/ExportProfileTask.cs | 45 +++++++------ .../DataExchange/ExportSegmenter.cs | 66 ++++++++++--------- 2 files changed, 57 insertions(+), 54 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index c67653bb36..4a6b5b2194 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -584,6 +584,12 @@ private List GetProducts(ExportProfileTaskContext ctx, int pageIndex) x => _productService.GetProductPicturesByProductIds(x) ); + try + { + _services.DbContext.DetachEntities(result); + } + catch { } + return result; } @@ -627,10 +633,20 @@ private List GetOrders(ExportProfileTaskContext ctx, int pageIndex, int p ); } + try + { + _services.DbContext.DetachEntities(result); + + // TODO: examine remaining attached entities + //foreach (var item in orderItems) + // _services.DbContext.DetachEntity(item.Product); + } + catch { } + return result; } - private List ToExpando(ExportProfileTaskContext ctx, Product product, List products) + private List ToExpando(ExportProfileTaskContext ctx, Product product) { var result = new List(); var languageId = (ctx.Projection.LanguageId ?? 0); @@ -828,17 +844,10 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod result.Add(expando); } - // finally detach entity - try - { - _services.DbContext.DetachEntity(product); - } - catch { } - return result; } - private List ToExpando(ExportProfileTaskContext ctx, Order order, List orders) + private List ToExpando(ExportProfileTaskContext ctx, Order order) { var result = new List(); var languageId = (ctx.Projection.LanguageId ?? 0); @@ -882,16 +891,6 @@ private List ToExpando(ExportProfileTaskContext ctx, Order order, }) .ToList(); - try - { - _services.DbContext.DetachEntity(order); - - // TODO: examine remaining attached entities - //foreach (var item in orderItems) - // _services.DbContext.DetachEntity(item.Product); - } - catch { } - return result; } @@ -965,7 +964,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) { ctx.Export.Data = new ExportSegmenter( pageIndex => GetProducts(ctx, pageIndex), - (entity, entities) => ToExpando(ctx, entity, entities), + entity => ToExpando(ctx, entity), new PagedList(ctx.Profile.Offset, ctx.Profile.Limit, ctx.PageIndex, ctx.PageSize, totalCount), ctx.IsPreview ? 0 : ctx.Profile.BatchSize ); @@ -976,7 +975,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.Data = new ExportSegmenter( pageIndex => GetOrders(ctx, pageIndex, ctx.PageSize, out unused), - (entity, entities) => ToExpando(ctx, entity, entities), + entity => ToExpando(ctx, entity), new PagedList(ctx.Profile.Offset, ctx.Profile.Limit, ctx.PageIndex, ctx.PageSize, totalCount), ctx.IsPreview ? 0 : ctx.Profile.BatchSize ); @@ -1135,8 +1134,6 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) try { - (ctx.Export.Data as IExportExecuter).Dispose(); - ctx.Countries.Clear(); ctx.Stores.Clear(); ctx.QuantityUnits.Clear(); @@ -1147,6 +1144,8 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) ctx.ProductDataContext = null; ctx.OrderDataContext = null; + (ctx.Export.Data as IExportExecuter).Dispose(); + ctx.Export.CustomProperties.Clear(); ctx.Export.Log = null; ctx.Log = null; diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs index 2d6b3cd857..683484bdcb 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs @@ -33,7 +33,7 @@ internal interface IExportExecuter public class ExportSegmenter : IExportSegmenter, IExportExecuter, IDisposable where T : BaseEntity { private Func> _loadData; - private Func, List> _convertData; + private Func> _convertData; private List _currentData; private List _currentSegment; private IPageable _pageable; @@ -50,7 +50,7 @@ public class ExportSegmenter : IExportSegmenter, IExportExecuter, IDisposable public ExportSegmenter( Func> loadData, - Func, List> convertData, + Func> convertData, PagedList pageable, int itemsPerFile) { @@ -144,16 +144,7 @@ public void Reset() public bool ReadNextSegment() { - ClearSegment(); - - if (_isBeginOfSegmentation) - { - _isBeginOfSegmentation = false; - _recordsCount = 0; - return _pageable.TotalCount > 0; - } - - if (_fileCaptured) + if (_fileCaptured) // do not write to current file anymore { return false; } @@ -163,6 +154,15 @@ public bool ReadNextSegment() return true; } + ClearSegment(); + + if (_isBeginOfSegmentation) + { + _isBeginOfSegmentation = false; + _recordsCount = 0; + return _pageable.TotalCount > 0; + } + if (_pageable.HasNextPage) { _pageable.PageIndex++; @@ -177,39 +177,43 @@ public ICollection CurrentSegment { get { + var tmpCount = 0; + if (_currentSegment == null) { _currentSegment = new List(); + } - var tmpCount = 0; + if (_currentData == null) + { _currentData = _loadData(_pageable.PageIndex); + } - foreach (var record in _currentData.Skip(_fileRecordsSkip)) + foreach (var record in _currentData.Skip(_fileRecordsSkip)) + { + if (_recordsPerFile > 0 && _recordsPerFileCount >= _recordsPerFile) { - if (_recordsPerFile > 0 && _recordsPerFileCount >= _recordsPerFile) - { - _fileCaptured = true; - _fileIndex++; - _recordsPerFileCount = 0; - _fileRecordsSkip = tmpCount; - - return _currentSegment.AsReadOnly(); - } + _fileCaptured = true; + _fileIndex++; + _recordsPerFileCount = 0; + _fileRecordsSkip = tmpCount; - foreach (var obj in _convertData(record, _currentData)) - { - _currentSegment.Add(obj); + return _currentSegment.AsReadOnly(); + } - ++_recordsPerFileCount; - ++_recordsCount; - } + foreach (var obj in _convertData(record)) + { + _currentSegment.Add(obj); - ++tmpCount; + ++_recordsPerFileCount; + ++_recordsCount; } - _fileRecordsSkip = 0; + ++tmpCount; } + _fileRecordsSkip = 0; + return _currentSegment.AsReadOnly(); } } From 178e0fa8f8c235a0273d86fdfe16082ffd9ec9e9 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 5 Sep 2015 11:54:18 +0200 Subject: [PATCH 044/874] Pass more export context information to the export provider --- .../DataExchange/ExportExecuteContext.cs | 82 +++++++++++++++---- .../DataExchange/ExportProfileTask.cs | 4 + 2 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs index c259535ae6..58639939fc 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs @@ -3,31 +3,80 @@ using System.IO; using System.Threading; using SmartStore.Core.Logging; -using SmartStore.Utilities; namespace SmartStore.Services.DataExchange { public interface IExportExecuteContext { + /// + /// Provides the data to be exported + /// IExportSegmenter Data { get; } - ILogger Log { get; } + /// + /// The store context to be used for the export + /// + ExpandoObject Store { get; } - bool IsCanceled { get; } + /// + /// The customer context to be used for the export + /// + ExpandoObject Customer { get; } - ExpandoObject Store { get; } + /// + /// The currency context to be used for the export + /// + ExpandoObject Currency { get; } - object ConfigurationData { get; } + /// + /// The language identifier to be used for the export. Can be 0. + /// + int LanguageId { get; } - Dictionary CustomProperties { get; set; } + /// + /// To log information into the export log file + /// + ILogger Log { get; } - int SuccessfulExportedRecords { get; set; } + /// + /// Whether the export has been cancelled + /// + bool IsCanceled { get; } + /// + /// The maximum allowed file name length + /// int MaxFileNameLength { get; } + /// + /// The path of the export folder + /// string Folder { get; } + + /// + /// The name of the current export file + /// string FileName { get; } + + /// + /// The path of the current export file + /// string FilePath { get; } + + /// + /// Provider specific configuration data + /// + object ConfigurationData { get; } + + /// + /// Use this dictionary for any custom data required along the export + /// + Dictionary CustomProperties { get; set; } + + /// + /// Number of successful exported records. Should be incremented by the provider. Will be logged. + /// + int SuccessfulExportedRecords { get; set; } } @@ -45,6 +94,11 @@ internal ExportExecuteContext(CancellationToken cancellation, string folder) public IExportSegmenter Data { get; internal set; } + public ExpandoObject Store { get; internal set; } + public ExpandoObject Customer { get; internal set; } + public ExpandoObject Currency { get; internal set; } + public int LanguageId { get; internal set; } + public ILogger Log { get; internal set; } public bool IsCanceled @@ -52,14 +106,6 @@ public bool IsCanceled get { return _cancellation.IsCancellationRequested; } } - public ExpandoObject Store { get; internal set; } - - public object ConfigurationData { get; internal set; } - - public Dictionary CustomProperties { get; set; } - - public int SuccessfulExportedRecords { get; set; } - public int MaxFileNameLength { get; internal set; } public string Folder { get; private set; } @@ -81,5 +127,11 @@ public string FilePath { get { return Path.Combine(Folder, FileName); } } + + public object ConfigurationData { get; internal set; } + + public Dictionary CustomProperties { get; set; } + + public int SuccessfulExportedRecords { get; set; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index 4a6b5b2194..63b1b9d876 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -1074,6 +1074,10 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) var stores = Init(ctx); + ctx.Export.Customer = ctx.ProjectionCustomer.ToExpando(); + ctx.Export.Currency = ctx.ProjectionCurrency.ToExpando(ctx.Projection.LanguageId ?? 0); + ctx.Export.LanguageId = (ctx.Projection.LanguageId ?? 0); + stores.ForEach(x => ExportCoreInner(ctx, x)); } From ad748baeef7500f16db13f0b94c910b4f54627a1 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 7 Sep 2015 13:20:25 +0200 Subject: [PATCH 045/874] Profile list now uses the new task based resources --- .../201509021536425_ExportFramework1.cs | 4 + .../DataExchange/ExportExecuteContext.cs | 16 +- .../DataExchange/ExportProfileTask.cs | 136 ++++++++++------- .../Tasks/TaskExecutionContext.cs | 17 ++- .../Administration/Content/admin.less | 12 ++ .../Controllers/ExportController.cs | 29 ++-- .../Models/DataExchange/ExportProfileModel.cs | 1 + .../Administration/Views/Export/Edit.cshtml | 9 +- .../Administration/Views/Export/List.cshtml | 134 ++++++++++------ .../Export/_CreateOrUpdate.Filter.cshtml | 144 +++++++++--------- 10 files changed, 301 insertions(+), 201 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs index 9e6f73a2b9..e81034073f 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs @@ -46,6 +46,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.System.ScheduleTask", "Scheduled task", "Geplante Aufgabe"); + builder.AddOrUpdate("Admin.Configuration.Export.ProgressInfo", + "{0} of {1} records exported", + "{0} von {1} Datenstzen exportiert"); + builder.AddOrUpdate("Admin.Configuration.Export.FileNamePattern", "Pattern for file names", "Muster fr Dateinamen", diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs index 58639939fc..bd32d426c5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs @@ -83,6 +83,7 @@ public interface IExportExecuteContext public class ExportExecuteContext : IExportExecuteContext { private CancellationToken _cancellation; + private IExportSegmenter _segmenter; internal ExportExecuteContext(CancellationToken cancellation, string folder) { @@ -92,7 +93,20 @@ internal ExportExecuteContext(CancellationToken cancellation, string folder) CustomProperties = new Dictionary(); } - public IExportSegmenter Data { get; internal set; } + public IExportSegmenter Data + { + get + { + return _segmenter; + } + internal set + { + if (_segmenter != null) + (_segmenter as IExportExecuter).Dispose(); + + _segmenter = value; + } + } public ExpandoObject Store { get; internal set; } public ExpandoObject Customer { get; internal set; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index 63b1b9d876..36d0dbb74f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -146,30 +146,6 @@ private ProductSearchContext GetProductSearchContext(ExportProfileTaskContext ct return searchContext; } - private int GetTotalRecords(ExportProfileTaskContext ctx) - { - int result = 0; - - if (ctx.TotalRecords.HasValue) - { - return ctx.TotalRecords.Value; - } - else if (ctx.Provider.Value.EntityType == ExportEntityType.Product) - { - var searchContext = GetProductSearchContext(ctx, ctx.Profile.Offset, 1); - - var anySingleProduct = _productService.SearchProducts(searchContext); - - result = anySingleProduct.TotalCount; - } - else if (ctx.Provider.Value.EntityType == ExportEntityType.Order) - { - var unused = GetOrders(ctx, 0, 1, out result); - } - - return result; - } - private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic expando, Product product) { try @@ -311,6 +287,20 @@ private void GetDeliveryTimeAndQuantityUnit(ExportProfileTaskContext ctx, dynami expando.QuantityUnit = null; } + private void SetProgress(ExportProfileTaskContext ctx, int loadedRecords) + { + if (!ctx.IsPreview && loadedRecords > 0) + { + int totalRecords = ctx.RecordsPerStore.Sum(x => x.Value); + + ctx.RecordCount = Math.Min(ctx.RecordCount + loadedRecords, totalRecords); + + var msg = ctx.ProgressInfo.FormatInvariant(ctx.RecordCount, totalRecords); + + ctx.TaskContext.SetProgress(ctx.RecordCount, totalRecords, msg, true); + } + } + private void SendCompletionEmail(ExportProfileTaskContext ctx) { var emailAccount = _emailAccountService.GetEmailAccountById(ctx.Profile.EmailAccountId); @@ -584,6 +574,8 @@ private List GetProducts(ExportProfileTaskContext ctx, int pageIndex) x => _productService.GetProductPicturesByProductIds(x) ); + SetProgress(ctx, products.Count); + try { _services.DbContext.DetachEntities(result); @@ -595,7 +587,7 @@ private List GetProducts(ExportProfileTaskContext ctx, int pageIndex) private List GetOrders(ExportProfileTaskContext ctx, int pageIndex, int pageSize, out int totalCount) { - var pagedList = _orderService.SearchOrders( + var orders = _orderService.SearchOrders( ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId, ctx.Projection.CustomerId ?? 0, ctx.Filter.CreatedFrom.HasValue ? (DateTime?)_dateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _dateTimeHelper.CurrentTimeZone) : null, @@ -612,17 +604,17 @@ private List GetOrders(ExportProfileTaskContext ctx, int pageIndex, int p ctx.EntityIdsSelected ); - totalCount = pagedList.TotalCount; + totalCount = orders.TotalCount; if (ctx.Projection.OrderStatusChange != ExportOrderStatusChange.None) { ctx.EntityIdsLoaded = ctx.EntityIdsLoaded - .Union(pagedList.Select(x => x.Id)) + .Union(orders.Select(x => x.Id)) .Distinct() .ToList(); } - var result = pagedList as List; + var result = orders as List; if (pageSize > 1) { @@ -631,6 +623,8 @@ private List GetOrders(ExportProfileTaskContext ctx, int pageIndex, int p x => _addressesService.GetAddressByIds(x), x => _orderService.GetOrderItemsByOrderIds(x) ); + + SetProgress(ctx, orders.Count); } try @@ -898,6 +892,8 @@ private List ToExpando(ExportProfileTaskContext ctx, Order order) private List Init(ExportProfileTaskContext ctx) { + List result = null; + if (ctx.Projection.CurrencyId.HasValue) ctx.ProjectionCurrency = _currencyService.GetCurrencyById(ctx.Projection.CurrencyId.Value); else @@ -912,7 +908,7 @@ private List Init(ExportProfileTaskContext ctx) if (!ctx.IsPreview && ctx.Profile.PerStore) { - return new List(ctx.Stores.Values.Where(x => x.Id == ctx.Filter.StoreId || ctx.Filter.StoreId == 0)); + result = new List(ctx.Stores.Values.Where(x => x.Id == ctx.Filter.StoreId || ctx.Filter.StoreId == 0)); } else { @@ -920,8 +916,33 @@ private List Init(ExportProfileTaskContext ctx) ctx.Store = ctx.Stores.Values.FirstOrDefault(x => x.Id == (storeId ?? _services.StoreContext.CurrentStore.Id)); - return new List { ctx.Store }; + result = new List { ctx.Store }; + } + + // get total records for progress + foreach (var store in result) + { + if (ctx.TotalRecords.HasValue) + { + ctx.RecordsPerStore.Add(store.Id, ctx.TotalRecords.Value); + } + else if (ctx.Provider.Value.EntityType == ExportEntityType.Product) + { + ctx.Store = store; + var searchContext = GetProductSearchContext(ctx, ctx.Profile.Offset, 1); + var anySingleProduct = _productService.SearchProducts(searchContext); + ctx.RecordsPerStore.Add(store.Id, anySingleProduct.TotalCount); + } + else if (ctx.Provider.Value.EntityType == ExportEntityType.Order) + { + ctx.Store = store; + int totalCount = 0; + var unused = GetOrders(ctx, 0, 1, out totalCount); + ctx.RecordsPerStore.Add(store.Id, totalCount); + } } + + return result; } private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) @@ -958,7 +979,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) .Replace("%Store.Name%", ctx.Profile.PerStore ? SeoHelper.GetSeName(ctx.Store.Name, true, false) : "allstores"); - var totalCount = GetTotalRecords(ctx); + var totalCount = ctx.RecordsPerStore.First(x => x.Key == ctx.Store.Id).Value; if (ctx.Provider.Value.EntityType == ExportEntityType.Product) { @@ -1008,10 +1029,10 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) } } - if (ctx.Cancellation.IsCancellationRequested) + if (ctx.TaskContext.CancellationToken.IsCancellationRequested) ctx.Log.Warning("Export aborted. A cancellation has been requested"); - return (goOn && !ctx.Cancellation.IsCancellationRequested); + return (goOn && !ctx.TaskContext.CancellationToken.IsCancellationRequested); }); } } @@ -1045,6 +1066,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) ctx.Log = logger; ctx.Export.Log = logger; + ctx.ProgressInfo = _services.Localization.GetResource("Admin.Configuration.Export.ProgressInfo"); if (ctx.Profile.ProviderConfigData.HasValue()) { @@ -1204,11 +1226,13 @@ public void Execute(TaskExecutionContext context) var selectedIdsCacheKey = "ExportTaskSelectedIds" + id.ToString(); var selectedIds = HttpRuntime.Cache[selectedIdsCacheKey] as string; - var ctx = new ExportProfileTaskContext(profile, _exportService.LoadProvider(profile.ProviderSystemName), context.CancellationToken, selectedIds); + var ctx = new ExportProfileTaskContext(context, profile, _exportService.LoadProvider(profile.ProviderSystemName), selectedIds); HttpRuntime.Cache.Remove(selectedIdsCacheKey); ExportCoreOuter(ctx); + + context.CancellationToken.ThrowIfCancellationRequested(); } public void Preview(ExportProfile profile, IComponentContext context, int pageIndex, int pageSize, int totalRecords, Action previewData) @@ -1219,12 +1243,15 @@ public void Preview(ExportProfile profile, IComponentContext context, int pageIn if (context == null) throw new ArgumentNullException("context"); - InitDependencies(new TaskExecutionContext(context, null)); - + var taskContext = new TaskExecutionContext(context, null); var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(5.0)); - var ctx = new ExportProfileTaskContext(profile, _exportService.LoadProvider(profile.ProviderSystemName), cancellation.Token, - null, pageIndex, pageSize, totalRecords, previewData); + taskContext.CancellationToken = cancellation.Token; + + InitDependencies(taskContext); + + var ctx = new ExportProfileTaskContext(taskContext, profile, _exportService.LoadProvider(profile.ProviderSystemName), null, + pageIndex, pageSize, totalRecords, previewData); ExportCoreOuter(ctx); } @@ -1240,15 +1267,18 @@ public int GetRecordCount(ExportProfile profile, Provider provi if (context == null) throw new ArgumentNullException("context"); - InitDependencies(new TaskExecutionContext(context, null)); - + var taskContext = new TaskExecutionContext(context, null); var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(5.0)); - var ctx = new ExportProfileTaskContext(profile, provider, cancellation.Token, previewData: x => { }); + taskContext.CancellationToken = cancellation.Token; + + InitDependencies(taskContext); + + var ctx = new ExportProfileTaskContext(taskContext, profile, provider, previewData: x => { }); var unused = Init(ctx); - int result = GetTotalRecords(ctx); + int result = ctx.RecordsPerStore.First().Value; return result; } @@ -1260,20 +1290,21 @@ internal class ExportProfileTaskContext private ExportProductDataContext _productDataContext; private ExportOrderDataContext _orderDataContext; - public ExportProfileTaskContext(ExportProfile profile, + public ExportProfileTaskContext( + TaskExecutionContext taskContext, + ExportProfile profile, Provider provider, - CancellationToken cancellation, string selectedIds = null, int pageIndex = 0, int pageSize = 100, int? totalRecords = null, Action previewData = null) { + TaskContext = taskContext; Profile = profile; Provider = provider; Filter = XmlHelper.Deserialize(profile.Filtering); Projection = XmlHelper.Deserialize(profile.Projection); - Cancellation = cancellation; EntityIdsSelected = selectedIds.SplitSafe(",").Select(x => x.ToInt()).ToList(); PageIndex = pageIndex; PageSize = pageSize; @@ -1287,8 +1318,9 @@ public ExportProfileTaskContext(ExportProfile profile, CategoryPathes = new Dictionary(); Countries = new Dictionary(); - Export = new ExportExecuteContext(Cancellation, FolderContent); + Export = new ExportExecuteContext(TaskContext.CancellationToken, FolderContent); + RecordsPerStore = new Dictionary(); EntityIdsLoaded = new List(); } @@ -1298,12 +1330,17 @@ public ExportProfileTaskContext(ExportProfile profile, public int PageIndex { get; private set; } public int PageSize { get; private set; } public int? TotalRecords { get; set; } + public int RecordCount { get; set; } + public Dictionary RecordsPerStore { get; set; } + public string ProgressInfo { get; set; } + public Action PreviewData { get; private set; } public bool IsPreview { get { return PreviewData != null; } } + public TaskExecutionContext TaskContext { get; private set; } public ExportProfile Profile { get; private set; } public Provider Provider { get; private set; } public ExportFilter Filter { get; private set; } @@ -1312,7 +1349,6 @@ public bool IsPreview public Currency ProjectionCurrency { get; set; } public Customer ProjectionCustomer { get; set; } - public CancellationToken Cancellation { get; private set; } public TraceLogger Log { get; set; } public Store Store { get; set; } @@ -1357,10 +1393,7 @@ public ExportProductDataContext ProductDataContext set { if (_productDataContext != null) - { _productDataContext.Clear(); - _productDataContext = null; - } _productDataContext = value; } @@ -1374,10 +1407,7 @@ public ExportOrderDataContext OrderDataContext set { if (_orderDataContext != null) - { _orderDataContext.Clear(); - _orderDataContext = null; - } _orderDataContext = value; } diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs index 7113794245..f18a23e6ca 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs @@ -46,7 +46,7 @@ public T ResolveNamed(string name) where T : class public ScheduleTask ScheduleTask { get; set; } /// - /// Persists a task's progress information information to the database + /// Persists a task's progress information to the database /// /// Percentual progress. Can be null or a value between 0 and 100. /// Progress message. Can be null. @@ -75,5 +75,20 @@ public void SetProgress(int? progress, string message, bool immediately = false catch { } } } + + /// + /// Persists a task's progress information to the database + /// + /// Progress numerator + /// Progress denominator + /// Progress message. Can be null. + /// if true, saves the updated task entity immediately, or lazily with the next database commit otherwise. + public void SetProgress(int numerator, int denominator, string message, bool immediately = false) + { + float fraction = (float)numerator / (float)Math.Max(denominator, 1f); + int percentage = (int)Math.Round(fraction * 100f, 0); + + SetProgress(Math.Min(Math.Max(percentage, 0), 100), message, immediately); + } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index 8de49ed650..35fabba11f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -832,6 +832,18 @@ tr.adminSeparator hr { } } +.admin-table { + .disabled { + .muted(); + } + .centered { + text-align: center; + } + .progress-info { + min-width: 260px; + } +} + /* SERVER CONTROLS */ label { padding-left: 3px; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 7df243acf8..921addfd29 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -106,6 +106,7 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile model.Enabled = profile.Enabled; model.ScheduleTaskId = profile.SchedulingTaskId; model.ScheduleTaskName = profile.ScheduleTask.Name.NaIfEmpty(); + model.IsTaskRunning = profile.ScheduleTask.IsRunning; model.Provider = new ExportProfileModel.ProviderModel { @@ -387,32 +388,20 @@ public ActionResult List() if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); - return View(); - } - - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult List(GridCommand command) - { - var model = new GridModel(); + var providers = _exportService.LoadAllExportProviders().ToList(); + var profiles = _exportService.GetExportProfiles().ToList(); + var model = new List(); - if (_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + foreach (var profile in profiles) { - var providers = _exportService.LoadAllExportProviders().ToList(); - var query = _exportService.GetExportProfiles(); - var profiles = query.ToList(); - - model.Total = profiles.Count; + var profileModel = new ExportProfileModel(); - model.Data = profiles.Select(x => - { - var profileModel = new ExportProfileModel(); - PrepareProfileModel(profileModel, x, providers.FirstOrDefault(y => y.Metadata.SystemName == x.ProviderSystemName)); + PrepareProfileModel(profileModel, profile, providers.FirstOrDefault(x => x.Metadata.SystemName == profile.ProviderSystemName)); - return profileModel; - }); + model.Add(profileModel); } - return new JsonResult { Data = model }; + return View(model); } public ActionResult Create() diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 2b007f0312..599cf65cae 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -31,6 +31,7 @@ public partial class ExportProfileModel : EntityModelBase [SmartResourceDisplayName("Admin.System.ScheduleTask")] public int ScheduleTaskId { get; set; } public string ScheduleTaskName { get; set; } + public bool IsTaskRunning { get; set; } [SmartResourceDisplayName("Admin.Configuration.Export.LastExecution")] [AllowHtml] diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 2a452b2915..6aefa01bf3 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -360,19 +360,14 @@ $('#CriticalCharactersContainer').toggle($(this).is(':checked')); }).trigger('change'); - @if (Model.ProductFilter != null) - { - // show/hide product category filter - $('#@(Html.FieldIdFor(x => x.ProductFilter.WithoutCategories))').change(function () { + $('#@(Html.FieldIdFor(x => x.Filter.WithoutCategories))').change(function () { $('#FilterCategoryIdsContainer').toggle(_.isEmpty($(this).val())); }).trigger('change'); // show/hide product manufacturer filter - $('#@(Html.FieldIdFor(x => x.ProductFilter.WithoutManufacturers))').change(function () { + $('#@(Html.FieldIdFor(x => x.Filter.WithoutManufacturers))').change(function () { $('#FilterManufacturerIdContainer').toggle(_.isEmpty($(this).val())); }).trigger('change'); - - } }); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index bad4693935..d85d5cdcc2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -1,6 +1,7 @@ @using SmartStore.Core.Domain.DataExchange; @using SmartStore.Admin.Models.DataExchange; @using Telerik.Web.Mvc.UI; +@model List @{ ViewBag.Title = T("Common.Export").Text; } @@ -20,53 +21,60 @@ - - -
- +
+ - + + + + + + + + -
- @(Html.Telerik().Grid() - .Name("export-profile-grid") - .DataKeys(keys => keys.Add(x => x.Id) - .RouteKey("Id")) - .Columns(columns => - { - columns.Bound(x => x.Provider.ThumbnailUrl) - .Width(70) - .Centered() - .ClientTemplate("<#= Id #>"); - columns.Bound(x => x.Name) - .Width(400) - .ClientTemplate("\"><#= Name #>"); - columns.Bound(x => x.Provider.SystemName) - .ClientTemplate("<#= Provider.FriendlyName #> (<#= Provider.SystemName #>)"); - columns.Bound(x => x.Enabled) - .Width(100) - .ClientTemplate(@Html.SymbolForBool("Enabled")) - .Centered(); - columns.Bound(x => x.Provider.EntityTypeName); - columns.Bound(x => x.Provider.FileExtension) - .Centered(); - columns.Bound(x => x.LastExecution) - .ClientTemplate( - "" - ) - .ReadOnly(); - }) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Export")) - .ClientEvents(events => events.OnRowDataBound("onRowDataBound_ExportProfiles")) - .EnableCustomBinding(true)) - @T("Common.Image")@T("Admin.Configuration.Export.Name")@T("Common.Enabled")@T("Admin.Configuration.Export.EntityType")@T("Admin.Configuration.Export.FileExtension")@T("Admin.System.ScheduleTasks.LastStart")@T("Admin.System.ScheduleTasks.NextRun")@T("Admin.Common.Actions")
-
+ + + @foreach (var profile in Model) + { + + + @profile.Provider.FriendlyName + + + @profile.Provider.FriendlyName (@profile.Provider.SystemName) + + + @Html.SymbolForBool(profile.Enabled) + + + @profile.Provider.EntityTypeName + + + @profile.Provider.FileExtension + + +
+ + +
+
+ + + + + + @T("Common.Cancel") + + + + } + +
 @T("Admin.Common.Preview") + @if (Model.LogFileExists) + { +  @T("Admin.Configuration.ActivityLog") + } From 19e400211362a0373528c05d9c4e89be52bc371c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 14 Sep 2015 12:13:19 +0200 Subject: [PATCH 061/874] Export framework further implementation --- .../Domain/DataExchange/ExportDeployment.cs | 2 +- .../201509021536425_ExportFramework1.cs | 15 ++- .../DataExchange/ExportExecuteContext.cs | 2 +- .../DataExchange/ExportExtensions.cs | 13 +- .../DataExchange/ExportProfileTask.cs | 9 +- .../DataExchange/ExportService.cs | 20 ++- .../Controllers/ExportController.cs | 116 ++++++++-------- .../DataExchange/ExportDeploymentModel.cs | 4 +- .../Administration/SmartStore.Admin.csproj | 8 +- .../Administration/Views/Export/Create.cshtml | 2 +- .../Administration/Views/Export/Edit.cshtml | 124 ++---------------- .../Views/Export/InfoDeployment.cshtml | 39 ++++++ ...{ProfileInfo.cshtml => InfoProfile.cshtml} | 0 .../Administration/Views/Export/List.cshtml | 8 +- .../Views/Export/_Tab.Deployment.cshtml | 104 +++++++++++++++ ...pdate.Filter.cshtml => _Tab.Filter.cshtml} | 0 ...ojection.cshtml => _Tab.Projection.cshtml} | 0 17 files changed, 275 insertions(+), 191 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml rename src/Presentation/SmartStore.Web/Administration/Views/Export/{ProfileInfo.cshtml => InfoProfile.cshtml} (100%) create mode 100644 src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml rename src/Presentation/SmartStore.Web/Administration/Views/Export/{_CreateOrUpdate.Filter.cshtml => _Tab.Filter.cshtml} (100%) rename src/Presentation/SmartStore.Web/Administration/Views/Export/{_CreateOrUpdate.Projection.cshtml => _Tab.Projection.cshtml} (100%) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs index 8b9bd9b90e..6261a9df0d 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs @@ -84,7 +84,7 @@ public ExportHttpTransmissionType HttpTransmissionType public string FileSystemPath { get; set; } /// - /// Multiple email addresses can be separated by semicolon + /// Multiple email addresses can be separated by commas /// public string EmailAddresses { get; set; } diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs index ff696ac5a8..b53e598c8d 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs @@ -40,12 +40,14 @@ public void Seed(SmartObjectContext context) context.Execute("DELETE FROM [dbo].[ScheduleTask] WHERE [Type] = 'SmartStore.Billiger.StaticFileGenerationTask, SmartStore.Billiger'"); context.Execute("DELETE FROM [dbo].[ScheduleTask] WHERE [Type] = 'SmartStore.ElmarShopinfo.StaticFileGenerationTask, SmartStore.ElmarShopinfo'"); context.Execute("DELETE FROM [dbo].[ScheduleTask] WHERE [Type] = 'SmartStore.Guenstiger.StaticFileGenerationTask, SmartStore.Guenstiger'"); + context.Execute("DELETE FROM [dbo].[ScheduleTask] WHERE [Type] = 'SmartStore.Shopwahl.StaticFileGenerationTask, SmartStore.Shopwahl'"); context.MigrateSettings(x => { x.DeleteGroup("BilligerSettings"); x.DeleteGroup("ElmarShopinfoSettings"); x.DeleteGroup("GuenstigerSettings"); + x.DeleteGroup("ShopwahlSettings"); }); } @@ -53,7 +55,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) { builder.AddOrUpdate("Admin.Common.ExportSelected", "Export selected", "Ausgewhlte exportieren"); builder.AddOrUpdate("Admin.Common.ExportAll", "Export all", "Alle exportieren"); - builder.AddOrUpdate("Common.Disabled", "Disabled", "Deaktiviert"); + builder.AddOrUpdate("Common.Public", "public", "ffentlich"); builder.AddOrUpdate("Admin.System.ScheduleTask", "Scheduled task", "Geplante Aufgabe"); @@ -262,8 +264,6 @@ private void RemoveObsoleteResources(LocaleResourcesBuilder builder) "Plugins.Feed.ElmarShopinfo.ProductPictureSize.Hint", "Plugins.Feed.ElmarShopinfo.Currency", "Plugins.Feed.ElmarShopinfo.Currency.Hint", - "Plugins.Feed.ElmarShopinfo.ShippingCost", - "Plugins.Feed.ElmarShopinfo.ShippingCost.Hint", "Plugins.Feed.ElmarShopinfo.ShippingTime", "Plugins.Feed.ElmarShopinfo.ShippingTime.Hint", "Plugins.Feed.ElmarShopinfo.Brand", @@ -321,6 +321,15 @@ private void RemoveObsoleteResources(LocaleResourcesBuilder builder) "Plugins.Feed.Guenstiger.Generate", "Plugins.Feed.Guenstiger.ConfigSaveNote" ); + + builder.Delete( + "Plugins.Feed.Shopwahl.TaskEnabled", + "Plugins.Feed.Shopwahl.TaskEnabled.Hint", + "Plugins.Feed.Shopwahl.StaticFileUrl", + "Plugins.Feed.Shopwahl.StaticFileUrl.Hint", + "Plugins.Feed.Shopwahl.GenerateStaticFileEachMinutes", + "Plugins.Feed.Shopwahl.GenerateStaticFileEachMinutes.Hint" + ); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs index 0cb0829a6a..141467b0f5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs @@ -161,7 +161,7 @@ public string FileName get { var finallyResolvedPattern = FileNamePattern - .Replace("%Misc.FileNumber%", (Data.FileIndex + 1).ToString("D5")) + .Replace("%Misc.FileNumber%", (Data.FileIndex + 1).ToString("D4")) .ToValidFileName("") .Truncate(MaxFileNameLength); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs index dc741d6dc1..ab4ad74de6 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs @@ -35,6 +35,17 @@ public static bool Supports(this Provider provider, ExportProje return false; } + /// + /// Get temporary folder for an export profile + /// + /// Export profile + /// Folder path + public static string GetExportFolder(this ExportProfile profile, bool content = false) + { + var path = Path.Combine(FileSystemHelper.TempDir(), @"Profile\Export\{0}{1}".FormatInvariant(profile.FolderName, content ? @"\Content" : "")); + return path; + } + /// /// Get log file path for an export profile /// @@ -42,7 +53,7 @@ public static bool Supports(this Provider provider, ExportProje /// Log file path public static string GetExportLogFilePath(this ExportProfile profile) { - var path = Path.Combine(FileSystemHelper.TempDir(), @"Profile\Export\{0}\log.txt".FormatInvariant(profile.FolderName)); + var path = Path.Combine(profile.GetExportFolder(), "log.txt"); return path; } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index eb49c10ecf..2122eaa02b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -361,7 +361,7 @@ private void DeployFileSystem(ExportProfileTaskContext ctx, ExportDeployment dep { FileSystemHelper.CopyDirectory(new DirectoryInfo(ctx.FolderContent), new DirectoryInfo(folderDestination)); - ctx.Log.Information("Copied export data to " + folderDestination); + ctx.Log.Information("Copied export data files to " + folderDestination); } } @@ -989,7 +989,10 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.FileNamePattern = ctx.Profile.FileNamePattern .Replace("%ExportProfile.Id%", ctx.Profile.Id.ToString()) - .Replace("%Store.Name%", ctx.Profile.PerStore ? SeoHelper.GetSeName(ctx.Store.Name, true, false) : "allstores"); + .Replace("%ExportProfile.SeoName%", SeoHelper.GetSeName(ctx.Profile.Name, true, false).Replace("/", "").Replace("-", "")) + .Replace("%ExportProfile.FolderName%", ctx.Profile.FolderName) + .Replace("%Store.Id%", ctx.Store.Id.ToString()) + .Replace("%Store.SeoName%", ctx.Profile.PerStore ? SeoHelper.GetSeName(ctx.Store.Name, true, false) : "allstores"); var totalCount = ctx.RecordsPerStore.First(x => x.Key == ctx.Store.Id).Value; @@ -1152,7 +1155,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) } catch (Exception exc) { - logger.Error("Deployment \"{0}\" failed.".FormatInvariant(deployment.Name), exc); + logger.Error("Deployment \"{0}\" of type {1} failed.".FormatInvariant(deployment.Name, deployment.DeploymentType.ToString()), exc); } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index 54c0e82bb2..c0a1d44045 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -57,7 +57,7 @@ public virtual ExportProfile InsertExportProfile(Provider provi var task = new ScheduleTask { - Name = string.Concat(name, " Export Task"), + Name = string.Concat(name, " export task"), CronExpression = "0 */6 * * *", // every six hours Type = taskType, Enabled = false, @@ -71,18 +71,32 @@ public virtual ExportProfile InsertExportProfile(Provider provi { Name = name, FolderName = seoName.ToValidPath().Truncate(_dataExchangeSettings.MaxFileNameLength), - FileNamePattern = "%Misc.FileNumber%-%ExportProfile.Id%-{0}-%Store.Name%".FormatInvariant(seoName), + FileNamePattern = "%ExportProfile.Id%-%Store.Id%-%Misc.FileNumber%-%ExportProfile.SeoName%", ProviderSystemName = systemName, SchedulingTaskId = task.Id, Filtering = XmlHelper.Serialize(new ExportFilter()), Projection = XmlHelper.Serialize(new ExportProjection()) - }; + }; _exportProfileRepository.Insert(profile); task.Alias = profile.Id.ToString(); _scheduleTaskService.UpdateTask(task); + if (systemName.StartsWith("Feeds.")) + { + profile.Deployments.Add(new ExportDeployment + { + ProfileId = profile.Id, + Enabled = true, + IsPublic = true, + DeploymentType = ExportDeploymentType.FileSystem, + Name = profile.Name + }); + + UpdateExportProfile(profile); + } + _eventPublisher.EntityInserted(profile); return profile; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index f609b0869e..a79d3bcfc4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -164,6 +164,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile model.SerializedCompletedEmailAddresses = string.Join(",", profile.CompletedEmailAddresses.SplitSafe(",").Select(x => x.EncodeJsString())); + // get and initialize provider specific configuration data if (provider != null) { model.Provider.ProjectionSupport = provider.Metadata.ExportProjectionSupport; @@ -314,12 +315,48 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) .ToList(); } + + // deployment + model.Deployments = profile.Deployments + .Select(x => + { + var deploymentModel = PrepareDeploymentModel(x, null, false); + + if (x.IsPublic) + { + try + { + int? storeId = (filter.StoreId == 0 ? projection.StoreId : filter.StoreId); + var store = (storeId.HasValue ? _services.StoreService.GetStoreById(storeId.Value) : _services.StoreContext.CurrentStore); + + deploymentModel.PublicRootUrl = store.Url.EnsureEndsWith("/") + ExportProfileTask.PublicFolder.EnsureEndsWith("/"); + + var publicFolder = Path.Combine(HttpRuntime.AppDomainAppPath, ExportProfileTask.PublicFolder); + + foreach (var path in Directory.GetFiles(profile.GetExportFolder(true))) + { + var fileName = Path.GetFileName(path); + if (System.IO.File.Exists(Path.Combine(publicFolder, fileName))) + { + deploymentModel.PublicFileNames.Add(fileName); + } + } + + deploymentModel.PublicFileNames = deploymentModel.PublicFileNames.OrderBy(y => y).ToList(); + } + catch (Exception exc) + { + exc.Dump(); + } + } + + return deploymentModel; + }) + .ToList(); } - private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment, Provider provider) + private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment, Provider provider, bool forEdit) { - var allEmailAccounts = _emailAccountService.GetAllEmailAccounts(); - var model = new ExportDeploymentModel { Id = deployment.Id, @@ -339,21 +376,27 @@ private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment EmailSubject = deployment.EmailSubject, EmailAccountId = deployment.EmailAccountId, PassiveMode = deployment.PassiveMode, - UseSsl = deployment.UseSsl + UseSsl = deployment.UseSsl, + PublicFileNames = new List() }; - model.AvailableDeploymentTypes = ExportDeploymentType.FileSystem.ToSelectList(false).ToList(); - model.AvailableHttpTransmissionTypes = ExportHttpTransmissionType.SimplePost.ToSelectList(false).ToList(); + if (forEdit) + { + var allEmailAccounts = _emailAccountService.GetAllEmailAccounts(); - model.SerializedEmailAddresses = string.Join(",", deployment.EmailAddresses.SplitSafe(",").Select(x => x.EncodeJsString())); + model.AvailableDeploymentTypes = ExportDeploymentType.FileSystem.ToSelectList(false).ToList(); + model.AvailableHttpTransmissionTypes = ExportHttpTransmissionType.SimplePost.ToSelectList(false).ToList(); - model.AvailableEmailAccounts = allEmailAccounts - .Select(x => new SelectListItem { Text = x.FriendlyName, Value = x.Id.ToString() }) - .ToList(); + model.SerializedEmailAddresses = string.Join(",", deployment.EmailAddresses.SplitSafe(",").Select(x => x.EncodeJsString())); - if (provider != null) - { - model.ThumbnailUrl = GetThumbnailUrl(provider); + model.AvailableEmailAccounts = allEmailAccounts + .Select(x => new SelectListItem { Text = x.FriendlyName, Value = x.Id.ToString() }) + .ToList(); + + if (provider != null) + { + model.ThumbnailUrl = GetThumbnailUrl(provider); + } } return model; @@ -439,7 +482,7 @@ public ActionResult Create() { var item = new SelectListItem { - Text = "{0} ({1})".FormatInvariant(x.Metadata.FriendlyName, x.Metadata.SystemName), + Text = "{0} ({1})".FormatInvariant(_pluginMediator.GetLocalizedFriendlyName(x.Metadata), x.Metadata.SystemName), Value = x.Metadata.SystemName }; @@ -822,37 +865,6 @@ public ActionResult DownloadLogFile(int id) #region Export deployment - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult DeploymentList(GridCommand command, int id) - { - var model = new GridModel(); - - if (_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) - { - var profile = _exportService.GetExportProfileById(id); - if (profile != null) - { - model.Total = profile.Deployments.Count; - - model.Data = profile.Deployments.Select(x => - { - var deploymentModel = new ExportDeploymentModel - { - Id = x.Id, - ProfileId = profile.Id, - Name = x.Name, - Enabled = x.Enabled, - DeploymentType = x.DeploymentType, - DeploymentTypeName = x.DeploymentType.GetLocalizedEnum(_services.Localization, _services.WorkContext) - }; - return deploymentModel; - }).ToList(); - } - } - - return new JsonResult { Data = model }; - } - public ActionResult CreateDeployment(int id) { if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) @@ -864,7 +876,7 @@ public ActionResult CreateDeployment(int id) var provider = _exportService.LoadProvider(profile.ProviderSystemName); - var fileSystemName = ExportDeploymentType.FileSystem.GetLocalizedEnum(_services.Localization, _services.WorkContext); + //var fileSystemName = ExportDeploymentType.FileSystem.GetLocalizedEnum(_services.Localization, _services.WorkContext); var model = PrepareDeploymentModel(new ExportDeployment { @@ -872,7 +884,7 @@ public ActionResult CreateDeployment(int id) Enabled = true, DeploymentType = ExportDeploymentType.FileSystem, Name = profile.Name - }, provider); + }, provider, true); return View(model); } @@ -915,7 +927,7 @@ public ActionResult EditDeployment(int id) var provider = _exportService.LoadProvider(deployment.Profile.ProviderSystemName); - var model = PrepareDeploymentModel(deployment, provider); + var model = PrepareDeploymentModel(deployment, provider, true); return View(model); } @@ -942,13 +954,12 @@ public ActionResult EditDeployment(ExportDeploymentModel model, bool continueEdi return SmartRedirect(continueEditing, deployment.ProfileId, deployment.Id); } - model = PrepareDeploymentModel(deployment, _exportService.LoadProvider(deployment.Profile.ProviderSystemName)); + model = PrepareDeploymentModel(deployment, _exportService.LoadProvider(deployment.Profile.ProviderSystemName), true); return View(model); } - [HttpPost] - public ActionResult DeleteDeployment(int id, bool? fromGrid, GridCommand command) + public ActionResult DeleteDeployment(int id) { if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); @@ -961,9 +972,6 @@ public ActionResult DeleteDeployment(int id, bool? fromGrid, GridCommand command _exportService.DeleteExportDeployment(deployment); - if (fromGrid ?? false) - return DeploymentList(command, id); - NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); return SmartRedirect(false, profileId, 0); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs index bbdb6edb88..5339594432 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs @@ -43,7 +43,7 @@ public string DeploymentTypeIconClass case ExportDeploymentType.Http: return "fa-globe"; case ExportDeploymentType.Ftp: - return "fa-clone"; + return "fa-files-o"; default: return "fa-question"; } @@ -55,6 +55,8 @@ public string DeploymentTypeIconClass [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.IsPublic")] public bool IsPublic { get; set; } + public string PublicRootUrl { get; set; } + public List PublicFileNames { get; set; } [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.Username")] public string Username { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index c758c2e7c7..2c49600954 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -520,9 +520,11 @@ - - - + + + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml index bcc338ef69..9ac1ac143d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml @@ -14,7 +14,7 @@ @Html.SmartLabelFor(model => model.Provider.SystemName) - @Html.DropDownListFor(model => model.Provider.SystemName, Model.Provider.AvailableExportProviders) + @Html.DropDownListFor(model => model.Provider.SystemName, Model.Provider.AvailableExportProviders, new { style = "width: 500px;" }) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 8309ce6b49..a4f60629c0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -197,12 +197,12 @@ @helper TabFilter() { - @Html.Partial("_CreateOrUpdate.Filter", Model) + @Html.Partial("_Tab.Filter", Model) } @helper TabProjection() { - @Html.Partial("_CreateOrUpdate.Projection", Model) + @Html.Partial("_Tab.Projection", Model) } @helper TabConfiguration() @@ -234,115 +234,7 @@ @helper TabDeployment() { -
- - @Html.Raw(T("Admin.DataExchange.Export.Deployment.Note")) -
- - -
- @(Html.Telerik().Grid() - .Name("export-deployment-grid") - .DataKeys(keys => keys.Add(x => x.Id) - .RouteKey("Id")) - .Columns(columns => - { - columns.Bound(x => x.DeploymentTypeName) - .Template(x => " {1}".FormatInvariant(x.DeploymentTypeIconClass, x.DeploymentTypeName)) - .ClientTemplate(" <#= DeploymentTypeName #>") - .Width(280); - columns.Bound(x => x.Name) - .Template(x => Html.ActionLink(x.Name, "EditDeployment", "Export", new { id = x.Id }, null)) - .ClientTemplate("\"><#= Name #>"); - columns.Bound(x => x.Enabled) - .Width(100) - .Template(item => @Html.SymbolForBool(item.Enabled)) - .ClientTemplate(@Html.SymbolForBool("Enabled")) - .Centered(); - columns.Command(commands => - commands.Delete().Localize(T) - ).Width(100); - }) - .ToolBar(commands => - { - commands.Custom() - .Name("export-deployment-add-button") - .HtmlAttributes(new { @class = "btn btn-primary", href = @Url.Action("CreateDeployment", new { id = Model.Id }) }) - .Text(" " + @T("Common.AddNew")); - }) - .DataBinding(dataBinding => - dataBinding.Ajax() - .Select("DeploymentList", "Export", new { id = Model.Id }) - .Delete("DeleteDeployment", "Export", new { id = Model.Id, fromGrid = true }) - ) - .ClientEvents(events => events.OnRowDataBound("onRowDataBound_ExportDeployments")) - .EnableCustomBinding(true)) -
- -

- - - - - - - - - - - - - - - - - - - - - - - - - - -
- @Html.SmartLabelFor(x => x.FolderName) - - @Html.EditorFor(x => x.FolderName) - @Html.ValidationMessageFor(x => x.FolderName) -
- @Html.SmartLabelFor(x => x.FileNamePattern) - - @Html.TextBoxFor(x => x.FileNamePattern, new { @class = "input-large" }) - @Html.ValidationMessageFor(x => x.FileNamePattern) -
- @Html.SmartLabelFor(x => x.CreateZipArchive) - - @Html.EditorFor(x => x.CreateZipArchive) - @Html.ValidationMessageFor(x => x.CreateZipArchive) -
- @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) - - @Html.EditorFor(x => x.Cleanup) - @Html.ValidationMessageFor(x => x.Cleanup) -
+ @Html.Partial("_Tab.Deployment", Model) } @Html.DeleteConfirmation("export-profile-delete") @@ -357,11 +249,6 @@ $('#ProjectionStoreContainer').toggle(!$(this).is(':checked')); }).trigger('change'); - // show/hide completion email addresses - $('#@(Html.FieldIdFor(x => x.EmailAccountId))').change(function () { - $('#CompletedEmailAddressesContainer').toggle(!_.isEmpty($(this).val())); - }).trigger('change'); - $('#@(Html.FieldIdFor(x => x.Projection.AppendDescriptionText))').select2({ tags: [@(Html.Raw(Model.Projection.SerializedAppendDescriptionText))] }); $('#@(Html.FieldIdFor(x => x.Projection.CriticalCharacters))').select2({ tags: [@(Html.Raw(Model.Projection.SerializedCriticalCharacters))] }); @@ -384,5 +271,10 @@ $('#@(Html.FieldIdFor(x => x.Filter.WithoutManufacturers))').change(function () { $('#FilterManufacturerIdContainer').toggle(_.isEmpty($(this).val())); }).trigger('change'); + + // show/hide completion email addresses + $('#@(Html.FieldIdFor(x => x.EmailAccountId))').change(function () { + $('#CompletedEmailAddressesContainer').toggle(!_.isEmpty($(this).val())); + }).trigger('change'); }); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml new file mode 100644 index 0000000000..170836c8f7 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml @@ -0,0 +1,39 @@ +@using SmartStore.Core.Domain.DataExchange +@using SmartStore.Admin.Models.DataExchange +@model ExportDeploymentModel +@{ + Layout = null; +} +
+  @Model.DeploymentTypeName + @if (Model.DeploymentType == ExportDeploymentType.FileSystem && Model.IsPublic) + { + (@T("Common.Public")) + } +
+@if (Model.DeploymentType == ExportDeploymentType.FileSystem) +{ + if (Model.IsPublic) + { + foreach (var fileName in Model.PublicFileNames) + { + + } + } + else + { +
@Model.FileSystemPath
+ } +} +else if (Model.DeploymentType == ExportDeploymentType.Email) +{ +
@Model.EmailAddresses
+} +else if (Model.DeploymentType == ExportDeploymentType.Http) +{ +
@Model.Url
+} +else if (Model.DeploymentType == ExportDeploymentType.Ftp) +{ +
@Model.Url
+} diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileInfo.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml similarity index 100% rename from src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileInfo.cshtml rename to src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index b14454420a..8e76e75008 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -23,7 +23,7 @@ @if(Model.Count > 0) { - +
@@ -72,7 +72,7 @@
From c1d62c1c43e2a3f45f4057367344ccf86a4a79f6 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 15 Sep 2015 17:43:25 +0200 Subject: [PATCH 066/874] Option to apply settings from another profile when creating a new export profile --- .../Domain/DataExchange/ExportDeployment.cs | 33 ++++++- .../Domain/DataExchange/ExportProfile.cs | 33 ++++++- .../201509150931528_ExportFramework2.cs | 6 ++ .../DataExchange/ExportService.cs | 89 +++++++++++++------ .../DataExchange/IExportService.cs | 3 +- .../Controllers/ExportController.cs | 17 +++- .../Models/DataExchange/ExportProfileModel.cs | 4 + .../Administration/Views/Export/Create.cshtml | 15 +++- .../Administration/Views/Export/List.cshtml | 1 + 9 files changed, 165 insertions(+), 36 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs index 6261a9df0d..3b8190d6a4 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs @@ -1,8 +1,9 @@ -using SmartStore.Core.Domain.DataExchange; +using System; +using SmartStore.Core.Domain.DataExchange; namespace SmartStore.Core.Domain { - public class ExportDeployment : BaseEntity + public class ExportDeployment : BaseEntity, ICloneable { /// /// The profile identifier @@ -109,5 +110,33 @@ public ExportHttpTransmissionType HttpTransmissionType public bool UseSsl { get; set; } public virtual ExportProfile Profile { get; set; } + + public ExportDeployment Clone() + { + var deployment = new ExportDeployment + { + Name = this.Name, + Enabled = this.Enabled, + IsPublic = this.IsPublic, + CreateZip = this.CreateZip, + DeploymentTypeId = this.DeploymentTypeId, + Username = this.Username, + Password = this.Password, + Url = this.Url, + HttpTransmissionTypeId = this.HttpTransmissionTypeId, + FileSystemPath = this.FileSystemPath, + EmailAddresses = this.EmailAddresses, + EmailSubject = this.EmailSubject, + EmailAccountId = this.EmailAccountId, + PassiveMode = this.PassiveMode, + UseSsl = this.UseSsl + }; + return deployment; + } + + object ICloneable.Clone() + { + return this.Clone(); + } } } diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs index bfa2dd424b..75331d4a2c 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs @@ -5,7 +5,7 @@ namespace SmartStore.Core.Domain { - public class ExportProfile : BaseEntity + public class ExportProfile : BaseEntity, ICloneable { private ICollection _deployments; @@ -121,5 +121,36 @@ public virtual ICollection Deployments get { return _deployments ?? (_deployments = new HashSet()); } set { _deployments = value; } } + + public ExportProfile Clone() + { + var profile = new ExportProfile + { + Name = this.Name, + FolderName = null, + FileNamePattern = this.FileNamePattern, + ProviderSystemName = this.ProviderSystemName, + Enabled = this.Enabled, + SchedulingTaskId = 0, + Filtering = this.Filtering, + Projection = this.Projection, + ProviderConfigData = this.ProviderConfigData, + ResultInfo = null, + Offset = this.Offset, + Limit = this.Limit, + BatchSize = this.BatchSize, + PerStore = this.PerStore, + EmailAccountId = this.EmailAccountId, + CompletedEmailAddresses = this.CompletedEmailAddresses, + CreateZipArchive = this.CreateZipArchive, + Cleanup = this.Cleanup + }; + return profile; + } + + object ICloneable.Clone() + { + return this.Clone(); + } } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index 43289394ec..469aa86132 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -28,6 +28,12 @@ public void Seed(SmartObjectContext context) public void MigrateLocaleResources(LocaleResourcesBuilder builder) { builder.AddOrUpdate("Common.Example", "Example", "Beispiel"); + + builder.AddOrUpdate("Admin.DataExchange.Export.CloneProfile", + "Apply settings from", + "Einstellungen bernehmen von", + "Specifies an export profile from which to apply the settings.", + "Legt das Exportprofil fest, von welchem die Einstellungen bernommen werden sollen."); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index 687eaf4316..c95afc64fc 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -40,58 +40,89 @@ public ExportService( #region Export profiles - public virtual ExportProfile InsertExportProfile(Provider provider, string name) + public virtual ExportProfile InsertExportProfile(Provider provider, string name, int cloneFromProfileId = 0) { if (provider == null) throw new ArgumentNullException("provider"); + var cloneProfile = GetExportProfileById(cloneFromProfileId); + var systemName = provider.Metadata.SystemName; if (name.IsEmpty()) name = systemName; + ScheduleTask task = null; + ExportProfile profile = null; var seoName = SeoHelper.GetSeName(name, true, false).Replace("/", "").Replace("-", ""); - - var taskType = (new ExportProfileTask()).GetType().AssemblyQualifiedNameWithoutVersion(); - - var task = new ScheduleTask + + if (cloneProfile == null) { - Name = string.Concat(name, " export task"), - CronExpression = "0 */6 * * *", // every six hours - Type = taskType, - Enabled = false, - StopOnError = false, - IsHidden = true - }; + task = new ScheduleTask + { + CronExpression = "0 */6 * * *", // every six hours + Type = (new ExportProfileTask()).GetType().AssemblyQualifiedNameWithoutVersion(), + Enabled = false, + StopOnError = false, + IsHidden = true + }; + } + else + { + task = cloneProfile.ScheduleTask.Clone(); + task.LastEndUtc = task.LastStartUtc = task.LastSuccessUtc = null; + } + task.Name = string.Concat(name, " export task"); + _scheduleTaskService.InsertTask(task); - var profile = new ExportProfile + if (cloneProfile == null) + { + profile = new ExportProfile + { + FileNamePattern = "%Store.Id%-%ExportProfile.Id%-%Misc.FileNumber%-%ExportProfile.SeoName%", + Filtering = XmlHelper.Serialize(new ExportFilter()), + Projection = XmlHelper.Serialize(new ExportProjection()) + }; + } + else { - Name = name, - FolderName = seoName.ToValidPath().Truncate(_dataExchangeSettings.MaxFileNameLength), - FileNamePattern = "%Store.Id%-%ExportProfile.Id%-%Misc.FileNumber%-%ExportProfile.SeoName%", - ProviderSystemName = systemName, - SchedulingTaskId = task.Id, - Filtering = XmlHelper.Serialize(new ExportFilter()), - Projection = XmlHelper.Serialize(new ExportProjection()) - }; + profile = cloneProfile.Clone(); + } + + profile.Name = name; + profile.FolderName = seoName.ToValidPath().Truncate(_dataExchangeSettings.MaxFileNameLength); + profile.ProviderSystemName = systemName; + profile.SchedulingTaskId = task.Id; _exportProfileRepository.Insert(profile); task.Alias = profile.Id.ToString(); _scheduleTaskService.UpdateTask(task); - if (systemName.StartsWith("Feeds.")) + if (cloneProfile == null) + { + if (systemName.StartsWith("Feeds.")) + { + profile.Deployments.Add(new ExportDeployment + { + ProfileId = profile.Id, + Enabled = true, + IsPublic = true, + DeploymentType = ExportDeploymentType.FileSystem, + Name = profile.Name + }); + + UpdateExportProfile(profile); + } + } + else { - profile.Deployments.Add(new ExportDeployment + foreach (var deployment in cloneProfile.Deployments) { - ProfileId = profile.Id, - Enabled = true, - IsPublic = true, - DeploymentType = ExportDeploymentType.FileSystem, - Name = profile.Name - }); + profile.Deployments.Add(deployment.Clone()); + } UpdateExportProfile(profile); } diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs index 0ac96c7f1b..c3aeafb28f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs @@ -12,8 +12,9 @@ public interface IExportService /// /// Export provider /// Name of the profile + /// Identifier of a profile the settings should be copied from /// New export profile - ExportProfile InsertExportProfile(Provider provider, string name); + ExportProfile InsertExportProfile(Provider provider, string name, int cloneFromProfileId = 0); /// /// Updates an export profile diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index df17f7c9e9..3908bb4d9a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -482,6 +482,8 @@ public ActionResult Create() return Content(T("Admin.AccessDenied.Description")); var model = new ExportProfileModel(); + model.UnspecifiedString = T("Common.Unspecified"); + model.Provider = new ExportProfileModel.ProviderModel(); model.Provider.ProviderDescriptions = new Dictionary(); @@ -504,6 +506,19 @@ public ActionResult Create() return item; }).ToList(); + model.AvailableProfiles = _exportService.GetExportProfiles() + .ToList() + .Select(x => + { + var item = new SelectListItem + { + Text = "{0} ({1})".FormatInvariant(x.Name, x.ProviderSystemName), + Value = x.Id.ToString() + }; + return item; + }) + .ToList(); + return PartialView(model); } @@ -518,7 +533,7 @@ public ActionResult Create(ExportProfileModel model) var provider = _exportService.LoadProvider(model.Provider.SystemName); if (provider != null) { - var profile = _exportService.InsertExportProfile(provider, _pluginMediator.GetLocalizedFriendlyName(provider.Metadata)); + var profile = _exportService.InsertExportProfile(provider, _pluginMediator.GetLocalizedFriendlyName(provider.Metadata), model.CloneProfileId ?? 0); return RedirectToAction("Edit", new { id = profile.Id }); } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index edcd4fc51a..366d60ade9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -68,6 +68,10 @@ public partial class ExportProfileModel : EntityModelBase [SmartResourceDisplayName("Admin.DataExchange.Export.Cleanup")] public bool Cleanup { get; set; } + [SmartResourceDisplayName("Admin.DataExchange.Export.CloneProfile")] + public int? CloneProfileId { get; set; } + public List AvailableProfiles { get; set; } + public ProviderModel Provider { get; set; } public ExportFilterModel Filter { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml index 9ac1ac143d..306cb37df2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml @@ -10,7 +10,7 @@
@T("Common.Image") - @@ -167,7 +167,7 @@ else function refreshGrid() { setTimeout(function () { - $('#export-profile-grid').data('tGrid').ajaxRequest(); + $('#export-profile-list').data('tGrid').ajaxRequest(); }, 500); } @@ -192,7 +192,7 @@ else }); Admin.TaskWatcher.startWatching({ - context: $('#export-profile-grid'), + context: $('#export-profile-list'), elementsSelector: '.task-progress', defaultProgressMessage: '@T("Admin.System.ScheduleTasks.RunNow.IsRunning")', pollUrl: '@Url.Action("GetRunningTasks", "ScheduleTask")', diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml new file mode 100644 index 0000000000..dd10d4baba --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml @@ -0,0 +1,104 @@ +@model ExportProfileModel +@using SmartStore.Core.Domain.DataExchange; +@using SmartStore.Admin.Models.DataExchange + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ @Html.SmartLabelFor(x => x.FolderName) + + @Html.EditorFor(x => x.FolderName) + @Html.ValidationMessageFor(x => x.FolderName) +
+ @Html.SmartLabelFor(x => x.FileNamePattern) + + @Html.TextBoxFor(x => x.FileNamePattern, new { @class = "input-large" }) + @Html.ValidationMessageFor(x => x.FileNamePattern) +
+ @Html.SmartLabelFor(x => x.CreateZipArchive) + + @Html.EditorFor(x => x.CreateZipArchive) + @Html.ValidationMessageFor(x => x.CreateZipArchive) +
+ @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) + + @Html.EditorFor(x => x.Cleanup) + @Html.ValidationMessageFor(x => x.Cleanup) +
+ +

 

+ +
+ @Html.Raw(T("Admin.DataExchange.Export.Deployment.Note")) +
+ + +  @T("Common.AddNew") + + +
+ + + + + + + + + + + @foreach (var deployment in Model.Deployments) + { + + + + + + + } + +
@T("Admin.DataExchange.Export.Deployment.Name")@T("Common.Enabled")@T("Admin.DataExchange.Export.Deployment.DeploymentType")@T("Admin.Common.Actions")
+ @deployment.Name + + @Html.SymbolForBool(deployment.Enabled) + + @Html.Partial("InfoDeployment", deployment) + + + @T("Common.Delete") + +
+
\ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Filter.cshtml similarity index 100% rename from src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Filter.cshtml rename to src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Filter.cshtml diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml similarity index 100% rename from src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Projection.cshtml rename to src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml From 8ce692f44091fba5cd70a25f9826cc903aac8b19 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 14 Sep 2015 17:41:32 +0200 Subject: [PATCH 062/874] Minor fix --- .../SmartStore.Web/Controllers/CheckoutController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs b/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs index 0f14efb632..ff8b371c7d 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs @@ -692,7 +692,7 @@ public ActionResult PaymentMethod() _genericAttributeService.SaveAttribute( _workContext.CurrentCustomer, SystemCustomerAttributeNames.SelectedPaymentMethod, - (!isPaymentWorkflowRequired || !model.PaymentMethods.Any()) ? null : model.PaymentMethods[0].PaymentMethodSystemName, + !model.PaymentMethods.Any() ? null : model.PaymentMethods[0].PaymentMethodSystemName, _storeContext.CurrentStore.Id); _httpContext.GetCheckoutState().IsPaymentSelectionSkipped = true; From 4a3e371527d38e37c75823d0d9acf7c0145af488 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 15 Sep 2015 10:54:13 +0200 Subject: [PATCH 063/874] Convert Shopwahl plugin to support new export framework --- .../201509021536425_ExportFramework1.cs | 36 ++++++++++++++++++- .../DataExchange/ExportProfileTask.cs | 27 +++++++++----- .../DataExchange/ExportService.cs | 12 ++++--- .../DataExchange/IExportService.cs | 3 +- .../Controllers/ExportController.cs | 26 +++++++------- 5 files changed, 77 insertions(+), 27 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs index b53e598c8d..f78c96d2bf 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs @@ -328,7 +328,41 @@ private void RemoveObsoleteResources(LocaleResourcesBuilder builder) "Plugins.Feed.Shopwahl.StaticFileUrl", "Plugins.Feed.Shopwahl.StaticFileUrl.Hint", "Plugins.Feed.Shopwahl.GenerateStaticFileEachMinutes", - "Plugins.Feed.Shopwahl.GenerateStaticFileEachMinutes.Hint" + "Plugins.Feed.Shopwahl.GenerateStaticFileEachMinutes.Hint", + "Plugins.Feed.Shopwahl.ShippingCost", + "Plugins.Feed.Shopwahl.ShippingCost.Hint", + "Plugins.Feed.Shopwahl.ShippingTime", + "Plugins.Feed.Shopwahl.ShippingTime.Hint", + "Plugins.Feed.Shopwahl.Currency", + "Plugins.Feed.Shopwahl.Currency.Hint", + "Plugins.Feed.Shopwahl.ProductPictureSize", + "Plugins.Feed.Shopwahl.ProductPictureSize.Hint", + "Plugins.Feed.Shopwahl.BuildDescription", + "Plugins.Feed.Shopwahl.BuildDescription.Hint", + "Plugins.Feed.Shopwahl.Automatic", + "Plugins.Feed.Shopwahl.DescShort", + "Plugins.Feed.Shopwahl.DescLong", + "Plugins.Feed.Shopwahl.DescTitleAndShort", + "Plugins.Feed.Shopwahl.DescTitleAndLong", + "Plugins.Feed.Shopwahl.DescManuAndTitleAndShort", + "Plugins.Feed.Shopwahl.DescManuAndTitleAndLong", + "Plugins.Feed.Shopwahl.NoSpec", + "Plugins.Feed.Shopwahl.UseOwnProductNo", + "Plugins.Feed.Shopwahl.UseOwnProductNo.Hint", + "Plugins.Feed.Shopwahl.DescriptionToPlainText", + "Plugins.Feed.Shopwahl.DescriptionToPlainText.Hint", + "Plugins.Feed.Shopwahl.Brand", + "Plugins.Feed.Shopwahl.Brand.Hint", + "Plugins.Feed.Shopwahl.ExportFormat", + "Plugins.Feed.Shopwahl.ExportFormat.Hint", + "Plugins.Feed.Shopwahl.Store", + "Plugins.Feed.Shopwahl.Store.Hint", + "Plugins.Feed.Shopwahl.ConvertNetToGrossPrices", + "Plugins.Feed.Shopwahl.ConvertNetToGrossPrices.Hint", + "Plugins.Feed.Shopwahl.LanguageId", + "Plugins.Feed.Shopwahl.LanguageId.Hint", + "Plugins.Feed.Shopwahl.Generate", + "Plugins.Feed.Shopwahl.ConfigSaveNote" ); } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index 2122eaa02b..4d64b3b368 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -572,7 +572,7 @@ private List GetProducts(ExportProfileTaskContext ctx, int pageIndex) x => _productAttributeService.GetProductVariantAttributesByProductIds(x, null), x => _productAttributeService.GetProductVariantAttributeCombinations(x), x => _productService.GetTierPrices(x, ctx.ProjectionCustomer, ctx.Store.Id), - x => _categoryService.GetProductCategoriesByProductIds(x, true), + x => _categoryService.GetProductCategoriesByProductIds(x), x => _manufacturerService.GetProductManufacturersByProductIds(x), x => _productService.GetProductPicturesByProductIds(x) ); @@ -657,13 +657,15 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod expando._DetailUrl = ctx.Store.Url + expando.SeName; + expando._CategoryName = null; + expando._CategoryPath = _categoryService.GetCategoryPath( product, null, x => ctx.CategoryPathes.ContainsKey(x) ? ctx.CategoryPathes[x] : null, (id, value) => ctx.CategoryPathes[id] = value, x => ctx.Categories.ContainsKey(x) ? ctx.Categories[x] : _categoryService.GetCategoryById(x), - productCategories.FirstOrDefault() + productCategories.OrderBy(x => x.DisplayOrder).FirstOrDefault() ); expando.ProductPictures = productPictures @@ -706,6 +708,9 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod exp.IsFeaturedProduct = x.IsFeaturedProduct; exp.Category = x.Category.ToExpando(languageId); + if (expando._CategoryName == null) + expando._CategoryName = (string)exp.Category.Name; + return exp as ExpandoObject; }) .ToList(); @@ -806,17 +811,23 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod exp._ShippingCosts = ctx.Projection.ShippingCosts; } - if (ctx.Provider.Supports(ExportProjectionSupport.OldPrice) && product.OldPrice != decimal.Zero && product.OldPrice != (decimal)exp.Price && - !(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)) + if (ctx.Provider.Supports(ExportProjectionSupport.OldPrice)) { - if (ctx.Projection.ConvertNetToGrossPrices) + if (product.OldPrice != decimal.Zero && product.OldPrice != (decimal)exp.Price && !(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)) { - decimal taxRate; - exp._OldPrice = _taxService.GetProductPrice(product, product.OldPrice, true, ctx.ProjectionCustomer, out taxRate); + if (ctx.Projection.ConvertNetToGrossPrices) + { + decimal taxRate; + exp._OldPrice = _taxService.GetProductPrice(product, product.OldPrice, true, ctx.ProjectionCustomer, out taxRate); + } + else + { + exp._OldPrice = product.OldPrice; + } } else { - exp._OldPrice = product.OldPrice; + exp._OldPrice = null; } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index c0a1d44045..687eaf4316 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -40,12 +40,11 @@ public ExportService( #region Export profiles - public virtual ExportProfile InsertExportProfile(Provider provider) + public virtual ExportProfile InsertExportProfile(Provider provider, string name) { if (provider == null) throw new ArgumentNullException("provider"); - var name = provider.Metadata.FriendlyName; var systemName = provider.Metadata.SystemName; if (name.IsEmpty()) @@ -71,7 +70,7 @@ public virtual ExportProfile InsertExportProfile(Provider provi { Name = name, FolderName = seoName.ToValidPath().Truncate(_dataExchangeSettings.MaxFileNameLength), - FileNamePattern = "%ExportProfile.Id%-%Store.Id%-%Misc.FileNumber%-%ExportProfile.SeoName%", + FileNamePattern = "%Store.Id%-%ExportProfile.Id%-%Misc.FileNumber%-%ExportProfile.SeoName%", ProviderSystemName = systemName, SchedulingTaskId = task.Id, Filtering = XmlHelper.Serialize(new ExportFilter()), @@ -118,7 +117,7 @@ public virtual void DeleteExportProfile(ExportProfile profile) throw new ArgumentNullException("profile"); int scheduleTaskId = profile.SchedulingTaskId; - var folder = CommonHelper.MapPath("~/App_Data/_temp/Profile/Export/" + profile.FolderName); + var folder = profile.GetExportFolder(); _exportProfileRepository.Delete(profile); @@ -127,7 +126,10 @@ public virtual void DeleteExportProfile(ExportProfile profile) _eventPublisher.EntityDeleted(profile); - FileSystemHelper.ClearDirectory(folder, true); + if (System.IO.Directory.Exists(folder)) + { + FileSystemHelper.ClearDirectory(folder, true); + } } public virtual IQueryable GetExportProfiles(bool? enabled = null) diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs index 27eb47cf80..0ac96c7f1b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs @@ -11,8 +11,9 @@ public interface IExportService /// Inserts an export profile /// /// Export provider + /// Name of the profile /// New export profile - ExportProfile InsertExportProfile(Provider provider); + ExportProfile InsertExportProfile(Provider provider, string name); /// /// Updates an export profile diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index a79d3bcfc4..0480c88a00 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -326,23 +326,25 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile { try { - int? storeId = (filter.StoreId == 0 ? projection.StoreId : filter.StoreId); - var store = (storeId.HasValue ? _services.StoreService.GetStoreById(storeId.Value) : _services.StoreContext.CurrentStore); - - deploymentModel.PublicRootUrl = store.Url.EnsureEndsWith("/") + ExportProfileTask.PublicFolder.EnsureEndsWith("/"); - var publicFolder = Path.Combine(HttpRuntime.AppDomainAppPath, ExportProfileTask.PublicFolder); + var exportFolder = profile.GetExportFolder(true); - foreach (var path in Directory.GetFiles(profile.GetExportFolder(true))) + if (Directory.Exists(exportFolder)) { - var fileName = Path.GetFileName(path); - if (System.IO.File.Exists(Path.Combine(publicFolder, fileName))) + foreach (var path in Directory.GetFiles(exportFolder).OrderBy(y => y)) { - deploymentModel.PublicFileNames.Add(fileName); + var fileName = Path.GetFileName(path); + if (System.IO.File.Exists(Path.Combine(publicFolder, fileName))) + { + deploymentModel.PublicFileNames.Add(fileName); + } } - } - deploymentModel.PublicFileNames = deploymentModel.PublicFileNames.OrderBy(y => y).ToList(); + int? storeId = (filter.StoreId == 0 ? projection.StoreId : filter.StoreId); + var store = (storeId.HasValue ? _services.StoreService.GetStoreById(storeId.Value) : _services.StoreContext.CurrentStore); + + deploymentModel.PublicRootUrl = store.Url.EnsureEndsWith("/") + ExportProfileTask.PublicFolder.EnsureEndsWith("/"); + } } catch (Exception exc) { @@ -510,7 +512,7 @@ public ActionResult Create(ExportProfileModel model) var provider = _exportService.LoadProvider(model.Provider.SystemName); if (provider != null) { - var profile = _exportService.InsertExportProfile(provider); + var profile = _exportService.InsertExportProfile(provider, _pluginMediator.GetLocalizedFriendlyName(provider.Metadata)); return RedirectToAction("Edit", new { id = profile.Id }); } From 626df393a6aa57cfcae7050e755e7d57d50a9c81 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 15 Sep 2015 14:08:29 +0200 Subject: [PATCH 064/874] Display links to public feed export files grouped by store --- .../Domain/DataExchange/ExportProfile.cs | 7 +- .../Domain/DataExchange/ExportResultInfo.cs | 29 ++++ .../SmartStore.Core/SmartStore.Core.csproj | 1 + .../Mapping/DataExchange/ExportProfileMap.cs | 1 + .../201509150927576_Merge3.Designer.cs | 29 ++++ .../Migrations/201509150927576_Merge3.cs | 16 +++ .../Migrations/201509150927576_Merge3.resx | 126 ++++++++++++++++++ ...1509150931528_ExportFramework2.Designer.cs | 29 ++++ .../201509150931528_ExportFramework2.cs | 32 +++++ .../201509150931528_ExportFramework2.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 14 ++ .../DataExchange/ExportExecuteContext.cs | 19 +-- .../DataExchange/ExportProfileTask.cs | 35 ++++- .../Controllers/ExportController.cs | 26 ++-- .../DataExchange/ExportDeploymentModel.cs | 11 +- .../Views/Export/InfoDeployment.cshtml | 14 +- 16 files changed, 480 insertions(+), 35 deletions(-) create mode 100644 src/Libraries/SmartStore.Core/Domain/DataExchange/ExportResultInfo.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.resx create mode 100644 src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.resx diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs index cabc876a29..bfa2dd424b 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs @@ -58,10 +58,15 @@ public ExportProfile() public string Projection { get; set; } /// - /// Provider specific configuration data + /// XML with provider specific configuration data /// public string ProviderConfigData { get; set; } + /// + /// XML with information about the last export + /// + public string ResultInfo { get; set; } + /// /// The number of records to be skipped /// diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportResultInfo.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportResultInfo.cs new file mode 100644 index 0000000000..b45b3d766c --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportResultInfo.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace SmartStore.Core.Domain.DataExchange +{ + [Serializable] + public class ExportResultInfo + { + /// + /// Files created by last export + /// + public List Files { get; set; } + } + + + [Serializable] + public class ExportResultFileInfo + { + /// + /// Store identifier + /// + public int StoreId { get; set; } + + /// + /// Name of file + /// + public string FileName { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index a3ab21c916..17477a6b83 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -160,6 +160,7 @@ + diff --git a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs index 2ae4dd4c9d..d54bf034e3 100644 --- a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs @@ -16,6 +16,7 @@ public ExportProfileMap() this.Property(x => x.Filtering).IsMaxLength(); this.Property(x => x.Projection).IsMaxLength(); this.Property(x => x.ProviderConfigData).IsMaxLength(); + this.Property(x => x.ResultInfo).IsMaxLength(); this.Property(x => x.CompletedEmailAddresses).HasMaxLength(400); this.Property(x => x.FileNamePattern).HasMaxLength(400); diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.Designer.cs new file mode 100644 index 0000000000..e4155c6945 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.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 Merge3 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(Merge3)); + + string IMigrationMetadata.Id + { + get { return "201509150927576_Merge3"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.cs b/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.cs new file mode 100644 index 0000000000..24ce9a2d5f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.cs @@ -0,0 +1,16 @@ +namespace SmartStore.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class Merge3 : DbMigration + { + public override void Up() + { + } + + public override void Down() + { + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.resx b/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.resx new file mode 100644 index 0000000000..9da77ed83f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.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/Migrations/201509150931528_ExportFramework2.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.Designer.cs new file mode 100644 index 0000000000..1ff305bd2d --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.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 ExportFramework2 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ExportFramework2)); + + string IMigrationMetadata.Id + { + get { return "201509150931528_ExportFramework2"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs new file mode 100644 index 0000000000..0c170a71d5 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -0,0 +1,32 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using SmartStore.Data.Setup; + + public partial class ExportFramework2 : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.ExportProfile", "ResultInfo", c => c.String()); + } + + public override void Down() + { + DropColumn("dbo.ExportProfile", "ResultInfo"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.resx b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.resx new file mode 100644 index 0000000000..f24f629459 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.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 + + + H4sIAAAAAAAEAOy923IcObIg+L5m+w8yPc2s9UglVZdZn7KqHSMpUkprXdgkJZ3TL7RgJMiMVmREVlx46bX9sn3YT5pfGABxQwCOOyIyk5UPVUoGHA7A4e5wOByO//X//f+//c/HdfriHhVlkme/v3zz6qeXL1AW58sku/v9ZV3d/o+/vfyf//f/+X/8drpcP7741sH9TOBwzaz8/eWqqja/vn5dxiu0jspX6yQu8jK/rV7F+fp1tMxfv/3pp/94/ebNa4RRvMS4Xrz47aLOqmSN6B/4z5M8i9GmqqP0U75Eadl+xyWXFOuLz9EalZsoRr+/vFxHRXVZ5QV69S6qopcvjtIkwt24ROntyxdRluVVVOFO/vq1RJdVkWd3lxv8IUqvnjYIw91GaYnazv86gJuO46e3ZByvh4odqrguq3xtifDNzy1hXvPVncj7siccJt0pJnH1REZNyYcp9JTFn6LNBk/tyxd8e7+epAWBHRH4hFIZgyUZJfbpY7yKsjv0ikH1lxdchb/0bPL21dtXP7366S8vTuq0qgv0e4bqqojSv7w4r2/SJP47errKf6Ds96xOU7bvuPe4bPQBfzov8g0qqqcLdNuOaLF8+eL1uN5rvmJfjanTDHSRVT+/ffniM248uklRzxoMUeio3qMMFVGFludRVaEiIzgQJa7QOtdWMwX6FtVYLvO6iBEeV4cGszWdw0/R40eU3VUrLLi/YIE8Sx7RsvvSov6aJViacaWqqJF1080AyG9V2z9N0jZWChV6rLbTeEPzD1G5UrT9V7OmNcOkgog5g2MSk1pdn6Q9xD+DdfE4z9OuKfIbRZmWilhNoOWX7GsVdzWxGkFXyVo3I7+9HtSXUqmdPm7yonqHNmn+tEaEitaajUdx0GjytvCft0mKfFXadoT6NCPAS4GJLdEsymayffGcFAjPwT+TjS+igXUJHt+5wXZTkann56+G86PhpagsH/JiOXlDX4tU3UaIRj5gO+2qiLJynZREV4SYCghnPxJs77aqCwazbOsMi/XlU1mhNVYK6lUvBLlOsepNj5bLApUlKqefHtreZX3zLxRXMw0ujnO8zfHlASIlyT0iGyNfPUF2RKW4huuwfI7ukzu6esCLwcsXFyil5eUq2Yw4c9BM1z3wWZGvL/IUWLs7mOvG+sIdzTWAV1Fxhyov06Hvl6Pd0NY/GA3ytraz2p/l6RIVW2obcwT51ZJq+qW0yO8TPNpGgWvGbKxRt2NeEZfPsk5xr6+i8oev+sQzgWdgjl0S/pMsLtR3M31TdLrxFvk2uWvcYBM3eYFKrL0W2W0+eVNfbm9LxG+HLaf9Y7JOPFEcR1W8ukz+jTzXbyyVRH36ikVIm+IkX29ShFW5jRUWwpHQ7XiOiniFjRrvHRSpVdvvn6QWzWBilOGtms5Y0Vo1nflj2utWYyKiLxXd7tGPwPmOQ1ASgwwE9TLJxl2ztshItfIVi+Rglsnb0pgJv0xjJdDjmgDN6jRNnmEmJXpNvSC/CbO9HO3MxVb+ttsWV5VvvmSnRZEXvqg+o8fqooYdvpoVOyqxmGJZdax7mi1dW63jGPOJa7ss2aZjsEX5IVliwfadIPznHZEKbJbEyPLQoav7Cf8X3Wm2VSEGfZE/tNq6H3aSRQXWbOTEuC4KlMVPnyg62tZY5uAOOChUYi7VFVo6Lmr/qCP6CQ/d5XjiXVLgbUVePL1iER0WNoUS0i1sU+jid6iMi2Sj2f8Ztq1pKik3afT0MY+jNMBAjRr7UuD9pt+uY1G+Q7cR5kk7HWYhZ6jutjRVFcUrx/PAVr2Vr0CEB8GTt8UQzDvoAfcETwLh8ZFI0e3MeEYEUGsPu/K8I9BadgYdm2pEj3HsdAufibfFSzu+ncZU/YTNKY2RHKpl6T75THZYAQr69Rm3sZdDCftjBSi0P1Z1mkFl0fdRLd0QmK+GI2FreG343+UPWZpHSyc1vUyiVx2Cg1pW6JGWRu/rpG+1+W1/gNjhYs7UncMWRFST+ZS7tjo1Glqp0mC1Jv5i8rEQNaIJ0AjT0OkjHpPGfxKmpUX5GT34x+XQIIiE2dY6n5RvlkTSJo1cG6noIEbqQQfK2zovkrygcF6niXglnd9lSloN5KzVeDFVh3sTDe4qn2VoF2iTPgUZn1E7s4zp5GTyJo7jePI29FFZgbZgx/lSFUMfKFiannE6LR0AaYhNUSTD0azjZhqjkXRovkNn3InlpyirozR9sl6fpdujYVMCn9gG3h7xZ7jmGyrTAbEk143oegwMDmQEo9rejQH9IutG3XK3bVg8B+NGI6Yqz8ovv0zi8m5cwzqHkmHjmlDovFStEBON8DwvPKOIDKLpp+q7Prx+opabY2mXUGPA39AcGeBVlcoCrjXR6cF5RINwPqFqlbu4o9r65asRooPWUjEoQ6jtRq4+xmm9RMvmohsqyNq4WE4fpdK3Sxa44knd5NtAFyHaNi+xRUHu7zbkn2O07ShpB4gEtBc8fe3KT0mWrOs1PaA8WjcmR2vlojhZR1gLnRf4V3vD/W8vX1ySc1PMTVrM0eNEmBtsF6jEtKYhw4FocYbBzQ6ina7VmCvTIl/WcXVcZ0ssSViwsXFcJDd1hZp4bAf9ehJVUZrfvdKhPmhceVsD0XwZrSd6METforT2xrYosUDiXRKKK4ewOOmebKAbuCPTseQ1W3/YpFlUE/ZtNnW9tnJCQyFF9yCr8rZaYvlKREPsQMi6ADQ/LHjbGIdcTDt8bUgjE5noHMWqNUNDuD1XNPrfZLEOdm6IWbBXFJYbKWBLnizR1ape32SM98EV2Tc88Tep98xR5VCu/GOiw4W9BXUIexxMaryo7OoBu1JnWOZ496rLEmm3qLctmA34mqujGOQYVL9+c/C2UUJ2wzAagHnXVZ22NTT8zYuDUaFQjw2JSHP+eQkK3Ob7Iq83uCNh7It2EVhky+Q+WbqdFE2zlNuedc2+thtv/MO0d7RcJ9lJvl4zwTZTXnemXIvWeEn233LiyXn4kn3IyfV8f1uxwxPOYPiEqggrDeK7n/5KLmlsTsYh7V0llfLeQpiRHaVp/tB7dNF9gh68Td6jzabI79HyAmvj7O6yXvvN9Oe8CoyxQ3eFF4yUG7R3H8MhbUM+rvKj2HvnQK/6k87RtdF7ii9/1NMLXZTVt1FMTJACr6PVZ7yJGrTGZM2+xyw2eSOL8n1yW51EhfcWrMMTwlq5QH/USYG+VCtM8WY58eaUFudg/Ki0daAQpqOa2MZVEhPb6Gi55PrgPaZFOUTkh4nu9k5Hl6WNgHcIvceIZ6URuC+3Ak5Hj1aL5vRxkxTUUH8XPfE4zVAcxVVyT1GEYPsPUXkZkVwfoWZ1jM3yPhPuDQmHOLorEGINR4/wgAHZFXqc3hJdlBeI3Dtm8gm5a48W0clTnKKmU746jsV4jook95a+Hidd+yliT1lZlOTkOVgqzjM8/d1Rtrd1t1wmRPKitMN4siIuDXNHtf2Nhejx9BHva7xlYVGelt7kxL05wRusu7zwTlqNTRy8KVpk91g0MbouysDTbKzy+EewQwiyXaMYj+6jBNdNUgarp98Y7KnzgpVkAUf+MX9oRk1WmgDpyfEGIbl9og6Bs7zo+niM8O7L8+gqin/kZDtNMjx4nzOT3SDBuGhoiXcg/abX26Kgm/42DiXMJDUYm/iTMBgpAdCyRZYos30FMlaxLJAOHtdPx3VVMUk83GWLQH9PylWalFUYpK3wpwgzL9brI/+Nc14ybJ1TdEnsf6LFIgm+An1Jl9M20G5MTpjw3eBtXG4wnii1HIg5TpqWqDtlc7hFweI6zZYemDp31mlGTumC8FertkaY0cRM0Sq2WdvEm4+rBDUy6a3w8RqKyqo/jDzJ1zdJ1p7LBWRC3OcuuKE82mzSxN9i/o6Su9V0ojjexwRH/z1ZToj9w7S06VcaX33SI/JTJuFOLMJFXSCaltR7/d2dkAtxgMk9Kp5IZUvvCZv9y7LqcVSidvUJsu3u8X1CUVkXqMlsJjUng+Sh6tsMG4ndoyU/xqjNSEvDIOiJlsYDFca93oaJoIJEXoRyfIyQEmqExnm5yht/SlTYezrkUUvNktgvkWD0S1d63UJf5YOvfAh/kUMJMUkKUNsQJFYbqEJ3rseAQtTOqFwWsDMGcgwwGqJ+DGPE3EOmtLFgkhAryxG1Di+6IZYPqHOLKUKneBDZAAQ4x46zh4jKvrOAiv5DYLIxgLCO4zhPKB7lEFoYRe85CFnHeTDHPveH4PIuNyCKHo8BZB3moBz7S3egt0mTII+NAJb3H66iGI+6gmx8mlqO472K7pSDux7BifqUKZaqUxbGUZt+i4oEG3XQPlLZfUU9xQQZ1JLNkklVx6niUVsN3GK0tkN0Htc43a+CB8eAIhOy5VIuHAHZsiF/NqzoLA8qdncMIe0wB2bbZdaHA3S3L4Y4QygUeEGEgGbfPLthf9PFIetza9T2vw5xzfK2NFG+E+U07WYmRKTG15LslQLdX5LfiAru1Oqast2VW5/8ti4zlyRGjZfMoWYbXVWe5PWG3NH2f4JPxOTxKJnZzND4SCplvjw6YCLEs40D0W3jr3LNVkzceLMVVHv5AU5YlpTAtosT4GKYwiOhH4PgvDD2SLSoWs6X59Xq24RrAOOAAOVDAaG9Lhh1+26PG0YdisNSLG9LsxRPdOGG3H8J1LLOgT/fXYnjvMJ8Ouu1Hs3jTGFWpk6MQl3oOVyYCfQeN7nRFyoAsPW0WZ5UkZtU/m8ujq7+lFd5kxGEx+0ckdPi+TIOHvPYdxhEAF1E2d0MLzQGvhsXPn5hh28O7dop+LNMYSCQiqYH/Jagh0/qTZWjNg59+OiwZdEfQAL7G2MvbKOmwd4PJ1Ud0NBjvkyw5AUAv+wAXQ8cnwlp6x8Md4X6akg01QsZBg8A/XWau/goN3gx4+dAlx+fzTsWoU9uRRUCQ2hPbjl946ZORjj9c44c1Mtc+cxk24r5bCOnpdRZGiTHaDKhsZTgsDEXur6GSBME+AWDnK4x+A5SrJSbII9BAGS/qFNklAU6jF/lcoMmP7U6TtKURD126Z09bz92l1LDoOuyD9MNxCjxtZXHZkDTa3iVmyLM5DGNsknE52y4mw29Fyh0y62/wnKeTh+xomJfFJ1hjzzEP0x3giTdHSvPm9zWnlEUrsdD3iyiw2ojb0uzEEzzkPdJnubFB/RIE1LP3vpevO1tu88KcPTKYTpIjbytQDutUAdBi/IMRWQiOgNhi35t6Uo1cKjBHYABWH4JoP8h2RSJgBNs4VxuLej6G2QXdy5eI/DXDSy2g36YXD+w5P5T6IgxtxretxlXUl+6Gf0hkUN5hQn0h8/NIZP+B9ElgZTIQXvscnzXrBE3DCccQpR2K0TpEFS0h0FFhzib/YuzcTrlGi/u4hkXVC4YCiBQiN1Gc5XWf5/R4AlmI7wgmSfa9mnUTati6JOAWfVyDltep71DubAJ4WZJ1NqkU/cjygeUbm7r9L9QSZOfBkH2OXfB5WPD295Dl9jtktvqlh1tsLS0yFBZYlnVpV4Q6lxz8iwZkbSG+qq9vJrX2UEn1S7qp7M5+l+H/Ym8rY5G7+ukb7X57ZBdWhM0FugVYM3r7WFaMXgOPGxDZ3mxjvzjfVpsl1GqTjEVJLv+nK/aBEz+fHR7m6RJgG0qzQnsvZcJZFcvyiYc5CgePRfpfHPAJLYkDB99jMpqsTlaLvFuZ4brVCF3DaTrH/O7JHO8Mk3qd5mlpSjcrwo3JJUYDJ3qv2bAmIh1oVSMWRdBbA2dNuSnn3tVN3lYoK9jEHmHOTjbXvN7DlW3W5jrwaIR+83DCCaXFND2/gIbuaQhNwcKUHsEISf2GMyW1qePxKCJ0qO6WhGTpkl9dIHixjcIjEBVA5wGowrCnJjVsp0gbAbU6/O8lNyQ6YvBgYilQq8BEKcuXuWbJFb0kZbLOzkuhnvJwdh2kzqY4B7SIrBz4xKhX1yxbZcuEN5jZCTCCskmeAQCdhGGELoqAbPv8kNULM/zJKvKD0lZyQ79AThJ7+VwwBgUwLYj6SIQjdYaARhQfxyMXAHygNZ5p5h0oPJckjwUSH4pkEB7OaTXxr4nqP2+Hm808uxVi+Cwp5e3dZYUZTVLVDgxX2dpSLfpD9MKZrFNlD3N0A4ckq7ZkWHuIOlb75Mstj3TO2EeRJnwlVsqmm/maujt5A39M9kQWylKNSmoAj3cu8ozpH3cMtALyN2rftMzu9d+W76TaWQIXAq7hbaHGRZArkhYtPly67WaFVJl5zhIsYsjAGlHx1B+KZ86cnnE6rc4DsuzvC3Ngmnq5XKJxyhbx4e3B5UiC5VP/+oh/4gIERdlrlG0bwMopKtVgZBpgz8HaBArWlQkMdeY51PQ3yJvF2+4yJBgIR2+sS+KfSx/E62/AwfupViY66Fyo964dCR6aGB3pa1ivb9lFwLJsEZrBbA6ggBi50Eor23hmB4OK1CHgJu6wzqkUEvPPjQ1nF5a3GWY1s37tt4ReaePcVovsayPrhGrTr7CnLO1kkqbJyJATxMe7c+cDZQsoyfnUK+8cWyhkd3U1dh297CXR5gO2krLvN7P727H+L65KdB9Emm0XpgD7l0w65y27Y7miSD8SiPGKxytP/ANEJXW4jrIvELmW1J535czCC4Jc09gQRJ3YtZ7Vo8yaqMJwgdAyM7SpJESTmJ9nOZ3ffyatUiT2uUrBsduxLi3nZklbpwMnjjIw5mQHUaQpRhaXw+AAztB5QIrgUDebNR0xYOHCILDWiBv62OU3dXRnXfMZqi7G7a5rfJliAM4p0t4DbN7b1u7la3FdwI8/mprjOdVeKTNI3UTK77tPLBzuBa7ixcsJzrgZNYp2IvitBryvlzlkmna1U43S7tJsQ5Q4z6OCsHlegzhtVYPnbBeq2mSqeTfFPhVh+ewZMvb2o6vpZuZlurKLd8U7ePaf9ToEs1xlHmWRneLNR4sSdmtvasR4qJo9XyfkZjA29XkpWsm4wKVVLXByhyCBDWmElBQ72poryM7CLW3VgVwHjSsXtcFSG5Laa1RIRM9DNq1rkshOdEmaVES6TpP67skE5SI9lJiXseQ8gliOgVSCrxJZaZCnJTCueRueKg0EgzK3fC2jXrnK4bfo7IdYLjAl/Px7f85EwcocyPo8w04MeBn9FC6u3RJ7VcMit3gsc6jO0vWkTndx4TS5G5JOPdxhxFkdGZirwfAga2hcoGJQSBvlm264s6vpP7BUJK3tefe48tVXoQQR8tmz6jqmrvVYDu67Thp/4w+953yqB6c5kFG5rZl6ZdEaJsiFIKr6xjC9gIOsz7DnhYnK4D3qyhNBbedU56m3/KKbslimnjI2hogKMpXPKLdsGFJr46y8sEhmEe+telxwvsajhDXLDyzmZGDiTsYBazfvpnpmse8NygOhqCaDyeOIZ3ILGqua365xfw3rLA79JxfTlSERAxbEWlgxqLHFIHixpZbp1HkxFWSONFfTwgpEg10irOe8NIQB90gbyvUJnE7+qFJ2NZautPfVQ+2SVvlD1+yD9iQOKfWoud+iWy+3tP0Olc50ZS7c7VxO7vRyS5UKncBVFtDO4BRAajv3S3/QcfK9bzhQgTpc2ih8rpMQFAHuUlAEB30urwt3anmL79M4jwrEAp2Qz5U6lOMqNVoBA83BRrVGiTZaZeltGF+z6XDJEWp6ewaaxlUrJOy1OQg5KHk2RTVkKImUoP7GZccbpdr2CiuC4xVIMBBPznrp0ntxe3oxuFJwMlb9kiFGkyIBTvHTOadhFiVltTL2lAhPki3gtcDXWKcJwVeN8vN6G6TGRKEdU22hsmMLX8hvEy5b56mjuIYleU8DZ6TC9B402qk5kO/ZK68GDpRfmZeydpldXbStX0yZQfFSuuWQ0LngwqVt0UzQXs/0BxIDweKjLLdN53v5fsI27h4bp0jndccijTqph0cEphrusgCQp1kMqHLu8kA+euztjNeCo3iOGg0eVuUSjuj0chsEQS+iNrkhKpYFMNbSPZno+2bCT7d/5agB08UJCG2U0IECZ5QU9zha/Su3RHC81gQ7B6kAHWt5NEKqzVB18EWCOwd/anqWgPgvwR4av+D4pe3RQn0vsjrzcSnzBPd1ZozuPHz8MyMF6GCLA5EgwbZhzzHJeLP9abzIMNybX7NAnHanCmDtTkL4K/N2054qXSK46DXFUrm2WvjZynjc0XLNLLkry7Ah9IgfeKkLtq5tdYUzVNrzT8HJSFvixJopmdXaFverzrTAe+Kn2L8aqe3s2L8Kpvlg0qUvCTTbB2sI2GwnUdP5FZKUGRNGu0pT5IkHHNSFwXK4qdZnl/qGruIhrDRdyhO1lH68sV5gX9RLfbyzd9evrgkGRR+f/k3Z1kYB2L5ztO3qJpTrVzWNxVWiekii9MrgtaIWH+1HtaoMZK2fo7GrkhjeG7oK9pzjXDU6DwjbbXOPCNsG5t1ZLghC1G2b2ykHPEqkhATIUrPEJqapvKWpyawvOWpqd3inz61J+WhyZm0k/VpW6FqZbImsOFdZ0u0PFpPOhL2FeDvqECY7QKkV1+h+EdeV0cV5qObukJzbqWFxsv/XM/wumlreoS65nR0e5ukCZbJYPuDzeQ0oPeDyKaGvGxcIKy/TjBvjW0nZ5bCmNio+ukmknR5FjNcoM10iQyi8gdayqZk0hGe3N9P/5Isaej0cZMU1JHwKc+q1cxt/heKpqcnK1/NI0fv0E3ifW2FQXUU03XzQ54uZ+APseGZGJNp+DjKfsyyAebanEXFsG0uTuZsrn9faL4mFzfRDMZFu5pSA7BPhTe13Nd4Q1C0aVyviigro5j8HEyD2ZueRWRkjV+gsk6nJ/tJtCGO9HkJLjY602gv65veRp93yOd1Ea+iEs3pwD+PEtdL3/xrqlPPS9sc2Z9jhbOpm9OS2b3G71CKqmf18pZiJ3yByNEbc7fY6NjiQ1R+Rg+tF+dzTu7ZxKNXCf2POsenNOCRJxWjax5wOPaEyoVICRDINiZPGTfYtACFDI5LJF1zDhTsfENfS7xL/5CUVS55MBECvG4PcYfOyqGE42QFqO2bIO+TW7qR0w4CArz+WqLl96RaCYPRQwuDMqhiOzhai+R8U/A3TQkn9J8rEjrLlzv17HOTIkrWM1Is6dlQBPeMKbft2QVaIrRGS1aJnUrfAmWhtEyhBRYGo69h/fw67rs86WBXKpJ9XAI+FM8Uu/RqY6SNBUhe23EAEqXHQ3mFr8GK0OG15QZNCSrMQ8CKvK3+GNTTw9toNV838TTvbA0nMaYr7FBDs8h2HwVZUUPb2gutTpnCQDDq+FhzOUk6bC24BqJB2A5iLm+ro5evhI5W0BDIRi/CBD9YnEihdNQ0t3aHGhpDt/soyKUa2lahcIbWHEa70YBgm85L4fgrmYNikbfV+c0YrQA992GmnUIEyk0cqrAou87SbGlRAG9Uh/AkrzczObUvMDE2CXEQzeG161ubJ8HNJcrIPnaOkTVNzTOsT3izxeRPnaydRTlwB3UfbtvfOpEDyn5JNnI7CQu46WhgTaocz7WsjjgiCah0VZbBey3JTA+9bqAc3kIy2YxjInnfDgli5583j7/5ovlHHbWj99p0ZEl1XiTx5CG8fUOTR+zOMZpZRjJ1hHHn22hsxKlJNm5tatptJcx13vDWd/lDlubRMsBzVouyQxbMjP+IBSQrUYfXcgtEtPV3lNytLLYumrt0dbZMETazoukDGRoFf0Lz5IXib6mhdFSWeUyikpedsQKfegS0kmSWn86q8vfvWhw0gsc1wEGksUHazKqmWz0U1LG2UNG1DsLfrvxM34LwsisJioNdKW8riDX4mXmzY77sit1VzHwI9tjJPa1GE5gd7IPiBhz8OwkbcLDulXUawHeQQQXfBbpq3xA8BI7jKI2yGHluy8rJb5nN5UCbSDMoA9mgaBcorE0FJ6gNJXD4E6fAAUEmowl43NSF8bg8qdFG1PQxRAf9J28riA1yVUTxD0zxmQK96SXdsLs7yjPINXz8HUqTe1Q8OVaf3fYxDq7jhV4Se2cb8icPBGUhrgcdIHZwBCANAhxDeaV6YlGGUEoHp7teIimdgkTSQYfo8/jLtcIQUg5k8gpLi+N+BbNjdoH+IG9GujsIRmgOcqCQgxAJxYIJQajtUpjDpwsUlXl2lhcNN83vBmn5F1G3d5ADAr8OqC9PmU1tewN2+rwxl1V0e/s5nyNFzdFynWT6q71//SnIgyAj3RYmr9wOXbFz3VEzNJHspSEIYN8JgnluNnNqnJ3gRcgvnoLHdFjZVPIfYGU7j4rWvrE8IWzO8xwqslMcIsIymAcyTFjIdlIsYZFEBeYhEhYxmccyjNGxD6p4YG7J7mKspa5ZeHaPIQUDdhpy2KB3jIWGoLVECqTvt7NHVnW+K7QCHPPKYPRdDnLo2yK53GBZ6+6497rA5UQqqqI0v3ulxHtYHifX5zDhv2zGiUAc1wqSvOosSfHAArzZjvn64UvWDvucOVpyT4LlnIDfScqVrA6JvFEF8TFko1rWvlkFnziMVo3OlAQqLJZ0UaLy2z0oh+qsOFVoD3rTVuFN/HjUtGE2U2gwmEySd923Ler8+U4AreGnF2ErT9muBA9jANpXF21DBxwTKL/gau+g8P4MqknFvZLz4vlFTjhr9hBbn53aVXTnvy3DSA6i5Sxafw30SJlurVauytfDVJbAcssWy9bREUwI3vwWFUmUDenaT/L1TZJFnhaxHvmBl+VtXf6oA7Cy5u59lagOQcM08inK6tsoJpNYnOPZ1kadhWnW0jetud1EOxIK2/dkGQ7Zh7DXpqISUcrZRiMboiU/xqiNDnGOyjK5y7AUJZSPFkvVGfSbMMfCXZQg8e1bnjl1JxfkEqztTbiSXsnzdqbt5fESVpnxjzDHPtTX+aWuvtxSpM15rzVR5UnVGOZQrfaKxe96jEOwBEyryhxrxvVDnpwYtC13rBrUchmsq4uVlWLXwY5xWI2Yreoy7FH9EGdNfGPhTcODPTi5Qm/RBHO0XqHHCn9ab6ZPj09S8/xRJ4X/ZXVGXrKqyNMQcSFbOr3iRchCvdrqVGdFyvOcy3hAf5BpHdsRqp2tBkPlEdM8j0q3AFjjWqp5tTRQ1pc5FuyQhPA8BFxMDquIwSpylCaRZu8UQFNvycU9WeoXX0upTfMa2lyiaA/cLm9LQjf/OJo9EiJNdGSe5sXlH3VUoPLi7mbyQTW+peW/sCSwwf3BY0eb66YzNLQoMSa8kKM4QBIlDxuSw0RVQwiz9mOS/SCBvtvMYWdrbe2OnWVod5oZa04r0lWCitYV7rz89DgOa41Ks4WJ/wxxy6ILiiZM5+i69vUyBL0YoA09JwMFpb5n3esx6CDXMIQguRKwkL7ToQlgDy8UKnqo2qebRwoRRnS5OU++lM0/B30hb2umWAWu1a+F6hRnokYvy/Q0I/DeBtIlijGvhBmE5lg1L5V3agMdMn7M7/L+UHNrDkh+7NU6Pc6XT3O8tZpnFZaX7jzqM6oe8uLHHMEBWDE9URE+qYsCZfGTv2ed4jx9jFdRdofoY6GuqBXrh7QROOCOjPBaWYsJr9MBi/Gr2hr2C6Q4M/qRceCSIY2g1GMZg3otpEO3HB52I09r58XTqw7JYUWVt6VZUX+ZZG3rJkbzcMkvk2STMM8W/jfX5eRjThD4k9Vkw3KWF2s86czdtKna+5isE8xiV3ljrXoH2xABK1e75ADaoWvP/BipbjvNlnhmg1tYdtubTxG9meu5y2mxHFSzvK2G/L4WVoNlOxsnB7+MuY9uhdaIuv1uUpctN61fvhqhOXCjvC1KqMn3GIH41MDPr2jlbZht6pTcfxzFPxYZjdK8rG+YI1xnf7UE40Ekgk7wZC7wUBl2tpGJWsJ6YNYTHaywQ9VWCOmNljUG+KY1oMbjCOK3Zq+bXKE1Nqe9gosgdAdFIm9rO9bZtwQ94K6u5m/Zdcdm7j7C03CXF08BeJlHdeDjAx/Pxsetcg/AxhymAxcfuHg2LqZTkcReYcv5ep1nr3hMBzaWtxXGfYRH9L7I6838bIlb3oIUalwDE0VhT+kqOIrTCxTn5NVqe48tCR3A6F71SA4SN7XEbdNhq4yBsw7yXSyHQzD/28PasLWeR6VhazCEsNWWgHntsL8WPlKYv+rrHwTwOQvgZVqrznEnajVU/oSPUXZXR3eTrWS3t0mauO2D+rrl8PMgSvK2jpbLApXebzq8QykKcMfDkT3l70E3o4OXkY49rnsoZgXhC8XFQ4Dw88yiqMT8170E5xENNcJ0YH15WxrNb3qny1bzP5UVWmM6PFAjYe7mLwjppo6Lms7F0DL3u2SNstLtIFSQlB7ZQVgOwvKchOUsL+q1Z9gAxVG+ElAdZEXBtQyd3tdJ33Lze0sH/nQGvRPa5Jsk3sfQA4F/waADOZRg/ilAPdNJJfeYFv2D5K4CO8ZzkFaFtIaI7iE8FEpSr/JQmDB//gvFylcCpwn8J4mv5veukxxYkfcedFG2m9njp6O6WuVFQIQX2HLYJEzKBVecE+lQlo0liQJYtXI9hmfzAUjBgGv/cljbmK1BdEx6z0JL+z4A6XrOQHotAdQJgj99zF2C73GtO/I2N4PloP3lbTFkIk3uTHim9n3XiXToRIqFI7PkXKeHuRbg2cMdKRhwwiOHDfqSH9sQpFSgcmVvg6uShowh1AmBP6gUeVtbdlpoPDVvp2nVKYeBRZznehMldy4OjHZ1Ll91OA6s68w7U0XFaLcpEzVM8jjs2xIb8rKuhdudCpFHfGovhhyqgzQ6S+NEmvw4jk/xlKXtsSZSXUM27YKv/AdKKBNI3ucJsmgmIY7JeyD+iQqDXum/RNmS3P2J0vTJF9dRVUXximx23pwl1hnZhtpvvWr/rK1trCs/o4fyIyKi7nn00qtMGONBc8rbginmfRZDRVKlDn+ZJJVJGH0S1OyYMpD5ZIXiH3ntlf68edLmlYDqIDQKodlKJORevpFxuUo2GwLfXrcKhXdRXkWPp4+IoYYrKoyou9IY7J2r3XgRJJS9Jff08VpD8UIFDHsN6LDBGWhaR3iJwrii1+sTcCsBlfAhNb9uqeYpNvGL7xOp9kM++4k0oLnmml9n8Yca1srOSWddkAtshK/PoyfCAh+SkoR4uistCcKD1pK3xZPMV2nRmdjRYDN+rKCcSXjoWqw8yJlpHUHOjCsGlbNwAnaQLAUTP8UpGj+z7CgNBNE5KpJ86R+lVkUpxVf6Ibqs8LQTkQzhlwjlfA10n2mRJVUSpbusydguGmmx63ENueoaAWr11RjaNjhDuv7PrZb5LZu1PndSyzQRcfJv+hYRJYtTpAfFQYFfCQgP6lneVpjryG6XagE8NCn19vJ59O1vq+m5cntIlVE3k6D2ESTregAf9I0cSlCkClBPUy+l0tClNHROSTVGdFAk8rbOuxyhb/xUQI/n7XactOauxiIvSywWqT+X8agOfLarfGZh1fgEwB8C3zVr5d1HdI9S7ztGq7yo+utqc8fSnWHwcI1rNnObPlHExAFR0lsEGnHEVFA//xWGEJjJUVGgYo62ZnyqQ3fDD/1RN08V+V3soG96fYhKdQZIF9K5XV2QXVlQXlUId0XhErWP2jjkisxuk7u6aHasLZ6Dype3tZ140meYapGJrXB5kSR6fMVgOHCsM8fua6JcmkbBhXNIvbL558A2CtmnN7ACXWbRefjjtF6iRXaZ4Caj3t/mHgF2HpUluTmGC6oggQYdwslpcZVUytfwwrQySxD/J1RF7Q0+lbkfrrF3iAkVn6G9eeYq8L2DC5RhlXxUfk+WzSGLF7YGyz/zbHo6NE19L6JNa/8LfTepj7faD6N58xv5cU4WuAD6pUjygmplr/NkMrCrSJUV1WUu5GtyvWZW5NPHTV5UH6pqc1VEWblOSrKSEoCXLxblWRrdlX1r9hnnoirq3v99JW8p2IqO6YEnNn3C9GNnZEz+T2h9g4puwUzWmxSd5yVmTGqv//7yJ2HCRjU+4Y4kG9xd8i4pGeCo8htxEhpyq6aA3nfqb+QQMkR3iJxuNErRexr6+zTqlrY4DcdpfmM6AeTCEnXEoIxszGHC85WahydaWOCCkzhJR2WZxwklzEhUsPTeJiSndrxCy5pIbvmDG9pptnzReAvkVQbHQjNzxCExgsYKgHIapi6W4t9f/l/CAI2a6Y/lh2ZGPedaEcnYkppEKGAVXuJZT7JKtJSTLMZSkRp3iMNgaG2Tyerb4kveoQ1ZJrPKmPgmnWjrEV7GNeEu9S1zWwMd7X57zXCZCfPhhtK8CZvo2ETNE2IFOeMNsPa8B7QDcJ6Swd/w0/Hbl6yJRnpBQptIDPNJVMbRUlxRsQAvg3GtfCiz8ax81ky60FbZGqv+o0Y1WnJrzfWZilnlVSB2BaFteFbRHMC17/KHLM1JfrVxCz+9erV9ntUPZQau1U+fSSfOdoRpr9kL6CYsO6qgYVhHNh03AalWts87p1l1I5mXR8H5MukCn5lg25zKCBvLYnZqjyXMfMqWbRVgZ4XE7BQ3q0e0NdULzKlJX5hqW2PvNrTmPKGv6163/0p5GgaHGHkMacPBkjYAtpUg3z7LqocwA5+q58nIrG2q7AxjtoF7pkzDv3o9BWNyL2WLbew+Y46HsAXGHM+T4X6re+B+K4zZnadrdSUPCDHjcLxvzoYCXnPNSPdQQVxOsk7MwEIyuu6DVnuXlI0ZfLTBk0JOrNrRJKiUMpKqEsRUHbwNUynbABjMkHG9SNNKug1huirTkaVvwVztB6BJ96NNAEOsTgOyQLVsKBN0vTLtH+QSEuH0k+ik1oxIN4OOMyKRST+6+tu2464x3yT3qHiiMbAaC2sErLDhHIy3MWqI19h+TrZ8qnoznxUG0tmIq5h62+as3hzolyQNCwgVFBzmYp9Jm7FYRndmpyAdxXxcKp0vky6wucl2gksNN7I8/MQ8usd7WdkYtsCge7ibJel1byO6KSq0O1oIGGJMFs6GK0H829jdqjoyA1+p6LwPu9yWqUfDGPGERi1JKyq0oCvPaZsE+E/V1s7oRe2I5lOQ2vk06Qpbb6c423BFh+rMxM97vLqrxrElBt7DVZ7mEU6yu0+oWuXL6wuEsSXkzscJ8Q0onY/6qhATj2vZ8K9Be9A+ihYG9EZeVtiow9N2jxkFXXfopZ2GoEG6sIBWZAFbMKfE9gVaOYIZRFk5R0ZbyabK1oS4zQqgZUYODmLDPsGAOQPyWM1ZL5xZLunDDMwjoek+sc1Y9eimeSwtYVlojBsKmVdpyfDsBPZnRqYCaW3S/qji9g7Eu/wMw0tOMg4QQcFD8T5LhMWhuIgZYCwzpnUZ+3GSkksLXQPabo7hg1OBQ29OClG6HKjRXj4c0n3o+stXUNGjv9loTxahGWgRk5B9+waUbhQzaCzdfBmth0zSoa3oqyZ7Y3PvE28vaM+H5IgyHlLWgvgVqmDDtOoGAc4d8jvuGucaDWUG9jWaQ5N+sDlUt8LDx2l+R27C6vlWgIR4tQOy4U8R8V7xpLT7M/ChdE72hvfad+Ove8ZRcQkPLOPA/jF6OyYU0AN8KGPw3eBD2QhmYkXZ/Jg039XZtkf+At0n6OEDSje3dZoRm3dUoHPOa6or/PRCTQdnva51ud++Hd3OcbXdwOZz5BvOs4VPv6m4NfYnL5AuKrTWGwICJMTUHZANE4uI98oQkHZ/Bq6UzsleGAKk993a0TOOikt4YBkHOhgCIHqAD2UMvht8KBvBTKwomx+T5rs62zME8jTVa8ERFLis56nVrfExwr3SfGDX51iLoTnYC41Hen6UlQ8kCiAXclqOmYKBk/FZA2LLbSxiyDIEGHg3eA3o+EzcBsyFkYmH4bfKa9/yirqq4rxYXjMco2IPSR0ZD7Lgtpwoa0rClTC77wZvaoYyE59q5s6UZ5ta2+NcVLTpBtuRdC5/MlD52Zi6Gsi/XA0r/lW3pjgZIgATnCCaEQkGD35+uCVinD6SBMxRelRXK8KLTaZEboIUOdgMasMJ2eQV7ZKzmXRgrw4drYY0g5a0mmOT/mz9OPIsL+o1dbtqGVwEhbi5h7JhXQC1FZ8GCciRd2IGzpITd3/YiGbsN+SjMayUkdo3BCw5iUO+JVaCezEXL8EE3h9muqb/b173VE42AyhlI2sOYpEC7MP0befWTFn/52I8YD5Mmh5q7YISa7jGQMk0Q55CfTWYZcy3o3wHdH1ehTeaD2O+2wHzi+EXvZXEDDi8CcYgl3EfyNc7woKSMcxqw4nzY9I8rbA1VmyepDeMJIaAIVZsX5cyZ0MQsXkAcSD7TdWLGRhJRV2T5sc1t8xR2v3AGCwgF+2nzwPu+2xct5c7hi7P1Ffy9MmHBPemeOqTV2kzlYG1VJnK2Aou+dzgBhWpx3aPS42GMgPTGs2hST+2nqoMHEmj+az4qRHjubi3aQ1gXVBn7yjfjgaxLaYdzZtJJ2iF7S7uNExHzaMcnHR5t4374/HuDwtKej7XCi/OxT4xmy7diQA5AcPtY0ITae9nZbs9TF7yPrmtTiISEIG7vIpKtPyeVKuBg2TsoqkHsWVXxYYrdc3I1CLE/eHuthv2agbeM5wGI04EMWydMUdGRM9COn4Ba6mY0tVqVDcIsKdMCravQ42GMiNPK+fQpB9dnd3i4a+siNkx8qjqbNw8bnV/DFHzwWyLqcH5NOnMqOJ2zdbPeYVM9kgDnNRkJSDWJiuDd39YU9LzuYxVcS52f490gR6w+JznGEHZyY/W966qBLEhAG/DkMrm9spLbzKSGbjVZP72woMPDcTMENDWNFZ74P7HpiE3gfFKekQSVtJbc2oijcFkOTNtrz1yWPdneYE7PoO8wvOw+4tL1+9mz9yxio4vRtAqprP1xcENQLn8JFy9OywIDmFGTgTnyKT9HsF2wwu6jL1mESscdMBgAx6zT9I7L6tZ1pO5bGcZjU15arMDkSsXqKqLjLx6hUxuRsDgsDnAQNpZzmATe2Yzq8Ywi7Wsmqe9sJOHThvqPVmF4Jf2QipAK8skp82eREWzlh3X2TJFyrMZRR3YShmD21kq8qbk54fMGCZbLAx6NosZop0Kk14MtbZoInMj0S4b0hqTM+F+rh/aYWyDX/dyFRFGoQurkFWYnFP3MdZCN4htsOkeRl6Mc+PpOBSE1mZOdE6XuJeMqRzBDFypnKM9YsnLDYqT2/b2/1FVFclNXSFTFlXXVrAsXNGBhTU92EOWNhvRfCxuNsf7wPLwSL5syJ/XEo6Uru72uEADQ4HGythw6A7kYzYSy+2Lisdw57BY3HnDyLMIYtjRhUTJ4G66XU3erS05ym4Zyxos+tuXOP+xb33FMuEbd/lr8GxbCrtl+Sq6U+SHE2EVcuMgISPMchMMF4fLeNbi/BYVSZRV/bSc5OubJKOA11hikntUPF0la0VeWzs8CsIpUDjQ1LhD0N1LduCTeWEdOzqfXrCdU5OesfW2Lfyq8Rlu6AxQ7ATH7/H+zmJYuyEae7jTMxjVP2pcgjnja5Z4SQWLZydEY9QhQD5GA9/mYgB1dDc4HppTk56x9XaN911XAAe1H4Chn5OC3x2t/oxUeTcUvafOFMF2GFzpjhPGuG8svw3vm+18WwjBzjjb+MF9i9K6H6HArZasqURmISUUTwBRUfdHLjY6qd1Z6TEa8PYkyYg/LKSKR7Ft4bLaGpja/y5etJ2y5Ldsrj8Lm/z6MlpvUvQuf8jSPJJnNoDBA3MXhxxyG3b9nJy34L7MyF0wpY1OA0Y1t8ZhVwkq8KhjNHpxRMphMDjEYT2kDY9J0Fs+f0L5bNtrtHooM7CoeqpMOsDW2wEG1TkiBMgJ2HIf3QvS3s/KhPvrQmDC8Zufhh4xWT3Feiy/9GCwMkvb2ydmNRzLfEu8bhZNejKquEPsbM/I87GwC/OGtC3lXdkK7z0H9dnv1M+StCLZ3fXX1CxwGPEkV92LRRVdcbnWtoO6Vz/CbciCngXMdfJ2r8tVeUEMo2QdFU+nj/Eqyu7QRVShk7rATcRP8ihiXU0wZphUsgoO1rYCbszavk+jnY37NANnGs+CmbaWotkNBqV/2HHmqEp4lhyj3zIvgp2ZmwlBgltw36j+1tjuOIp/LDLcl/jHZX1TxkXSxNpqbxPrKkIsKKljw5TaZvfqirHpaGbgbdP5tHFm7RxX6zZhmnoz8/Q++hQMx7JFht7D3d1RnALvtku5GAaHmLeHtGFXCXrLg4Pts6p6HDNwqHqe9ubU4Oj2NknxF3StSw4kQII82QFZ8aSAefZH/KRdmIOTZIQ1aXvb2c/o65V2xqe8ivSBUtfFWdHUlh763ralqCf+XtiIeLz3WGI+YdaP7tD1WZGvtXynqAO7RFlwOweovKH52c6gN7M4J7XEN+kFW29XmO8qt2W9ocakjMc0s3W2E/syP9OJZDfpw1Bri1uIKrnHk/Mxv7tmfpPJVOwjpHXgzUQPYredkLcCGXFc53dwW6Edzyx7C+3cGdmG46o7wb5aVQkBT8Sw++l2VI1gZt7cS9PxZIXiH3nNh9sLn+VZcg0RgFlzwbpWOXRNW4eYWhjjznG37fBm4Hjb+TaSAr7yFtOXx3WBSXF3Hj3RtP6LLCF4dQ+pKGrByczHFezymasaM39JJIhda9SZGbjSaAaM+sHU2xku7N7MEdjGlEdkCEx40+mxKsPmAW7Vicb2lbLt6LbA/rr5NukSX3dr0vAxj6M0+Tdadn28/hhldzXxBsgYUF4F4ngB2obXFU0B3N33fOe4Wj+OGfhYP28mnejqbJFjDbZ2ui2d5VbOdQu3E7eetrR725Nd2ym9UYrrVLgGKroou3VUVF9u/oXiihShRzzfMe1YlGV5RbH8+rVEJ2lB2KD8/WVV1KLKJKgvUdUhfcriTxFNgF6+fNGUsoF3QzHAimNUp4+bvKgwMdKcrh8QPh7GECmekdskRXKMLYAW3WW8Qss6RVdR+QMcL1OuRcZe/YWQje9Pa5GhGi1P11GS4l1RFK9kJAQBtei7C6QQxuEernkfNT3TzyvtfxzntYRRmHItstZg+YSqVQ6OcASgR6eJJAdb0N4gsG3UqBVTtApketZJSuk0dWVaJCdRhe7yIgFluC180o8miau6AHG0RaYEUWEaQRiTh7yVlRRIJrYAmB41k/wRxDnKu2k2cOVEjGEM5qOB/xRl9W1EyaUSDhZMi1qH0wpZt36DzNfbSRokbawJSLg+PEfXEcICMimgZXqid6+HydXdGEKPENsO5NbhPbaiwJ6NAIyJTY0UWBg4EC3K4zS/O89LEFdXpkXS7RIgJMM2TYeEbFjQZUX2yReopCYkiBCA0yL/jB5KmervyvQymafpUVY+SESxLzVCJENhzATE7lZxQBNeqesJKtZJWWL7tok5BHvFwRhYlnhZzqL0qK5WxEhv8m3LG1DBaxujcUcyDu4LzdBc5ZskluKhpSaIIhkGs168L/J6I+0FLdUiok5XCEfrwTZceb+S8IbGAwZrVgBQr2bfJ7d4ISyWWuwAoDl2FUIz+skURl9ohuZzXoGj6wu1aMT322F6ge+8G6x5MstqeLjaEImMYuMntrWjZR5IhYc5er9W27fxK2Rw//jX4szMM/hBAYWhJnvKQjcGxcMF4HiUL1Q4NWbejCnx6EsHUkrRJwbMECkSAisaUOZ1dmvYojXTJkxwWyMF8wJadL6NLdA01meegTAzWYC0NnQO7ybbq6tac4nepZRsDPrrqSadUHnzmHI9YVZ4k0ppegObcCMAvQkPX2kCLXrZtTSLLeMVWm/SCGZICM7UgfGkQszDGOsYBU4ORG9YoAxhjlVKJA+j3/p2t33Ave9wEUuD5muhQNMX6nvT3RgBezNcwdGxC4pKzALfUXK3AlfwEYApuncJNiFKCWfzMGamtk5egDsiWr5jI4NhthtHYWsZpA+Lg1mEiVY0R0WPMdTomjhareyuN1FyBxKvKzOYX0oKpWIZgxjt9z+iCu8rdVMMQ+oHzsdZgRQQw+Js0UqXZlnAn9a4hsIbZNsJSfiKZRsmuM3cQ8x5NtxlIP5A21nCUEuFN38MoZ/CIi9LXDFVoORhDEYPSr+J1ONflcxqaYr0Bkv0qHRuD+V6ekt9LLB7hTnHlRxdXo9PFpkK0DnmGJo/cB5iAOSV+gCDfmTiWapwkm2EuAs1YBBzp6p8yMCYOsaUG06Jr/vjXRndAFjd4MQqcpqNDrW1ZAMwA0RTz4YD1cCD4eszCd0U0PLxyStBtJMdaSsIqGgAICFzqB2Setfjw2cl7cawRgMbVdHQzZhaY6QQu40P3CfitlEIgCnTsZVsWYMlwZQsyLYDUFc1ZQ7EHR9BX/dH0SJFJZDy4cEVINoJJ+UKokmwApSSoQtApc5K0VMJSqWjHA+XQycQlbgcOSLWEFTqbB4VFwkw8jHwoBBNWDNMQQ4B1bTs0p0CXR9tNmmCllc520+RKEp4+ahU1SBiMeE1ClopsQJ0k09BEMoN/GlCtx7aZnxdpZA063FOK3J9w3BskIJkYAWDEUL1AhAORAsZYuA4Qyn48cPKcvWueIAZUsPwO8sGHKFHBpFoHMsVija9Dh22tlL6iLDaYQlVFHQy1PpSzFNrMb5hvb0ggJqPSm8xOJJrHqOBPSJRGQ4gnHwwEDhEIi74T0EeEOMsZueo5XE0opSh5HW0DCCtqmAxczJqGwFIqsQeiK56GQXB7Qaql1U/Qs4jsuOgz+sLVFYFZnq0ZGJORSoa1JIPVl8ZoqkQv6ogp0EL0LIxjNifrGzs63UfowtQEgRUDA2CB+nFheeqyAXinJhCXdo+BW14EPkIOEiIHkwctoIUPKKZiMCFSstJMQbUj2M8td5kGaODfPpKrnPxQ3T37piOAp4IEUrhQBCAQW/EEPyvckaIuACqSInsQ5DjJE2xjuvvECiowoEaDGdcIwB9OIQzEam9KTDcslVQSYDVj4qvoqLTcK/BgFwCYkgPySbAgV7QvYPh+jxANHUF+QCV9SDySW5OKGiobgIgJHPDw5uQ3bUSJfFEIPloBFiISMxFFwVhRFQzEOMkX9OTkOG+DUwPAU49Dh7cW/JApAB9pKR237pcoPsEPXxA6ea2TjOiHUcFil2MrqZ2w6FB4E1Vw2YU2k3WjAO5u7tQStkUgeSjE2AhejG3sxSEElFNLJukwY7fh0tiMD0EOPU4eHBvLgKRAvSRktpFNvM0VTLKGEAhAiwc6CxoLsyppGiEYmLGGK76XTdX9uChsyDqnjOQsuH3Fw81RGBRQW4RiJKOJPiWV9R+oI8VsNcfYXLIwNXjkdTyV7sK3BK6SabAhXrclcpr7lInQEB1DcU4lRVBbhPvhKroqMavWLbasYbb7WmJKIE02JppiWa51ZudSKo7tsqtn1lF+ZCN6sPxgcpbxMpYQZMmJ94u9lePldQFoOTjEoEhurEXohVEApDNQREa5asnCQemGcYYWkqULvRYRxUO3RxkuWbvdktowsJoRsCASqgR6cnAIgFoMLqrHpI52qvySs5oYIzmsRlLGJ5ocMHEECjqpTTYjAMqtcHAmcg6M5YQqoNBJ+MQmMAOtGnyz+o9uiCcfDAQOESbLqOCgi4gqom9uE2bKn3KQei6r9KixjSYS3dC6Seuh8xZ8nAwuIJ8WMp6qnAwPseFQWwY3IYiNmwySrbJQgzJCCT21o9vnNd7AgKOc3lrmdlVBKm/RUovHkQjPwOkVAr17jIe0zwkUISViEAmnVeEkFgTYp5wkS7JzPU57vEqKtHye1KtmKQxIml0VeSD09SEyMYkyFFQTYdYxk6hnItQ7p/rIX+PnIZwBf1AwXoq+lloJnUbACmlcxSKkl/ZCTUm57iW5XhHlack7LihyfUeSeGkUf0MiEZPDZBSjdfmk9JpPAbTlCQAck4pzVElvHxIqmoQpSQpsxREU7YwsR0Lta2VUH0lu8E6io0lXte5cozbpAdiUhJyEPJxjQFlsZf6K5AcnikFk825dj1kcpMTYQyoH8MIXkUSvYkGo4Ri52RUdt46d3GwWo8CD6jbA3PwXvtpHtfEToVRgj2NNgchVQoCqgBrhXEaQKXWAZFOrLSHzutZSAprcFZkwEiWp08zsxOfXfGazbIO6SM5uEqFSGvBuknICqnUT3Lk8s3kKON8eCqq5FIObDFIlXR60m8uGRUaVvgnpLAW41J4KzwpNo/vYhxzpr8jxAEqwgQg+LCxc/OSCM59akAyTUXtYNX1FVetpHld9QTWtDnx9StFMttrWQpaQLwd0Cjk0x4bqBTUiX1VCsKhA5BBbcYUweVEnaHYVniU2Fy5W03hGcVM2RHjOZUwlUfygC6BKs3jLJ2yEZiWBCy0d+YAFpdcRzX9D0UVRVpp02wLxii0FDDFpKC0Orm2fhKMuzBfZgdVl/Trt0ltL6roV/Ip52RWI0rVkfEbb07TMULhRRUW05YmZtQFMFHa6M28qabIQULcxcJBFpzovF2u71pXmq/GdZ0HrzRUp6C20jDlYSakP82cfC0ptJgLNR5rMinRWcxRl0LafqLUPZBPmpZLPIxOw2XBUvebKngzu3NLqvr6MlpvUjQ8OiqnDgepH9K4gjeFOHQTZ6DtHxsZ3buA6COBlA8IrgDRh30ORUEhCcIZrogMLSuWeBHIZCiKZdyaLLMu1Yyfv/mpt36kVbRSIaupkDbFEYNVC3MT04qMLgScknRbIhr37rHmFM2musXg5ViMSC2+92xDeUXjs53CkYeeMAck66h4On2MV1F2hy4ivFXrn5ICXM/aSgo/r64unNgq12VM1uMFl5zhMa2wpKR/GNNwDG04yFGlEFQbI5yaXJK3u5THwNo68pHqqoI5VqQvkamyiugamviIWNa+YonSVbEfrGK5CkrUeZau/t0yrb0tgZQPDK4AZm1jXlZTpW2DEc5gb/fvqV0rAodEIMVQeFiQLMwTbyqyCLgmDhISnlrTXxK2VWnyStLLkOYSp0A+sQIbPyh3fVbkaxXpVOAqW0xaCzb8uGfwlGaeHPW8pLvKLQjHABuPbagTmGgM4olJxrwQeC28FggpeDm4SilLa8Gqnn0jUans5Xgh/SY8mBiUfCpmA+HMBqZiMSdKzRZQCz5peA28oggE2JrWlQ/ZFAUYtSZ7t1EVjmvaHkR24PnJAGHf4zcZrxdZUiVRqriBoqogH7myHhwLLrwoqQwHV6Gf9jIU+GLmtfjapZ6Y0rrmA5ehMCGx6aUpwxYBqmsn1TVJK/v+pz5DKwwtH7K8kjQ3K/9sqYKgCuwTJ7XTrUiGK5FuBdKuPFOsOL+9bhCQoOIoyVDRl/32mjyxuY7aD7+9xiAx2lR1lH7Klygtu4Lu8fehZvvlxeUmiok6/x+XL188rtOs/P3lqqo2v75+XVLU5at1Ehd5md9Wr+J8/Tpa5q/f/vTTf7x+8+b1usHxOh7t637jetu3hGULzzZXStKtL9FZUpTVu6iKbqISU/xkuRbALtdRUX25+ReKKxpd/cjpkt96KncNto66pyzuhg/MPKlDzKOuEvndViQNUi/ZK9KzVwwiHs9AzzM8RKIO6GgR655T1cb1L7HkREUnN23fF0tMizyt19nwN8988tpN6zyO4as5pkYS/o6exqiYz7a9Ir+hfjXfzbG1rCCiGxXYjvRDVK6goTbfLXpHpX1BlsxR34bPtriarNcQuq7EFuNxnqcQvua7BeUwg6Pll+xrFXOkYwtEfL+95kSHl9HXgpByipMXfCO1ID7g66MbeGwOCkKPYhotcd68NMwjYT6b4xLF0Fb+TrPoJkWCymo/muNZlOf1TZpwrDh8tZCSAkUV+mey4YRk+GyOa5hdwko8ycVSc8xfS5I0kqf+8NWCH6KyfMgLnh36rxZ9KjjFQj+Y1/+A7ZCrIsrKNkUrRDMZjF8r+jZsWiDPVF8+lRVan0cVt67wZRaSQl8iHnzYI4HhyiyxXtbUzAJw9iW2/WwemBZsEa7MjkuTe0SsXJFR+wIr+bkseXZtv+3YmnXePQ3vv2C1qJxXK2n9aZYq/+XlLE9JchkBD/vdTrDJLyy6JGevKNmjQqsl+T7B/WkUg9hbqHz+JZZsPZc1yRZ5FZU/+JkVS63oikkm2LnMZytaEo2V5JlAw/67/czg3cVtckckCZ4Zttwc+wUq67RaZLf5GCv73Rzbl9vbEnFKvPtmjuVjsk44JO0ncxzHURWvLpN/c2zMfLaYAcz2RBdxdO+/bnthOslJbHCFlqr1WQrkYJYeFfEKL3gS67QvtcCcoiireXO3+7gzi2GrYBBRL56OHQaTi2dHWX1XV8KjNIk4rmw/2fBgnmFDgDCvoF/5MnOsogFua3AHW+GqfPMlOy2KvOAWN7bAYtbQY3VRix4S9ruFZo5KzMWYlQV84xI7jKfZEsTXfbfsXx3HmAPgHjJlln0UZ4T5bOMk+JAsl4jj3OGrlV1wR1gdr0Mx4t19QqE93jZSAsbbF1pYGvnDN1SIUst+3xlNP75w5KPpVdeqDDS9uvquavp3qI/q4v1NTIEFvqTcpNFT8wojh3FcZI2zPasHULYlNrL9Dt1G2Gzmhbv/vEP8jerOFKyqKF75+6NBlE4cb4RnGtZnGucRcUVWSzo5hCQcKgqEUGjjj+I9e7b+vDPACX9m7YEXt6S2m1B/hfMpWSPRiBu+7ozgDbc0fWRNdg/VQLzkVaeRqK6993XCnz2MSqz8pl1V0d3Pldn3U0Dpie84yaLiCUbZlVmePTeHNcDZc1dgpwEy0TXZf7XYAD3i9kXrjvlss5B+Jk/UjhdR+skGBz0/SQTTeFRgwXWbZVSBh83jkp3RNMx6FWxh91vO5zteTvICt89vXbqvFuJR5GtONOgXOwzi8jZ8tfBRcB7aKyvP7FUu9qL7ZuMt3qRPfEf6j9Z4xC6NCizU4gmnDU+sfMUxJ9H0g4WBB51dOhxbHudLjmebL7YeWkhJjUssxkYWlSaqdTS64bMdLihYZ/i8bU867sryU5TVUZrycWejkp1R8uxQPU9nGUwuh7PK6tPo+WZREafewQsgaqJRgUVoR15yiqD5YrF65QXvzKNf9jswp3GFC/EOzGe7DUDjUcFabYm5MYnSUhgsBLIzcttG8H9C1Sr33AuOUDlIrqb+RCYa26g05kAGZLMridN6iZbsVeTFkg9dkgE5tEP0X/Ekb4Ipt8fePUPQUETaCABms8OkPaS4yL6tDWrmp1sBZuO1yZJ1vaaO1qN18wTl2H8jlltgjx7V2IFyi8NMWuMClVWR0JgOCaFUcBbbiDpNpU51oXB31Jw2nY2X5tNgd1GG1iin0Y9DB3g84xILfu0GIjAoW+CAj17ClCLtS63iqAt0iVK8e+IP1Lmi3eXzwIwdgpNnDO4nDQPB/d1nWzGQYBQKbY55mlNV/oSn+2q1f4jFpWX4ao+pPbIXjt6h8jmPWC5X9K6IZA0SS21jL9mb2WIIpiqBrhzzh2SJrlb1+iYTdopckTnOb0mZ3PCn0P1HCzkh10LKFa/imM/bPMsO70naOwf6eZffKYAqd1fgM6tt6MILV2SzyyvwyN4Xeb2h97PBdUECYy2Qi2yZ3CdLwHcHAjwT1em5L5Bad8t1gncq67VwfDYusecvtMb6SLRCgWIr+j58yT7k5OYDv2zyZTauvKaOXLnCEBY7VFRFf0dPD03OtdHedFRih1HKD0KhHd6rpOKXPeazTRhwmj/0vhb6EpkQFQxBWLSw2RT5PVpeRBXWsJc1d4YHFFtog7zSoIch7Ht/lVdRCtMHhHAag7wRKZD1wdhVfhRz1te4xPJqCOlQl3pUuCTCFlr09EfNdZB8sPE1ZfVtFFd1gQq8olWfsWHJawsZjHkr7zE3jXE2X2z21t0D8PzGevhu0Z+2DmQ08GU2h8N/1EmBvlQrksG8y7E5PiWGIKxbGMwNGP+o3EJ+a6y3sODHxNA4Wi45bLwsa6GtAlD7pwy4CNT+u30Ek3BnnPluYf5naSOezDsFo00AUG7l623E6cutBD8MYU+N08dNUtBYzXfRUwlThoexb4Vmk6MYwFv7UigL6yYq+dcvRqaNWGyhTUc1hWuaQqlVr8k55tFdgZBom4qldoemfcUrkupHOD3lim3ksk/dxQsmU2Cjv9pKJ09xij6i7I6PBIYhXFs4R0WSC/Mog3FohRoYFI2giSEIG8qTwzDwchZXZIPzDPNBd8jGIx2X2ey4lgmR5Cjtap+sSC4vfvclg7KKjIweTx/xTkuMjBwKbPCdlgJt6SeL8LTo8QRv++7yQkgexRVZ2WR4b7bI7rHE4srdaahglMFAVvH98Q/Yo8wVWTv0aP2j+yhJo5skFdDLodxaggcBQ1id8iqwi6UWu4H8oRl7m3uVn1yo3GqXlNw+UX/HWV50/TtGeG8q7JTkgBaHHVH8IycuBJJIRDjs4Ast99vypPjCxts8f768TeoKaY/v4XmHIWxbaI7wVS3wEJZ0Q8u2qhACCRRbSR1Zd47rp+O6qoRbckKpNebvSblKk7JSoOdBLCjT6JoUYXY/x/sw0TMGQ1gcP+DdEK2axPxFh1GJVVA4j8oax5d0CaAZvlp7Q0+EaMFRgcUaRF6wjlKgd+MSN4z0Yvc7vAiTd4XF+F0lpFuLp9nSqD0ezoLDWvfiaUbiUCBGgwCsY5tGWBDEPkpA62gng/ZUgFY7MfYlOm4bpnqkTo4Tr9eoBB/zBcahBbYaS3e6Xh5tNmnC7xVAAHP831Fyt+IEvftmQR1gn2e/s/ueLHkk7ScLegHj+WA9nn6NUKsXBZhDWyrFIgXa5kF80DABRDMAcR3rPu5ncIBirMk9Kp7IJIpZL8dl9oFDJB2DeEF8XGZj+ZeoXc4AR4VY6oD5E4rKukDNI8Ag9hGEQwtQiK1Q6ICX/FDiZgFsQ8vo4aXo7RMKrUPWUEFi7mAnkQTEsQ1CA3UTPYTzKPLG1RTxN1MUYDsTxtMv3H437ruIPvtAHnnVaSJ5yP/94lW6HoNnD1yZlYddFtvIFe1K0GRXG1I/fJmNv7C1Y8SdzajEHGNrrwj42O/Wp6PlSV5vyHUFPt0tVG5hO0iwumHrZoEe/VPjX8avYwjLiIPmjA1bC1DEAVu4M1qvdZknnjl7O8+7g9qTV91VtUfi5UQsw1cb4zNs/qvjvMKbdSlWoNjGzRkgK2M717LoPqj8ECanxtUExspOxcRSC8wJDUISUA6fbXp5h8Q0u8NXx7DA8ipvrvnA+HWw9v3/Ah2HCIWW7uYL8kQ4j5Mt2HqI68SOr/0JANxFN8vzvtUhpx69t/8tQQ/iUwtC4c4YXK329Lwt0iBxuS0iqzmNsdU2ByU344psTkxsc/hJ1Q7K4cRmowKbCJdDPjKx3O06VRg5GeFyv1w1t9QEvBob0ohzX2i27NNrXRH+yWsBhB6ePiWWaVira1rm/nDDxgzkou5ejRK3yAbgVmEAsMdtVGCxd05S8iRMn3mF2zoLpTY2eRuCCaMGim2ccE3CEGLNjvPS8M44OZxLa+fSKxEQhFML49RHslY4KJeWugkA93YKMLdZospANT09gIVj97EqIjG/MvN5d1Qyc6bqqYsZTC5KWFl9V32PuG5efECPNF8K7xkfFVkv6zuSNX+7Zmcgf/gYm4tbXIth901PmU/SzRu5KM9QRGzWbmURblTwxc+fW9mrokEYlkXozrRqLLvPuGz/gYsgo7IDAzsxcDjO9WTZbfCqvxkQ+uCQpYLscE4GczigU+M6HKPt3zHa4fjouRwfbSuuppWW9r2VIMZZk1nF3SyT1d99gwxQxdZquBm9GMPLfrfARjP2cJjabzYXJNLNbZ3+FyrpvXX+pgRXaI33cy5H25eFFxkNdy9K8vvL7X8TgsjGMvPfXYLJxijm4vWuWWGny3y32Chs2meXuS4Nn202HV1yJn63MXz/cyp2Sx18nOZ3XTo7e75U1p6IJ5vmRIU3KrA4H8FDOM9LMb0u833rszRa6VpNlxGR8Vw0lagmXT+b9iWr6FBojvd7VLajGaNkv299Hj+jh9Jd2pS1p5U20VAZl2xPfglRoETx7Petz/t5nqbfcmJGXaCYPB3jILdaFBPJK273KCsfRCtgXLJzWxTPyP8Wi4exNreVJj4xOi6xuqoU6AWlEG9UhXuFqatzlhfrCEjFzJfaY76M+OevxyUWjqzgKYhDJ746ur1N0gTwqI4KLPCRNEW8i679Nr/7aFE28Tvdo24cxbhCi6gZSRCRW7TQxwhbqPC+iiva5m6IdOVjfpdk4DU9sdQOc5feSopcANiZtaqdHc9T+W6O7Zcqac1pVqqzpCgrkfWZz3YzL6Iavs67RmF1vImyJ8GibT7a4AFj6dzi8yrM7/jbfZLFgo4WCi36KKQaO7FNLdaw3Rt+Xeu+WmN6C2J6a4Ppn8mGbPejVLybyxVZWAWrPENQLuxRgYX8dEl8OfkZPs+h5bdl11MZ8L7R20iSi1kvq7mrh+n0eLNsI3yBk8+hyBYnnNeDL7M4B3nIP6KqwhvJMhfFTyy1wLwqEFLhBsot5qjGm6skBjHzZQ63Br9F8KPabcmWjmeDH6v6HkpvSR+Nnxn100pjXA7KSYdgV3VU6ICf8Ny5uMswqZs00/zJ1bjIJq59nmd5p3jMdlvSxtqrnsLGonKRNXX9qRzx4TYGAUyLm5sC3SeRKLfjkn1borbE3N1Zox9fd1gcz3HhqtNw88cou6tJ4nkOC/t93jCa43zJbWqbL7aRh41bFnzQqy+ywNkGM7R1T0RPJAxhoQuGZ67kjUiBLOYouuOI0nyxcmfsdN6wP2cU8FQRpXvnnuhUl58O77A46HB51V01/rsen9QpTUMALgV9ocXxZZb8UaNLBOzKuSIL11sa3S3WuD8kgYY4dKDYIuiy4tzP9MP2ZfBPbZM1d0Mvq4JGXJR5XfjuOyCMLnJuhGYf7Lau96I4jUvsMQLXiLkim4PgsyJfn6f1Hf/65LjE6iA+r2NBrpjPOyMFXRSVH+d3WBxj3uCq+8DhIXYm9H1szuBtPlmsXnXKLTHNl+2sBbtuyR92cqp2ds/i/3PuyLZ1Rb+P+vS8/NXjcQyGlVWeZl0gLUIBsHPvmbpHfL/lFS97XNEzts0J4f25z5Hv9tMS8ee8JkCx1ZpQ7GJftCWrYpIUwXTBf1+jssLWOZEswB4YF2/zsHLXLas9Pepnj2DDXCsgmDyuFsDVd9XPJ38v2vW1aEkgu1MYO67UipuYfZcvmzuYvYszp/MNRqA3JVOHn29rnUfFOilJSEJz58lzzeewuaz/WhS7KoNhrx306dvG+7T+685w0OljRS4tpUd1tcKNJjENTAjBTSrMDpxlh24aLgt73T9EWH1HlcWS0OQ24e0kqNwee6vidY0AYOZtfSETe5X/QJzTgP1uie0ojlFZynCOSi0schLXhK1Omb6AyndG2s/yol77B9L0aBzkWFF3GqG9yjdJzKPoP25L+MXr3bb3uhfncLaQ85240bZ3iZsoX1K2CCAbFI+rcEgqTyMdtEUeRf9xa9JBSAC9LjkqsL4xAF4WsHQuNtqT9yu2X80xkRdqODTtJxsHWFlBmVnY7/bYZDMJldtjp1m3QbxNyUFn2emsyF9ZueqpWVXU+yKvN6Ce6kv2+VrG537t4TVL93kbSoqIJmg+jQoOCsuQZw75Nicy26gKCGC2UTyu6lBSeVf9Xbt/sWyXuPuZndfQufATmGY67WVFUm8aMaGNQRkBRgWW+MQ0Usxnm6NI4D0ghzeAwu682ivvrQ9ByPkolNocPzfHWBLUQLHlvJBrh7WAlyuy7y+MViy1cCE2b1fBiIVCa7zNFWepf1IGZM9xJ3VRoCx+Em8RwBA2LTT1LiI+nmBcYt/n8YGlTGZEKIudfVRBGof5bMvX9U1FUjYvsjjFHYPYm4dwbIFccFa30EPYt0DTTvfPDarGAkN6tqgcGwxp22KrEhRj4yEcW1CMhYdwbAHXFWUPhnDUT1jPJ8TGjNIzhECSGYCHaBskpgF4iLZBMhuAWwVvkCrC1dLuqyV/wFznwmmdtAHohiLb3olZ79nvNtdkbutsiZbQM6N8mQ3Wh6hYnudJVpXfUYHw3PKBfRIQi3VuheIfeV0dVVWR3NQVku7z1JAeLZb/uU41TTUg9qu3LOQTKrcIRQqcp7O3vDcSi3xjHWBJ9gVY6eKNL1YHJ5hFIBNDDWkTvFIsxVCv4asdJtEEHb5aYgLG7DbCT1H5Ay3V1JTB2PX55P7+rdjj5qsdptPHTVLQoJdPeVatRKQCgCv+/0IRQGW+3I2D3yUFiqt36Cbhb/fIgGxcUH21Nu/shzwF3FEyKJ+WIA6SQzm1dBxlP8StFgjgjF8UVhDADf/iRI6alDlh7bIOyjD35U7YFzcR7x7lC+3XBWqTgJmrYQgLSauxzVgk/6ZielVEWRnF5KewsCng/FsTmVQN6d/iBSprPq+4DtZGO25IhgUFPWEInxagEcmhrCIheitPMSAFmM3llSJeRSWSemFBAJt9VQLfvRgV2Hv82hyBoL+vK7PHSjZcJOFxXTVrtNJLZ1zJ5sAnTN73fTiWYbdRF2gdJZlws0ICYt7Gh6j8jB7azfrnnES8xkC+NwXYzhwHdRvuryXeO31Iysr/IXQApUtOYTM00xwe9R5IXmaY75YOCvBwwnKp2Le8T++TW7p/CshcAEoX5jJDMw1zdW3zWNjvFhq0RMvvSbUCmUwotMMLpIphPv8JGDcMr3rw52xX+FtzjOEW4N0wOZQ990NncHyZxe4A8Nbae2kXZdcDeh0xEmwmEMB+7HhjuoH2SlC5jeUTJ5uEmBqCTckVOeAEbizxZRZ2McqI1S+avsx3W2xAB0cFFh5CVJbCLfn+ow03DWSnxp/ITQDAM9aqvcYIEH7kmDJLUXfCMCTSoCR0aCia3548D/hc9z/qiFJ5jGr4amF4ZEl1XiQxfDIsljpgBs99xVIbSsr669pXeT9d+wgeODscK3d7oGZ5BQctAXFtAySDBMTCZNAek/oejyqORZ2PQ9/lD1maR0sgfxlXZLNQdVWlZg8AYBMpG6OsRB0OIVhfLLboO9af31Fyt+JfIWS+W4RA1tkyRWSV4YIfme/W+vWE3HiFNGxTsFvL82eaIMt7eSZoXJdnuO6Uy7P/UvpZyH702S3h0VXeP5ELRdKzxc/YUmQ9tGH8RiJGF7eREZZpODVsrHczBE4rtd9ssRzjEvLaC4CsL7LzO0G7efb7/Hu5vZMgcorUJGL1kZoOi+OrW3DV3dbkV0UU/8ADgw4t+TILrCQMEbJTRgWWJ4sIPgLly6zOCZN7VMCvBQuFfwLp8XdWsJg8pGhOl0XfJnAO3n13cIDAsmntP7Z3NGzNhqnqIrtAf9AMmJ7GC4PKyWxR1p+IjQJdcgvNQGENqXB+rwsUlXl2lhfNbPG+dq7QBi+ddkT37ryLQyh0xiuP5FAC2s8bnHxeLLXh1Oj29rOYNJr9buH1Wa6TDAzxG5fYUJoRX/jqoARkP0N7trbo5/TPE6x8Qyz8Y2xOi78OxW5r7vOoaE0X8aYrW2Lrn4MwjktsjJ2BxtCRNFS+tU17wPOaKXzT3agwC6MCLelBBTzuMcQ2VtuDvuv45nKD4j4ycZh7v3TCKtQuuYXt8E2jFkOKHzySLxswIFsDa3kb5yxJsfAJQbF8mZUKffiStWSQvS8wKrb2iu/P8xuqyfI0KBSYXYwLK3QTGRpgH8z4f/akSc+DE6fgwWDct0cptPaNG1r9Sx+WDrCyYzzuyzhYedszvd15+RYVCTZie0k4ydc3SRb5rxp6/O7zaIV0ohXkR80tFuSDRURuxT9c2nyxOGGMsvo2isntvAJva8HbwDIYy3AwIA7MKgwHZXf85fHumzmW78mSR9J+srg/BRyEfbAP1olKRIkAHRcLhQ54yQ8lbhbAwgovy+Quw7vfhHLEgn+AESq3P8MjKUqF60tcmf3GmwRJ8lj5MpvAM+gpoOHr83GNYA0a/4C9F1yR5W7uS119uaUo2sSO4qZOBNn11W+SJS/cOrePDoe2knSTBZVbhDegxwp/Eh8dY77bXZz4o04K6L5E991BnE/yrCryFPLyymCe/4YgrMwFELbZpewoTSJBbdJPs24oHXMy75bipjdDJ9HezZ3TYCpcgm5SPc73QaKCIbB942ZcNy8u/6ijApUXdzfcAQtfaLkNOlr+qy4r8XhdKLTY1NDNhwyxWGqzlp0X6BKlKAZuVYyK5lhrZBipOEAL46jA5sZH9oMc74H2i1D4DIO/rhK80yf86KcMezQOmk9Rd/fN1dAZ0slTsLJD+K5sG8fQNn6dbZ0jNC8YeB0bkJ8upwRwvW27iqURAAXnLqAfLJi+TE+z6CblFwr2uwU2FNcFEvrEfLZw2ImPCn2wflEov8t7lxa3IoyLtrkSfqjW6XG+fOK7yH63sYOyCjN253f7jKqHvPjBm0MwjJUSwQL3RKWlS5IvamEYxrqV08d4FWV3iKYnUzcmA90Z1dZ2yvduWTc2p0frZVV3VcmpXn5we/FBvJXucCWdCPvHHBcKOUxHRbaWw1lerKOqEqJlxFIb49jv4SCpgNY3aVKu+NWD+bxNxbpLAXfSUeckGeBptsQzys0LV7QzKoyySfspgJHWgrnaatLq02izpnkex/DVFpOoHdnv0+1etrVJXaE1ou4mbFt6blRZVC6bVXX9adiHNsodWDSf5lxQgSR71gn29oThjqP4xyKjR5FslmE/1pMgdWBCY0zTsGOwaxcB/TBhLzDs3T1gNnDnCq2x4eN7UAZhdGBVMzS7uocgz52fR3wY0fD1GR+7nuCJusuLpzDcxGNz2YFqURy4aOe4qAukDcJEHDKPuN4DC+0PC71HGSqSOFD8B4/NJeWxFsWubyL/jp6al8BHmIavVpgEJIc9BcjHR3F6geLcN1d3j8aBcxV1d51lw/o9pjltXZSLJe/W7b7tDBt+LYKwYY/GgQ0Vdf9cbHiZ1py7vvmyjcB02bOV6ucqt6VLu0cvPXVph8ZFl8rrTsPEkjfpnd6iD/XOEsR8ctbblm8ERWVdoCY4ztMpwqJy8Yao6+/q7qN5zAubdg9EaY8V1rjI6jA1yYXTVPLpGe9l2vl/l+AmS2+HMo/NnSEVKA48+cx58iwv6nW4Uw4BnQNXGuCY6GSDaVN8B0Ms3db5BCUQj6r/aHGwmG+SmMfTf3zG5yXnBX0doE2w7OuWZHE5eSXVCHb7FO+syNcy7ubLbDhThnNcYiXb/0JxJYh089HuCqF4edD22mAEXBmMLL0NrcF+/NQ8I8wjFIqdcPcvP0nRMxDPWGPQbQ0u+Jh7hi8xiJxceIraE208hybBK6li8daO7cH3yu3zmO4zcxLMwRiUAPkxKYxhohVtgi2B/0YFvCmhuCaxtfP+9SZK7jyt/w6L0/m+rOrObkEDWRTkusYYSfNlmzHLvrHfW3OwUBs6UAjUGJmTe0WDYVdZ+ziO6XOkrXeZZwCgeB/FJtzpSUONGHyLnS/b/gUM8t4sCcyL0pTP8TMqsbARqyqKV0QG3pwl4pkqUOyC+60a91sf3D+rcf8sx70lVfcZPZQfUVWhIpzzDsbpoPhMEU2k/8DWRYeeCs5S9gGRn+4gbT4zYE9ia05WKP6R16GSBAnoXMxZPY5dXfx3OUkWfTOM7J3aUE4YtxzKpt9X0ePpIxLIMCqwoGr02IUwCy7NcZHV8rXTKcPCGV67olgCJI6CcYZQMbMmixJaF3x4EMCceuyQhMlPerf2Hl1cF+Rth/PoibQa6l1dCKvb27qGmKaRO755HhtUbo491Ouke+dC58kWltsCsNlsev0pThGUgntUYInvHBVJvhRWiHGRzelwFaW0NucUGRXYbHPw9MFv1I5KtuFaChWruciSKolSUMD5smcs5zTBSvJvmtePDMR3XeHxuawoBjimkfXTYIHxbuHjUmw0Cw58TYkvc8AqQWiPC7i+NCrYGa6/QOT8octl6bu2sbicVjY1gml4/bxLK/CG3wgM3x2wvZVge/usbfSTIi9LvBtJg3AUj81lL6xF8Xy5amsrqWcklFsE1IyRT7ipj+gepcKixny3cV3mRdUFeXLuSrbEIvSyTlMQ4ajAwkTctAe6HNGGz9uK7CLPMQoZQvuPNpvnW1QUqBBwjQq2eeISPlncWUFe2ya5G7nY3P6zTYQdzQH6ISr5zSFbsDMqCn8gSQ898801SFxSzclq7urJz5/oOv5w5uK7A2ROb1wSyqlq7yqf7J29Su+0eE4zQeEywXC9aaa2iXMVJ5j9buMyitN6iRbZZYJrR9xGXCy1OuiIypIE4+KCCj7uEAFs7IWmMm8wdF8tXI1Jxed0aD/NGan2CVVRG73MGWfjEjuM0rd8hEI7vADNmM/bj3+7QBlWT0fl92R5hyreEByXWRwL0hr/zDNu6Ox3W2zfi2jTmlYQ0lGxLW7y3jkwU0KhLd7jnCh/XqD5MqtjXLxI849YDF8tZZk86iuKM/06/3J1VJZ5nNCXaIU1Cy+lNGb0+mizSRPC6YzBolio1PX45amDlhkheI1ciqu/tIHry7wuwGdgjFa7HjW07BFi9n3x7OZVVBDxduxmRyzLbv72GpxvH5bo/W9WDDHUkrGDxI1mROYO+07zQt9JP05o0czNCDTADf+Jl9VVvry+QGVVJMRIOiGj1GkIk9rCXnZUpwE0URb6tjzZhGsgALMYdNlTfTTUm5tpOtfbNXNtQ/VYiAgtvgrSwHT+QT1tRaye098jDDDxQOf8JrrFs7WJPk7SFH/u3bcGs81XkU25zVyPcXrSlEMWcto51H6Mad0xbMgvEzKfLxbl5zpNf395G6Ulshy9N/Oco2KdlCQZUJOk75pNiKjkIV1NIe8FB8+CG7CXujnP2ROQB+AzTYc91xQG19b0jTGryGrItI0la8Dod3GleS4scIEeomJ5nidZVbYBsddfS7T8nlSr1hmsiijRVhZjSIQqBnyhbchzBsa4AvCJvsO7uEjpyBBO4XSmuo2FI9QJYeJwSD35iMcWUuPwuHeRgfTj9zdz2o17++9VdKe2bSBwMZEXLRxgTKwYEbGv6eLkkjDuXBB/CcYUfIXqfK3EIR4lGSp4kN6Z237p/y67D2RCozv0KV+itBzqXcYrtI7oiMpNFNMd/BKdJUVJQsOjm6hEDcjLF7jv9wnWet2RW+tw+SM9SZsUWR3ApyhLblFZXeU/UPb7y7c/vXn78sVRmkQliRtIb1++eFynWflrTOU2yrK8okP//eWqqja/vn5d0hbLV+skLvIyv61exfn6dbTMX2NcP79+8+Y1Wq5f89VbtEZYfvqPDktZLlN2xpmD0/7gMYtbao5Z47e/I4HRugm+QLcvZGrkt9d8xd8A1iI9+P1lQghLhZG+zUCDSyNyrTsjUIj29eULom3IndBe47xWoh+ioJlGeHL+usiW6PH3l/8PrfTri8V/XjOUuD5+arD85QVdCn998dNfsNr7miV/1Bj4qqjRi//XumONdqDxyk3PsvuoiFdR4dC9Bpe6e5+ix+bWx+8v3/zykyMdm6Nn7/5y5Hwj9td0qEDV8VB/sh8qPYp7rKYZ61v3sQJVvcfaNNHEe/FDZVD/dYS5KsRYIoGIlE4LIm2M4LnguKzI9Sm+e/9tHT3+dzeEx3meduhuEutekTnq4/caLCSir0oIuyjJzx4zKtXw6eMmL6p3aJPmwKWxvdXF+M/bNv+KnTLuKw4qzkHjwgLtKUB9CjiQn4xQLEr6ZnLsg6OJU/1nsvFBMjBcl10AFF0jXHjzWGQ6gv/1J2u9MgT/hMVLI4bVKK1xfsDG2VURZWXrL/SnKoTRBx9Jh9RYuM0DZGFpyicXC0xeir7PODZF14dsX+4kJgyb3NPNiI9sYnm6LGXLlv9a02rYZ7LQTKLpz/IULz3ToMakJ7/akQfXme1mlQ0ftZDFudZBsvle1uRc7Coqfzhs2bj6XsYCnhI8FaEMT/wn0VJ05x4GHZ1QvFO5Te6IbyII2gtU1mm1yG7zIOi+3N6WSGb/G00CDQ71QXAcVfHqMvm31yJ5jiWHKCgf3g63npzk6w3NGmC3ulpvlTqD8gijo7kOPIzTFEVZbWGaGi9ercgjIvB/lrXrFwftTD2SNljNWCTPsPVA+A9QbPyiaI2etW2t/C+UP8aukb8FWtEMWv+MHquLOrtuqw+eKhcfYZVvvmSnRZEXPvLXdgl0WHiMiFtWTeb0Y1RWNBeLa196BPTXaebdG4wifF/eOFKmjmMsTBrHkvHIWLYJJpWL8kOyxLrNhx/xn3dEa+CFNUZeHsIOU39LOvBoL/KHb6hg1VuRP9x3XySqn6zQNb3pFGaR+0cd0U9fs+S5eAINFjkH7xVz08kYtQkXtLchm5ww0+BmgmvczELyks9thM33Caysf9So7uzNPpX2M+FEZmz2O85RZa/tZntkTViMZWA3h5PGk+eiCM+c/PZnSqe9kXgwe1s8lpski4onp92oVuW8dbARP+G1GbZTrRAbS+K7/CFL82j5TISvG06Tq71pqKYHnAlFeZsgpclvL2dfS9Q1ynj8nQ5LRDRe3pIO3zFl8TA8314qhTjUCR+RZ+hQxwnZ6WPVvlYbAtui/Iwe/E7h6MFKwhikplu+PqXLNYPEa+M3ThLjsDdRd0m/QrnYBs9EKQ1Xkj3O1Yp8HdyJQ5DaeoeMHC2Cu9e7q1f5FB29QJv0ya63Fmin6PHJSWiMx3EcGqXJEaqLzdhkxwgRtjNKwmUVcgOMliyIw9VaN/kmSLTd8TxnNtDy49p+G5DRk1HhT5mZnj6TdaJ9HUhl+P/yi71Z2fgD9LuVMW6jcJS8VAu5S3/P88LrhM4oTsipYyaBQi6IGze4VTAGvA9p/DVYuVEOxLUmkLs263xzB/6ZCN5oTJPGNDzS1FijG62LZfBztL6ZJp+AroW3LqFSbRPjnAgTjKUdA22P7O3aWGq/I+9PSZas6zVd3Y7WdAnpll0UJ+soJfcx8K+SXqx48zfMcsRTi2fdtvt48DO11GDvklKEohTJGGvqB9eH3JlrmeY60HGdLbGEYHnsHwtqwmieieIZxmdvqrF1vQy10TtM7pwyfm3KDxf3qFH4RYxnr2fCT+ddPnCHePimohcnNfR07gRX3asr3RGnDxd2yaGm0th9Tq3m1Jo5bnaKyTCwWKw38it6a0Sq/l19vpjNmSc5PQb9IVmiq1W9vsmYrZMLom94Pm9Srwmgdy7KlV/IaqjT26AeDrXzOKhKfl6KmLTgG+xf4C7QJ236l1H8MLacjtVvcp8sLd0z/mrHzHc0id5Rm7JOOI+W6wQb2us1c8jjG4hNGQetN+QxHL+pJslav2QfcnIPwG9x6XCEUk/j7MRhFy0hSXGAaWHSFIft7FGa5g+9OwDdJ+jBa0E82myK/B4tLyLywsBlvfaZpc95FRRfh4y+xscN1rN/oVC2RxdX+VHsZU8IWajdUV3+qIOLSJTVt1Fc1QUq8ApTfcYW1CDQoVp5j1kmNM5F+T65rU6iwsvK6nD4r8/tC+JfqhUqzkc5d9161r1I3i/1avXocop2VJONWJXEZOk/Wi65Jr26vyj7gKIAkTCeF4aztBHCDp3XyDDRGyn5civgc9pctkhOHzdJQXfE76InGUabcB/6kihF6M/dH6LyMiK3g0LM6hiTw208rr5PFCAeGDknOrorEFrzAToOB049oiv0GMYQXJT9Q79++qRFMnql10ffsfjO+zd6A2AcvdHr4zskZxJBsiic4UntDji8jLJlk9osSjtsJyuSLcvFsWQaehY9nj7iTYQXZy/K09KLhMwjRp4HNlFGHovN7rGIYWTdaZOXpVfl8Y9AbkKyJ6L4ju6jBNdNUganhxsI7KPT6pVkwcb7MX9oxkoWGjEVlr0Nn9w+0X30WV50/TtGeFvkg/Y4in/QN0VImgTPgw2yRSP4Fg0N8Rah31x6mRR0+WqPIkNMTIOvOXAMgY8OHC1bVInmSrCLKYrZnAAf10/HdVXlXnfAWlzfk3KVJmXlj7CV5RRhvsSKeeTzcLq0jO1tiiqJ/fzMLILgS8eXdDltA+0244SJHQrexuUG44lS54EYnY0wbdBLm+/wSneVrFGAID4W92m2DIi5czWdZhUqSm9ebLXXCCuamIFaDTdrm3i/cJWgRna9dD5ePFFZ9YdfJ/ma3MYgS8mkzIr73502lu1TNz6j+I6Su9V04jvepgRH/z1ZToj9w7S06Vel0DqnRxxW4YQ6MQhzuopoopMRipCXwXbl1BUceXKPiicyq/aul3FtH8cLe/Xc5XosW9unH8dRidrl1dtR0OP6hKKyLhC9Vq8Og3Tu7rQxhH0z5Me4KQePZBPP0zyjCLjFfI8C2nAhVJAArhBumhFCQoaQ+C5XeeP5iYbw9oARbX2s0vOIn9BGFbjcre5o5O8X/1qiMEFb8hCw4IZD15S7BjF0q7VmSYi7TI0lEgBTe8ZVnuT1hkRG+6XOFLGYJG60CQmkx8d0FfRj1AEPWbot/OrGigd+H/b5Kh6XcCYSeWSL2IhbAke5HOcVtsCCx0hpU8U5CErHdmECpQ7hSKNwwzBnNucJDTBxCEbvKvoY+CRazTdP5yg4q7zKmxsJPGYnH26L48v4FMHE1DGLIsQ2/EWU3YVP4xkwpjCsT2oHI7f2wmfx3CLOIQrSe7HfEvTwCbDZDDSweRR7o7qeiTHUjiZkAiGj/FZ/dYgYR7ksl9AI9c8uIYeHREDbSATUHhQ+M5Ha7g296WyyCdcaaydYu9t/Rkklu5E5uO77mr4zx5P2ou6eeoH2tk6rw+UGTe6gal9K73MjeMWQdeF8IZB1V/eJ7TlOEOHhgx+Qnksjyl2fdugQs9kzQiPvKCzZKHlib418LwqfPlZF5PhihrlmYw7gnolK0zrEHDI2n+RpXnxAjzQFQWjku5e02daUema+2i3bUhIvmUH7Q02vDizKM7zlroerOwEijifmQPbi2YELQ3AhS1H7Poxr/2m48Rmy4SQHV8HPLRi6H05uwp7cHE5btL3bv9OWw2HEfh9GmIdRtHzfJth/JotSNyoHE7mv6WeUbNr3DEPL/KLsMk94BRLtK5s+M/5UPxdiHxyoT0DsktVVmx7aAalRUmMPvGd5sY4CPHhNcF1Gw2NUofoZPIFToKvXR7e3SZp4m8f0jq6X7TT1ir8omzOELpW7TwSIInOzsst+LESeRjRbZ1zi3EKuEKSnH/O7JAsUVUvwdbfADVDaHM+05Lx2yCLKI/DZ13THD+7dETGY98d4We6Y73msymdJUVbBzhQJkwZDBq7Bji9prTdRFug5E/D40cTK7ip6yQhGT7L23idZ7OB/4Kr79OSESXjgmdaRytObkMjeBkH2z2RznpckUY5bJBtgXa3yDMEJ4NzSbHaJssKwtvv6Z76paaTgmWhPrSP4jYMjmPrmynZp9bJrKaIQt/WuHvKPiBBrUebaqzhvbfnualUgZI7/Z1v8WERQkcQcbq+Mld8iL+t5p9JXO/s9jYV+/HDJn0X2d+EQKNgD1XcZpl6TSc3LMz7TqzwBXrIx527WqHomzD2dlbvFdfXmpkD3SQQJl6/rYIsK3ZhPj9P8jtizz4RFP0bZXU0SFVrz6FDTi0nBo2VBS5s5Ykwe27Q42G18rEGSm7e4ThT5KcxEesgfHgrlVXQXJkh1N+/RH8Itwh/Mz7HF7dTLM9Gzk6zFHY1O6pReOFKfGNsfC9JzxUs0wWb1LI3uFmvcdXIFTksb++jki+p53aid1qZpAsQvK5LT+gKVeV08GxN86/ZNR08tk7vkB+qQg7cH3EwfkkM8X5+n9V0iy0VreKic17Gc7T2Ojj6jh/IZvYC4dQ4NaIHTV7DCoCIZaMJgCqKBd9O4/TPtU3bDfj1sMDq0xvdo8jQ9ysqHZxOCRwbkELROawX357mpxO5doG955fd0ydR3sDDRngnXbH2ZD8c9TdRaq/PCnOgHWaKD5Xmiy+r7GpUVtmKJjOzCAeNuWiDTH3uyZ1zPRBfo94K//GJvMAd6+ilIMDBG0vI9wSFheyML1zscuAvXpfwzUayufOp8DCdUrJOSnLteoJho2ufL/D7r0CTC1Ocd8ENsPNenj4SqUXpUVytC15gaA89q3rd+sypcJGw3Wwvmyk1AtK3mDIz9C+Gtq/wHCrONpOiO4hiVZTikNP4DT7BXkiZjoTvLi3r9jM7yr/JNEtuLV1vNL7PItoXb6BVTM3vhfB8uxsxxdZLKB+WOZyIgdED2LNpW23MBIfNIWvbMH9fE7qp9hw6pqj43mtjLGUXS5XohIBdvSC88XzBosUim3BEbzaPmuVnfTw30nJTP+yKvN44aqK07bUio0ys1gU8oPrerjpcsh1AoRPYgo+qgVibxcO6neqJi+Ux01F6oh+fGbttwqjfEex5MS8cS8AInxRc0v4o6Ze9+7B/GSQp8kxx4doZLUeCd48DvEjetSe4v1XxPHIcVAleb4zogqubeYeD86X0WsbooUBY/Bbsk3iG8iIbjW01m9r85S+X4uMuP2t+iKrQyw5v2CstCusjiFHfVkBx/ddPEXWPk1uAcjV2Rxvo3A2Ya4ajReUba6oZ5Rtg2NuvIcEMWwmrf2EiNYcWfkLUhSs8Qmpqm8panJrC85amp3eIPc7WM8snkjNi/SDxpK1R1TNYEttRrbO0sJ36S5AI9RMXyPMcLXPkdFQizlmdeyRWKf+R1dVRVRXJTVyj0rlFooPzPdaC8SK0RIInws409C5NGr7f6N0HGSCPiyDaGPB1TIKxHTvD8j+0Up2nHWNgIIb+JIF0KZpgK4/PMI17+QEsZ6bx7enJ/HyZbE0F2+rhJCrpz+pRn1WoCvP+FojBjZ/nyXYKVW/UOUT70idfs0bQZHj/k6TLQXInIAzICg/w4yn4E20pxeIOJGIt3cRIaZZcjKzDaxU0UaEFqNTQ1CkLmcyWBOVgg/t08ok7eO4zi8bvsk6APxm6yBi5QWYvpdB210YZcmA5PHBFxwF7jfWdvE4Xv+nlN6pYotDv1PEpCBZtzGbCC0LRFSfYqJMFl3bgMJ/F1TZ2YeHcPNNjtwgVaR0nGBKk7RIV/iMgLt+3m9nNO3PLxKCNRwKOSblf4tcS2/YcET9ezSby49Tc6aVX71ttqfgFZM+QweZ/cUmP/GbJONzT7yRtqes3f1xItvyfVypGFuOreXRllYAjubpmTWZ8Jg3bmDDPLeC13uLcrweNzYNiR2v+waGI336LsukrvYUW89eKoNPDWbBNww3CBB7shb7oHs9R6jOHujFz+7/aurDluHEn/lYl+3If2umf3zbMRsmR1K8KHRpLbsfvCoKugEscssppk2db8+gXBC0fiJMDLfum2CgkgM/EhiSORibJ6Xe2Lw6Y5f+y9Q2VJvZodmVBkGBGyNpst5ZCdP8yGgsr08nj1Y5lxuXY7b87Sf57jdiDcbfXHLKlI4r3Q1419R8FvF6eQZhJJQt+Gdvul5nsdWmVsb6F1F+zKzv9V3VX+LUvzeD86GNFN2TXlZWH0FkM8K1HXpnRNaPT1xVb/E0oOT07LQpMeXp+zfYqu4ir2c0DYpnqvH2L6wajdZ/99HeBjS5/9WT7Q76kwKeMeYHaedHmfunHhi0f6dHNbpzyzex03Sh3lvEpaeB2ncZ1XYMzyrQzuVeNzE+b3MoA+/3BIKcfXD5BQrr7IOW4nD/GMlvyhiHdfkuzg8Q6Q+LyFXZSQizzk66bxCqXJV1SY5IhcSk6wbgJs6BSjF8npfUdT08P9lHhuHPz0wGLtUZ2L7A79VUer28qwj38u5WngZl//+Dh3ukNxmWfXedFgxc8ivUUcIvtfg/2+S6OmLghW74y4YMljM548Pr7PfXm5qzJ788F57TMi0IbCx6uwpXibWHwgc+LqcxkXm/pIjreWt3HRLhxGnTw150Ju16x03XHpr4dBHn+9Orv9n/naI9xjCTw7UYHhXx/uB9tP+/h6rs7MtSN/f8I67Nzi+nHciM2beVrAuv1wYr1xjfcqytbGzd/6ecR1kmJ1j4wK3ATXbtU3Nr524KjxKoVuZALAIvrC3mIizc8ClI1AZDWj0BqVh/iwYc1b5rS2Vd6fcZHgtU6PYbyL/Jxk8ZYs3pezjU6N3DCrRJ3C2CVjT5ydH+Nd/QiowNsqyfO+sb04L5uNUjoQvkK1/inZh2v8j7B+D3GJiObdrwGtuqn/wXblkkChLJNDhmdgQkCpyxL/0uFoqbssqWMJOjwlYWqP2ft3273a/82eD7b2GD5uyvFZKza38cffg90XHxtysqf5cK4+PJImiZAhQuRJvqwb+ZyOxVf3/14v0buYHLwNAIzodWOPxpfup2Aj9jliC6OjzeOfqCQ5ox3m/zonxTgnQmrxl1VFno4//pQu/CfAyW/aYbGdyVubwhdpEntPUuBx1zjKG3ismW7e421joCUiOltBUFeyQp92fD7EGl1N5Gle3P91jgtU3h0+++az2Tns/4WHib7g9X7p0bhZTdDRTYlbukcp2o30fvfwmfECaf0nR+SddDT+S/s2yb7Ut2HQomw5zkYPCSqao4dNGdbZrpH8hYWukxu6X1I3tX1svUd5IXi8Cza/AqjHYCNw9nDuDFxZF8L+f3Sb92X6JqvpR3027tHuXCBL/oxOEem8Id5Oyd7mh7w/hAuyK7Ns54/qmL7O98++Ql/hXScGbnec9x5V3/LiS4CT72NcPJOp04X4drHfUCsjPXVIk2++YymzAyLxpMbyBzdmwaZFkuWm9R/FEjpkKFNFqGfbtvd7DRmjvjUXb/O6AQulmHtzXefFEQ8Z5WHiqXnnPCjA9PSRYX77mWeu8jo425tsjwdz7CfQbg3WHgtuxAA1Mo77zjdtBFnUWe88zLeIT+iIyIYbN7WRwSQy+V7I2I6rSZtMEDJZ/i7rVsOB5XW8+3KTkWs8OrbpRmDjwzF/3gOK2d3fp3g3SXvsPKAj/sBv5oYoyLejzrKLWRPCwo9uOLCb4SXW6SEvnn+O8XbHuDV7P4d4u0NM9JrstnaZ72PDgEVoktH6HlLcsH/8QWtVN0+DcAvUi116h3b5ZoLiLnxbOsVtluHV9s1+OCzz6FT4sfgJqCnPOdKz+kzSpVEf7r0uucLMrVaXQmwjIHPO8usn2TCUtsNWAkvAmG9eUVzijWsbWmkbw621BC9dLAFJ5YIl+1YbX9+t39UIDH2DE2pF3WLoKjmirNzO8dtPGE0Lo+u8OB+3eIxLieQ1mvrsp6tkxMat3R7yU7KbLY6QxbkQidHcxdfcCDDH3y9cF/nRHYZs7XEPanJ3Nui64/wxz5//hXaa0FsOniP1ayE/Jx31Q6F41Er0pmyXs6+fmzyLnhrrM36MaW8KU0CW4vint/lWfA0oiepeHLZJfAMrvzQ0zKW6REjWv24ElmGXytrV/W8OjVo7JVtcPh5PcXLYypoY0r7j2T38zXVrrPZmnn3aA4d9rm6MFlt4srD90e49Xeb4692OZE1rzwbF0K2qHgyzNGtXkS7vB4zAPVV4ikaFOyh77FSTA9A8yva1N0+cps9j2rmoqnj3VK8hXl4nwk2UnbKHtn7z2NbfLdsyNiTv0bfyLaon7gZPcmDhvJ7pgAkZjRa/NXOpwFxEGozarT77+Jo2U/9t/+hgvBHw+pGETjN8q81jTInLJ7T7kp83F1QiyDXsIsOlkIQhNX3rNOajzZvyIf7+5juiJHVpBjfSeSuO+7hOGBIm+CW6+9TcUiwQQTiHYxaxCe/hXkebjJ/RMvzOQIuEMrtzUUeLvo2fa4VsK6EdL5397BFb8JBhaPEXaLzUG4HD5fMuRWy8U6cRqJu5RUWSy1O3Gl6DVXFKWhuV8/C+iovKIHXZZO5uHlyobrKkSuLUMS8eW3vxb5LIY+3k3yQiEOl7I/MNdBc1GD5BH9El3k3k52w/Kh6ZxAvTD0P/5cIQeaQv9+P3w9nfudMDl/VZz2koJn/zxqTzSwOLr2NK5kezkdzIXL3tnpC+HPMp6lv5bfpdp/mOscjLEq/N058j6GcELT50W/HCwJK8RV9ROm4BeP+UF5VpGmgzFy9M7jOv9M2pvTnzfW8W0m/EKLIU1hAQTMxJSRipqChQ4au9WQLGmDB2XdQJJevAUWO2T03ksD/iUvca1dMt1z1qohVtw/IEuSZfxfNM6pz8RxnLBb7IJj7gG9F/47Rm5N5k9LHMdul5j26y+wQ3G/c7Ore7pdu4LGtnOlxQjT5G7hrzIudDUomx5Zxa8ubl8g5Vcet86Cc7c93guJj2cJv+dOfRleYO4VVWcVF+SvYHNOous2nh//LMj4xNc5+K+NSuGiS8mbdVZ7ZkxsBdytd5bWJHzssiyYuRkZSJOHU+PXuNG1t9/FEh3l9jPsBdG5H4JQDpu74Aev2Xg+nMboNBVR13dEzzb7nHGaqGiP/aSTjiAMR2MNuu1jqWNPt2HAw1Q4wkcWvBmxv8aXnK95e1qG6Tk23IeIo2HTqNKtCjnWaFBka+IxlEsZysfc0wsZqb447uHMZhbLsmTEe17cppVJm+3A52xo4kzb4dB0PNECN5i4pjUtbOJU1QGDqCjcuw8u0ZT1qqX6cxBju2tIpiE16eofUyuUGvrR5yHo8dddvJPHq0FzCjlz+67Vf+dlRya/kaSUWOO3ObxdMuaaQMdPw78dBUDjiid+hrgr79gdLT4znNHD/AHo6+TLxevAxHI7DzgHTVR7HyKS5bjQdwGH6d5ofu0fBWx7KVzygUgdE5HdbZbV46OFAONUNM0vo9yI8ylt4ODn2Dox6EmwrxsWUMn/M0NYNY8DxN/8zrq79ZgzBOYLmxoBdZ+c3lNp2uG/wzut0RmDdyvz/b0IyTN9NQhzgb8gQ5pS1r1gH/i0riLe2hqfe5ZUsyrF+UZb5LyMi2Pbz5fsqLehPwmOCdxv3uCe3P9dF8+YVD/pts/7d6N/KPXxiilqV7lD7+yha8O6dVckqTHebjH7+8FEQdGmR4YFrkStgm/0Nokni11BercXqZZ2VVxFhV4nxNsl1yilNIHI7YcHrXCu+b5Uuu0Km+q8oqUFqTDlsOMSxrHuHu+144u6NTyasXFCZMoII7SnPyxCICZRg/ri95pbz6kDX++X+rHf3rh06XcbmL9+L0wbDfq7kZ2AcYoguDYM1+7L2AjZLLpM+Wv9mA9s8zOqN9E8GhjygQXaugdpV/y9J8iDxGBnX4kR3M//z110AYAzlneJJQBEFbL/8EQIPlMun4eiFQi+iQIXKbRhMxFoQpmMSiUczLMBbKjklVFRxdRt3x0V/mxhZlxqSijBvYH8Kg2cJgNptG1ZwNft35fpPROWr/L8VcV06Pbf/bJFhjGWYZ4YqCoAvUUBhkcfIYLcqGzNyLgBPohyMMJjSKPwqcIA0tB07D8c8scOrdxTzaJbKulx8z9I6AdFPDj2u3KbCfo2z4Z7YmvZPbxQmrvHYJbtlPUBnd5+dip9jrtXXZvV7/4yTGRXAuhXgJjK1e5AnApXamlfSpdricHWYPcVG7jnsxFxuGmdWQzwEzhdP1bDBrP6+rsmXQkk0o24wls1meLdGQ9QjTmLH5F+Lz42vCpbgLvJROdJOiq/tHG2qyPu/gvceWabsohkF2mPLN2DBaKhs7NvchQoRHPvmKiueH+kW/dGxpImZQmQKrPaCFNfSFC6moYbBh9V2jeJsbE/0qTvJeb1kLc45pCE8bWpbzEtmsyheDq9WcWM4IqunPLO1OrWY+tKxzNzzG5OSsmPDgku6WaY4tWPsBJiPNGg4xWzgyqJALMXZAwxodKUtgeRCoWY+/Hwtk3S1dYVHYW80XbgFgm/5LZ2/eZv7acY/V7xBuLamj+jSvxw1uatgWWF9hrmgS+MGhB+Rs9RRhHI9ZFUyARIPQC5KOTWItLASTumsdYNQl4/xDoNAGBbPBTx60YyrcVXifgvn6ikVFEcj6sjBG88tiiy1ZPaYYcSywNBuUuogdHkGk2T9y8XBJS/1vawcAGGRm4UOvAO34Obw4MNhP0QkhwTA3nzdcF3WkT/qqW1p3NVjz0P84yUeGj/MEsRLYznQCT4AqZVQrSZfqyDczIUuzQLYwEJvFlc0YzwArebSzqVH1Oknrd6lwpDcnTOmceG3s3nrRYNQXq/z50dAGvI1gIRb68eKi9DKs9GWb+Xh1Etl8vGbDVZMd6b4qSDyUkiyIoi4llhRcPQE9osOPk4AL4pzlByQIAjNYYWFgBopl0i+d6GwWrHVBn9aBr45bhofhx9XjqBdlFdjpYkKtAzsdtwwPw4+rx04vyiqwUwezWgduak7Zy1ryw+rxQsRYDVaawGeRyLTtSAVEScOjwEH3c5j7fNNR9ICWVhCT3mry+fDCR6ymowprTxj52ux4CoXToEsVj1zFH0sYBoG8SqZAo3F8dhk+jQKyLwCsult9GRBUA/8DgtQaIHMDVBMLfurzTisLuqhjLt0EmRB8kx54uQJucWBbkQVcIuimsnijgTevpXvzvU5QGacX5+qpbnFHCrlP8qItnkoCLuaninD1FlApng0gZ8PidV6cj+RQ1jfw5PeLfZ9MS9Svq8fFIMt6QEAy+E6NgiZtsACD9udt4ABIjbxoIETkv78X+fkkRQFFIgxe+/MkHyLSochCIOjIFBMQPEYdDXwtwYQAfNuPWEi8zGB0zMdyWotDyBew+JCxPWboQkJo8tWL5bhOun4hbM0GIpIgJYCTnGrtQvpkGml/WbtvXCOGSUeswmce/VVsmqcFzZTLXHPUzL7C7cIzfSzjA/ojwdwUzxEcV2qhYbhozkF+WILNBOJixDLpd/ZIXCDWgLlibyN+AIiZ25S58EU4nPfDRzziloyonkuRg4CucJNhZxBkTYBZfgyTeWAzYbwSO+DMHaXk9+SxuoyLfXR7rpM+lmj/KameJDK4D6PmWVLHBdPY8GM4S2I8TiNB0ctihAlwKGaHCLPWgQVyHdJAtgbiHORngkWPFQI8oc163dNVXBbWPtJTYaGroSVBbbI1kjPOmBGdd9n0Pq/Q8tfZNZciB82v68bQIMjy19l36BtG+22OGyg747SK80mAcYYdsHz1Z5eQVKs4yYRw5vUjqFmOLwcuk5khV6wwwzJvYD+SH3XJX7KOSSFYX8C8EpMBqJdj+Z+xjlVyYgTz7TpqgZEjHDiwBcHijBoPrCcQGR9L9BXmvaTtQlNOF8jm5zX9L5zSZ1ytVOciq5PpoAC+xoHWwxTL3NKGKdnAGpiWZxWr3/4Bz/RW5Wd4LKl5mXNtmxNGLuOiWbK8Pmf7FCmvJ9prFYoSuPGiS61gwnPELUn4wpA3WjJdhAGQIJtJnwOLy4HQKr5SS4DZlN8qJ3TN/rkSsLX8O/glAGvCG3knXM19Md/2f39Cu+SxfX14UVVF8vlcoRVgTMk/xJeMcu3oU4u3BijCrH841X9GJnLRpkePBzMghDKNClkNuOwIw5xEuUNorAVVCWu0mQAbWKhxNZc2NHB+SNs8GmyzG2yVBHNjvls6PMQHfQyzpawiBp4hXujSjawWKJEsVggzhh8DwKWJuKMeU2AwfwSImQ74nCjD5LMD7c+4SOKs6m3rZX78nGSEMJKnjKffw9BEzDsYpsDqWFDPGwQtJXmYJ15SBQVFm0pQEy5otpeMv9XszRcP1Om/u2MxOvdW3QCe/zzjEjxKH7NEjlGGiMYCW7BJ8yhX0KKhR7O9NPyt1yaaQHKz1m/NJq+DnP5IUiAEhnzqY8jFonHSY0cPsFzMWSMvxJ9xeu5BqpYwDC6mRS4R14TNljAkhp3wFAbKjbQWeOYbmBvWsy8nTZYM614Yrm71F93Hx1OKrvJvWZrH8ifCPQFz/tL/uHA0wOLNjwRW+bNh4SFBBZZvh5g47Fp/K0JkGn+dYCLIV6znnuGF+nUTwdYHeUw6o7lbAKyWv6WcB0QTbhvt8LOQjeLgjto6Ei8fSALndt7M6wOWKJJJp8xwLghmXgGmXQL9RIm608Xho9/LXSdpVce7Df68YSozxUmmZlIg3s6zCa2s5tZt3mcUVV7UC6/kGBfPb77vnuLsgO7iCl2eC9zF7lmxvG8J2KV996O5kSMssP50zS+BFuSQXGFA0shhZsGkA7AMaJA/fmJiBkwwmp8vG3y8+3KTYV52X+7Pn8tdkTS+4qt4dSVhns0VL6NZ/RssmWQ2BwSLw93yd3jLA92E6/gxmJt7NX+xS4GEu36POQNhrmedfTg//LqJM85BntWccV48PiYp/gVFE+U86TtkGxp+XXvgg0EUk87mDqhCUvQEXTjJoSD0LaZC2tiKR5RpFWsdLN9XDOh3GKjxAUXXRX6cDiVs59y6iS1aPT44gUx6pAdjKQB5yH/CYyHwGIZixmVrlXzFKn+bHyLq3/VAyRcbHB27auTKJlrB9r3KuAm1dlHpLAzQaKGMVjEsi4uA2ioOgOZD1ZR2yxZOs5usyye0+5KfeQ9a4Wc5tgRKBmRi6SRog8VSsxbSR1ajzzBglEhohEu+6oxxMHfnAkt8uI2fSSzmmyyp2/UUklm+3OI75gJa8oXrjrMsyGPUJzUSi8FHF9ldLZGvcQ4WSRUUSsla2JDxbgDxC0qbCPJ83dnw+TbfxWnyb7TveIzextnhLOxqqNHvCejhHn6cBIEC2ywzYmkQ1MGqCoM2USaTTjsOZ8SX7w1AOP9vfu2/jTX/atb67a3eHfqaoG9/oPT0eE6zOghxPSRD4+TUs8cU/r2ql10PeSSrr4Vd2waIvr7M2iVT5AO4aQaIDF7AjMJcJ9Ekx2QmI2LV95w4ZAp0fgUtlXTMZ0OUX1cBSBkLBBNTeT5XlTQ/XOZHsk80NGpUlantGN0145JC/74hayVVtVV3E8Oo/vdtzmd54EaREPBD2Pw4jaOTPZI8WSlYPcvAT8fbbDh6j76VluaIqjK1OaK7pptkft+QOZKq2qq7iWFU/1v5iKUn4IdwwicrDkjyZI5g9SwDPx1v86278zT9M6+S7NA6V5ru+7h6k+/3+P6ZVblQuCETpda8VZ9z4Kv+4SIrvylOpigSflS7n6d5ZTcGY752fBJ1LQhbA4vLOD1wOrqa9bzK/MBi3ZbL0+nF5IBa/qsWRzit7wWLPYamfbfyhsSGqicVroGK7pg/36PrpCirq7iKP8eleM9X17pHVffG8znbvYtJKqVf/tYU0m8u6dL73RM6xv/4Zf85x+Mef0756oIJYrt68/2UFxXWQ5q3t9lCfyIJ1KlIZdQzHp7HpH6JI+m2L5f32ZNoOqxb2J9T9BCXXyC1MsWgXhkKTW9swDWhN7YY6o2l0PaGzmj/5hgn6UVVxbsnyVhK6OD+QVINI0NUMaHvoQjqbig1F1UtoFYsPUSJ8LtdfoYnBlMMApSh0PTWuiW8Q9VTDumPK4f640h0HWojQ4g8aKuAbGlr2XJqwpohL6ady7tUdqSfMUkpA9hQBM6YvlTTw2VcoQPxAxJ6GIqgHoZSnYqSXXUuICvel4Aq6grNRkDRC0egGI+oJYpMv5CdmuvUzkmBJIYVpFKNGkOoY4FJlSH2zRSDnTIUZtpWoEagUOm7ozJWeFvxXZydH2MyVgpLxFKp+KApjXnRMKHvnaXQTVR+QynOV54CnLY8kWG3iv7UHWl76J/KCh30JVD7faFOgHpCwfatKwHZ7wp1C8c2Qbf0y8wTgItHjkbXZ4WnDYbuV7wxguY8Vw72yJJoOhyuJ4W+hiKom6FU08Pgrij0MBRBPQyluh5qfz10XxXkBKtscq4BvYFkYM8gpYaL4WZF6HkognqrS/XWkToGFY0iVQjaQqrcoB9JD/K2jU1NE31Bam6aYpXJaSh0MqDimJQl3qN3h7SiPAIJKJtApd3c4mVtFqcX5+qpPkdoMhRKuVCTw1tfVQ0Nd+QltmSyU2VQv6S4jIwmPKF9yE/JTtZPW6joqKUw6UnWibL9lsKk/d+L/HySddIWKnpqKTQ9tW9ShE7a36H22yLDZezH+vVs/zBAuo5lyVQLWZZSw8XvySNeNhZ7DRcwGcQFTGnIhaJndW9mwyj5CFBl0uE02pgSyvd5BVlSqkzaSVOs6eQOfcMC3+ZJVpXyAQOpoI5BQoP1l2TbNRTJ1lxGG6yOUDJibLGqJ6Nxu0N4O5DVG0AEmmCuHFYjQ6IVLydrTwxduYg8CSwmT2W2l5OlNZbt6mT0qv2dJMe06U5PnS1a1JeSHNSdsoYTd8Z82XBkOqYkRatsAEmhYrRIuVk/yrx0sv6VlRR8Keu58WvOpObcisuzZHueYsCQGSfWSJFkHjJVS0tuMWptDQ13VH4AgRWqDOqXKtbu43PwxLL9Hd635wZnokP8VmAX1RXBO6iu1IR1xTUkUywVxBSlD7g+IgP5GdwacuXgqLAkugMPWZRH8fxDRgkeh8iILU4aH9DxlMbgTIXJdCePA6XhVYGCA5FEdXVg3HP3YZB3LFCovi+m3f6OMoQnsso4iiTg1kCg0p2JDvEfxVPRoQw8Fx2KNZ18LOSdUGVQJ1SxTpIhfqEoyVAGSjIU6+YHiksM5U8oOTxBi2auHJwRLIlZh1cJXteXsGEQSRTdUlQmpw0amwTQKM4erCwRHzkMmIgsgeqciCPVzgkq8g8wK6hSeF5QBOZdNYG0VN01FJouGyKtgT2e4uQADelQBBvUrlSLW6Jr1TeEp4BRyxEZnHq/RVWFCg1yZYSyE3GIVqdkMcSQqG2RBlS7SGbbuWy5KyM0YsNsiSsNCAKcOUgo4dMHCbElOwZ8mDFgdjPERqeAr4VYGumdEEumFbueQXu5HwlPAIvM0uhQWORliRtP5b2KJCDyBCqtqiHbLbXZJrYa/6uSbEK6EnD/0RXqth7xd4WzA1MKbjtoAl1XktsQxUWI2Q1IdxqvEEQkUR3rRxenU5qg/UPe0idIfznZVZaDTqAw46El13PAXrPLHQMkdPqb+ztUYhNMfLUNHBPa20q5/4NAobr0bIkMBoK/sNTcu6rJTW5Eo5rU+Eiq91Axuw42vxZmkgPYnpCZHGwanm9GA50tF0AwCRkzAKmCJ4DawCekf5wpHovQpTLPkJ7AYPko74oplS0UTbsSn26J+hVIZB4PLJXNCOuG1WAshQ6pVwkSR/uIdWKnKkBu9yw1/3yCSgwFuNZjBtQu80wL4GsA0oTayf8FK7KxOoaHClHftEwZAK13QYA2xEcZVDOKhxb2KgFd/aNriVIU1HKR+HcBRBSZzz9TU/m0gTRj9lJhlFoi1pNfqRSWVjHKwOuCZoQVrwZkqpEpJBg6mD5MQUJX8irWQhDD+qFHvT+6qB8JpVwozvmdiCHxn2frgb7zTXWuyLv43RpfLz78ghB6Yynyvxjxe093xbgLNP5HnN8CkorSHaq9mKodYnTf+siKkptUU3w/uFcyzfdD8gIGrAmqRSwMq56HuDggcEaYVPM05gtTT7e3t8IOXykkciDbI5SFVIwNavhK/k3qTGoBHk6VSpWAFULiBHghxjSievLl/LGN2Ldd0k8tS6cQBnhr1kiheEPmiit3sfvv6GCapKKLtCEMqeTZHC2+RzPKi6ZfawmkwRZbE6mAedCnWHKBdP6XXdBTQVJZ+QTQeeS554x0F1IIyOt4FgtChbQhsDyImvSTBCQPNlEmVgl7kRF1Nxmovf5QL9/NKyuOCcGHlM1BofJ5pKIV7ooHaEz2zDOIAuVLNfPKiq8TIKxEvKWojH6EGvXtAuoBCQOoAnpY26hA+V7WXvT2tk4lNE/iX1zu5pHUkz2rdheRU51cUJbQ+yhNKHp/49hfyiqMp5xYMeLcq/xmyCUv7sGakAKEshCKkBtBObGXcVyCGl4naYoNad+yQgkcaTgVGGFohNBdWMWhabnUAm3IKcBF0mAakIXHsFcDFCZgyOkE6EJdQS4WH0KByCMLj8DWVIRGaFoxCXVgr5ouOoRSHSJRCBXwES5ITWn0CntRu+AOSlFFohCi8iEoSM3hx/GbqzxNlWKyBCFEpONTNHsmKP6Em2hNiIyoaREWjiYZyaRQgw3v0deTBO5wEJH33aKdplTLGLOKCuEk8TgaETUxNpQtQY5kYKvK+CHeVSlfCJlV1H/gBIFVAi5ShaDrntFi2hJ9HlYTOqVPoxqD5fXkaJpLRao4OMrlqFnFEGgyiQ1033h6WcT4sVddH+lHqSeAKoRShJBEpCr1qx9xiTO9Xl6OLJjAzHOAQWLY5d9R5IiOTySRl6bRsMxEQRpYBqMbibXFin5Htm1SOawNzWhG5xlNMhPpZlUzlqLzyPrE85aEKjI4VQHp/B+pMAG6SC04/paroCrbxFGEMEvhxIPihUWDf4XckQOuoPBgGO3IAUUnY1pRRhvzpJpW74Z6aahHj+si1NFHQJPrgCfxK7gQvW2o5+kAZeBfcSctEvm/iQ4vahczL7rF+7OnuET7T0n1RPUgCq6r4k0cpi4fFZBUlUb8c1cEM2+H9uVqgCt4EgSsKZ3xRuEWPanmIz3uxvpha/m1C3Opp4/WqDGIFEkAg0hHmhzqgUEk7UUE4kIql0JK+hALI0UcTNKISWBLP2rRzgt9Jb/omEc1XchN+ZzgKPwKzQclve+8aDy599IRRaOhXbmULKEnxsGawvdVGf3UdSPUeSRpt3w84Zr2fEwcV43BAynDmDogPm07k1VhZ0cccBsMtpTW/3iHdpvgw+hGdDYiaIbLybU7AjFnEr03kKdB4mY+HEC4nf2aoMAeFKSaGXLiEJNjdlUoNoxSWv/7xqnVoAwMbeDYranoX0FGka/p1gyjMTsgSBF4WhI1GwSXQzMK/Oj1YqYQg1bZIN6KtiXBuH2jVd25LYSVrU2sqoXOADEoj9LLRU4dzEyIoYfoFujSIMpQ+KnIqbVCSaQBxFiGShTB301fWRo3oTh19/UA0yBwPq1Rm9D3QVSs/4ib1A42RdemTjZ/sJNOmSbkCoJSHRNVqFIYr1XFDjCdHpsm2gqvIuVy0riuVnil1GbrxkWqkYT4jaTdGqtU3c4kSjFplwmTrGocDn3svhAyNJSTW8OwARSi+/h4StEQgk0uOEepWLo4B3kLK3KfroXxi4VEllDqD5LcPWeFNDSkujzDzBjxFd8ukcj/1yq8qMKxZntWqv9oS6sE+2i7nsx6UYuVQjativ67cp2kVe1BqDyLt6k+zdm8jiN10wKxhxf/OYnolxzj4vnN991TnB3QXYyX533iKeBwUVtJZYLZTFit+YWzXLFnYHRyruawC8y9NVIJ5A9j6VnqdYktSX6lvL7R1glxi6NJ/kUaMs3k5U9NCqOsq+LfNM+roj7FlXbJJqEMuWQTUnc1N8vSrFwO4nd5sSLFjbhI5P8qXMjt1dSUpu1yfNZgbC0U1CHshDQV133/zMEz8NmMWdF1kR9V+lCRh1AInBysNSrKXF+jVfGQWyiCIl67GqgUY5GQbgyyh3JylUkD06G1hk2Z40zWjqwNzypRgQKkC4GHacSGs4BFQI4ywH3KtK5CPbK0ao2edJnS1G2Jx3+GOc9cnO7Y7GHRTZZUSZwqHGpVFeRCujnTwjnS7hv/O03es/HK6ByGxa70epHWDSSusiXYE9kscZ1jCCA6K5w+/g9MLRdwZPAfKPtd04Q2oZ2LOtSGObhB5g2xHwPc7puETEpR/V28zLOyKuKkToaCl2G9fF3Aq4c8ktVXnDT570uvboeoWdCWU5rJit6D6rNSeRwmpsBK6WxNQ9Hl8i5afVTeLlOwMam+gNOLcS2GRyyQ6aw54FCkMBun2CHwmFpdPZ2aeePAZjOITSVnMx19Jp+bqKCRLYbHE5DOjrShylM3TrFDdDe1uno6NfPG0eNmEJvPu2f8RRTy9QEfAF9tT/CVleQxbD4PutyEHtROB6Uz0CRFrhbKKv7dvCqhP8JOCzPtIsSt1YnXeOYLndFK1l9cc4TBLq09iv7qRdNIrXg8yqjoy169aLKAtj/gP/GGFe/33uV7lJbk11cv7s649hE1f12hMjkMTbzCbWaI5CseGu1obrLHvNv1cRx1JF1xl3QDVfE+ruKLokrq1AS4eIdXnSS/LTmfqdMXfkb7m+zDuTqdKywyOn5OmT3lqxfq/l+9EHh+1bw3KH2IgNlMsAjoQ/b6nKT7nu/rOC25fbSsiUus/d8R/r0ZSzw1K3R47lt6n2eGDbXqu0InlO3xlHtAx1Od5738kN3HX5ELbx9L9BYd4t3zbR0knRw3yRrRDwSr9ldXSXwo4mPZtjHUx39iDO+P3//n/wENr7KMtqEIAA== + + + 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 3c1a2ff149..4d43b4cb7e 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -358,6 +358,14 @@ 201509021536425_ExportFramework1.cs + + + 201509150927576_Merge3.cs + + + + 201509150931528_ExportFramework2.cs + @@ -673,6 +681,12 @@ 201509021536425_ExportFramework1.cs + + 201509150927576_Merge3.cs + + + 201509150931528_ExportFramework2.cs + diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs index 141467b0f5..989af21e30 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs @@ -63,7 +63,7 @@ public interface IExportExecuteContext /// /// The public URL of the export file (accessible through the internet) /// - string FileUrl { get; } + string FilePublicUrl { get; } /// /// The path of the current export file @@ -156,27 +156,16 @@ public bool IsMaxFailures public string Folder { get; private set; } public string FileNamePattern { get; internal set; } public string FileExtension { get; internal set; } - public string FileName - { - get - { - var finallyResolvedPattern = FileNamePattern - .Replace("%Misc.FileNumber%", (Data.FileIndex + 1).ToString("D4")) - .ToValidFileName("") - .Truncate(MaxFileNameLength); - - return string.Concat(finallyResolvedPattern, FileExtension); - } - } + public string FileName { get; internal set; } public string FilePath { get { return Path.Combine(Folder, FileName); } } - public string FileUrl + public string FilePublicUrl { get { - var url = string.Concat(((string)Store.Url).EnsureEndsWith("/"), ExportProfileTask.PublicFolder, "/", FileName); + var url = string.Concat(((string)Store.Url).EnsureEndsWith("/"), ExportProfileTask.PublicFolder.EnsureEndsWith("/"), FileName); return url; } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index 4d64b3b368..c09187d974 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -1041,9 +1041,25 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) if (!ctx.IsPreview) { + var finallyResolvedPattern = ctx.Export.FileNamePattern + .Replace("%Misc.FileNumber%", (ctx.Export.Data.FileIndex + 1).ToString("D4")) + .ToValidFileName("") + .Truncate(ctx.Export.MaxFileNameLength); + + ctx.Export.FileName = finallyResolvedPattern + ctx.Export.FileExtension; + ctx.Provider.Value.Execute(ctx.Export); ctx.Log.Information("Provider reports {0} successful exported record(s)".FormatInvariant(ctx.Export.RecordsSucceeded)); + + if (File.Exists(ctx.Export.FilePath)) + { + ctx.ResultInfo.Files.Add(new ExportResultFileInfo + { + StoreId = ctx.Store.Id, + FileName = ctx.Export.FileName + }); + } } else if (ctx.Export.Data.ReadNextSegment()) { @@ -1182,6 +1198,17 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) } finally { + try + { + if (!ctx.IsPreview) + { + ctx.Profile.ResultInfo = XmlHelper.Serialize(ctx.ResultInfo); + + _exportService.UpdateExportProfile(ctx.Profile); + } + } + catch { } + try { if (!ctx.IsPreview && ctx.Profile.Cleanup && ctx.Export.Abort != ExportAbortion.Hard) @@ -1364,6 +1391,11 @@ public ExportProfileTaskContext( RecordsPerStore = new Dictionary(); EntityIdsLoaded = new List(); + + ResultInfo = new ExportResultInfo + { + Files = new List() + }; } public List EntityIdsSelected { get; private set; } @@ -1385,8 +1417,9 @@ public bool IsPreview public TaskExecutionContext TaskContext { get; private set; } public ExportProfile Profile { get; private set; } public Provider Provider { get; private set; } - public ExportFilter Filter { get; private set; } + public ExportResultInfo ResultInfo { get; set; } + public ExportFilter Filter { get; private set; } public ExportProjection Projection { get; private set; } public Currency ProjectionCurrency { get; set; } public Customer ProjectionCustomer { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 0480c88a00..47ff5fd6e6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -327,23 +327,25 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile try { var publicFolder = Path.Combine(HttpRuntime.AppDomainAppPath, ExportProfileTask.PublicFolder); - var exportFolder = profile.GetExportFolder(true); + var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); - if (Directory.Exists(exportFolder)) + if (resultInfo != null && resultInfo.Files != null) { - foreach (var path in Directory.GetFiles(exportFolder).OrderBy(y => y)) + foreach (var fileInfo in resultInfo.Files) { - var fileName = Path.GetFileName(path); - if (System.IO.File.Exists(Path.Combine(publicFolder, fileName))) + if (System.IO.File.Exists(Path.Combine(publicFolder, fileInfo.FileName)) && !deploymentModel.PublicFiles.Any(y => y.FileName == fileInfo.FileName)) { - deploymentModel.PublicFileNames.Add(fileName); + var store = allStores.FirstOrDefault(y => y.Id == fileInfo.StoreId) ?? _services.StoreContext.CurrentStore; + + deploymentModel.PublicFiles.Add(new ExportDeploymentModel.PublicFile + { + StoreId = store.Id, + StoreName = store.Name, + FileName = fileInfo.FileName, + FileUrl = string.Concat(store.Url.EnsureEndsWith("/"), ExportProfileTask.PublicFolder.EnsureEndsWith("/"), fileInfo.FileName) + }); } } - - int? storeId = (filter.StoreId == 0 ? projection.StoreId : filter.StoreId); - var store = (storeId.HasValue ? _services.StoreService.GetStoreById(storeId.Value) : _services.StoreContext.CurrentStore); - - deploymentModel.PublicRootUrl = store.Url.EnsureEndsWith("/") + ExportProfileTask.PublicFolder.EnsureEndsWith("/"); } } catch (Exception exc) @@ -379,7 +381,7 @@ private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment EmailAccountId = deployment.EmailAccountId, PassiveMode = deployment.PassiveMode, UseSsl = deployment.UseSsl, - PublicFileNames = new List() + PublicFiles = new List() }; if (forEdit) diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs index 5339594432..ffecae4f0a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs @@ -55,8 +55,7 @@ public string DeploymentTypeIconClass [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.IsPublic")] public bool IsPublic { get; set; } - public string PublicRootUrl { get; set; } - public List PublicFileNames { get; set; } + public List PublicFiles { get; set; } [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.Username")] public string Username { get; set; } @@ -91,5 +90,13 @@ public string DeploymentTypeIconClass [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.UseSsl")] public bool UseSsl { get; set; } + + public class PublicFile + { + public int StoreId { get; set; } + public string StoreName { get; set; } + public string FileName { get; set; } + public string FileUrl { get; set; } + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml index 170836c8f7..67ce817d18 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml @@ -4,20 +4,26 @@ @{ Layout = null; } -
+

 @Model.DeploymentTypeName @if (Model.DeploymentType == ExportDeploymentType.FileSystem && Model.IsPublic) { (@T("Common.Public")) } -

+

@if (Model.DeploymentType == ExportDeploymentType.FileSystem) { if (Model.IsPublic) { - foreach (var fileName in Model.PublicFileNames) + foreach (var grp in Model.PublicFiles.OrderBy(x => x.StoreId).GroupBy(x => x.StoreId)) { - +

+

@(grp.First().StoreName):
+ @foreach (var file in grp) + { + + } +

} } else From e5dc295d19ea2262a0f40c847eb46d9d9f4d6cf6 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 15 Sep 2015 15:19:24 +0200 Subject: [PATCH 065/874] Example button for export file name pattern resolving --- .../201509150931528_ExportFramework2.cs | 1 + .../DataExchange/ExportExecuteContext.cs | 15 +++++------- .../DataExchange/ExportExtensions.cs | 24 +++++++++++++++++++ .../DataExchange/ExportProfileTask.cs | 15 ++---------- .../Controllers/ExportController.cs | 22 +++++++++++++++-- .../Models/DataExchange/ExportProfileModel.cs | 1 + .../Administration/Views/Export/Edit.cshtml | 12 ++++++++++ .../Views/Export/_Tab.Deployment.cshtml | 13 ++++++++-- 8 files changed, 77 insertions(+), 26 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index 0c170a71d5..43289394ec 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -27,6 +27,7 @@ public void Seed(SmartObjectContext context) public void MigrateLocaleResources(LocaleResourcesBuilder builder) { + builder.AddOrUpdate("Common.Example", "Example", "Beispiel"); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs index 989af21e30..5675529cfe 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Dynamic; +using System.Collections.Generic; using System.IO; using System.Threading; using SmartStore.Core.Domain.DataExchange; @@ -61,14 +59,14 @@ public interface IExportExecuteContext string FileName { get; } /// - /// The public URL of the export file (accessible through the internet) + /// The path of the current export file /// - string FilePublicUrl { get; } + string FilePath { get; } /// - /// The path of the current export file + /// The public URL of the export file (accessible through the internet) /// - string FilePath { get; } + string FilePublicUrl { get; } /// /// Provider specific configuration data @@ -154,9 +152,8 @@ public bool IsMaxFailures public int MaxFileNameLength { get; internal set; } public string Folder { get; private set; } - public string FileNamePattern { get; internal set; } - public string FileExtension { get; internal set; } public string FileName { get; internal set; } + public string FileExtension { get; internal set; } public string FilePath { get { return Path.Combine(Folder, FileName); } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs index ab4ad74de6..7ed95478e3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs @@ -2,6 +2,7 @@ using System.Linq; using SmartStore.Core.Domain; using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Stores; using SmartStore.Core.Plugins; using SmartStore.Utilities; @@ -56,5 +57,28 @@ public static string GetExportLogFilePath(this ExportProfile profile) var path = Path.Combine(profile.GetExportFolder(), "log.txt"); return path; } + + /// + /// Resolves the file name pattern for an export profile + /// + /// Export profile + /// Store + /// File number + /// The maximum length of the file name + /// Resolved file name pattern + public static string ResolveFileNamePattern(this ExportProfile profile, Store store, int fileNumber, int maxFileNameLength) + { + var result = profile.FileNamePattern + .Replace("%ExportProfile.Id%", profile.Id.ToString()) + .Replace("%ExportProfile.SeoName%", SeoHelper.GetSeName(profile.Name, true, false).Replace("/", "").Replace("-", "")) + .Replace("%ExportProfile.FolderName%", profile.FolderName) + .Replace("%Store.Id%", store.Id.ToString()) + .Replace("%Store.SeoName%", profile.PerStore ? SeoHelper.GetSeName(store.Name, true, false) : "allstores") + .Replace("%Misc.FileNumber%", fileNumber.ToString("D4")) + .ToValidFileName("") + .Truncate(maxFileNameLength); + + return result; + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index c09187d974..31c78965a5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -998,14 +998,6 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.FileExtension = ctx.Provider.Value.FileExtension.ToLower().EnsureStartsWith("."); - ctx.Export.FileNamePattern = ctx.Profile.FileNamePattern - .Replace("%ExportProfile.Id%", ctx.Profile.Id.ToString()) - .Replace("%ExportProfile.SeoName%", SeoHelper.GetSeName(ctx.Profile.Name, true, false).Replace("/", "").Replace("-", "")) - .Replace("%ExportProfile.FolderName%", ctx.Profile.FolderName) - .Replace("%Store.Id%", ctx.Store.Id.ToString()) - .Replace("%Store.SeoName%", ctx.Profile.PerStore ? SeoHelper.GetSeName(ctx.Store.Name, true, false) : "allstores"); - - var totalCount = ctx.RecordsPerStore.First(x => x.Key == ctx.Store.Id).Value; if (ctx.Provider.Value.EntityType == ExportEntityType.Product) @@ -1041,12 +1033,9 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) if (!ctx.IsPreview) { - var finallyResolvedPattern = ctx.Export.FileNamePattern - .Replace("%Misc.FileNumber%", (ctx.Export.Data.FileIndex + 1).ToString("D4")) - .ToValidFileName("") - .Truncate(ctx.Export.MaxFileNameLength); + var resolvedPattern = ctx.Profile.ResolveFileNamePattern(ctx.Store, ctx.Export.Data.FileIndex + 1, ctx.Export.MaxFileNameLength); - ctx.Export.FileName = finallyResolvedPattern + ctx.Export.FileExtension; + ctx.Export.FileName = resolvedPattern + ctx.Export.FileExtension; ctx.Provider.Value.Execute(ctx.Export); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 47ff5fd6e6..df17f7c9e9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -28,7 +28,6 @@ using SmartStore.Services.Media; using SmartStore.Services.Messages; using SmartStore.Services.Security; -using SmartStore.Utilities; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Plugins; @@ -54,6 +53,7 @@ public class ExportController : AdminControllerBase private readonly IComponentContext _componentContext; private readonly AdminAreaSettings _adminAreaSettings; private readonly IDateTimeHelper _dateTimeHelper; + private readonly DataExchangeSettings _dataExchangeSettings; public ExportController( ICommonServices services, @@ -69,7 +69,8 @@ public ExportController( IEmailAccountService emailAccountService, IComponentContext componentContext, AdminAreaSettings adminAreaSettings, - IDateTimeHelper dateTimeHelper) + IDateTimeHelper dateTimeHelper, + DataExchangeSettings dataExchangeSettings) { _services = services; _exportService = exportService; @@ -85,6 +86,7 @@ public ExportController( _componentContext = componentContext; _adminAreaSettings = adminAreaSettings; _dateTimeHelper = dateTimeHelper; + _dataExchangeSettings = dataExchangeSettings; } #region Utilities @@ -158,6 +160,8 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile model.Cleanup = profile.Cleanup; model.LogFileExists = System.IO.File.Exists(profile.GetExportLogFilePath()); + model.FileNamePatternExample = profile.ResolveFileNamePattern(_services.StoreContext.CurrentStore, 1, _dataExchangeSettings.MaxFileNameLength); + model.AvailableEmailAccounts = allEmailAccounts .Select(x => new SelectListItem { Text = x.FriendlyName, Value = x.Id.ToString() }) .ToList(); @@ -867,6 +871,20 @@ public ActionResult DownloadLogFile(int id) return result; } + public ActionResult ResolveFileNamePatternExample(int id, string pattern) + { + var profile = _exportService.GetExportProfileById(id); + + _services.DbContext.DetachEntity(profile); + profile.FileNamePattern = pattern.EmptyNull(); + + var provider = _exportService.LoadProvider(profile.ProviderSystemName); + + var resolvedPattern = profile.ResolveFileNamePattern(_services.StoreContext.CurrentStore, 1, _dataExchangeSettings.MaxFileNameLength); + + return this.Content(resolvedPattern); + } + #region Export deployment public ActionResult CreateDeployment(int id) diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 676bf7598f..edcd4fc51a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -27,6 +27,7 @@ public partial class ExportProfileModel : EntityModelBase [SmartResourceDisplayName("Admin.DataExchange.Export.FileNamePattern")] public string FileNamePattern { get; set; } + public string FileNamePatternExample { get; set; } [SmartResourceDisplayName("Common.Enabled")] public bool Enabled { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index a4f60629c0..d4929f7064 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -276,5 +276,17 @@ $('#@(Html.FieldIdFor(x => x.EmailAccountId))').change(function () { $('#CompletedEmailAddressesContainer').toggle(!_.isEmpty($(this).val())); }).trigger('change'); + + // resolve file name pattern example + $('#FileNamePatternExample').find('button:first').click(function () { + var pattern = $('#@(Html.FieldIdFor(x => x.FileNamePattern))').val(); + $(this).doAjax({ + type: 'GET', + url: '@Url.Action("ResolveFileNamePatternExample", new { id = Model.Id })' + '?pattern=' + encodeURIComponent(pattern), + callbackSuccess: function (resp) { + $('#FileNamePatternExample').find('div:first').text(resp); + } + }); + }); }); \ 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 dd10d4baba..f0a276753b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml @@ -17,8 +17,17 @@ @Html.SmartLabelFor(x => x.FileNamePattern)
- @Html.TextBoxFor(x => x.FileNamePattern, new { @class = "input-large" }) - @Html.ValidationMessageFor(x => x.FileNamePattern) +
+ @Html.TextBoxFor(x => x.FileNamePattern, new { @class = "input-large" }) + @Html.ValidationMessageFor(x => x.FileNamePattern) +
+
+ +    +
@Model.FileNamePatternExample
+
- - + @if(Model.AvailableProfiles.Count > 0) + { + + + + + }
+ @Html.SmartLabelFor(model => model.Provider.SystemName) @@ -18,7 +18,7 @@
+   @@ -28,6 +28,17 @@ }
+ @Html.SmartLabelFor(model => model.CloneProfileId) + + @Html.DropDownListFor(model => model.CloneProfileId, Model.AvailableProfiles, Model.UnspecifiedString, new { style = "width: 500px;" }) +
} diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 8e76e75008..7b78bc903a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -139,6 +139,7 @@ else callbackSuccess: function (resp) { dialog.find('.modal-body').html(resp); $('select[name="Provider.SystemName"]').selectWrapper(); + $('select[name="CloneProfileId"]').selectWrapper(); toggleProviderDescription(); }, callbackComplete: function () { From 9c5af676709905130e1b7336fa26795aba355703 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 16 Sep 2015 10:40:55 +0200 Subject: [PATCH 067/874] Getting started to convert GMC plugin to support new export framework --- .../201509150931528_ExportFramework2.cs | 52 +++++++++++++++++ .../AdminMenu.cs | 10 +--- .../{images/mc_logo.png => branding.png} | Bin ... => FeedGoogleMerchantCenterController.cs} | 4 +- .../DependencyRegistrar.cs | 1 - .../Description.txt | 2 +- ...n.cs => GoogleMerchantCenterFeedPlugin.cs} | 11 +++- .../Localization/resources.de-de.xml | 12 +++- .../Localization/resources.en-us.xml | 12 +++- .../Models/ProfileConfigurationModel.cs | 14 +++++ .../Providers/ProductExportXmlProvider.cs | 53 ++++++++++++++++++ .../RouteProvider.cs | 5 +- .../SmartStore.GoogleMerchantCenter.csproj | 24 +++++--- .../StaticFileGenerationTask.cs | 22 -------- .../Configure.cshtml | 6 +- .../ProductEditTab.cshtml | 0 .../ProfileConfiguration.cshtml | 8 +++ .../changelog.md | 6 +- 18 files changed, 188 insertions(+), 54 deletions(-) rename src/Plugins/SmartStore.GoogleMerchantCenter/Content/{images/mc_logo.png => branding.png} (100%) rename src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/{FeedFroogleController.cs => FeedGoogleMerchantCenterController.cs} (97%) rename src/Plugins/SmartStore.GoogleMerchantCenter/{FroogleFeedPlugin.cs => GoogleMerchantCenterFeedPlugin.cs} (87%) create mode 100644 src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs create mode 100644 src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs delete mode 100644 src/Plugins/SmartStore.GoogleMerchantCenter/StaticFileGenerationTask.cs rename src/Plugins/SmartStore.GoogleMerchantCenter/Views/{FeedFroogle => FeedGoogleMerchantCenter}/Configure.cshtml (98%) rename src/Plugins/SmartStore.GoogleMerchantCenter/Views/{FeedFroogle => FeedGoogleMerchantCenter}/ProductEditTab.cshtml (100%) create mode 100644 src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index 469aa86132..6872d2965d 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -23,6 +23,13 @@ public bool RollbackOnFailure public void Seed(SmartObjectContext context) { context.MigrateLocaleResources(MigrateLocaleResources); + + context.Execute("DELETE FROM [dbo].[ScheduleTask] WHERE [Type] = 'SmartStore.GoogleMerchantCenter.StaticFileGenerationTask, SmartStore.GoogleMerchantCenter'"); + + //context.MigrateSettings(x => + //{ + // x.DeleteGroup("FroogleSettings"); + //}); } public void MigrateLocaleResources(LocaleResourcesBuilder builder) @@ -34,6 +41,51 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Einstellungen bernehmen von", "Specifies an export profile from which to apply the settings.", "Legt das Exportprofil fest, von welchem die Einstellungen bernommen werden sollen."); + + + + builder.Delete( + "Plugins.Feed.Froogle.TaskEnabled", + "Plugins.Feed.Froogle.TaskEnabled.Hint", + "Plugins.Feed.Froogle.StaticFileUrl", + "Plugins.Feed.Froogle.StaticFileUrl.Hint", + "Plugins.Feed.Froogle.GenerateStaticFileEachMinutes", + "Plugins.Feed.Froogle.GenerateStaticFileEachMinutes.Hint", + //"Plugins.Feed.Froogle.ShippingCost", + //"Plugins.Feed.Froogle.ShippingCost.Hint", + //"Plugins.Feed.Froogle.ShippingTime", + //"Plugins.Feed.Froogle.ShippingTime.Hint", + //"Plugins.Feed.Froogle.Currency", + //"Plugins.Feed.Froogle.Currency.Hint", + //"Plugins.Feed.Froogle.ProductPictureSize", + //"Plugins.Feed.Froogle.ProductPictureSize.Hint", + //"Plugins.Feed.Froogle.BuildDescription", + //"Plugins.Feed.Froogle.BuildDescription.Hint", + //"Plugins.Feed.Froogle.Automatic", + //"Plugins.Feed.Froogle.DescShort", + //"Plugins.Feed.Froogle.DescLong", + //"Plugins.Feed.Froogle.DescTitleAndShort", + //"Plugins.Feed.Froogle.DescTitleAndLong", + //"Plugins.Feed.Froogle.DescManuAndTitleAndShort", + //"Plugins.Feed.Froogle.DescManuAndTitleAndLong", + //"Plugins.Feed.Froogle.NoSpec", + //"Plugins.Feed.Froogle.UseOwnProductNo", + //"Plugins.Feed.Froogle.UseOwnProductNo.Hint", + //"Plugins.Feed.Froogle.DescriptionToPlainText", + //"Plugins.Feed.Froogle.DescriptionToPlainText.Hint", + //"Plugins.Feed.Froogle.Brand", + //"Plugins.Feed.Froogle.Brand.Hint", + //"Plugins.Feed.Froogle.ExportFormat", + //"Plugins.Feed.Froogle.ExportFormat.Hint", + //"Plugins.Feed.Froogle.Store", + //"Plugins.Feed.Froogle.Store.Hint", + //"Plugins.Feed.Froogle.ConvertNetToGrossPrices", + //"Plugins.Feed.Froogle.ConvertNetToGrossPrices.Hint", + //"Plugins.Feed.Froogle.LanguageId", + //"Plugins.Feed.Froogle.LanguageId.Hint", + "Plugins.Feed.Froogle.Generate", + "Plugins.Feed.Froogle.ConfigSaveNote" + ); } } } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs index 97b051cfbc..b6c3684093 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs @@ -1,11 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web.Routing; -using System.Web.Mvc; +using SmartStore.Collections; using SmartStore.Web.Framework.UI; -using SmartStore.Collections; namespace SmartStore.GoogleMerchantCenter { @@ -20,7 +14,7 @@ protected override void BuildMenuCore(TreeNode pluginsNode) var menuItem = new MenuItem().ToBuilder() .Text("Google Merchant Center") .ResKey("Plugins.FriendlyName.SmartStore.GoogleMerchantCenter") - .Action("ConfigurePlugin", "Plugin", new { systemName = "SmartStore.GoogleMerchantCenter", area = "Admin" }) + .Action("ConfigurePlugin", "Plugin", new { systemName = GoogleMerchantCenterFeedPlugin.SystemName, area = "Admin" }) .ToItem(); root.Append(menuItem); diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Content/images/mc_logo.png b/src/Plugins/SmartStore.GoogleMerchantCenter/Content/branding.png similarity index 100% rename from src/Plugins/SmartStore.GoogleMerchantCenter/Content/images/mc_logo.png rename to src/Plugins/SmartStore.GoogleMerchantCenter/Content/branding.png diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedFroogleController.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs similarity index 97% rename from src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedFroogleController.cs rename to src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs index c2af6dee3a..b4f51714c4 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedFroogleController.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs @@ -13,14 +13,14 @@ namespace SmartStore.GoogleMerchantCenter.Controllers { [AdminAuthorize] - public class FeedFroogleController : PluginControllerBase + public class FeedGoogleMerchantCenterController : PluginControllerBase { private readonly FroogleSettings _settings; private readonly IGoogleFeedService _googleService; private readonly ISettingService _settingService; private readonly IPermissionService _permissionService; - public FeedFroogleController( + public FeedGoogleMerchantCenterController( FroogleSettings settings, IGoogleFeedService googleService, ISettingService settingService, diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/DependencyRegistrar.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/DependencyRegistrar.cs index b7a1b25c54..ac6e3dd393 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/DependencyRegistrar.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/DependencyRegistrar.cs @@ -1,6 +1,5 @@ using Autofac; using Autofac.Core; -using Autofac.Integration.Mvc; using SmartStore.Core.Data; using SmartStore.Core.Infrastructure; using SmartStore.Core.Infrastructure.DependencyManagement; diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt index 917162cdc2..6cfd9dfe99 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt @@ -1,7 +1,7 @@ FriendlyName: Google Merchant Center (GMC) SystemName: SmartStore.GoogleMerchantCenter Group: Marketing -Version: 2.2.0.3 +Version: 2.2.0.4 MinAppVersion: 2.2.0 Author: SmartStore AG DisplayOrder: 1 diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/FroogleFeedPlugin.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs similarity index 87% rename from src/Plugins/SmartStore.GoogleMerchantCenter/FroogleFeedPlugin.cs rename to src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs index a4f5d4d476..0b90d2b25b 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/FroogleFeedPlugin.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs @@ -7,12 +7,12 @@ namespace SmartStore.GoogleMerchantCenter { - public class FroogleFeedPlugin : BasePlugin, IConfigurable + public class GoogleMerchantCenterFeedPlugin : BasePlugin, IConfigurable { private readonly IGoogleFeedService _googleService; private readonly ICommonServices _services; - public FroogleFeedPlugin( + public GoogleMerchantCenterFeedPlugin( IGoogleFeedService googleService, ICommonServices services) { @@ -20,6 +20,11 @@ public FroogleFeedPlugin( _services = services; } + public static string SystemName + { + get { return "SmartStore.GoogleMerchantCenter"; } + } + /// /// Gets a route for provider configuration /// @@ -30,7 +35,7 @@ public void GetConfigurationRoute(out string actionName, out string controllerNa { actionName = "Configure"; controllerName = "FeedFroogle"; - routeValues = new RouteValueDictionary() { { "Namespaces", "SmartStore.GoogleMerchantCenter.Controllers" }, { "area", "SmartStore.GoogleMerchantCenter" } }; + routeValues = new RouteValueDictionary() { { "Namespaces", "SmartStore.GoogleMerchantCenter.Controllers" }, { "area", SystemName } }; } /// diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml index b72e213a61..7588112265 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml @@ -2,7 +2,17 @@ Google Merchant Center (GMC) Feed - + + Ermöglicht den Export von Produktdaten im Feed-Format von Google Merchant Center (GMC). + + + Google Merchant Center (GMC) XML Feed + + + Ermöglicht den Export von Produktdaten im XML Feed-Format von Google Merchant Center (GMC). + + +
  • Für die Produktidentifizierung muss entweder die GTIN (z.B. als UPC, EAN etc.) oder der Hersteller samt Hersteller-Artikelnummer (MPN) hinterlegt sein. Google empfiehlt die Angabe aller drei Informationen.
  • diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml index ed9d4592d0..84aea32856 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml @@ -1,7 +1,17 @@  - Google Merchant Center (GMC) Feed + Google Merchant Center (GMC) feed + + Allows you to export product data in the feed format of Google Merchant Center (GMC). + + + Google Merchant Center (GMC) XML feed + + + Allows you to export product data in the XML feed format of Google Merchant Center (GMC). + + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs new file mode 100644 index 0000000000..f01d7ee9a0 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs @@ -0,0 +1,14 @@ +using System; +using SmartStore.Web.Framework; + +namespace SmartStore.GoogleMerchantCenter.Models +{ + [Serializable] + public class ProfileConfigurationModel + { + public ProfileConfigurationModel() + { + } + + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs new file mode 100644 index 0000000000..16950de170 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs @@ -0,0 +1,53 @@ +using System; +using System.IO; +using System.Text; +using System.Xml; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; +using SmartStore.Services.DataExchange; +using SmartStore.GoogleMerchantCenter.Models; + +namespace SmartStore.GoogleMerchantCenter.Providers +{ + [SystemName("Feeds.GoogleMerchantCenterProductXml")] + [FriendlyName("Google Merchant Center XML product feed")] + [DisplayOrder(1)] + [ExportProjectionSupport( + ExportProjectionSupport.Description, + //ExportProjectionSupport.UseOwnProductNo, + //ExportProjectionSupport.Brand, + //ExportProjectionSupport.MainPictureUrl, + //ExportProjectionSupport.ShippingTime, + //ExportProjectionSupport.ShippingCosts, + ExportProjectionSupport.AttributeCombinationAsProduct)] + public class ProductExportXmlProvider : IExportProvider + { + public bool RequiresConfiguration(out string partialViewName, out Type modelType, out Action initialize) + { + partialViewName = "~/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml"; + modelType = typeof(ProfileConfigurationModel); + initialize = null; + return true; + } + + public ExportEntityType EntityType + { + get { return ExportEntityType.Product; } + } + + public string FileExtension + { + get { return "XML"; } + } + + public void Execute(IExportExecuteContext context) + { + } + + public void ExecuteEnded(IExportExecuteContext context) + { + // nothing to do + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/RouteProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/RouteProvider.cs index 57ac853189..2a58266db7 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/RouteProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/RouteProvider.cs @@ -10,11 +10,12 @@ public void RegisterRoutes(RouteCollection routes) { routes.MapRoute("SmartStore.GoogleMerchantCenter", "Plugins/SmartStore.GoogleMerchantCenter/{action}", - new { controller = "FeedFroogle", action = "Configure" }, + new { controller = "FeedGoogleMerchantCenter", action = "Configure" }, new[] { "SmartStore.GoogleMerchantCenter.Controllers" } ) - .DataTokens["area"] = "SmartStore.GoogleMerchantCenter"; + .DataTokens["area"] = GoogleMerchantCenterFeedPlugin.SystemName; } + public int Priority { get diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj index a5e760e9b6..d98bfbdd12 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj @@ -153,7 +153,7 @@ Properties\AssemblyVersionInfo.cs - + @@ -173,9 +173,10 @@ - + - + + @@ -205,7 +206,7 @@ 201403112356126_Initial.cs - + PreserveNewest @@ -216,8 +217,8 @@ - - Always + + PreserveNewest Always @@ -226,10 +227,10 @@ PreserveNewest - Always + PreserveNewest - Always + PreserveNewest PreserveNewest @@ -257,7 +258,7 @@ - + PreserveNewest @@ -266,6 +267,11 @@ PreserveNewest + + + PreserveNewest + + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/StaticFileGenerationTask.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/StaticFileGenerationTask.cs deleted file mode 100644 index 474c79bab7..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/StaticFileGenerationTask.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Autofac; -using SmartStore.GoogleMerchantCenter.Services; -using SmartStore.Services.Tasks; - -namespace SmartStore.GoogleMerchantCenter -{ - public class StaticFileGenerationTask : ITask - { - private readonly IGoogleFeedService _feedService; - - public StaticFileGenerationTask(IGoogleFeedService feedService) - { - this._feedService = feedService; - } - - public void Execute(TaskExecutionContext context) - { - _feedService.CreateFeed(context); - context.CancellationToken.ThrowIfCancellationRequested(); - } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedFroogle/Configure.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml similarity index 98% rename from src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedFroogle/Configure.cshtml rename to src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml index e6e53097e9..461d4e0066 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedFroogle/Configure.cshtml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml @@ -17,7 +17,7 @@ } - + @(Html.SmartStore().TabStrip().Name("googlebase-configure").Items(x => { @@ -38,7 +38,7 @@ } -
    +
    @@ -491,7 +491,7 @@ }) .DataBinding(dataBinding => { - dataBinding.Ajax().Select("GoogleProductList", "FeedFroogle"); + dataBinding.Ajax().Select("GoogleProductList", "FeedGoogleMerchantCenter"); }) .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) .PreserveGridState() diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedFroogle/ProductEditTab.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProductEditTab.cshtml similarity index 100% rename from src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedFroogle/ProductEditTab.cshtml rename to src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProductEditTab.cshtml diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml new file mode 100644 index 0000000000..39679306c4 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml @@ -0,0 +1,8 @@ +@using SmartStore.GoogleMerchantCenter.Models; +@model ProfileConfigurationModel +@{ + Layout = null; +} + + +
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md index f57dd1be33..03e71794e6 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md @@ -1,4 +1,8 @@ -#Release Notes# +#Release Notes + +##Google Merchant Center (GMC) 2.2.0.4 +###Improvements +* Supports new export framework ##Google Merchant Center (GMC) 2.2.0.3 ###Bugfixes From 54f928b2bc0e1e2a6a4344abe5348c406df6c7a2 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 16 Sep 2015 11:07:16 +0200 Subject: [PATCH 068/874] GMC feed: Availability value "available for order" deprecated --- changelog.md | 3 ++- .../Services/GoogleFeedService.cs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index c5ce89da7a..26c2ede45e 100644 --- a/changelog.md +++ b/changelog.md @@ -53,7 +53,8 @@ * Preselected price was wrong for product attributes with multiple preselected values (same on product detail page) * #749 Visual Studio 2015 compilation error: CS0009: Metadata file. SmartStore.Licensing.dll could not be opened -- Illegal tables in compressed metadata stream * PayPal Express: fixed capture method -* #770 Resizing browser with product details page causes product image to disappear +* #770 Resizing browser with product details page causes product image to disappear +* GMC feed: Availability value "available for order" deprecated ## SmartStore.NET 2.2.2 diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs index 368f6413d5..76a11bad4d 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs @@ -171,7 +171,9 @@ private string Availability(Product product) return "out of stock"; case BackorderMode.AllowQtyBelow0: case BackorderMode.AllowQtyBelow0AndNotifyCustomer: - return "available for order"; + if (product.AvailableForPreOrder) + return "preorder"; + return "out of stock"; } } return "in stock"; From dca1b87c7d94ceb0fad9f6a1cb2ef1cdb5312ba5 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 16 Sep 2015 11:31:24 +0200 Subject: [PATCH 069/874] Resolved migration merge conflict --- .../201509150927576_Merge3.Designer.cs | 29 ---- .../Migrations/201509150927576_Merge3.cs | 16 --- .../Migrations/201509150927576_Merge3.resx | 126 ------------------ .../SmartStore.Data/SmartStore.Data.csproj | 7 - 4 files changed, 178 deletions(-) delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.Designer.cs delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.cs delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.resx diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.Designer.cs deleted file mode 100644 index e4155c6945..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.Designer.cs +++ /dev/null @@ -1,29 +0,0 @@ -// -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 Merge3 : IMigrationMetadata - { - private readonly ResourceManager Resources = new ResourceManager(typeof(Merge3)); - - string IMigrationMetadata.Id - { - get { return "201509150927576_Merge3"; } - } - - string IMigrationMetadata.Source - { - get { return null; } - } - - string IMigrationMetadata.Target - { - get { return Resources.GetString("Target"); } - } - } -} diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.cs b/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.cs deleted file mode 100644 index 24ce9a2d5f..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace SmartStore.Data.Migrations -{ - using System; - using System.Data.Entity.Migrations; - - public partial class Merge3 : DbMigration - { - public override void Up() - { - } - - public override void Down() - { - } - } -} diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.resx b/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.resx deleted file mode 100644 index 9da77ed83f..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201509150927576_Merge3.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/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index ebc5f3d1c8..8764a6a9ed 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -362,10 +362,6 @@ 201509021536425_ExportFramework1.cs - - - 201509150927576_Merge3.cs - 201509150931528_ExportFramework2.cs @@ -688,9 +684,6 @@ 201509021536425_ExportFramework1.cs - - 201509150927576_Merge3.cs - 201509150931528_ExportFramework2.cs From cc61ec95d587f503dea54deba4f83d88cfb83289 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 17 Sep 2015 13:42:12 +0200 Subject: [PATCH 070/874] Convert GMC plugin to support new export framework --- .../201509150931528_ExportFramework2.cs | 70 +- .../DataExchange/ExpandoExtensions.cs | 1 + .../DataExchange/ExportProfileTask.cs | 30 +- .../Content/smartstore.feed.froogle.css | 10 - .../FeedGoogleMerchantCenterController.cs | 145 ++-- .../Description.txt | 2 +- .../Extensions/MiscExtensions.cs | 3 +- .../FroogleSettings.cs | 39 -- .../GoogleMerchantCenterFeedPlugin.cs | 20 +- .../Localization/resources.de-de.xml | 112 +-- .../Localization/resources.en-us.xml | 102 --- .../Models/FeedFroogleModel.cs | 270 -------- .../Models/FeedGoogleMerchantCenterModel.cs | 108 +++ .../Models/ProfileConfigurationModel.cs | 67 ++ .../Providers/ProductExportXmlProvider.cs | 366 +++++++++- .../Services/GoogleFeedService.cs | 639 +----------------- .../Services/IGoogleFeedService.cs | 16 +- .../SmartStore.GoogleMerchantCenter.csproj | 8 +- ...or.cs => ProfileConfigurationValidator.cs} | 4 +- .../FeedGoogleMerchantCenter/Configure.cshtml | 459 +------------ .../ProfileConfiguration.cshtml | 161 ++++- .../changelog.md | 2 + .../Controllers/ExportController.cs | 2 +- 23 files changed, 896 insertions(+), 1740 deletions(-) delete mode 100644 src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.feed.froogle.css delete mode 100644 src/Plugins/SmartStore.GoogleMerchantCenter/FroogleSettings.cs delete mode 100644 src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedFroogleModel.cs create mode 100644 src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedGoogleMerchantCenterModel.cs rename src/Plugins/SmartStore.GoogleMerchantCenter/Validators/{ConfigurationValidator.cs => ProfileConfigurationValidator.cs} (68%) diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index 6872d2965d..7ad267340b 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -26,10 +26,10 @@ public void Seed(SmartObjectContext context) context.Execute("DELETE FROM [dbo].[ScheduleTask] WHERE [Type] = 'SmartStore.GoogleMerchantCenter.StaticFileGenerationTask, SmartStore.GoogleMerchantCenter'"); - //context.MigrateSettings(x => - //{ - // x.DeleteGroup("FroogleSettings"); - //}); + context.MigrateSettings(x => + { + x.DeleteGroup("FroogleSettings"); + }); } public void MigrateLocaleResources(LocaleResourcesBuilder builder) @@ -51,40 +51,36 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Plugins.Feed.Froogle.StaticFileUrl.Hint", "Plugins.Feed.Froogle.GenerateStaticFileEachMinutes", "Plugins.Feed.Froogle.GenerateStaticFileEachMinutes.Hint", - //"Plugins.Feed.Froogle.ShippingCost", - //"Plugins.Feed.Froogle.ShippingCost.Hint", - //"Plugins.Feed.Froogle.ShippingTime", - //"Plugins.Feed.Froogle.ShippingTime.Hint", - //"Plugins.Feed.Froogle.Currency", - //"Plugins.Feed.Froogle.Currency.Hint", - //"Plugins.Feed.Froogle.ProductPictureSize", - //"Plugins.Feed.Froogle.ProductPictureSize.Hint", - //"Plugins.Feed.Froogle.BuildDescription", - //"Plugins.Feed.Froogle.BuildDescription.Hint", - //"Plugins.Feed.Froogle.Automatic", - //"Plugins.Feed.Froogle.DescShort", - //"Plugins.Feed.Froogle.DescLong", - //"Plugins.Feed.Froogle.DescTitleAndShort", - //"Plugins.Feed.Froogle.DescTitleAndLong", - //"Plugins.Feed.Froogle.DescManuAndTitleAndShort", - //"Plugins.Feed.Froogle.DescManuAndTitleAndLong", - //"Plugins.Feed.Froogle.NoSpec", - //"Plugins.Feed.Froogle.UseOwnProductNo", - //"Plugins.Feed.Froogle.UseOwnProductNo.Hint", - //"Plugins.Feed.Froogle.DescriptionToPlainText", - //"Plugins.Feed.Froogle.DescriptionToPlainText.Hint", - //"Plugins.Feed.Froogle.Brand", - //"Plugins.Feed.Froogle.Brand.Hint", - //"Plugins.Feed.Froogle.ExportFormat", - //"Plugins.Feed.Froogle.ExportFormat.Hint", - //"Plugins.Feed.Froogle.Store", - //"Plugins.Feed.Froogle.Store.Hint", - //"Plugins.Feed.Froogle.ConvertNetToGrossPrices", - //"Plugins.Feed.Froogle.ConvertNetToGrossPrices.Hint", - //"Plugins.Feed.Froogle.LanguageId", - //"Plugins.Feed.Froogle.LanguageId.Hint", + "Plugins.Feed.Froogle.Currency", + "Plugins.Feed.Froogle.Currency.Hint", + "Plugins.Feed.Froogle.ProductPictureSize", + "Plugins.Feed.Froogle.ProductPictureSize.Hint", + "Plugins.Feed.Froogle.AppendDescriptionText", + "Plugins.Feed.Froogle.AppendDescriptionText.Hint", + "Plugins.Feed.Froogle.BuildDescription", + "Plugins.Feed.Froogle.BuildDescription.Hint", + "Plugins.Feed.Froogle.Automatic", + "Plugins.Feed.Froogle.DescShort", + "Plugins.Feed.Froogle.DescLong", + "Plugins.Feed.Froogle.DescTitleAndShort", + "Plugins.Feed.Froogle.DescTitleAndLong", + "Plugins.Feed.Froogle.DescManuAndTitleAndShort", + "Plugins.Feed.Froogle.DescManuAndTitleAndLong", + "Plugins.Feed.Froogle.UseOwnProductNo", + "Plugins.Feed.Froogle.UseOwnProductNo.Hint", + "Plugins.Feed.Froogle.DescriptionToPlainText", + "Plugins.Feed.Froogle.DescriptionToPlainText.Hint", + "Plugins.Feed.Froogle.Brand", + "Plugins.Feed.Froogle.Brand.Hint", + "Plugins.Feed.Froogle.Store", + "Plugins.Feed.Froogle.Store.Hint", + "Plugins.Feed.Froogle.ConvertNetToGrossPrices", + "Plugins.Feed.Froogle.ConvertNetToGrossPrices.Hint", + "Plugins.Feed.Froogle.LanguageId", + "Plugins.Feed.Froogle.LanguageId.Hint", "Plugins.Feed.Froogle.Generate", - "Plugins.Feed.Froogle.ConfigSaveNote" + "Plugins.Feed.Froogle.ConfigSaveNote", + "Plugins.Feed.Froogle.AvailabilityAvailableForOrder" ); } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs index 9b52c95507..76f76a1df0 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs @@ -441,6 +441,7 @@ public static ExpandoObject ToExpando(this Product product, int languageId) expando.BasePriceMeasureUnit = product.BasePriceMeasureUnit; expando.BasePriceAmount = product.BasePriceAmount; expando.BasePriceBaseAmount = product.BasePriceBaseAmount; + expando.BasePriceHasValue = product.BasePriceHasValue; expando.VisibleIndividually = product.VisibleIndividually; expando.DisplayOrder = product.DisplayOrder; expando.BundleTitleText = product.GetLocalized(x => x.BundleTitleText, languageId, true, false); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index 31c78965a5..4ba8f7cb23 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -254,11 +254,12 @@ private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic exp private decimal CalculatePrice(ExportProfileTaskContext ctx, Product product, bool forAttributeCombination) { decimal price = product.Price; - var priceCalculationContext = ctx.ProductDataContext as PriceCalculationContext; // price type if (ctx.Projection.PriceType.HasValue && !forAttributeCombination) { + var priceCalculationContext = ctx.ProductDataContext as PriceCalculationContext; + if (ctx.Projection.PriceType.Value == PriceDisplayType.LowestPrice) { bool displayFromMessage; @@ -672,11 +673,16 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod .OrderBy(x => x.DisplayOrder) .Select(x => { + int pictureSize = _mediaSettings.ProductDetailsPictureSize; + + if (ctx.Provider.Supports(ExportProjectionSupport.MainPictureUrl) && ctx.Projection.PictureSize > 0) + pictureSize = ctx.Projection.PictureSize; + dynamic exp = new ExpandoObject(); exp._Entity = x; exp.Id = x.Id; exp.DisplayOrder = x.DisplayOrder; - exp.Picture = x.Picture.ToExpando(_pictureService, ctx.Store, _mediaSettings.ProductThumbPictureSize, _mediaSettings.ProductDetailsPictureSize); + exp.Picture = x.Picture.ToExpando(_pictureService, ctx.Store, _mediaSettings.ProductThumbPictureSize, pictureSize); return exp as ExpandoObject; }) @@ -833,7 +839,25 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod if (ctx.Provider.Supports(ExportProjectionSupport.SpecialPrice)) { - exp._SpecialPrice = ConvertPrice(ctx, product, _priceCalculationService.GetSpecialPrice(product)); + exp._SpecialPrice = null; + exp._RegularPrice = null; // price if a special price would not exist + + if (!(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)) + { + var specialPrice = _priceCalculationService.GetSpecialPrice(product); + + exp._SpecialPrice = ConvertPrice(ctx, product, specialPrice); + + if (specialPrice.HasValue) + { + decimal tmpSpecialPrice = product.SpecialPrice.Value; + product.SpecialPrice = null; + exp._RegularPrice = CalculatePrice(ctx, product, combination != null); + product.SpecialPrice = tmpSpecialPrice; + + _services.DbContext.SetToUnchanged(product); + } + } } }; diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.feed.froogle.css b/src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.feed.froogle.css deleted file mode 100644 index 3c4b0af7b7..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.feed.froogle.css +++ /dev/null @@ -1,10 +0,0 @@ -.config-logo { - width: 274px; - height: 31px; -} -.edit-taxonomy { - min-width: 460px; -} -.google-product-search { - margin-bottom: 8px !important; -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs index b4f51714c4..2dd0107819 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs @@ -1,13 +1,14 @@ using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Web.Mvc; -using SmartStore.Core.Localization; +using SmartStore.Core; +using SmartStore.Core.Domain.Common; using SmartStore.GoogleMerchantCenter.Models; +using SmartStore.GoogleMerchantCenter.Providers; using SmartStore.GoogleMerchantCenter.Services; -using SmartStore.Services.Configuration; -using SmartStore.Services.Security; +using SmartStore.Services.DataExchange; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Plugins; using Telerik.Web.Mvc; namespace SmartStore.GoogleMerchantCenter.Controllers @@ -15,32 +16,25 @@ namespace SmartStore.GoogleMerchantCenter.Controllers [AdminAuthorize] public class FeedGoogleMerchantCenterController : PluginControllerBase { - private readonly FroogleSettings _settings; - private readonly IGoogleFeedService _googleService; - private readonly ISettingService _settingService; - private readonly IPermissionService _permissionService; + private readonly IGoogleFeedService _googleFeedService; + private readonly AdminAreaSettings _adminAreaSettings; + private readonly IExportService _exportService; public FeedGoogleMerchantCenterController( - FroogleSettings settings, - IGoogleFeedService googleService, - ISettingService settingService, - IPermissionService permissionService) + IGoogleFeedService googleFeedService, + AdminAreaSettings adminAreaSettings, + IExportService exportService) { - _settings = settings; - _googleService = googleService; - _settingService = settingService; - _permissionService = permissionService; - } - - private ActionResult RedirectToConfig() - { - return RedirectToAction("ConfigurePlugin", "Plugin", new { systemName = _googleService.Helper.SystemName, area = "Admin" }); + _googleFeedService = googleFeedService; + _adminAreaSettings = adminAreaSettings; + _exportService = exportService; } public ActionResult ProductEditTab(int productId) { + var culture = CultureInfo.InvariantCulture; var model = new GoogleProductModel { ProductId = productId }; - var entity = _googleService.GetGoogleProductRecord(productId); + var entity = _googleFeedService.GetGoogleProductRecord(productId); if (entity != null) { @@ -57,27 +51,39 @@ public ActionResult ProductEditTab(int productId) { model.Exporting = true; } - - ViewBag.DefaultCategory = _settings.DefaultGoogleCategory; - ViewBag.DefaultGender = T("Common.Auto"); - ViewBag.DefaultAgeGroup = T("Common.Auto"); - ViewBag.DefaultColor = _settings.Color; - ViewBag.DefaultSize = _settings.Size; - ViewBag.DefaultMaterial = _settings.Material; - ViewBag.DefaultPattern = _settings.Pattern; - var ci = CultureInfo.InvariantCulture; - - if (_settings.Gender.HasValue() && _settings.Gender != PluginHelper.NotSpecified) - { - ViewBag.DefaultGender = T("Plugins.Feed.Froogle.Gender" + ci.TextInfo.ToTitleCase(_settings.Gender)); - } + ViewBag.DefaultCategory = ""; + ViewBag.DefaultColor = ""; + ViewBag.DefaultSize = ""; + ViewBag.DefaultMaterial = ""; + ViewBag.DefaultPattern = ""; + ViewBag.DefaultGender = T("Common.Auto"); + ViewBag.DefaultAgeGroup = T("Common.Auto"); - if (_settings.AgeGroup.HasValue() && _settings.AgeGroup != PluginHelper.NotSpecified) + // we do not have export profile context here, so we simply use the first profile + var profile = _exportService.GetExportProfilesBySystemName("Feeds.GoogleMerchantCenterProductXml").FirstOrDefault(); + if (profile != null) { - ViewBag.DefaultAgeGroup = T("Plugins.Feed.Froogle.AgeGroup" + ci.TextInfo.ToTitleCase(_settings.AgeGroup)); - } - + var config = XmlHelper.Deserialize(profile.ProviderConfigData, typeof(ProfileConfigurationModel)) as ProfileConfigurationModel; + if (config != null) + { + ViewBag.DefaultCategory = config.DefaultGoogleCategory; + ViewBag.DefaultColor = config.Color; + ViewBag.DefaultSize = config.Size; + ViewBag.DefaultMaterial = config.Material; + ViewBag.DefaultPattern = config.Pattern; + + if (config.Gender.HasValue() && config.Gender != ProductExportXmlProvider.Unspecified) + { + ViewBag.DefaultGender = T("Plugins.Feed.Froogle.Gender" + culture.TextInfo.ToTitleCase(config.Gender)); + } + + if (config.AgeGroup.HasValue() && config.AgeGroup != ProductExportXmlProvider.Unspecified) + { + ViewBag.DefaultAgeGroup = T("Plugins.Feed.Froogle.AgeGroup" + culture.TextInfo.ToTitleCase(config.AgeGroup)); + } + } + } ViewBag.AvailableGenders = new List { @@ -99,71 +105,24 @@ public ActionResult ProductEditTab(int productId) public ActionResult GoogleCategories() { - var categories = _googleService.GetTaxonomyList(); + var categories = _googleFeedService.GetTaxonomyList(); return Json(categories, JsonRequestBehavior.AllowGet); } public ActionResult Configure() { - var model = new FeedFroogleModel(); - model.Copy(_googleService.Settings, true); - - if (TempData["GenerateFeedRunning"] != null) - model.IsRunning = (bool)TempData["GenerateFeedRunning"]; - - _googleService.SetupModel(model); - - return View(model); - } - - [HttpPost] - [FormValueRequired("save")] - public ActionResult Configure(FeedFroogleModel model) - { - if (!ModelState.IsValid) - return Configure(); - - model.Copy(_googleService.Settings, false); - _settingService.SaveSetting(_googleService.Settings); - - _googleService.Helper.UpdateScheduleTask(model.TaskEnabled); + var model = new FeedGoogleMerchantCenterModel(); - NotifySuccess(_googleService.Helper.GetResource("ConfigSaveNote"), true); - - _googleService.SetupModel(model); + model.AvailableGoogleCategories = _googleFeedService.GetTaxonomyList(); + model.GridPageSize = _adminAreaSettings.GridPageSize; return View(model); } - public ActionResult GenerateFeed() - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageScheduleTasks)) - return AccessDeniedView(); - - if (_googleService.Helper.RunScheduleTask()) - TempData["GenerateFeedRunning"] = true; - - return RedirectToConfig(); - } - - [HttpPost] - public ActionResult GenerateFeedProgress() - { - string message = _googleService.Helper.GetProgressInfo(true); - return Json(new { message = message }, JsonRequestBehavior.DenyGet); - } - - public ActionResult DeleteFiles() - { - _googleService.Helper.DeleteFeedFiles(); - - return RedirectToConfig(); - } - [HttpPost] public ActionResult GoogleProductEdit(int pk, string name, string value) { - _googleService.UpdateInsert(pk, name, value); + _googleFeedService.Upsert(pk, name, value); return this.Content(""); } @@ -173,7 +132,7 @@ public ActionResult GoogleProductList(GridCommand command, string searchProductN { return new JsonResult { - Data = _googleService.GetGridModel(command, searchProductName, touched) + Data = _googleFeedService.GetGridModel(command, searchProductName, touched) }; } } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt index 6cfd9dfe99..b27832dad7 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt @@ -1,4 +1,4 @@ -FriendlyName: Google Merchant Center (GMC) +FriendlyName: Google Merchant Center (GMC) feed SystemName: SmartStore.GoogleMerchantCenter Group: Marketing Version: 2.2.0.4 diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs index 6ab4f71335..c3b13a9939 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs @@ -27,8 +27,7 @@ public static bool IsTouched(this GoogleProductRecord product) if (product != null) { return product.Taxonomy.HasValue() || product.Gender.HasValue() || product.AgeGroup.HasValue() || product.Color.HasValue() || - product.Size.HasValue() || product.Material.HasValue() || product.Pattern.HasValue() || product.ItemGroupId.HasValue() || - !product.Export; + product.Size.HasValue() || product.Material.HasValue() || product.Pattern.HasValue() || product.ItemGroupId.HasValue() || !product.Export; } return false; } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/FroogleSettings.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/FroogleSettings.cs deleted file mode 100644 index f65f63ecef..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/FroogleSettings.cs +++ /dev/null @@ -1,39 +0,0 @@ -using SmartStore.Core.Configuration; -using SmartStore.Utilities; -using SmartStore.Web.Framework.Plugins; - -namespace SmartStore.GoogleMerchantCenter -{ - public class FroogleSettings : PromotionFeedSettings, ISettings - { - public FroogleSettings() - { - ProductPictureSize = 125; - StaticFileName = "google_merchant_center_{0}.xml".FormatWith(CommonHelper.GenerateRandomDigitCode(10)); - Condition = "new"; - OnlineOnly = true; - AdditionalImages = true; - SpecialPrice = true; - } - - public string DefaultGoogleCategory { get; set; } - public string Condition { get; set; } - public bool SpecialPrice { get; set; } - public string Gender { get; set; } - public string AgeGroup { get; set; } - public string Color { get; set; } - public string Size { get; set; } - public string Material { get; set; } - public string Pattern { get; set; } - public bool OnlineOnly { get; set; } - public int ExpirationDays { get; set; } - public bool ExportShipping { get; set; } - public bool ExportBasePrice { get; set; } - - public string AppendDescriptionText1 { get; set; } - public string AppendDescriptionText2 { get; set; } - public string AppendDescriptionText3 { get; set; } - public string AppendDescriptionText4 { get; set; } - public string AppendDescriptionText5 { get; set; } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs index 0b90d2b25b..3808bca487 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/GoogleMerchantCenterFeedPlugin.cs @@ -9,14 +9,14 @@ namespace SmartStore.GoogleMerchantCenter { public class GoogleMerchantCenterFeedPlugin : BasePlugin, IConfigurable { - private readonly IGoogleFeedService _googleService; + private readonly IGoogleFeedService _googleFeedService; private readonly ICommonServices _services; public GoogleMerchantCenterFeedPlugin( - IGoogleFeedService googleService, + IGoogleFeedService googleFeedService, ICommonServices services) { - _googleService = googleService; + _googleFeedService = googleFeedService; _services = services; } @@ -34,7 +34,7 @@ public static string SystemName public void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) { actionName = "Configure"; - controllerName = "FeedFroogle"; + controllerName = "FeedGoogleMerchantCenter"; routeValues = new RouteValueDictionary() { { "Namespaces", "SmartStore.GoogleMerchantCenter.Controllers" }, { "area", SystemName } }; } @@ -43,15 +43,8 @@ public void GetConfigurationRoute(out string actionName, out string controllerNa /// public override void Install() { - var settings = new FroogleSettings(); - settings.CurrencyId = _services.StoreContext.CurrentStore.PrimaryStoreCurrencyId; - - _services.Settings.SaveSetting(settings); - _services.Localization.ImportPluginResourcesFromXml(this.PluginDescriptor); - _googleService.Helper.InsertScheduleTask(); - base.Install(); } @@ -60,11 +53,6 @@ public override void Install() /// public override void Uninstall() { - _googleService.Helper.DeleteFeedFiles(); - _googleService.Helper.DeleteScheduleTask(); - - _services.Settings.DeleteSetting(); - _services.Localization.DeleteLocaleStringResources(PluginDescriptor.ResourceRootKey); var migrator = new DbMigrator(new Configuration()); diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml index 7588112265..1eadc04c7b 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml @@ -29,12 +29,6 @@ Standard-Google-Kategorie fehlt - - Währung - - - Standardwährung fürs Erstellen des Feeds. - Standard-Google-Kategorie @@ -47,15 +41,6 @@ Produktspezifische Daten - - Feed erstellen - - - Thumbnail-Größe Produktbild - - - Die Standardgröße (in Pixel) für das Produkt-Thumbnail. - Produkt @@ -80,59 +65,11 @@ Muster - - Feed automatisch erstellen - - - Aktivieren Sie diese Option, falls die Feed-Datei automatisch erstellt werden soll. - - - Zeitspanne (in Minuten) - - - Die Zeitspanne nach der die Feed-Datei automatisch erstellt werden soll. - - - Einstellungen wurden erfolgreich gespeichert. Starten Sie die Anwendung bitte neu, falls "Feed automatisch erstellen" geändert wurde. - - - Artikelbeschreibung - - - Legen Sie fest, welche Informationen zur Beschreibung des Artikel wie verwendet werden sollen. - - - Automatisch - - - Kurzbeschreibung - - - Detailbeschreibung - - - Produktname + Kurzbeschreibung - - - Produktname + Detailbeschreibung - - - Hersteller + Produktname + Kurzbeschreibung - - - Hersteller + Produktname + Detailbeschreibung - - - Anzuhängender Text 1 - 5 - - - Einer der fünf Textbausteine wird an die Artikelbeschreibung angehängt, falls Kurz- und Langbeschreibung leer sind. - Zusätzliche Bilder - Aktivieren Sie diese Option, falls Sie mehr als ein Produktbild exportieren möchten. Bis zu 10 sind möglich. + Legt fest, ob mehr als ein Produktbild exportiert werden soll. Bis zu 10 sind möglich. Zustand @@ -158,9 +95,6 @@ Vorrätig - - Bestellbar - Vergriffen @@ -171,19 +105,7 @@ Aktionspreis exportieren - Aktivieren Sie diese Option, falls Sie den Aktionspreis inkl. Aktionszeitraum exportieren möchten. - - - Hersteller\Marke - - - Dieser Wert wird verwendet, wenn für ein Produkt kein Hersteller bzw. Marke hinterlegt ist. - - - Eigene Artikelnummer - - - Aktivieren Sie diese Option, falls die eigene Artikelnummer als Hersteller-Artikelnummer (MPN) exportiert werden soll, sofern diese fehlt. + Legt fest, ob ein Aktionspreis inkl. Aktionszeitraum exportiert werden soll, sofern ein solcher für das jeweilige Produkt existiert. Geschlecht @@ -240,13 +162,7 @@ Nur online zu kaufen - Aktivieren Sie diese Option, wenn Produkte nur online erworben werden können. Aktivieren Sie diese Option nicht, falls Produkte auch in Ihrem Ladengeschäft erhältlich sind. - - - HTML entfernen - - - Aktivieren Sie diese Option, falls für den Export alle HTML-Auszeichnungen aus der Artikelbeschreibung entfernt werden sollen. + Legt fest, ob Produkte nur online erworben werden können. Aktivieren Sie diese Option nicht, falls Produkte auch in Ihrem Ladengeschäft erhältlich sind. Leer @@ -269,12 +185,6 @@ Noch nicht bearbeitet - - Shop - - - Wählen Sie den Shop, für den der Feed erstellt werden soll. - Verfällt in Tagen @@ -288,24 +198,12 @@ Versanddaten exportieren - Aktivieren Sie diese Option, wenn Sie z.B. das Gewicht des Produkt exportieren möchten. + Legt fest, ob das Gewicht eines Produktes exportiert werden soll. Grundpreis exportieren - Aktivieren Sie diese Option, wenn Sie denGrundpreis des Produkt exportieren möchten. - - - Netto- in Bruttopreise umrechnen - - - Legt fest, dass Netto- in Bruttopreise umgerechnet werden sollen. - - - Sprache - - - Legt die Sprache fest, in der sprachabhängige Werte (z.B. der Produktname) exportiert werden sollen. + Legt fest, ob der Grundpreis eines Produktes exportiert werden soll. \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml index 84aea32856..6f989e68dd 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml @@ -29,12 +29,6 @@ Default Google category is not set - - Currency - - - The default currency that will be used to generate the feed. - Default Google category @@ -47,15 +41,6 @@ Product specific data - - Generate feed - - - Product thumbnail image size - - - The default size (pixels) for product thumbnail image. - Product @@ -80,54 +65,6 @@ Pattern - - Automatically generate feed - - - Check the box if you want the feed to be automatically generated. - - - A task period (minutes) - - - Specify a task period in minutes (generation of a new feed file). - - - Settings successfully saved. Please restart the application if "Automatically generate feed" has been changed. - - - Product description - - - Specify what information to use for the description of the product. - - - Automatic - - - Short description - - - Long description - - - Product name + short description - - - Product name + long description - - - Manufacturer + Product name + short description - - - Manufacturer + Product name + long description - - - Text 1 - 5 to be appended - - - One of the five text elements will be appended to the product description if long and short description are empty. - Additional images @@ -158,9 +95,6 @@ In stock - - Available for order - Out of stock @@ -173,18 +107,6 @@ Activate this option if you want to export special price and special price period. - - Manufacturer\Brand - - - This value is used if there's no manufacturer\brand for a product. - - - Own SKU - - - Check the box to export SKU as manufacturer part number (MPN) if it is missing. - Gender @@ -242,12 +164,6 @@ Check the box if products can only be purchased online. - - Remove HTML - - - Check the box if you want to remove all HTML from the product description for the export. - Empty @@ -269,12 +185,6 @@ Not edited yet - - Store - - - Select the store that will be used to generate the feed. - Expires in days @@ -296,16 +206,4 @@ Activate this option if you want to export the base price of the product. - - Convert net into gross prices - - - Determines to convert net into gross prices. - - - Language - - - Determines which language to use to export language dependent values (for instance the product name). - \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedFroogleModel.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedFroogleModel.cs deleted file mode 100644 index 8053a8fd41..0000000000 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedFroogleModel.cs +++ /dev/null @@ -1,270 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Web.Mvc; -using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Mvc; -using Newtonsoft.Json; -using SmartStore.Web.Framework.Plugins; -using SmartStore.Core.Domain.Catalog; -using FluentValidation.Attributes; -using SmartStore.GoogleMerchantCenter.Validators; - -namespace SmartStore.GoogleMerchantCenter.Models -{ - [Validator(typeof(ConfigurationValidator))] - public class FeedFroogleModel : PromotionFeedConfigModel - { - public string GridEditUrl { get; set; } - public int GridPageSize { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.ProductPictureSize")] - public int ProductPictureSize { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Currency")] - public int CurrencyId { get; set; } - public List AvailableCurrencies { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.DefaultGoogleCategory")] - public string DefaultGoogleCategory { get; set; } - - public string[] AvailableGoogleCategories { get; set; } - public string AvailableGoogleCategoriesAsJson - { - get - { - if (AvailableGoogleCategories != null && AvailableGoogleCategories.Length > 0) - return JsonConvert.SerializeObject(AvailableGoogleCategories); - return ""; - } - } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.TaskEnabled")] - public bool TaskEnabled { get; set; } - [SmartResourceDisplayName("Plugins.Feed.Froogle.GenerateStaticFileEachMinutes")] - public int GenerateStaticFileEachMinutes { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.BuildDescription")] - public string BuildDescription { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.AppendDescriptionText")] - public string AppendDescriptionText1 { get; set; } - public string AppendDescriptionText2 { get; set; } - public string AppendDescriptionText3 { get; set; } - public string AppendDescriptionText4 { get; set; } - public string AppendDescriptionText5 { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.AdditionalImages")] - public bool AdditionalImages { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Condition")] - public string Condition { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Availability")] - public string Availability { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.SpecialPrice")] - public bool SpecialPrice { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Brand")] - public string Brand { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.UseOwnProductNo")] - public bool UseOwnProductNo { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Gender")] - public string Gender { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.AgeGroup")] - public string AgeGroup { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Color")] - public string Color { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Size")] - public string Size { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Material")] - public string Material { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Pattern")] - public string Pattern { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.OnlineOnly")] - public bool OnlineOnly { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.DescriptionToPlainText")] - public bool DescriptionToPlainText { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.SearchProductName")] - public string SearchProductName { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.SearchIsTouched")] - public string SearchIsTouched { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Store")] - public int StoreId { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.ExpirationDays")] - public int ExpirationDays { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.ExportShipping")] - public bool ExportShipping { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.ExportBasePrice")] - public bool ExportBasePrice { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.ConvertNetToGrossPrices")] - public bool ConvertNetToGrossPrices { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.LanguageId")] - public int LanguageId { get; set; } - - public int ScheduleTaskId { get; set; } - - public void Copy(FroogleSettings settings, bool fromSettings) - { - if (fromSettings) - { - AppendDescriptionText1 = settings.AppendDescriptionText1; - AppendDescriptionText2 = settings.AppendDescriptionText2; - AppendDescriptionText3 = settings.AppendDescriptionText3; - AppendDescriptionText4 = settings.AppendDescriptionText4; - AppendDescriptionText5 = settings.AppendDescriptionText5; - ProductPictureSize = settings.ProductPictureSize; - CurrencyId = settings.CurrencyId; - DefaultGoogleCategory = settings.DefaultGoogleCategory; - BuildDescription = settings.BuildDescription; - AdditionalImages = settings.AdditionalImages; - Condition = settings.Condition; - Availability = settings.Availability; - SpecialPrice = settings.SpecialPrice; - Brand = settings.Brand; - UseOwnProductNo = settings.UseOwnProductNo; - Gender = settings.Gender; - AgeGroup = settings.AgeGroup; - Color = settings.Color; - Size = settings.Size; - Material = settings.Material; - Pattern = settings.Pattern; - OnlineOnly = settings.OnlineOnly; - DescriptionToPlainText = settings.DescriptionToPlainText; - StoreId = settings.StoreId; - ExpirationDays = settings.ExpirationDays; - ExportShipping = settings.ExportShipping; - ExportBasePrice = settings.ExportBasePrice; - ConvertNetToGrossPrices = settings.ConvertNetToGrossPrices; - LanguageId = settings.LanguageId; - } - else - { - settings.AppendDescriptionText1 = AppendDescriptionText1; - settings.AppendDescriptionText2 = AppendDescriptionText2; - settings.AppendDescriptionText3 = AppendDescriptionText3; - settings.AppendDescriptionText4 = AppendDescriptionText4; - settings.AppendDescriptionText5 = AppendDescriptionText5; - settings.ProductPictureSize = ProductPictureSize; - settings.CurrencyId = CurrencyId; - settings.DefaultGoogleCategory = DefaultGoogleCategory; - settings.BuildDescription = BuildDescription; - settings.AdditionalImages = AdditionalImages; - settings.Condition = Condition; - settings.Availability = Availability; - settings.SpecialPrice = SpecialPrice; - settings.Brand = Brand; - settings.UseOwnProductNo = UseOwnProductNo; - settings.Gender = Gender; - settings.AgeGroup = AgeGroup; - settings.Color = Color; - settings.Size = Size; - settings.Material = Material; - settings.Pattern = Pattern; - settings.OnlineOnly = OnlineOnly; - settings.DescriptionToPlainText = DescriptionToPlainText; - settings.StoreId = StoreId; - settings.ExpirationDays = ExpirationDays; - settings.ExportShipping = ExportShipping; - settings.ExportBasePrice = ExportBasePrice; - settings.ConvertNetToGrossPrices = ConvertNetToGrossPrices; - settings.LanguageId = LanguageId; - } - } - } - - - public class GoogleProductModel : ModelBase - { - public int TotalCount { get; set; } - - //this attribute is required to disable editing - [ScaffoldColumn(false)] - public int ProductId - { - get { return Id; } - set { Id = value; } - } - public int Id { get; set; } - - //this attribute is required to disable editing - [ReadOnly(true)] - [ScaffoldColumn(false)] - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.ProductName")] - public string Name { get; set; } - - public string SKU { get; set; } - public int ProductTypeId { get; set; } - public ProductType ProductType { get { return (ProductType)ProductTypeId; } } - public string ProductTypeName { get; set; } - public string ProductTypeLabelHint - { - get - { - switch (ProductType) - { - case ProductType.SimpleProduct: - return "smnet-hide"; - case ProductType.GroupedProduct: - return "success"; - case ProductType.BundledProduct: - return "info"; - default: - return ""; - } - } - } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.GoogleCategory")] - public string Taxonomy { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Gender")] - public string Gender { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.AgeGroup")] - public string AgeGroup { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Color")] - public string Color { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Size")] - public string Size { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Material")] - public string Material { get; set; } - - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Pattern")] - public string Pattern { get; set; } - - [SmartResourceDisplayName("Common.Export")] - public int Export { get; set; } - [SmartResourceDisplayName("Common.Export")] - public bool Exporting - { - get { return Export != 0; } - set { Export = (value ? 1 : 0); } - } - - public string GenderLocalize { get; set; } - public string AgeGroupLocalize { get; set; } - public string ExportingLocalize { get; set; } - } -} \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedGoogleMerchantCenterModel.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedGoogleMerchantCenterModel.cs new file mode 100644 index 0000000000..ec99430213 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedGoogleMerchantCenterModel.cs @@ -0,0 +1,108 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Mvc; + +namespace SmartStore.GoogleMerchantCenter.Models +{ + public class FeedGoogleMerchantCenterModel + { + public int GridPageSize { get; set; } + + public string[] AvailableGoogleCategories { get; set; } + public string AvailableGoogleCategoriesAsJson + { + get + { + if (AvailableGoogleCategories != null && AvailableGoogleCategories.Length > 0) + return JsonConvert.SerializeObject(AvailableGoogleCategories); + return ""; + } + } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.SearchProductName")] + public string SearchProductName { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.SearchIsTouched")] + public string SearchIsTouched { get; set; } + } + + + public class GoogleProductModel : ModelBase + { + public int TotalCount { get; set; } + + //this attribute is required to disable editing + [ScaffoldColumn(false)] + public int ProductId + { + get { return Id; } + set { Id = value; } + } + public int Id { get; set; } + + //this attribute is required to disable editing + [ReadOnly(true)] + [ScaffoldColumn(false)] + [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.ProductName")] + public string Name { get; set; } + + public string SKU { get; set; } + public int ProductTypeId { get; set; } + public ProductType ProductType { get { return (ProductType)ProductTypeId; } } + public string ProductTypeName { get; set; } + public string ProductTypeLabelHint + { + get + { + switch (ProductType) + { + case ProductType.SimpleProduct: + return "smnet-hide"; + case ProductType.GroupedProduct: + return "success"; + case ProductType.BundledProduct: + return "info"; + default: + return ""; + } + } + } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.GoogleCategory")] + public string Taxonomy { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Gender")] + public string Gender { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.AgeGroup")] + public string AgeGroup { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Color")] + public string Color { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Size")] + public string Size { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Material")] + public string Material { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Pattern")] + public string Pattern { get; set; } + + [SmartResourceDisplayName("Common.Export")] + public int Export { get; set; } + [SmartResourceDisplayName("Common.Export")] + public bool Exporting + { + get { return Export != 0; } + set { Export = (value ? 1 : 0); } + } + + public string GenderLocalize { get; set; } + public string AgeGroupLocalize { get; set; } + public string ExportingLocalize { get; set; } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs index f01d7ee9a0..6b96944375 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs @@ -1,14 +1,81 @@ using System; +using System.Xml.Serialization; +using FluentValidation.Attributes; +using Newtonsoft.Json; +using SmartStore.GoogleMerchantCenter.Validators; using SmartStore.Web.Framework; namespace SmartStore.GoogleMerchantCenter.Models { [Serializable] + [Validator(typeof(ProfileConfigurationValidator))] public class ProfileConfigurationModel { public ProfileConfigurationModel() { + Condition = "new"; + OnlineOnly = true; + AdditionalImages = true; + SpecialPrice = true; } + [SmartResourceDisplayName("Plugins.Feed.Froogle.DefaultGoogleCategory")] + public string DefaultGoogleCategory { get; set; } + + [XmlIgnore] + public string[] AvailableGoogleCategories { get; set; } + + [XmlIgnore] + public string AvailableGoogleCategoriesAsJson + { + get + { + if (AvailableGoogleCategories != null && AvailableGoogleCategories.Length > 0) + return JsonConvert.SerializeObject(AvailableGoogleCategories); + return ""; + } + } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.AdditionalImages")] + public bool AdditionalImages { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Condition")] + public string Condition { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Availability")] + public string Availability { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.SpecialPrice")] + public bool SpecialPrice { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Gender")] + public string Gender { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.AgeGroup")] + public string AgeGroup { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Color")] + public string Color { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Size")] + public string Size { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Material")] + public string Material { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.Pattern")] + public string Pattern { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.OnlineOnly")] + public bool OnlineOnly { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.ExpirationDays")] + public int ExpirationDays { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.ExportShipping")] + public bool ExportShipping { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.ExportBasePrice")] + public bool ExportBasePrice { get; set; } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs index 16950de170..8e402cbb32 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs @@ -2,11 +2,17 @@ using System.IO; using System.Text; using System.Xml; +using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -using SmartStore.Services.DataExchange; +using SmartStore.GoogleMerchantCenter.Domain; using SmartStore.GoogleMerchantCenter.Models; +using SmartStore.GoogleMerchantCenter.Services; +using SmartStore.Services.DataExchange; +using SmartStore.Services.Directory; namespace SmartStore.GoogleMerchantCenter.Providers { @@ -15,19 +21,122 @@ namespace SmartStore.GoogleMerchantCenter.Providers [DisplayOrder(1)] [ExportProjectionSupport( ExportProjectionSupport.Description, - //ExportProjectionSupport.UseOwnProductNo, - //ExportProjectionSupport.Brand, - //ExportProjectionSupport.MainPictureUrl, - //ExportProjectionSupport.ShippingTime, - //ExportProjectionSupport.ShippingCosts, + ExportProjectionSupport.UseOwnProductNo, + ExportProjectionSupport.Brand, + ExportProjectionSupport.MainPictureUrl, + ExportProjectionSupport.SpecialPrice, ExportProjectionSupport.AttributeCombinationAsProduct)] public class ProductExportXmlProvider : IExportProvider { + private const string _googleNamespace = "http://base.google.com/ns/1.0"; + + private readonly IGoogleFeedService _googleFeedService; + private readonly IMeasureService _measureService; + private readonly MeasureSettings _measureSettings; + + public ProductExportXmlProvider( + IGoogleFeedService googleFeedService, + IMeasureService measureService, + MeasureSettings measureSettings) + { + _googleFeedService = googleFeedService; + _measureService = measureService; + _measureSettings = measureSettings; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + private string BasePriceUnits(string value) + { + const string defaultValue = "kg"; + + if (value.IsEmpty()) + return defaultValue; + + // TODO: Product.BasePriceMeasureUnit should be localized + switch (value.ToLowerInvariant()) + { + case "mg": + case "milligramm": + case "milligram": + return "mg"; + case "g": + case "gramm": + case "gram": + return "g"; + case "kg": + case "kilogramm": + case "kilogram": + return "kg"; + + case "ml": + case "milliliter": + case "millilitre": + return "ml"; + case "cl": + case "zentiliter": + case "centilitre": + return "cl"; + case "l": + case "liter": + case "litre": + return "l"; + case "cbm": + case "kubikmeter": + case "cubic metre": + return "cbm"; + + case "cm": + case "zentimeter": + case "centimetre": + return "cm"; + case "m": + case "meter": + return "m"; + + case "qm²": + case "quadratmeter": + case "square metre": + return "sqm"; + + default: + return defaultValue; + } + } + + private bool BasePriceSupported(int baseAmount, string unit) + { + if (baseAmount == 1 || baseAmount == 10 || baseAmount == 100) + return true; + + if (baseAmount == 75 && unit == "cl") + return true; + + if ((baseAmount == 50 || baseAmount == 1000) && unit == "kg") + return true; + + return false; + } + + public static string Unspecified + { + get { return "__nospec__"; } + } + public bool RequiresConfiguration(out string partialViewName, out Type modelType, out Action initialize) { partialViewName = "~/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml"; modelType = typeof(ProfileConfigurationModel); - initialize = null; + + initialize = obj => + { + var model = (obj as ProfileConfigurationModel); + + model.AvailableGoogleCategories = _googleFeedService.GetTaxonomyList(); + }; + return true; } @@ -43,6 +152,249 @@ public string FileExtension public void Execute(IExportExecuteContext context) { + var settings = new XmlWriterSettings + { + Encoding = Encoding.UTF8, + CheckCharacters = false + }; + + var path = context.FilePath; + var config = context.ConfigurationData as ProfileConfigurationModel; + dynamic currency = context.Currency; + string measureWeightSystemKey = ""; + var measureWeight = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId); + + if (measureWeight != null) + measureWeightSystemKey = measureWeight.SystemKeyword; + + context.Log.Information("Creating file " + path); + + using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + using (var writer = XmlWriter.Create(stream, settings)) + { + writer.WriteStartDocument(); + writer.WriteStartElement("rss"); + writer.WriteAttributeString("version", "2.0"); + writer.WriteAttributeString("xmlns", "g", null, _googleNamespace); + writer.WriteStartElement("channel"); + writer.WriteElementString("title", "{0} - Feed for Google Merchant Center".FormatInvariant((string)context.Store.Name)); + writer.WriteElementString("link", "http://base.google.com/base/"); + writer.WriteElementString("description", "Information about products"); + + while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + { + var segment = context.Data.CurrentSegment; + + foreach (dynamic product in segment) + { + if (context.Abort != ExportAbortion.None) + break; + + int productId = product.Id; + GoogleProductRecord gmc = null; + + try + { + gmc = _googleFeedService.GetGoogleProductRecord(productId); + } + catch (Exception exc) + { + context.Log.Error("Error while loading google product with id {0}: {1}".FormatInvariant(productId, exc.Message), exc); + ++context.RecordsFailed; + } + + if (gmc != null && !gmc.Export) + continue; + + writer.WriteStartElement("item"); + + try + { + string category = (gmc == null ? null : gmc.Taxonomy); + string productType = product._CategoryPath; + string mainImageUrl = product._MainPictureUrl; + var specialPrice = product._SpecialPrice as decimal?; + var price = (decimal)product.Price; + string brand = product._Brand; + string gtin = product.Gtin; + string mpn = product.ManufacturerPartNumber; + string condition = "new"; + string availability = "in stock"; + + if (category.IsEmpty()) + category = config.DefaultGoogleCategory; + + if (category.IsEmpty()) + context.Log.Error(T("Plugins.Feed.Froogle.MissingDefaultCategory")); + + if (config.Condition.IsCaseInsensitiveEqual(Unspecified)) + { + condition = ""; + } + else if (config.Condition.HasValue()) + { + condition = config.Condition; + } + + if (config.Availability.IsCaseInsensitiveEqual(Unspecified)) + { + availability = ""; + } + else if (config.Availability.HasValue()) + { + availability = config.Availability; + } + else + { + if ((int)product.ManageInventoryMethodId == (int)ManageInventoryMethod.ManageStock && (int)product.StockQuantity <= 0) + { + var backorderId = (int)product.BackorderModeId; + if (backorderId == (int)BackorderMode.NoBackorders) + availability = "out of stock"; + else if (backorderId == (int)BackorderMode.AllowQtyBelow0 || backorderId == (int)BackorderMode.AllowQtyBelow0AndNotifyCustomer) + availability = ((bool)product.AvailableForPreOrder ? "preorder" : "out of stock"); + } + } + + writer.WriteElementString("g", "id", _googleNamespace, productId.ToString()); + + writer.WriteStartElement("title"); + writer.WriteCData(((string)product.Name).Truncate(70).RemoveInvalidXmlChars()); + writer.WriteEndElement(); + + writer.WriteStartElement("description"); + writer.WriteCData(((string)product.FullDescription).RemoveInvalidXmlChars()); + writer.WriteEndElement(); + + writer.WriteStartElement("g", "google_product_category", _googleNamespace); + writer.WriteCData(category.RemoveInvalidXmlChars()); + writer.WriteFullEndElement(); + + if (productType.HasValue()) + { + writer.WriteStartElement("g", "product_type", _googleNamespace); + writer.WriteCData(productType.RemoveInvalidXmlChars()); + writer.WriteFullEndElement(); + } + + writer.WriteElementString("link", (string)product._DetailUrl); + + if (mainImageUrl.HasValue()) + { + writer.WriteElementString("g", "image_link", _googleNamespace, mainImageUrl); + } + + if (config.AdditionalImages) + { + var imageCount = 0; + foreach (dynamic productPicture in product.ProductPictures) + { + string pictureUrl = productPicture.Picture._ImageUrl; + if (pictureUrl.HasValue() && (mainImageUrl.IsEmpty() || !mainImageUrl.IsCaseInsensitiveEqual(pictureUrl)) && ++imageCount <= 10) + { + writer.WriteElementString("g", "additional_image_link", _googleNamespace, pictureUrl); + } + } + } + + writer.WriteElementString("g", "condition", _googleNamespace, condition); + writer.WriteElementString("g", "availability", _googleNamespace, availability); + + if (config.SpecialPrice && specialPrice.HasValue) + { + DateTime? specialPriceStart = product.SpecialPriceStartDateTimeUtc; + DateTime? specialPriceEnd = product.SpecialPriceEndDateTimeUtc; + if (specialPriceStart.HasValue && specialPriceEnd.HasValue) + { + var dateFormat = "yyyy-MM-ddTHH:mmZ"; + var specialPriceDate = "{0}/{1}".FormatInvariant(specialPriceStart.Value.ToString(dateFormat), specialPriceEnd.Value.ToString(dateFormat)); + + writer.WriteElementString("g", "sale_price", _googleNamespace, price.FormatInvariant() + " " + (string)currency.CurrencyCode); + writer.WriteElementString("g", "sale_price_effective_date", _googleNamespace, specialPriceDate); + + price = (product._RegularPrice as decimal?) ?? price; + } + } + + writer.WriteElementString("g", "price", _googleNamespace, price.FormatInvariant() + " " + (string)currency.CurrencyCode); + + writer.WriteCData("gtin", gtin, "g", _googleNamespace); + writer.WriteCData("brand", brand, "g", _googleNamespace); + writer.WriteCData("mpn", mpn, "g", _googleNamespace); + + if (config.Gender.IsCaseInsensitiveEqual(Unspecified)) + writer.WriteCData("gender", "", "g", _googleNamespace); + else + writer.WriteCData("gender", gmc != null && gmc.Gender.HasValue() ? gmc.Gender : config.Gender, "g", _googleNamespace); + + if (config.AgeGroup.IsCaseInsensitiveEqual(Unspecified)) + writer.WriteCData("age_group", "", "g", _googleNamespace); + else + writer.WriteCData("age_group", gmc != null && gmc.AgeGroup.HasValue() ? gmc.AgeGroup : config.AgeGroup, "g", _googleNamespace); + + writer.WriteCData("color", gmc != null && gmc.Color.HasValue() ? gmc.Color : config.Color, "g", _googleNamespace); + writer.WriteCData("size", gmc != null && gmc.Size.HasValue() ? gmc.Size : config.Size, "g", _googleNamespace); + writer.WriteCData("material", gmc != null && gmc.Material.HasValue() ? gmc.Material : config.Material, "g", _googleNamespace); + writer.WriteCData("pattern", gmc != null && gmc.Pattern.HasValue() ? gmc.Pattern : config.Pattern, "g", _googleNamespace); + writer.WriteCData("item_group_id", gmc != null && gmc.ItemGroupId.HasValue() ? gmc.ItemGroupId : "", "g", _googleNamespace); + + writer.WriteElementString("g", "online_only", _googleNamespace, config.OnlineOnly ? "y" : "n"); + writer.WriteElementString("g", "identifier_exists", _googleNamespace, product.Gtin.HasValue() || brand.HasValue() || mpn.HasValue() ? "TRUE" : "FALSE"); + + if (config.ExpirationDays > 0) + { + writer.WriteElementString("g", "expiration_date", _googleNamespace, DateTime.UtcNow.AddDays(config.ExpirationDays).ToString("yyyy-MM-dd")); + } + + if (config.ExportShipping) + { + string weightInfo; + var weight = ((decimal)product.Weight).FormatInvariant(); + + if (measureWeightSystemKey.IsCaseInsensitiveEqual("gram")) + weightInfo = weight + " g"; + else if (measureWeightSystemKey.IsCaseInsensitiveEqual("lb")) + weightInfo = weight + " lb"; + else if (measureWeightSystemKey.IsCaseInsensitiveEqual("ounce")) + weightInfo = weight + " oz"; + else + weightInfo = weight + " kg"; + + writer.WriteElementString("g", "shipping_weight", _googleNamespace, weightInfo); + } + + if (config.ExportBasePrice && (bool)product.BasePriceHasValue) + { + var measureUnit = BasePriceUnits((string)product.BasePriceMeasureUnit); + var basePriceBaseAmount = product.BasePriceBaseAmount as int?; + + if (BasePriceSupported(basePriceBaseAmount ?? 0, measureUnit)) + { + var basePriceAmount = product.BasePriceAmount as decimal?; + var basePriceMeasure = "{0} {1}".FormatInvariant((basePriceAmount ?? decimal.Zero).FormatInvariant(), measureUnit); + var basePriceBaseMeasure = "{0} {1}".FormatInvariant(basePriceBaseAmount ?? 1, measureUnit); + + writer.WriteElementString("g", "unit_pricing_measure", _googleNamespace, basePriceMeasure); + writer.WriteElementString("g", "unit_pricing_base_measure", _googleNamespace, basePriceBaseMeasure); + } + } + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.Log.Error("Error while processing product with id {0}: {1}".FormatInvariant(productId, exc.Message), exc); + ++context.RecordsFailed; + } + + writer.WriteEndElement(); // item + } + } + + writer.WriteEndElement(); // channel + writer.WriteEndElement(); // rss + writer.WriteEndDocument(); + } } public void ExecuteEnded(IExportExecuteContext context) diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs index 76a11bad4d..16cf7b9401 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs @@ -3,23 +3,13 @@ using System.IO; using System.Linq; using System.Text; -using System.Web; -using System.Web.Mvc; -using System.Xml; -using Autofac; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Logging; +using SmartStore.Core.Localization; +using SmartStore.Core.Plugins; using SmartStore.GoogleMerchantCenter.Domain; using SmartStore.GoogleMerchantCenter.Models; using SmartStore.Services; -using SmartStore.Services.Catalog; -using SmartStore.Services.Directory; -using SmartStore.Services.Localization; -using SmartStore.Services.Tasks; -using SmartStore.Web.Framework.Plugins; using Telerik.Web.Mvc; namespace SmartStore.GoogleMerchantCenter.Services @@ -28,49 +18,23 @@ public partial class GoogleFeedService : IGoogleFeedService { private const string _googleNamespace = "http://base.google.com/ns/1.0"; - private readonly FeedPluginHelper _helper; private readonly IRepository _gpRepository; - private readonly IProductService _productService; - private readonly IManufacturerService _manufacturerService; - private readonly IMeasureService _measureService; - private readonly MeasureSettings _measureSettings; - private readonly IDbContext _dbContext; - private readonly AdminAreaSettings _adminAreaSettings; - private readonly ICurrencyService _currencyService; private readonly ICommonServices _services; + private readonly IPluginFinder _pluginFinder; public GoogleFeedService( IRepository gpRepository, - IProductService productService, - IManufacturerService manufacturerService, - FroogleSettings settings, - IMeasureService measureService, - MeasureSettings measureSettings, - IDbContext dbContext, - AdminAreaSettings adminAreaSettings, - ICurrencyService currencyService, ICommonServices services, - IComponentContext ctx) + IPluginFinder pluginFinder) { _gpRepository = gpRepository; - _productService = productService; - _manufacturerService = manufacturerService; - Settings = settings; - _measureService = measureService; - _measureSettings = measureSettings; - _dbContext = dbContext; - _adminAreaSettings = adminAreaSettings; - _currencyService = currencyService; _services = services; + _pluginFinder = pluginFinder; - _helper = new FeedPluginHelper(ctx, "SmartStore.GoogleMerchantCenter", "SmartStore.GoogleMerchantCenter", () => - { - return Settings as PromotionFeedSettings; - }); + T = NullLocalizer.Instance; } - public FroogleSettings Settings { get; set; } - public FeedPluginHelper Helper { get { return _helper; } } + public Localizer T { get; set; } public GoogleProductRecord GetGoogleProductRecord(int productId) { @@ -110,392 +74,7 @@ public void DeleteGoogleProductRecord(GoogleProductRecord record) _gpRepository.Delete(record); } - private bool SpecialPrice(Product product, out string specialPriceDate) - { - specialPriceDate = ""; - - try - { - if (Settings.SpecialPrice && product.SpecialPrice.HasValue && product.SpecialPriceStartDateTimeUtc.HasValue && product.SpecialPriceEndDateTimeUtc.HasValue) - { - if (!(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)) - { - string dateFormat = "yyyy-MM-ddTHH:mmZ"; - string startDate = product.SpecialPriceStartDateTimeUtc.Value.ToString(dateFormat); - string endDate = product.SpecialPriceEndDateTimeUtc.Value.ToString(dateFormat); - - specialPriceDate = "{0}/{1}".FormatWith(startDate, endDate); - } - } - } - catch (Exception exc) - { - exc.Dump(); - } - return specialPriceDate.HasValue(); - } - private string ProductCategory(GoogleProductRecord googleProduct) - { - string productCategory = ""; - - if (googleProduct != null) - productCategory = googleProduct.Taxonomy; - - if (productCategory.IsEmpty()) - productCategory = Settings.DefaultGoogleCategory; - - return productCategory; - } - private string Condition() - { - if (Settings.Condition.IsCaseInsensitiveEqual(PluginHelper.NotSpecified)) - return ""; - - if (Settings.Condition.IsEmpty()) - return "new"; - - return Settings.Condition; - } - private string Availability(Product product) - { - if (Settings.Availability.IsCaseInsensitiveEqual(PluginHelper.NotSpecified)) - return ""; - - if (Settings.Availability.IsEmpty()) - { - if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock && product.StockQuantity <= 0) - { - switch (product.BackorderMode) - { - case BackorderMode.NoBackorders: - return "out of stock"; - case BackorderMode.AllowQtyBelow0: - case BackorderMode.AllowQtyBelow0AndNotifyCustomer: - if (product.AvailableForPreOrder) - return "preorder"; - return "out of stock"; - } - } - return "in stock"; - } - return Settings.Availability; - } - private string Gender(GoogleProductRecord googleProduct) - { - if (Settings.Gender.IsCaseInsensitiveEqual(PluginHelper.NotSpecified)) - return ""; - - if (googleProduct != null && googleProduct.Gender.HasValue()) - return googleProduct.Gender; - - return Settings.Gender; - } - private string AgeGroup(GoogleProductRecord googleProduct) - { - if (Settings.AgeGroup.IsCaseInsensitiveEqual(PluginHelper.NotSpecified)) - return ""; - - if (googleProduct != null && googleProduct.AgeGroup.HasValue()) - return googleProduct.AgeGroup; - - return Settings.AgeGroup; - } - private string Color(GoogleProductRecord googleProduct) - { - if (googleProduct != null && googleProduct.Color.HasValue()) - return googleProduct.Color; - - return Settings.Color; - } - private string Size(GoogleProductRecord googleProduct) - { - if (googleProduct != null && googleProduct.Size.HasValue()) - return googleProduct.Size; - - return Settings.Size; - } - private string Material(GoogleProductRecord googleProduct) - { - if (googleProduct != null && googleProduct.Material.HasValue()) - return googleProduct.Material; - - return Settings.Material; - } - private string Pattern(GoogleProductRecord googleProduct) - { - if (googleProduct != null && googleProduct.Pattern.HasValue()) - return googleProduct.Pattern; - - return Settings.Pattern; - } - - private string ItemGroupId(GoogleProductRecord googleProduct) - { - if (googleProduct != null && googleProduct.ItemGroupId.HasValue()) - return googleProduct.ItemGroupId; - - return ""; - } - - private bool BasePriceSupported(int baseAmount, string unit) - { - if (baseAmount == 1 || baseAmount == 10 || baseAmount == 100) - return true; - - if (baseAmount == 75 && unit == "cl") - return true; - - if ((baseAmount == 50 || baseAmount == 1000) && unit == "kg") - return true; - - return false; - } - - private string BasePriceUnits(string value) - { - const string defaultValue = "kg"; - - if (value.IsEmpty()) - return defaultValue; - - // TODO: Product.BasePriceMeasureUnit should be localized - switch (value.ToLowerInvariant()) - { - case "mg": - case "milligramm": - case "milligram": - return "mg"; - case "g": - case "gramm": - case "gram": - return "g"; - case "kg": - case "kilogramm": - case "kilogram": - return "kg"; - - case "ml": - case "milliliter": - case "millilitre": - return "ml"; - case "cl": - case "zentiliter": - case "centilitre": - return "cl"; - case "l": - case "liter": - case "litre": - return "l"; - case "cbm": - case "kubikmeter": - case "cubic metre": - return "cbm"; - - case "cm": - case "zentimeter": - case "centimetre": - return "cm"; - case "m": - case "meter": - return "m"; - - case "qm": - case "quadratmeter": - case "square metre": - return "sqm"; - - default: - return defaultValue; - } - } - private void WriteItem(FeedFileCreationContext fileCreation, XmlWriter writer, Product product, Currency currency, string measureWeightSystemKey) - { - GoogleProductRecord googleProduct = null; - - try - { - googleProduct = GetGoogleProductRecord(product.Id); - - if (googleProduct != null && !googleProduct.Export) - return; - } - catch (Exception exc) - { - fileCreation.Logger.Error(exc.Message, exc); - } - - writer.WriteStartElement("item"); - - try - { - var manu = _manufacturerService.GetProductManufacturersByProductId(product.Id).FirstOrDefault(); - var mainImageUrl = Helper.GetMainProductImageUrl(fileCreation.Store, product); - var category = ProductCategory(googleProduct); - - if (category.IsEmpty()) - fileCreation.ErrorMessage = Helper.GetResource("MissingDefaultCategory"); - - string manuName = (manu != null ? manu.Manufacturer.GetLocalized(x => x.Name, Settings.LanguageId, true, false) : null); - string productName = product.GetLocalized(x => x.Name, Settings.LanguageId, true, false); - string shortDescription = product.GetLocalized(x => x.ShortDescription, Settings.LanguageId, true, false); - string fullDescription = product.GetLocalized(x => x.FullDescription, Settings.LanguageId, true, false); - - var brand = (manuName ?? Settings.Brand); - var mpn = Helper.GetManufacturerPartNumber(product); - - bool identifierExists = product.Gtin.HasValue() || brand.HasValue() || mpn.HasValue(); - - writer.WriteElementString("g", "id", _googleNamespace, product.Id.ToString()); - - writer.WriteStartElement("title"); - writer.WriteCData(productName.Truncate(70)); - writer.WriteEndElement(); - - var description = Helper.BuildProductDescription(productName, shortDescription, fullDescription, manuName, d => - { - if (fullDescription.IsEmpty() && shortDescription.IsEmpty()) - { - var rnd = new Random(); - - switch (rnd.Next(1, 5)) - { - case 1: return d.Grow(Settings.AppendDescriptionText1, " "); - case 2: return d.Grow(Settings.AppendDescriptionText2, " "); - case 3: return d.Grow(Settings.AppendDescriptionText3, " "); - case 4: return d.Grow(Settings.AppendDescriptionText4, " "); - case 5: return d.Grow(Settings.AppendDescriptionText5, " "); - } - } - return d; - }); - - writer.WriteStartElement("description"); - writer.WriteCData(description.RemoveInvalidXmlChars()); - writer.WriteEndElement(); - - writer.WriteStartElement("g", "google_product_category", _googleNamespace); - writer.WriteCData(category); - writer.WriteFullEndElement(); - - string productType = Helper.GetCategoryPath(product); - if (productType.HasValue()) - { - writer.WriteStartElement("g", "product_type", _googleNamespace); - writer.WriteCData(productType); - writer.WriteFullEndElement(); - } - - writer.WriteElementString("link", Helper.GetProductDetailUrl(fileCreation.Store, product)); - writer.WriteElementString("g", "image_link", _googleNamespace, mainImageUrl); - - foreach (string additionalImageUrl in Helper.GetAdditionalProductImages(fileCreation.Store, product, mainImageUrl)) - { - writer.WriteElementString("g", "additional_image_link", _googleNamespace, additionalImageUrl); - } - - writer.WriteElementString("g", "condition", _googleNamespace, Condition()); - writer.WriteElementString("g", "availability", _googleNamespace, Availability(product)); - - decimal price = Helper.GetProductPrice(product, currency, fileCreation.Store); - string specialPriceDate; - - if (SpecialPrice(product, out specialPriceDate)) - { - writer.WriteElementString("g", "sale_price", _googleNamespace, price.FormatInvariant() + " " + currency.CurrencyCode); - writer.WriteElementString("g", "sale_price_effective_date", _googleNamespace, specialPriceDate); - - // get regular price ignoring any special price - decimal specialPrice = product.SpecialPrice.Value; - product.SpecialPrice = null; - price = Helper.GetProductPrice(product, currency, fileCreation.Store); - product.SpecialPrice = specialPrice; - - _dbContext.SetToUnchanged(product); - } - - writer.WriteElementString("g", "price", _googleNamespace, price.FormatInvariant() + " " + currency.CurrencyCode); - - writer.WriteCData("gtin", product.Gtin, "g", _googleNamespace); - writer.WriteCData("brand", brand, "g", _googleNamespace); - writer.WriteCData("mpn", mpn, "g", _googleNamespace); - - writer.WriteCData("gender", Gender(googleProduct), "g", _googleNamespace); - writer.WriteCData("age_group", AgeGroup(googleProduct), "g", _googleNamespace); - writer.WriteCData("color", Color(googleProduct), "g", _googleNamespace); - writer.WriteCData("size", Size(googleProduct), "g", _googleNamespace); - writer.WriteCData("material", Material(googleProduct), "g", _googleNamespace); - writer.WriteCData("pattern", Pattern(googleProduct), "g", _googleNamespace); - writer.WriteCData("item_group_id", ItemGroupId(googleProduct), "g", _googleNamespace); - - writer.WriteElementString("g", "online_only", _googleNamespace, Settings.OnlineOnly ? "y" : "n"); - writer.WriteElementString("g", "identifier_exists", _googleNamespace, identifierExists ? "TRUE" : "FALSE"); - - if (Settings.ExpirationDays > 0) - { - writer.WriteElementString("g", "expiration_date", _googleNamespace, DateTime.UtcNow.AddDays(Settings.ExpirationDays).ToString("yyyy-MM-dd")); - } - - if (Settings.ExportShipping) - { - string weightInfo, weight = product.Weight.FormatInvariant(); - - if (measureWeightSystemKey.IsCaseInsensitiveEqual("gram")) - weightInfo = weight + " g"; - else if (measureWeightSystemKey.IsCaseInsensitiveEqual("lb")) - weightInfo = weight + " lb"; - else if (measureWeightSystemKey.IsCaseInsensitiveEqual("ounce")) - weightInfo = weight + " oz"; - else - weightInfo = weight + " kg"; - - writer.WriteElementString("g", "shipping_weight", _googleNamespace, weightInfo); - } - - if (Settings.ExportBasePrice && product.BasePriceHasValue) - { - string measureUnit = BasePriceUnits(product.BasePriceMeasureUnit); - - if (BasePriceSupported(product.BasePriceBaseAmount ?? 0, measureUnit)) - { - string basePriceMeasure = "{0} {1}".FormatWith((product.BasePriceAmount ?? decimal.Zero).FormatInvariant(), measureUnit); - string basePriceBaseMeasure = "{0} {1}".FormatWith(product.BasePriceBaseAmount, measureUnit); - - writer.WriteElementString("g", "unit_pricing_measure", _googleNamespace, basePriceMeasure); - writer.WriteElementString("g", "unit_pricing_base_measure", _googleNamespace, basePriceBaseMeasure); - } - } - } - catch (Exception exc) - { - fileCreation.Logger.Error(exc.Message, exc); - } - - writer.WriteEndElement(); // item - } - - public string[] GetTaxonomyList() - { - try - { - string fileDir = Path.Combine(Helper.Plugin.OriginalAssemblyFile.Directory.FullName, "Files"); - string fileName = "taxonomy.{0}.txt".FormatWith(Helper.Language.LanguageCulture ?? "de-DE"); - string path = Path.Combine(fileDir, fileName); - - if (!File.Exists(path)) - path = Path.Combine(fileDir, "taxonomy.en-US.txt"); - - string[] lines = File.ReadAllLines(path, Encoding.UTF8); - - return lines; - } - catch (Exception exc) - { - exc.Dump(); - } - return new string[] { }; - } - - public void UpdateInsert(int pk, string name, string value) + public void Upsert(int pk, string name, string value) { if (pk == 0 || name.IsEmpty()) return; @@ -559,10 +138,13 @@ public void UpdateInsert(int pk, string name, string value) _gpRepository.Update(product); } } + public GridModel GetGridModel(GridCommand command, string searchProductName = null, string touched = null) { var model = new GridModel(); var textInfo = CultureInfo.InvariantCulture.TextInfo; + string yes = T("Admin.Common.Yes"); + string no = T("Admin.Common.No"); // there's no way to share a context instance across repositories which makes GoogleProductObjectContext pretty useless here. @@ -632,19 +214,15 @@ public GridModel GetGridModel(GridCommand command, string se data.ForEach(x => { if (x.ProductType != ProductType.SimpleProduct) - { - string key = "Admin.Catalog.Products.ProductType.{0}.Label".FormatWith(x.ProductType.ToString()); - x.ProductTypeName = Helper.GetResource(key); - } + x.ProductTypeName = T("Admin.Catalog.Products.ProductType.{0}.Label".FormatInvariant(x.ProductType.ToString())); - var googleProduct = GetGoogleProductRecord(x.Id); if (x.Gender.HasValue()) - x.GenderLocalize = Helper.GetResource("Gender" + textInfo.ToTitleCase(x.Gender)); + x.GenderLocalize = T("Plugins.Feed.Froogle.Gender" + textInfo.ToTitleCase(x.Gender)); if (x.AgeGroup.HasValue()) - x.AgeGroupLocalize = Helper.GetResource("AgeGroup" + textInfo.ToTitleCase(x.AgeGroup)); + x.AgeGroupLocalize = T("Plugins.Feed.Froogle-AgeGroup" + textInfo.ToTitleCase(x.AgeGroup)); - x.ExportingLocalize = Helper.GetResource(x.Export == 0 ? "Admin.Common.No" : "Admin.Common.Yes"); + x.ExportingLocalize = (x.Export == 0 ? no : yes); }); model.Data = data; @@ -663,186 +241,31 @@ public GridModel GetGridModel(GridCommand command, string se } return model; - - #region old code - - //var searchContext = new ProductSearchContext() - //{ - // Keywords = searchProductName, - // PageIndex = command.Page - 1, - // PageSize = command.PageSize, - // VisibleIndividuallyOnly = true, - // ShowHidden = true - //}; - - //var products = _productService.SearchProducts(searchContext); - - //var data = products.Select(x => - //{ - // var gModel = new GoogleProductModel() - // { - // ProductId = x.Id, - // Name = x.Name - // }; - - // var googleProduct = GetByProductId(x.Id); - - // if (googleProduct != null) - // { - // gModel.Taxonomy = googleProduct.Taxonomy; - // gModel.Gender = googleProduct.Gender; - // gModel.AgeGroup = googleProduct.AgeGroup; - // gModel.Color = googleProduct.Color; - // gModel.Size = googleProduct.Size; - // gModel.Material = googleProduct.Material; - // gModel.Pattern = googleProduct.Pattern; - - // if (gModel.Gender.HasValue()) - // gModel.GenderLocalize = Helper.GetResource("Gender" + CultureInfo.InvariantCulture.TextInfo.ToTitleCase(gModel.Gender)); - - // if (gModel.AgeGroup.HasValue()) - // gModel.AgeGroupLocalize = Helper.GetResource("AgeGroup" + CultureInfo.InvariantCulture.TextInfo.ToTitleCase(gModel.AgeGroup)); - // } - - // return gModel; - //}) - //.ToList(); - - //var model = new GridModel() - //{ - // Data = data, - // Total = products.TotalCount - //}; - - //return model; - - #endregion old code - } - - private void CreateFeed(FeedFileCreationContext fileCreation, TaskExecutionContext taskContext) - { - var xmlSettings = new XmlWriterSettings - { - Encoding = Encoding.UTF8, - CheckCharacters = false - }; - - using (var writer = XmlWriter.Create(fileCreation.Stream, xmlSettings)) - { - try - { - fileCreation.Logger.Information("Log file - Google Merchant Center feed."); - - var searchContext = new ProductSearchContext - { - OrderBy = ProductSortingEnum.CreatedOn, - PageSize = Settings.PageSize, - StoreId = fileCreation.Store.Id, - VisibleIndividuallyOnly = true - }; - - var currency = _currencyService.GetCurrencyById(Settings.CurrencyId); - var measureWeightSystemKey = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId).SystemKeyword; - - if (currency == null || !currency.Published) - currency = _services.WorkContext.WorkingCurrency; - - writer.WriteStartDocument(); - writer.WriteStartElement("rss"); - writer.WriteAttributeString("version", "2.0"); - writer.WriteAttributeString("xmlns", "g", null, _googleNamespace); - writer.WriteStartElement("channel"); - writer.WriteElementString("title", "{0} - Feed for Google Merchant Center".FormatWith(fileCreation.Store.Name)); - writer.WriteElementString("link", "http://base.google.com/base/"); - writer.WriteElementString("description", "Information about products"); - - for (int i = 0; i < 9999999; ++i) - { - searchContext.PageIndex = i; - - // Perf - _dbContext.DetachAll(); - - var products = _productService.SearchProducts(searchContext); - - if (fileCreation.TotalRecords == 0) - fileCreation.TotalRecords = products.TotalCount * fileCreation.StoreCount; // approx - - foreach (var product in products) - { - fileCreation.Report(); - - if (product.ProductType == ProductType.SimpleProduct || product.ProductType == ProductType.BundledProduct) - { - WriteItem(fileCreation, writer, product, currency, measureWeightSystemKey); - } - else if (product.ProductType == ProductType.GroupedProduct) - { - var associatedSearchContext = new ProductSearchContext - { - OrderBy = ProductSortingEnum.CreatedOn, - PageSize = int.MaxValue, - StoreId = fileCreation.Store.Id, - VisibleIndividuallyOnly = false, - ParentGroupedProductId = product.Id - }; - - foreach (var associatedProduct in _productService.SearchProducts(associatedSearchContext)) - { - WriteItem(fileCreation, writer, associatedProduct, currency, measureWeightSystemKey); - } - } - - if (taskContext.CancellationToken.IsCancellationRequested) - { - fileCreation.Logger.Warning("A cancellation has been requested"); - break; - } - } - - if (!products.HasNextPage || taskContext.CancellationToken.IsCancellationRequested) - break; - } - - writer.WriteEndElement(); // channel - writer.WriteEndElement(); // rss - writer.WriteEndDocument(); - - if (fileCreation.ErrorMessage.HasValue()) - fileCreation.Logger.Error(fileCreation.ErrorMessage); - } - catch (Exception exc) - { - fileCreation.Logger.Error(exc.Message, exc); - } - } } - public void CreateFeed(TaskExecutionContext context) + public string[] GetTaxonomyList() { - Helper.StartCreatingFeeds(fileCreation => + try { - CreateFeed(fileCreation, context); - return true; - }); - } + var descriptor = _pluginFinder.GetPluginDescriptorBySystemName(GoogleMerchantCenterFeedPlugin.SystemName); - public void SetupModel(FeedFroogleModel model) - { - Helper.SetupConfigModel(model, "FeedFroogle"); + var fileDir = Path.Combine(descriptor.OriginalAssemblyFile.Directory.FullName, "Files"); + var fileName = "taxonomy.{0}.txt".FormatWith(_services.WorkContext.WorkingLanguage.LanguageCulture ?? "de-DE"); + var path = Path.Combine(fileDir, fileName); - //model.GenerateStaticFileEachMinutes = Helper.ScheduleTask.Seconds / 60; - model.TaskEnabled = Helper.ScheduleTask.Enabled; - model.ScheduleTaskId = Helper.ScheduleTask.Id; + if (!File.Exists(path)) + path = Path.Combine(fileDir, "taxonomy.en-US.txt"); - model.AvailableCurrencies = Helper.AvailableCurrencies(); - model.AvailableGoogleCategories = GetTaxonomyList(); + string[] lines = File.ReadAllLines(path, Encoding.UTF8); - var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); - model.GridEditUrl = urlHelper.Action("GoogleProductEdit", "FeedFroogle", - new { Namespaces = "SmartStore.GoogleMerchantCenter.Controllers", area = "SmartStore.GoogleMerchantCenter" }); + return lines; + } + catch (Exception exc) + { + exc.Dump(); + } - model.GridPageSize = _adminAreaSettings.GridPageSize; + return new string[] { }; } } } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/IGoogleFeedService.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/IGoogleFeedService.cs index e5a6e1d87e..e2c39d3ebb 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/IGoogleFeedService.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/IGoogleFeedService.cs @@ -1,29 +1,23 @@ using SmartStore.GoogleMerchantCenter.Domain; using SmartStore.GoogleMerchantCenter.Models; -using SmartStore.Services.Tasks; -using SmartStore.Web.Framework.Plugins; using Telerik.Web.Mvc; namespace SmartStore.GoogleMerchantCenter.Services { public partial interface IGoogleFeedService { - FroogleSettings Settings { get; set; } - FeedPluginHelper Helper { get; } - GoogleProductRecord GetGoogleProductRecord(int productId); + void InsertGoogleProductRecord(GoogleProductRecord record); + void UpdateGoogleProductRecord(GoogleProductRecord record); + void DeleteGoogleProductRecord(GoogleProductRecord record); - string[] GetTaxonomyList(); - - void UpdateInsert(int pk, string name, string value); + void Upsert(int pk, string name, string value); GridModel GetGridModel(GridCommand command, string searchProductName = null, string touched = null); - void CreateFeed(TaskExecutionContext context); - - void SetupModel(FeedFroogleModel model); + string[] GetTaxonomyList(); } } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj index d98bfbdd12..eec6348512 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj @@ -174,15 +174,14 @@ - - + - + @@ -220,9 +219,6 @@ PreserveNewest - - Always - PreserveNewest diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ConfigurationValidator.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ProfileConfigurationValidator.cs similarity index 68% rename from src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ConfigurationValidator.cs rename to src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ProfileConfigurationValidator.cs index 169214a4cc..9799ada57f 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ConfigurationValidator.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Validators/ProfileConfigurationValidator.cs @@ -4,9 +4,9 @@ namespace SmartStore.GoogleMerchantCenter.Validators { - public class ConfigurationValidator : AbstractValidator + public class ProfileConfigurationValidator : AbstractValidator { - public ConfigurationValidator(ILocalizationService localize) + public ProfileConfigurationValidator(ILocalizationService localize) { RuleFor(x => x.ExpirationDays).InclusiveBetween(0, 29) .WithMessage(localize.GetResource("Plugins.Feed.Froogle.ExpirationDays.Validate")); diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml index 461d4e0066..60ba2e14a1 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml @@ -1,4 +1,4 @@ -@model FeedFroogleModel +@model FeedGoogleMerchantCenterModel @using SmartStore.GoogleMerchantCenter; @using SmartStore.GoogleMerchantCenter.Models; @using SmartStore.Web.Framework; @@ -8,411 +8,33 @@ @{ Layout = ""; - - Html.AddCssFileParts(true, - "~/Content/x-editable/bootstrap-editable.css", - "~/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.feed.froogle.css"); - - Html.AppendScriptParts(true, "~/Content/x-editable/bootstrap-editable.js"); + + Html.AddCssFileParts(true, "~/Content/x-editable/bootstrap-editable.css"); + Html.AppendScriptParts(true, "~/Content/x-editable/bootstrap-editable.js"); } - - - - -@(Html.SmartStore().TabStrip().Name("googlebase-configure").Items(x => { +@(Html.SmartStore().TabStrip().Name("googlebase-configure").Items(x => +{ x.Add().Text(T("Plugins.Feed.Froogle.General").Text).Content(@TabGeneral()).Selected(true); x.Add().Text(T("Plugins.Feed.Froogle.ProductData").Text).Content(@TabProductData()); })) @helper TabGeneral() { - - - - - - - - @if (Model.GeneratedFiles.Count > 0 && !Model.IsRunning) - { - - - - - - - - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - @Html.Raw(@T("Plugins.Feed.Froogle.AdminInstruction")) -
    -
    - @Html.SmartLabelFor(m => m.GeneratedFiles) - - @foreach (var group in Model.GeneratedFiles.GroupBy(x => x.StoreId)) - { - var firstFile = group.First(); -

    - @(firstFile.StoreName): - @foreach (var file in group) - { -
    @file.FileUrl - } -
    @T("Admin.Configuration.ActivityLog"), - @T("Common.UpdatedOn"): @(firstFile.LastWriteTime) -

    - } - -  @T("Admin.Common.Delete") - -
    -
    -
    - @Html.SmartLabelFor(m => m.ScheduleTaskId, "Common.Execution") - - @Html.Action("MinimalTask", "ScheduleTask", new { taskId = Model.ScheduleTaskId, returnUrl = Request.RawUrl, cancellable = true, area = "admin" }) -
    -
    -
    - @Html.SmartLabelFor(m => m.DefaultGoogleCategory) - - -
    - @Html.SmartLabelFor(m => m.ProductPictureSize) - - @Html.EditorFor(m => m.ProductPictureSize) - @Html.ValidationMessageFor(m => m.ProductPictureSize) -
    - @Html.SmartLabelFor(m => m.AdditionalImages) - - @Html.EditorFor(m => m.AdditionalImages) - @Html.ValidationMessageFor(m => m.AdditionalImages) -
    -
    -
    - @Html.SmartLabelFor(model => model.StoreId) - - @Html.DropDownListFor(model => model.StoreId, Model.AvailableStores) - @Html.ValidationMessageFor(model => model.StoreId) -
    - @Html.SmartLabelFor(model => model.LanguageId) - - @Html.DropDownListFor(model => model.LanguageId, Model.AvailableLanguages) - @Html.ValidationMessageFor(model => model.LanguageId) -
    - @Html.SmartLabelFor(m => m.BuildDescription) - - @Html.DropDownList("BuildDescription", new List { - new SelectListItem { Text = Model.Helper.GetResource("Automatic"), Value = "" }, - new SelectListItem { Text = Model.Helper.GetResource("Common.Unspecified"), Value = PluginHelper.NotSpecified }, - new SelectListItem { Text = Model.Helper.GetResource("DescShort"), Value = "short" }, - new SelectListItem { Text = Model.Helper.GetResource("DescLong"), Value = "long" }, - new SelectListItem { Text = Model.Helper.GetResource("DescTitleAndShort"), Value = "titleAndShort" }, - new SelectListItem { Text = Model.Helper.GetResource("DescTitleAndLong"), Value = "titleAndLong" }, - new SelectListItem { Text = Model.Helper.GetResource("DescManuAndTitleAndShort"), Value = "manuAndTitleAndShort" }, - new SelectListItem { Text = Model.Helper.GetResource("DescManuAndTitleAndLong"), Value = "manuAndTitleAndLong" } - }) - @Html.ValidationMessageFor(m => m.BuildDescription) -
    - @Html.SmartLabelFor(m => m.DescriptionToPlainText) - - @Html.EditorFor(m => m.DescriptionToPlainText) - @Html.ValidationMessageFor(m => m.DescriptionToPlainText) -
    - @Html.SmartLabelFor(m => m.AppendDescriptionText1) - - @Html.EditorFor(m => m.AppendDescriptionText1) - @Html.ValidationMessageFor(m => m.AppendDescriptionText1) -
    -   - - @Html.EditorFor(m => m.AppendDescriptionText2) - @Html.ValidationMessageFor(m => m.AppendDescriptionText2) -
    -   - - @Html.EditorFor(m => m.AppendDescriptionText3) - @Html.ValidationMessageFor(m => m.AppendDescriptionText3) -
    -   - - @Html.EditorFor(m => m.AppendDescriptionText4) - @Html.ValidationMessageFor(m => m.AppendDescriptionText4) -
    -   - - @Html.EditorFor(m => m.AppendDescriptionText5) - @Html.ValidationMessageFor(m => m.AppendDescriptionText5) -
    -
    -
    - @Html.SmartLabelFor(m => m.Condition) - - @Html.DropDownList("Condition", new List { - new SelectListItem { Text = Model.Helper.GetResource("Automatic"), Value = "" }, - new SelectListItem { Text = Model.Helper.GetResource("Common.Unspecified"), Value = PluginHelper.NotSpecified }, - new SelectListItem { Text = Model.Helper.GetResource("ConditionNew"), Value = "new" }, - new SelectListItem { Text = Model.Helper.GetResource("ConditionUsed"), Value = "used" }, - new SelectListItem { Text = Model.Helper.GetResource("ConditionRefurbished"), Value = "refurbished" } - }) - @Html.ValidationMessageFor(m => m.Condition) -
    - @Html.SmartLabelFor(m => m.Availability) - - @Html.DropDownList("Availability", new List { - new SelectListItem { Text = Model.Helper.GetResource("Automatic"), Value = "" }, - new SelectListItem { Text = Model.Helper.GetResource("Common.Unspecified"), Value = PluginHelper.NotSpecified }, - new SelectListItem { Text = Model.Helper.GetResource("AvailabilityInStock"), Value = "in stock" }, - new SelectListItem { Text = Model.Helper.GetResource("AvailabilityAvailableForOrder"), Value = "available for order" }, - new SelectListItem { Text = Model.Helper.GetResource("AvailabilityOutOfStock"), Value = "out of stock" }, - new SelectListItem { Text = Model.Helper.GetResource("AvailabilityPreorder"), Value = "preorder" } - }) - @Html.ValidationMessageFor(m => m.Availability) -
    - @Html.SmartLabelFor(m => m.Gender) - - @Html.DropDownList("Gender", new List { - new SelectListItem { Text = Model.Helper.GetResource("Automatic"), Value = "" }, - new SelectListItem { Text = Model.Helper.GetResource("Common.Unspecified"), Value = PluginHelper.NotSpecified }, - new SelectListItem { Text = Model.Helper.GetResource("GenderMale"), Value = "male" }, - new SelectListItem { Text = Model.Helper.GetResource("GenderFemale"), Value = "female" }, - new SelectListItem { Text = Model.Helper.GetResource("GenderUnisex"), Value = "unisex" } - }) - @Html.ValidationMessageFor(m => m.Gender) -
    - @Html.SmartLabelFor(m => m.AgeGroup) - - @Html.DropDownList("AgeGroup", new List { - new SelectListItem { Text = Model.Helper.GetResource("Automatic"), Value = "" }, - new SelectListItem { Text = Model.Helper.GetResource("Common.Unspecified"), Value = PluginHelper.NotSpecified }, - new SelectListItem { Text = Model.Helper.GetResource("AgeGroupAdult"), Value = "adult" }, - new SelectListItem { Text = Model.Helper.GetResource("AgeGroupKids"), Value = "kids" } - }) - @Html.ValidationMessageFor(m => m.AgeGroup) -
    - @Html.SmartLabelFor(m => m.Brand) - - @Html.EditorFor(m => m.Brand) - @Html.ValidationMessageFor(m => m.Brand) -
    - @Html.SmartLabelFor(m => m.Color) - - @Html.EditorFor(m => m.Color) - @Html.ValidationMessageFor(m => m.Color) -
    - @Html.SmartLabelFor(m => m.Size) - - @Html.EditorFor(m => m.Size) - @Html.ValidationMessageFor(m => m.Size) -
    - @Html.SmartLabelFor(m => m.Material) - - @Html.EditorFor(m => m.Material) - @Html.ValidationMessageFor(m => m.Material) -
    - @Html.SmartLabelFor(m => m.Pattern) - - @Html.EditorFor(m => m.Pattern) - @Html.ValidationMessageFor(m => m.Pattern) -
    -
    -
    - @Html.SmartLabelFor(m => m.CurrencyId) - - @Html.DropDownListFor(m => m.CurrencyId, Model.AvailableCurrencies) - @Html.ValidationMessageFor(m => m.CurrencyId) -
    - @Html.SmartLabelFor(m => m.ExpirationDays) - - @Html.EditorFor(m => m.ExpirationDays) - @Html.ValidationMessageFor(m => m.ExpirationDays) -
    - @Html.SmartLabelFor(m => m.SpecialPrice) - - @Html.EditorFor(m => m.SpecialPrice) - @Html.ValidationMessageFor(m => m.SpecialPrice) -
    - @Html.SmartLabelFor(m => m.ExportShipping) - - @Html.EditorFor(m => m.ExportShipping) - @Html.ValidationMessageFor(m => m.ExportShipping) -
    - @Html.SmartLabelFor(m => m.ExportBasePrice) - - @Html.EditorFor(m => m.ExportBasePrice) - @Html.ValidationMessageFor(m => m.ExportBasePrice) -
    - @Html.SmartLabelFor(m => m.UseOwnProductNo) - - @Html.EditorFor(m => m.UseOwnProductNo) - @Html.ValidationMessageFor(m => m.UseOwnProductNo) -
    - @Html.SmartLabelFor(m => m.OnlineOnly) - - @Html.EditorFor(m => m.OnlineOnly) - @Html.ValidationMessageFor(m => m.OnlineOnly) -
    - @Html.SmartLabelFor(m => m.ConvertNetToGrossPrices) - - @Html.EditorFor(m => m.ConvertNetToGrossPrices) - @Html.ValidationMessageFor(m => m.ConvertNetToGrossPrices) -
      - -
    - +
    +
    + + @Html.Raw(@T("Plugins.Feed.Froogle.AdminInstruction")) +
    +
    + @Html.Action("InfoProfile", "Export", new { systemName = GoogleMerchantCenterFeedPlugin.SystemName, returnUrl = Request.RawUrl, area = "admin" }) +
    +
    + + Google Merchant Center + +
    +
    } @helper TabProductData() @@ -426,7 +48,7 @@
    - + @@ -462,7 +84,7 @@ + + + + + + + + @@ -110,24 +129,6 @@ @Html.ValidationMessageFor(m => m.Pattern) - - - - - - - - + @if (Model.Provider.Description.HasValue()) + { + + + + + } + @if (Model.Provider.Url.HasValue()) + { + + + + + } - @if (Model.Provider.Url.HasValue()) - { - - - - - } - - - -
    @Html.SmartLabelFor(m => m.SearchProductName) @@ -441,9 +63,9 @@ @Html.DropDownList("SearchIsTouched", new List { - new SelectListItem { Text = Model.Helper.GetResource("Common.Unspecified"), Value = "" }, - new SelectListItem { Text = Model.Helper.GetResource("Plugins.Feed.Froogle.SearchIsTouched.Touched"), Value = "touched" }, - new SelectListItem { Text = Model.Helper.GetResource("Plugins.Feed.Froogle.SearchIsTouched.Untouched"), Value = "untouched" } + new SelectListItem { Text = T("Common.Unspecified"), Value = "" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.SearchIsTouched.Touched"), Value = "touched" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.SearchIsTouched.Untouched"), Value = "untouched" } })
    @(Html.Telerik().Grid() - .Name("froogleproducts-grid") + .Name("gmc-products-grid") .DataKeys(keys => { keys.Add(x => x.ProductId).RouteKey("ProductId"); @@ -506,19 +128,8 @@ $.fn.editable.defaults.mode = 'inline'; }); - $('#ProgressInfoButton').click(function() { - $(this).doAjax({ - callbackSuccess: function (resp) { - if (resp.message.length > 0) - $('#ProgressInfo').html(resp.message); - else - location.reload(); - } - }); - }); - $('#search-products').click(function () { - var grid = $('#froogleproducts-grid').data('tGrid'); + var grid = $('#gmc-products-grid').data('tGrid'); grid.currentPage = 1; grid.ajaxRequest(); return false; @@ -532,13 +143,13 @@ } function OnGridDataBound(e) { - var grid = $('#froogleproducts-grid'), + var grid = $('#gmc-products-grid'), options = { - emptytext: '@Model.Helper.GetResource("EmptyGridCell")', - url: '@Model.GridEditUrl' + emptytext: '@T("Plugins.Feed.Froogle.EmptyGridCell")', + url: '@Url.Action("GoogleProductEdit", "FeedGoogleMerchantCenter", new { Namespaces = "SmartStore.GoogleMerchantCenter.Controllers", area = GoogleMerchantCenterFeedPlugin.SystemName })' }, select2Options = { - placeholder: '@Model.Helper.GetResource("EmptyGridCell")', + placeholder: '@T("Plugins.Feed.Froogle.EmptyGridCell")', minimumResultsForSearch: 20, allowClear: true }; @@ -560,17 +171,17 @@ grid.find('a.edit-link-gender').editable($.extend(options, { select2: select2Options, source: [ - { id: 'male', text: '@Model.Helper.GetResource("GenderMale")' }, - { id: 'female', text: '@Model.Helper.GetResource("GenderFemale")' }, - { id: 'unisex', text: '@Model.Helper.GetResource("GenderUnisex")' } + { id: 'male', text: '@T("Plugins.Feed.Froogle.GenderMale")' }, + { id: 'female', text: '@T("Plugins.Feed.Froogle.GenderFemale")' }, + { id: 'unisex', text: '@T("Plugins.Feed.Froogle.GenderUnisex")' } ] })); grid.find('a.edit-link-agegroup').editable($.extend(options, { select2: select2Options, source: [ - { id: 'adult', text: '@Model.Helper.GetResource("AgeGroupAdult")' }, - { id: 'kids', text: '@Model.Helper.GetResource("AgeGroupKids")' } + { id: 'adult', text: '@T("Plugins.Feed.Froogle.AgeGroupAdult")' }, + { id: 'kids', text: '@T("Plugins.Feed.Froogle.AgeGroupKids")' } ] })); diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml index 39679306c4..a6c7588827 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml @@ -1,8 +1,167 @@ -@using SmartStore.GoogleMerchantCenter.Models; +@using SmartStore.GoogleMerchantCenter.Providers +@using SmartStore.GoogleMerchantCenter.Models; @model ProfileConfigurationModel @{ Layout = null; } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + @Html.SmartLabelFor(m => m.DefaultGoogleCategory) + + +
    + @Html.SmartLabelFor(m => m.Condition) + + @Html.DropDownList("Condition", new List { + new SelectListItem { Text = T("Common.Auto"), Value = "" }, + new SelectListItem { Text = T("Common.Unspecified"), Value = ProductExportXmlProvider.Unspecified }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.ConditionNew"), Value = "new" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.ConditionUsed"), Value = "used" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.ConditionRefurbished"), Value = "refurbished" } + }) + @Html.ValidationMessageFor(m => m.Condition) +
    + @Html.SmartLabelFor(m => m.Availability) + + @Html.DropDownList("Availability", new List { + new SelectListItem { Text = T("Common.Auto"), Value = "" }, + new SelectListItem { Text = T("Common.Unspecified"), Value = ProductExportXmlProvider.Unspecified }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.AvailabilityInStock"), Value = "in stock" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.AvailabilityOutOfStock"), Value = "out of stock" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.AvailabilityPreorder"), Value = "preorder" } + }) + @Html.ValidationMessageFor(m => m.Availability) +
    + @Html.SmartLabelFor(m => m.Gender) + + @Html.DropDownList("Gender", new List { + new SelectListItem { Text = T("Common.Auto"), Value = "" }, + new SelectListItem { Text = T("Common.Unspecified"), Value = ProductExportXmlProvider.Unspecified }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.GenderMale"), Value = "male" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.GenderFemale"), Value = "female" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.GenderUnisex"), Value = "unisex" } + }) + @Html.ValidationMessageFor(m => m.Gender) +
    + @Html.SmartLabelFor(m => m.AgeGroup) + + @Html.DropDownList("AgeGroup", new List { + new SelectListItem { Text = T("Common.Auto"), Value = "" }, + new SelectListItem { Text = T("Common.Unspecified"), Value = ProductExportXmlProvider.Unspecified }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.AgeGroupAdult"), Value = "adult" }, + new SelectListItem { Text = T("Plugins.Feed.Froogle.AgeGroupKids"), Value = "kids" } + }) + @Html.ValidationMessageFor(m => m.AgeGroup) +
    + @Html.SmartLabelFor(m => m.Color) + + @Html.EditorFor(m => m.Color) + @Html.ValidationMessageFor(m => m.Color) +
    + @Html.SmartLabelFor(m => m.Size) + + @Html.EditorFor(m => m.Size) + @Html.ValidationMessageFor(m => m.Size) +
    + @Html.SmartLabelFor(m => m.Material) + + @Html.EditorFor(m => m.Material) + @Html.ValidationMessageFor(m => m.Material) +
    + @Html.SmartLabelFor(m => m.Pattern) + + @Html.EditorFor(m => m.Pattern) + @Html.ValidationMessageFor(m => m.Pattern) +
    + @Html.SmartLabelFor(m => m.ExpirationDays) + + @Html.EditorFor(m => m.ExpirationDays) + @Html.ValidationMessageFor(m => m.ExpirationDays) +
    + @Html.SmartLabelFor(m => m.OnlineOnly) + + @Html.EditorFor(m => m.OnlineOnly) + @Html.ValidationMessageFor(m => m.OnlineOnly) +
    + @Html.SmartLabelFor(m => m.AdditionalImages) + + @Html.EditorFor(m => m.AdditionalImages) + @Html.ValidationMessageFor(m => m.AdditionalImages) +
    + @Html.SmartLabelFor(m => m.SpecialPrice) + + @Html.EditorFor(m => m.SpecialPrice) + @Html.ValidationMessageFor(m => m.SpecialPrice) +
    + @Html.SmartLabelFor(m => m.ExportShipping) + + @Html.EditorFor(m => m.ExportShipping) + @Html.ValidationMessageFor(m => m.ExportShipping) +
    + @Html.SmartLabelFor(m => m.ExportBasePrice) + + @Html.EditorFor(m => m.ExportBasePrice) + @Html.ValidationMessageFor(m => m.ExportBasePrice) +
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md index 03e71794e6..4c66bc8f31 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md @@ -3,6 +3,8 @@ ##Google Merchant Center (GMC) 2.2.0.4 ###Improvements * Supports new export framework +###Bugfixes +* Availability value "available for order" deprecated ##Google Merchant Center (GMC) 2.2.0.3 ###Bugfixes diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 3908bb4d9a..22bca5e888 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -840,7 +840,7 @@ public ActionResult Execute(int id, string selectedIds, bool exportAll) } [ChildActionOnly] - public ActionResult ProfileInfo(string systemName, string returnUrl) + public ActionResult InfoProfile(string systemName, string returnUrl) { var profiles = _exportService.GetExportProfilesBySystemName(systemName); From 84441bac5d82346748784b8bc6b692ea8b2ceac1 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 17 Sep 2015 18:11:17 +0200 Subject: [PATCH 071/874] Mobile: Shopping cart warnings weren't displayed to customers --- changelog.md | 3 ++- .../Controllers/ProductController.cs | 7 +++--- .../Views/Shared/_Notifications.Mobile.cshtml | 24 ++++++++++++++++++- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index 26c2ede45e..011b50f127 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ -# Release Notes +# Release Notes ## SmartStore.NET 2.5 @@ -55,6 +55,7 @@ * PayPal Express: fixed capture method * #770 Resizing browser with product details page causes product image to disappear * GMC feed: Availability value "available for order" deprecated +* Mobile: Shopping cart warnings weren't displayed to customers ## SmartStore.NET 2.2.2 diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index 2a8df6a0cb..f557e9ee81 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -301,9 +301,10 @@ public ActionResult AddProductToCart(int productId, FormCollection form) else { //Errors - foreach (string error in addToCartContext.Warnings) - ModelState.AddModelError("", error); - + foreach (string error in addToCartContext.Warnings) + { + this.NotifyError(error); + } //If we got this far, something failed, redisplay form var model = _helper.PrepareProductDetailsPageModel(parentProduct); diff --git a/src/Presentation/SmartStore.Web/Views/Shared/_Notifications.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/_Notifications.Mobile.cshtml index ba4addd51d..ba1ea8b6ca 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/_Notifications.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/_Notifications.Mobile.cshtml @@ -5,7 +5,11 @@ var successMessages = this.GetMessages(NotifyType.Success).ToList(); // error messages - var errorMessages = this.GetMessages(NotifyType.Error).ToList(); + var errorMessages = this.GetMessages(NotifyType.Error).ToList(); + + // warning messages + var warningMessages = this.GetMessages(NotifyType.Warning).ToList(); + } @if (successMessages.Count > 0) @@ -42,4 +46,22 @@ }); } + +@if (warningMessages.Count > 0) +{ + var warningsSb = new System.Text.StringBuilder(); + for (int i = 0; i < errorMessages.Count; i++) + { + warningsSb.Append(warningMessages[i]); + if (i != warningMessages.Count - 1) + { + warningsSb.Append("\\n"); + } + } + +} @Html.Widget("mobile_notifications") \ No newline at end of file From 54d50630e496bc81a85226979cece6c29d1e12f1 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 17 Sep 2015 20:59:42 +0200 Subject: [PATCH 072/874] Some changes to GMC plugin --- .../201509150931528_ExportFramework2.cs | 5 +- .../Content/smartstore.gmc.css | 6 + .../Extensions/MiscExtensions.cs | 6 +- .../Localization/resources.de-de.xml | 12 +- .../Localization/resources.en-us.xml | 12 +- .../Services/GoogleFeedService.cs | 2 +- .../SmartStore.GoogleMerchantCenter.csproj | 3 + .../FeedGoogleMerchantCenter/Configure.cshtml | 334 +++++++++--------- 8 files changed, 181 insertions(+), 199 deletions(-) create mode 100644 src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.gmc.css diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index 7ad267340b..f7a614fc8f 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -80,7 +80,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Plugins.Feed.Froogle.LanguageId.Hint", "Plugins.Feed.Froogle.Generate", "Plugins.Feed.Froogle.ConfigSaveNote", - "Plugins.Feed.Froogle.AvailabilityAvailableForOrder" + "Plugins.Feed.Froogle.AvailabilityAvailableForOrder", + "Plugins.Feed.Froogle.GridEditNote", + "Plugins.Feed.Froogle.General", + "Plugins.Feed.Froogle.ProductData" ); } } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.gmc.css b/src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.gmc.css new file mode 100644 index 0000000000..4a8bfa9c72 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.gmc.css @@ -0,0 +1,6 @@ +.edit-taxonomy { + min-width: 460px; +} +.gmc-product-search { + margin-bottom: 8px !important; +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs index c3b13a9939..c140111c1e 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs @@ -10,16 +10,16 @@ public static string XEditableLink(this HtmlHelper hlp, string fieldName, string string displayText = null; if (fieldName == "Gender" || fieldName == "AgeGroup" || fieldName == "Exporting") - displayText = "<#= {0}Localize #>".FormatWith(fieldName); + displayText = "<#= {0}Localize #>".FormatInvariant(fieldName); else - displayText = "<#= {0} #>".FormatWith(fieldName); + displayText = "<#= {0} #>".FormatInvariant(fieldName); string skeleton = "\" class=\"edit-link-{1}\"" + " data-pk=\"<#= ProductId #>\" data-name=\"{0}\" data-value=\"<#= {0} #>\" data-inputclass=\"edit-{1}\" data-type=\"{2}\">" + "{3}"; - return skeleton.FormatWith(fieldName, fieldName.ToLower(), type, displayText); + return skeleton.FormatInvariant(fieldName, fieldName.ToLower(), type, displayText); } public static bool IsTouched(this GoogleProductRecord product) diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml index 1eadc04c7b..0288180669 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml @@ -18,14 +18,10 @@
  • Für die Produktidentifizierung muss entweder die GTIN (z.B. als UPC, EAN etc.) oder der Hersteller samt Hersteller-Artikelnummer (MPN) hinterlegt sein. Google empfiehlt die Angabe aller drei Informationen.
  • Standard Steuer- und Versanddaten sind in den Einstellungen Ihres Google-Merchant-Center-Kontos zu hinterlegen.
  • Mehr Informationen zu benötigten Feldern finden Sie im Artikel Produkt-Feed-Spezifikationen.
  • +
  • Eine Liste mit allen gültigen Google-Produkt-Kategorie finden Sie hier.
  • ]]> - - - hier.]]> - - Standard-Google-Kategorie fehlt @@ -35,12 +31,6 @@ Wird verwendet, falls auf Produktebene keine Kategorie angegeben ist. - - Allgemein - - - Produktspezifische Daten - Produkt diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml index 6f989e68dd..4b7f107d62 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml @@ -18,14 +18,10 @@
  • Either the GTIN (such as UPC, EAN, etc.) or manufacturer and manufacturer part number (MPN) are required for product identification. Google recommends that all three pieces of information be specified.
  • Specify default tax and shipping values in your Google Merchant Center account settings.
  • In order to get more info about required fields look at the following article Product feed specification.
  • +
  • You can find a list of all Google categories here.
  • ]]> - - - here.]]> - - Default Google category is not set @@ -35,12 +31,6 @@ Will be used if there is no category specified on produkt level. - - General - - - Product specific data - Product diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs index 16cf7b9401..76b5c540f0 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs @@ -220,7 +220,7 @@ public GridModel GetGridModel(GridCommand command, string se x.GenderLocalize = T("Plugins.Feed.Froogle.Gender" + textInfo.ToTitleCase(x.Gender)); if (x.AgeGroup.HasValue()) - x.AgeGroupLocalize = T("Plugins.Feed.Froogle-AgeGroup" + textInfo.ToTitleCase(x.AgeGroup)); + x.AgeGroupLocalize = T("Plugins.Feed.Froogle.AgeGroup" + textInfo.ToTitleCase(x.AgeGroup)); x.ExportingLocalize = (x.Export == 0 ? no : yes); }); diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj index eec6348512..e571790b5d 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj @@ -205,6 +205,9 @@ 201403112356126_Initial.cs + + PreserveNewest + PreserveNewest diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml index 60ba2e14a1..28890a77a6 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml @@ -10,18 +10,12 @@ Layout = ""; Html.AddCssFileParts(true, "~/Content/x-editable/bootstrap-editable.css"); + Html.AddCssFileParts(true, "~/Plugins/SmartStore.GoogleMerchantCenter/Content/smartstore.gmc.css"); Html.AppendScriptParts(true, "~/Content/x-editable/bootstrap-editable.js"); } -@(Html.SmartStore().TabStrip().Name("googlebase-configure").Items(x => -{ - x.Add().Text(T("Plugins.Feed.Froogle.General").Text).Content(@TabGeneral()).Selected(true); - x.Add().Text(T("Plugins.Feed.Froogle.ProductData").Text).Content(@TabProductData()); -})) - -@helper TabGeneral() -{ -
    +
    +
    @Html.Raw(@T("Plugins.Feed.Froogle.AdminInstruction")) @@ -29,169 +23,165 @@
    @Html.Action("InfoProfile", "Export", new { systemName = GoogleMerchantCenterFeedPlugin.SystemName, returnUrl = Request.RawUrl, area = "admin" })
    -
    - - Google Merchant Center - -
    -} - -@helper TabProductData() -{ -
    - - @Html.Raw(@T("Plugins.Feed.Froogle.GridEditNote")) + - - - - + +
    - - - - - - - - - - - - - -
    - @Html.SmartLabelFor(m => m.SearchProductName) - - @Html.EditorFor(m => m.SearchProductName) -
    - @Html.SmartLabelFor(m => m.SearchIsTouched) - - @Html.DropDownList("SearchIsTouched", new List { - new SelectListItem { Text = T("Common.Unspecified"), Value = "" }, - new SelectListItem { Text = T("Plugins.Feed.Froogle.SearchIsTouched.Touched"), Value = "touched" }, - new SelectListItem { Text = T("Plugins.Feed.Froogle.SearchIsTouched.Untouched"), Value = "untouched" } + + +

     

    + +
    + + + + + + - - - - - -
    + + + + + + + + + + + + + + + + +
    + @(Html.Telerik().Grid() + .Name("gmc-products-grid") + .DataKeys(keys => + { + keys.Add(x => x.ProductId).RouteKey("ProductId"); }) -
      - -
    - -
    - @(Html.Telerik().Grid() - .Name("gmc-products-grid") - .DataKeys(keys => - { - keys.Add(x => x.ProductId).RouteKey("ProductId"); - }) - .Columns(c => - { - c.Bound(x => x.ProductId).ReadOnly().Visible(false); - c.Bound(x => x.Name) - .ReadOnly().Visible(true).Width(420) - .Template(x => @Html.LabeledProductName(x.ProductId, x.Name, x.ProductTypeName, x.ProductTypeLabelHint)) - .ClientTemplate(@Html.LabeledProductName("ProductId", "Name")); - c.Bound(x => x.SKU).ReadOnly().Visible(true); - c.Bound(x => x.Exporting).ClientTemplate(Html.XEditableLink("Exporting", "select2")); - c.Bound(x => x.Taxonomy).ClientTemplate(Html.XEditableLink("Taxonomy", "typeahead")); - c.Bound(x => x.Gender).ClientTemplate(Html.XEditableLink("Gender", "select2")).Width(100); - c.Bound(x => x.AgeGroup).ClientTemplate(Html.XEditableLink("AgeGroup", "select2")).Width(100); - c.Bound(x => x.Color).ClientTemplate(Html.XEditableLink("Color", "text")); - c.Bound(x => x.Size).ClientTemplate(Html.XEditableLink("Size", "text")); - c.Bound(x => x.Material).ClientTemplate(Html.XEditableLink("Material", "text")); - c.Bound(x => x.Pattern).ClientTemplate(Html.XEditableLink("Pattern", "text")); - }) - .ClientEvents(e => - { - e.OnDataBound("OnGridDataBound"); - e.OnDataBinding("OnGridDataBinding"); - }) - .DataBinding(dataBinding => - { - dataBinding.Ajax().Select("GoogleProductList", "FeedGoogleMerchantCenter"); - }) - .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) - .PreserveGridState() - .EnableCustomBinding(true) - ) -
    - - -} + .Columns(c => + { + c.Bound(x => x.ProductId).ReadOnly().Visible(false); + c.Bound(x => x.Name) + .ReadOnly().Visible(true).Width(420) + .Template(x => @Html.LabeledProductName(x.ProductId, x.Name, x.ProductTypeName, x.ProductTypeLabelHint)) + .ClientTemplate(@Html.LabeledProductName("ProductId", "Name")); + c.Bound(x => x.SKU).ReadOnly().Visible(true); + c.Bound(x => x.Exporting).ClientTemplate(Html.XEditableLink("Exporting", "select2")); + c.Bound(x => x.Taxonomy).ClientTemplate(Html.XEditableLink("Taxonomy", "typeahead")); + c.Bound(x => x.Gender).ClientTemplate(Html.XEditableLink("Gender", "select2")).Width(100); + c.Bound(x => x.AgeGroup).ClientTemplate(Html.XEditableLink("AgeGroup", "select2")).Width(100); + c.Bound(x => x.Color).ClientTemplate(Html.XEditableLink("Color", "text")); + c.Bound(x => x.Size).ClientTemplate(Html.XEditableLink("Size", "text")); + c.Bound(x => x.Material).ClientTemplate(Html.XEditableLink("Material", "text")); + c.Bound(x => x.Pattern).ClientTemplate(Html.XEditableLink("Pattern", "text")); + }) + .ClientEvents(e => + { + e.OnDataBound("OnGridDataBound"); + e.OnDataBinding("OnGridDataBinding"); + }) + .DataBinding(dataBinding => + { + dataBinding.Ajax().Select("GoogleProductList", "FeedGoogleMerchantCenter"); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .PreserveGridState() + .EnableCustomBinding(true) + ) +
    +
    + + \ No newline at end of file From 7ed204162f6ddbadbd49869ba35a16ac122cd973 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 17 Sep 2015 21:38:32 +0200 Subject: [PATCH 073/874] Dev tools: Added an example of how a plugin can integrate a page into the backend --- src/Plugins/SmartStore.DevTools/AdminMenu.cs | 17 ++++++++++------- .../Controllers/DevToolsController.cs | 11 +++++++++++ .../Models/BackendExtensionModel.cs | 9 +++++++++ .../SmartStore.DevTools.csproj | 4 ++++ .../Views/DevTools/BackendExtension.cshtml | 18 ++++++++++++++++++ 5 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 src/Plugins/SmartStore.DevTools/Models/BackendExtensionModel.cs create mode 100644 src/Plugins/SmartStore.DevTools/Views/DevTools/BackendExtension.cshtml diff --git a/src/Plugins/SmartStore.DevTools/AdminMenu.cs b/src/Plugins/SmartStore.DevTools/AdminMenu.cs index e0893067e1..d00117376f 100644 --- a/src/Plugins/SmartStore.DevTools/AdminMenu.cs +++ b/src/Plugins/SmartStore.DevTools/AdminMenu.cs @@ -1,11 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Web.Routing; -using System.Web.Mvc; +using SmartStore.Collections; using SmartStore.Web.Framework.UI; -using SmartStore.Collections; namespace SmartStore.DevTools { @@ -20,6 +14,15 @@ protected override void BuildMenuCore(TreeNode pluginsNode) .ToItem(); pluginsNode.Prepend(menuItem); + + // uncomment to add to admin menu (see plugin sub-menu) + //var backendExtensionItem = new MenuItem().ToBuilder() + // .Text("Backend extension") + // .Icon("area-chart") + // .Action("BackendExtension", "DevTools", new { area = "SmartStore.DevTools" }) + // .ToItem(); + + //pluginsNode.Append(backendExtensionItem); } } } diff --git a/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs b/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs index 89a261690f..20e212472d 100644 --- a/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs +++ b/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs @@ -4,6 +4,7 @@ using System.Web; using System.Web.Mvc; using SmartStore.Core; +using SmartStore.DevTools.Models; using SmartStore.Services; using SmartStore.Services.Configuration; using SmartStore.Services.Stores; @@ -86,5 +87,15 @@ public ActionResult WidgetZone(string widgetZone) return new EmptyResult(); } + [AdminAuthorize] + public ActionResult BackendExtension() + { + var model = new BackendExtensionModel + { + Welcome = "Hello world!" + }; + + return View(model); + } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/Models/BackendExtensionModel.cs b/src/Plugins/SmartStore.DevTools/Models/BackendExtensionModel.cs new file mode 100644 index 0000000000..d8ecaa66c0 --- /dev/null +++ b/src/Plugins/SmartStore.DevTools/Models/BackendExtensionModel.cs @@ -0,0 +1,9 @@ +using SmartStore.Web.Framework.Mvc; + +namespace SmartStore.DevTools.Models +{ + public class BackendExtensionModel : ModelBase + { + public string Welcome { get; set; } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/SmartStore.DevTools.csproj b/src/Plugins/SmartStore.DevTools/SmartStore.DevTools.csproj index 6ac06bf786..bb3a9cace1 100644 --- a/src/Plugins/SmartStore.DevTools/SmartStore.DevTools.csproj +++ b/src/Plugins/SmartStore.DevTools/SmartStore.DevTools.csproj @@ -155,6 +155,7 @@ + @@ -208,6 +209,9 @@ PreserveNewest + + PreserveNewest + diff --git a/src/Plugins/SmartStore.DevTools/Views/DevTools/BackendExtension.cshtml b/src/Plugins/SmartStore.DevTools/Views/DevTools/BackendExtension.cshtml new file mode 100644 index 0000000000..d2e818b648 --- /dev/null +++ b/src/Plugins/SmartStore.DevTools/Views/DevTools/BackendExtension.cshtml @@ -0,0 +1,18 @@ +@using SmartStore.DevTools.Models; +@model BackendExtensionModel +@{ + Layout = "~/Administration/Views/Shared/_AdminLayout.cshtml"; + + ViewBag.Title = "My SmartStore.NET backend extension page"; +} + +
    +
    + + My SmartStore.NET backend extension page +
    +
    + +
    + @Model.Welcome +
    From d51bd5d5e1b81d457115e75086998657582d2198 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 18 Sep 2015 11:46:22 +0200 Subject: [PATCH 074/874] Testing Google Merchant Center feed plugin --- .../Localization/resources.de-de.xml | 4 +- .../Localization/resources.en-us.xml | 4 +- .../Providers/ProductExportXmlProvider.cs | 18 +++---- .../Services/GoogleFeedService.cs | 10 ++++ .../Services/IGoogleFeedService.cs | 3 ++ .../ProfileConfiguration.cshtml | 41 ++++++++-------- .../Administration/Views/Export/Edit.cshtml | 48 +++++++++---------- 7 files changed, 66 insertions(+), 62 deletions(-) diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml index 0288180669..e4aa14c576 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml @@ -1,12 +1,12 @@  - Google Merchant Center (GMC) Feed + Google Merchant Center Feed Ermöglicht den Export von Produktdaten im Feed-Format von Google Merchant Center (GMC). - Google Merchant Center (GMC) XML Feed + Google Merchant Center XML Feed Ermöglicht den Export von Produktdaten im XML Feed-Format von Google Merchant Center (GMC). diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml index 4b7f107d62..8e7528f68e 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml @@ -1,12 +1,12 @@  - Google Merchant Center (GMC) feed + Google Merchant Center feed Allows you to export product data in the feed format of Google Merchant Center (GMC). - Google Merchant Center (GMC) XML feed + Google Merchant Center XML feed Allows you to export product data in the XML feed format of Google Merchant Center (GMC). diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs index 8e402cbb32..e4a7808e97 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using System.Text; using System.Xml; using SmartStore.Core.Domain.Catalog; @@ -185,23 +186,16 @@ public void Execute(IExportExecuteContext context) { var segment = context.Data.CurrentSegment; + int[] productIds = segment.Select(x => (int)((dynamic)x).Id).ToArray(); + var googleProducts = _googleFeedService.GetGoogleProductRecords(productIds); + foreach (dynamic product in segment) { if (context.Abort != ExportAbortion.None) break; int productId = product.Id; - GoogleProductRecord gmc = null; - - try - { - gmc = _googleFeedService.GetGoogleProductRecord(productId); - } - catch (Exception exc) - { - context.Log.Error("Error while loading google product with id {0}: {1}".FormatInvariant(productId, exc.Message), exc); - ++context.RecordsFailed; - } + var gmc = googleProducts.FirstOrDefault(x => x.ProductId == productId); if (gmc != null && !gmc.Export) continue; @@ -339,7 +333,7 @@ public void Execute(IExportExecuteContext context) writer.WriteCData("item_group_id", gmc != null && gmc.ItemGroupId.HasValue() ? gmc.ItemGroupId : "", "g", _googleNamespace); writer.WriteElementString("g", "online_only", _googleNamespace, config.OnlineOnly ? "y" : "n"); - writer.WriteElementString("g", "identifier_exists", _googleNamespace, product.Gtin.HasValue() || brand.HasValue() || mpn.HasValue() ? "TRUE" : "FALSE"); + writer.WriteElementString("g", "identifier_exists", _googleNamespace, gtin.HasValue() || brand.HasValue() || mpn.HasValue() ? "TRUE" : "FALSE"); if (config.ExpirationDays > 0) { diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs index 76b5c540f0..48d574215e 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; @@ -50,6 +51,15 @@ from gp in _gpRepository.Table return record; } + public List GetGoogleProductRecords(int[] productIds) + { + if (productIds == null || productIds.Length == 0) + return new List(); + + var lst = _gpRepository.TableUntracked.Where(x => productIds.Contains(x.ProductId)).ToList(); + return lst; + } + public void InsertGoogleProductRecord(GoogleProductRecord record) { if (record == null) diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/IGoogleFeedService.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/IGoogleFeedService.cs index e2c39d3ebb..6b89e81213 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/IGoogleFeedService.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/IGoogleFeedService.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using SmartStore.GoogleMerchantCenter.Domain; using SmartStore.GoogleMerchantCenter.Models; using Telerik.Web.Mvc; @@ -8,6 +9,8 @@ public partial interface IGoogleFeedService { GoogleProductRecord GetGoogleProductRecord(int productId); + List GetGoogleProductRecords(int[] productIds); + void InsertGoogleProductRecord(GoogleProductRecord record); void UpdateGoogleProductRecord(GoogleProductRecord record); diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml index a6c7588827..57b1c7b693 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml @@ -11,8 +11,27 @@ @Html.SmartLabelFor(m => m.DefaultGoogleCategory)
    - + @Html.TextBoxFor(m => m.DefaultGoogleCategory, new { data_provide = "typeahead", placeholder = Model.DefaultGoogleCategory, style = "width: 98%", autocomplete = "off", + data_items = 18, data_source = Model.AvailableGoogleCategoriesAsJson }) + @Html.ValidationMessageFor(m => m.DefaultGoogleCategory) +
    + @Html.SmartLabelFor(m => m.OnlineOnly) + + @Html.EditorFor(m => m.OnlineOnly) + @Html.ValidationMessageFor(m => m.OnlineOnly) +
    + @Html.SmartLabelFor(m => m.ExpirationDays) + + @Html.EditorFor(m => m.ExpirationDays) + @Html.ValidationMessageFor(m => m.ExpirationDays)
    - @Html.SmartLabelFor(m => m.ExpirationDays) - - @Html.EditorFor(m => m.ExpirationDays) - @Html.ValidationMessageFor(m => m.ExpirationDays) -
    - @Html.SmartLabelFor(m => m.OnlineOnly) - - @Html.EditorFor(m => m.OnlineOnly) - @Html.ValidationMessageFor(m => m.OnlineOnly) -
    @Html.SmartLabelFor(m => m.AdditionalImages) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index d4929f7064..8376b45daf 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -91,6 +91,28 @@
    + @Html.SmartLabelFor(x => x.Provider.Description) + + @Html.Raw(Model.Provider.Description) +
    + @Html.SmartLabelFor(x => x.Provider.Url) + + @Model.Provider.Url +
    @Html.SmartLabelFor(x => x.Provider.Author) @@ -107,17 +129,6 @@ @Html.DisplayFor(x => x.Provider.Version)
    - @Html.SmartLabelFor(x => x.Provider.Url) - - @Model.Provider.Url -
    @Html.SmartLabelFor(x => x.Provider.EntityTypeName) @@ -135,21 +146,6 @@ @Html.DisplayFor(x => x.Provider.FileExtension)
    - @Html.SmartLabelFor(x => x.Provider.Description) - - @if (Model.Provider.Description.HasValue()) - { - @Html.Raw(Model.Provider.Description) - } - else - { - @("".NaIfEmpty()) - } -
    } From 6b0a463a6a45ab44866814b162ed664e9dfd1196 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 18 Sep 2015 18:16:57 +0200 Subject: [PATCH 075/874] Export framework supports export providers with a non file based, on the fly data exporting --- .../201509150931528_ExportFramework2.cs | 3 + .../DataExchange/ExportExecuteContext.cs | 4 +- .../DataExchange/ExportExtensions.cs | 5 +- .../DataExchange/ExportProfileTask.cs | 80 +++++---- .../DataExchange/ExportService.cs | 39 +++-- .../DataExchange/IExportProvider.cs | 2 +- .../SmartStore.Services/Tasks/TaskExecutor.cs | 7 + .../Providers/ProductExportXmlProvider.cs | 1 - .../Extensions/HtmlExtensions.cs | 26 ++- .../Administration/Content/admin.less | 1 + .../Controllers/ExportController.cs | 2 +- .../Models/DataExchange/ExportProfileModel.cs | 5 + .../Administration/Views/Export/Edit.cshtml | 3 +- .../Administration/Views/Export/List.cshtml | 3 +- .../Views/Export/_Tab.Deployment.cshtml | 160 ++++++++++-------- 15 files changed, 194 insertions(+), 147 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index f7a614fc8f..9ae5a7d564 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -42,6 +42,9 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Specifies an export profile from which to apply the settings.", "Legt das Exportprofil fest, von welchem die Einstellungen bernommen werden sollen."); + builder.AddOrUpdate("Admin.DataExchange.Export.NonFileBasedExport.Note", + "The export provider does not explicit support any file type. Therefore, the export provider is responsible for futher deployment of export data.", + "Der Export-Provider untersttzt keinen expliziten Dateityp. Fr eine weitere Bereitstellung der Exportdaten ist daher der Export-Provider verantwortlich."); builder.Delete( diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs index 5675529cfe..fe442552d5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs @@ -156,13 +156,13 @@ public bool IsMaxFailures public string FileExtension { get; internal set; } public string FilePath { - get { return Path.Combine(Folder, FileName); } + get { return Path.Combine(Folder, FileName.EmptyNull()); } } public string FilePublicUrl { get { - var url = string.Concat(((string)Store.Url).EnsureEndsWith("/"), ExportProfileTask.PublicFolder.EnsureEndsWith("/"), FileName); + var url = string.Concat(((string)Store.Url).EnsureEndsWith("/"), ExportProfileTask.PublicFolder.EnsureEndsWith("/"), FileName.EmptyNull()); return url; } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs index 7ed95478e3..eee5d64533 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs @@ -17,10 +17,7 @@ public static class ExportExtensions /// true provider is valid, false provider is invalid. public static bool IsValid(this Provider provider) { - return ( - provider != null && - provider.Value.FileExtension.HasValue() - ); + return (provider != null); } /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index 4ba8f7cb23..0173bb0dc8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -354,9 +354,9 @@ private void DeployFileSystem(ExportProfileTaskContext ctx, ExportDeployment dep if (deployment.CreateZip) { var path = Path.Combine(folderDestination, ctx.Profile.FolderName + ".zip"); - FileSystemHelper.Copy(ctx.ZipPath, path); - ctx.Log.Information("Copied ZIP archive " + path); + if (FileSystemHelper.Copy(ctx.ZipPath, path)) + ctx.Log.Information("Copied ZIP archive " + path); } else { @@ -854,8 +854,6 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod product.SpecialPrice = null; exp._RegularPrice = CalculatePrice(ctx, product, combination != null); product.SpecialPrice = tmpSpecialPrice; - - _services.DbContext.SetToUnchanged(product); } } } @@ -1020,7 +1018,10 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.MaxFileNameLength = _dataExchangeSettings.MaxFileNameLength; - ctx.Export.FileExtension = ctx.Provider.Value.FileExtension.ToLower().EnsureStartsWith("."); + if (ctx.Provider.Value.FileExtension.HasValue()) + { + ctx.Export.FileExtension = ctx.Provider.Value.FileExtension.ToLower().EnsureStartsWith("."); + } var totalCount = ctx.RecordsPerStore.First(x => x.Key == ctx.Store.Id).Value; @@ -1057,15 +1058,18 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) if (!ctx.IsPreview) { - var resolvedPattern = ctx.Profile.ResolveFileNamePattern(ctx.Store, ctx.Export.Data.FileIndex + 1, ctx.Export.MaxFileNameLength); + if (ctx.IsFileBasedExport) + { + var resolvedPattern = ctx.Profile.ResolveFileNamePattern(ctx.Store, ctx.Export.Data.FileIndex + 1, ctx.Export.MaxFileNameLength); - ctx.Export.FileName = resolvedPattern + ctx.Export.FileExtension; + ctx.Export.FileName = resolvedPattern + ctx.Export.FileExtension.EmptyNull(); + } ctx.Provider.Value.Execute(ctx.Export); ctx.Log.Information("Provider reports {0} successful exported record(s)".FormatInvariant(ctx.Export.RecordsSucceeded)); - if (File.Exists(ctx.Export.FilePath)) + if (ctx.IsFileBasedExport && File.Exists(ctx.Export.FilePath)) { ctx.ResultInfo.Files.Add(new ExportResultFileInfo { @@ -1166,36 +1170,39 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) stores.ForEach(x => ExportCoreInner(ctx, x)); } - if (!(ctx.IsPreview || ctx.Export.Abort == ExportAbortion.Hard)) + if (!ctx.IsPreview && ctx.Export.Abort != ExportAbortion.Hard) { - if (ctx.Profile.CreateZipArchive || ctx.Profile.Deployments.Any(x => x.Enabled && x.CreateZip)) + if (ctx.IsFileBasedExport) { - ZipFile.CreateFromDirectory(ctx.FolderContent, ctx.ZipPath, CompressionLevel.Fastest, true); - } + if (ctx.Profile.CreateZipArchive || ctx.Profile.Deployments.Any(x => x.Enabled && x.CreateZip)) + { + ZipFile.CreateFromDirectory(ctx.FolderContent, ctx.ZipPath, CompressionLevel.Fastest, true); + } - foreach (var deployment in ctx.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) - { - try + foreach (var deployment in ctx.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) { - switch (deployment.DeploymentType) + try { - case ExportDeploymentType.FileSystem: - DeployFileSystem(ctx, deployment); - break; - case ExportDeploymentType.Email: - DeployEmail(ctx, deployment); - break; - case ExportDeploymentType.Http: - DeployHttp(ctx, deployment); - break; - case ExportDeploymentType.Ftp: - DeployFtp(ctx, deployment); - break; + switch (deployment.DeploymentType) + { + case ExportDeploymentType.FileSystem: + DeployFileSystem(ctx, deployment); + break; + case ExportDeploymentType.Email: + DeployEmail(ctx, deployment); + break; + case ExportDeploymentType.Http: + DeployHttp(ctx, deployment); + break; + case ExportDeploymentType.Ftp: + DeployFtp(ctx, deployment); + break; + } + } + catch (Exception exc) + { + logger.Error("Deployment \"{0}\" of type {1} failed.".FormatInvariant(deployment.Name, deployment.DeploymentType.ToString()), exc); } - } - catch (Exception exc) - { - logger.Error("Deployment \"{0}\" of type {1} failed.".FormatInvariant(deployment.Name, deployment.DeploymentType.ToString()), exc); } } @@ -1224,7 +1231,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) try { - if (!ctx.IsPreview && ctx.Profile.Cleanup && ctx.Export.Abort != ExportAbortion.Hard) + if (!ctx.IsPreview && ctx.IsFileBasedExport && ctx.Export.Abort != ExportAbortion.Hard && ctx.Profile.Cleanup) { FileSystemHelper.ClearDirectory(ctx.FolderContent, false); } @@ -1455,8 +1462,15 @@ public string LogPath get { return Path.Combine(FolderRoot, "log.txt"); } } + public bool IsFileBasedExport + { + get { return Provider.Value.FileExtension.HasValue(); } + } public string[] GetDeploymentFiles(ExportDeployment deployment) { + if (!IsFileBasedExport) + return new string[0]; + if (deployment.CreateZip) return new string[] { ZipPath }; diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index c95afc64fc..232e4a44cd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -101,30 +101,33 @@ public virtual ExportProfile InsertExportProfile(Provider provi task.Alias = profile.Id.ToString(); _scheduleTaskService.UpdateTask(task); - if (cloneProfile == null) + if (provider.Value.FileExtension.HasValue()) { - if (systemName.StartsWith("Feeds.")) + if (cloneProfile == null) { - profile.Deployments.Add(new ExportDeployment + if (systemName.StartsWith("Feeds.")) { - ProfileId = profile.Id, - Enabled = true, - IsPublic = true, - DeploymentType = ExportDeploymentType.FileSystem, - Name = profile.Name - }); - - UpdateExportProfile(profile); + profile.Deployments.Add(new ExportDeployment + { + ProfileId = profile.Id, + Enabled = true, + IsPublic = true, + DeploymentType = ExportDeploymentType.FileSystem, + Name = profile.Name + }); + + UpdateExportProfile(profile); + } } - } - else - { - foreach (var deployment in cloneProfile.Deployments) + else { - profile.Deployments.Add(deployment.Clone()); - } + foreach (var deployment in cloneProfile.Deployments) + { + profile.Deployments.Add(deployment.Clone()); + } - UpdateExportProfile(profile); + UpdateExportProfile(profile); + } } _eventPublisher.EntityInserted(profile); diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs index 692583adcb..74c4cef39f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs @@ -12,7 +12,7 @@ public partial interface IExportProvider : IProvider, IUserEditable ExportEntityType EntityType { get; } /// - /// Extension of the export files (without dot) + /// File extension of the export files (without dot). Return null for a non file based, on-the-fly export. /// string FileExtension { get; } diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs index 3548dab1e5..92e7fa4668 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs @@ -9,6 +9,7 @@ using SmartStore.Services.Customers; using SmartStore.Core; using SmartStore.Core.Domain.Customers; +using System.Diagnostics; namespace SmartStore.Services.Tasks { @@ -61,6 +62,12 @@ public void Execute(ScheduleTask task, bool throwOnError = false) try { taskType = Type.GetType(task.Type); + + Debug.WriteLineIf(taskType == null, "Invalid task type: " + task.Type.NaIfEmpty()); + + if (taskType == null) + return; + if (!PluginManager.IsActivePluginAssembly(taskType.Assembly)) return; } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs index e4a7808e97..4d818bc993 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs @@ -9,7 +9,6 @@ using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -using SmartStore.GoogleMerchantCenter.Domain; using SmartStore.GoogleMerchantCenter.Models; using SmartStore.GoogleMerchantCenter.Services; using SmartStore.Services.DataExchange; diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs index 1e58dfcc28..801231aa57 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs @@ -627,9 +627,9 @@ public static MvcHtmlString CollapsedText(this HtmlHelper helper, string text) return MvcHtmlString.Create(result); } - public static MvcHtmlString IconForFileExtension(this HtmlHelper helper, string fileExtension) + public static MvcHtmlString IconForFileExtension(this HtmlHelper helper, string fileExtension, bool renderExtensionText) { - string result = null; + string result = ""; if (fileExtension != null && fileExtension.StartsWith(".")) { @@ -638,31 +638,39 @@ public static MvcHtmlString IconForFileExtension(this HtmlHelper helper, string if (fileExtension.IsCaseInsensitiveEqual("xml")) { - result = ""; + result = ""; } else if (fileExtension.IsCaseInsensitiveEqual("xls")) { - result = ""; + result = ""; } else if (fileExtension.IsCaseInsensitiveEqual("pdf")) { - result = ""; + result = ""; } else if (fileExtension.IsCaseInsensitiveEqual("zip")) { - result = ""; + result = ""; } else if (fileExtension.IsCaseInsensitiveEqual("txt") || fileExtension.IsCaseInsensitiveEqual("csv")) { - result = ""; + result = ""; } else if (fileExtension.IsCaseInsensitiveEqual("doc")) { - result = ""; + result = ""; } else if (fileExtension.IsCaseInsensitiveEqual("jpg") || fileExtension.IsCaseInsensitiveEqual("png") || fileExtension.IsCaseInsensitiveEqual("gif")) { - result = ""; + result = ""; + } + + if (renderExtensionText) + { + if (fileExtension.IsEmpty()) + result = "{0}".FormatInvariant("".NaIfEmpty()); + else + result = result + "{0}"; } return MvcHtmlString.Create(result.FormatInvariant(fileExtension.NaIfEmpty().ToUpper())); diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index f46e61f11c..8ce3a53e84 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -886,6 +886,7 @@ label { } .mr8 { margin-right: 8px; } +.ml4 { margin-left: 4px; } /* MASTER HEADER */ .header-menu, diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 22bca5e888..218cf8bd82 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -133,7 +133,7 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile model.Provider.EntityType = provider.Value.EntityType; model.Provider.EntityTypeName = provider.Value.EntityType.GetLocalizedEnum(_services.Localization, _services.WorkContext); - model.Provider.FileExtension = provider.Value.FileExtension.ToUpper(); + model.Provider.FileExtension = provider.Value.FileExtension; } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 366d60ade9..5bcd1f7eb1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -126,6 +126,11 @@ public class ProviderModel [SmartResourceDisplayName("Admin.DataExchange.Export.FileExtension")] public string FileExtension { get; set; } + public bool IsFileBasedExport + { + get { return FileExtension.HasValue(); } + } + [SmartResourceDisplayName("Admin.DataExchange.Export.SupportedFileTypes")] public string SupportedFileTypes { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 8376b45daf..30b0fbce96 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -142,8 +142,7 @@ @Html.SmartLabelFor(x => x.Provider.FileExtension)
    - @Html.IconForFileExtension(Model.Provider.FileExtension) - @Html.DisplayFor(x => x.Provider.FileExtension) + @Html.IconForFileExtension(Model.Provider.FileExtension, true)
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 7b78bc903a..5db3e3f3d2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -57,8 +57,7 @@ @profile.Provider.EntityTypeName - @Html.IconForFileExtension(profile.Provider.FileExtension) - @profile.Provider.FileExtension + @Html.IconForFileExtension(profile.Provider.FileExtension, true)
    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 f0a276753b..5217919650 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml @@ -12,33 +12,45 @@ @Html.ValidationMessageFor(x => x.FolderName) - - - @Html.SmartLabelFor(x => x.FileNamePattern) - - -
    - @Html.TextBoxFor(x => x.FileNamePattern, new { @class = "input-large" }) - @Html.ValidationMessageFor(x => x.FileNamePattern) -
    -
    - -    -
    @Model.FileNamePatternExample
    -
    - - - - - @Html.SmartLabelFor(x => x.CreateZipArchive) - - - @Html.EditorFor(x => x.CreateZipArchive) - @Html.ValidationMessageFor(x => x.CreateZipArchive) - - + @if (Model.Provider.IsFileBasedExport) + { + + + @Html.SmartLabelFor(x => x.FileNamePattern) + + +
    + @Html.TextBoxFor(x => x.FileNamePattern, new { @class = "input-large" }) + @Html.ValidationMessageFor(x => x.FileNamePattern) +
    +
    + +    +
    @Model.FileNamePatternExample
    +
    + + + + + @Html.SmartLabelFor(x => x.CreateZipArchive) + + + @Html.EditorFor(x => x.CreateZipArchive) + @Html.ValidationMessageFor(x => x.CreateZipArchive) + + + + + @Html.SmartLabelFor(x => x.Cleanup) + + + @Html.EditorFor(x => x.Cleanup) + @Html.ValidationMessageFor(x => x.Cleanup) + + + } @Html.SmartLabelFor(x => x.EmailAccountId) @@ -57,57 +69,57 @@ @Html.ValidationMessageFor(x => x.CompletedEmailAddresses) - - - @Html.SmartLabelFor(x => x.Cleanup) - - - @Html.EditorFor(x => x.Cleanup) - @Html.ValidationMessageFor(x => x.Cleanup) - -

     

    -
    - @Html.Raw(T("Admin.DataExchange.Export.Deployment.Note")) -
    +@if (Model.Provider.IsFileBasedExport) +{ +
    + @Html.Raw(T("Admin.DataExchange.Export.Deployment.Note")) +
    - -  @T("Common.AddNew") - + +  @T("Common.AddNew") + -
    - - - - - - - - - - - @foreach (var deployment in Model.Deployments) - { - - - - - +
    +
    @T("Admin.DataExchange.Export.Deployment.Name")@T("Common.Enabled")@T("Admin.DataExchange.Export.Deployment.DeploymentType")@T("Admin.Common.Actions")
    - @deployment.Name - - @Html.SymbolForBool(deployment.Enabled) - - @Html.Partial("InfoDeployment", deployment) - - - @T("Common.Delete") - -
    + + + + + + - } - -
    @T("Admin.DataExchange.Export.Deployment.Name")@T("Common.Enabled")@T("Admin.DataExchange.Export.Deployment.DeploymentType")@T("Admin.Common.Actions")
    -
    \ No newline at end of file + + + @foreach (var deployment in Model.Deployments) + { + + + @deployment.Name + + + @Html.SymbolForBool(deployment.Enabled) + + + @Html.Partial("InfoDeployment", deployment) + + + + @T("Common.Delete") + + + + } + + +
    +} +else +{ +
    + @Html.Raw(T("Admin.DataExchange.Export.NonFileBasedExport.Note")) +
    +} \ No newline at end of file From 99d7c9c64fc247235b3a5889f9970e220489f316 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 18 Sep 2015 20:37:29 +0200 Subject: [PATCH 076/874] Fixed issue with export provider system name --- .../Migrations/201509021536425_ExportFramework1.cs | 4 ++-- .../Views/FeedGoogleMerchantCenter/Configure.cshtml | 2 +- .../Administration/Controllers/ExportController.cs | 3 ++- .../Models/DataExchange/ProfileInfoForProviderModel.cs | 2 ++ .../Administration/Views/Export/InfoProfile.cshtml | 3 +-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs index f78c96d2bf..e5ffff62d3 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs @@ -184,8 +184,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Das Exportprofil ist deaktiviert. Fr eine Exportvorschau muss das Exportprofil aktiviert sein."); builder.AddOrUpdate("Admin.DataExchange.Export.NoProfilesForProvider", - "There were no export profile found. Create now a new export profile.", - "Es wurde kein Exportprofil gefunden. Jetzt ein neues Exportprofil anlegen."); + "There was no export profile of type \"{0}\" found. Create now a new export profile.", + "Es wurde kein Exportprofil vom Type \"{0}\" gefunden. Jetzt ein neues Exportprofil anlegen."); builder.AddOrUpdate("Admin.DataExchange.Export.ProfileForProvider", "Export profile", diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml index 28890a77a6..32f56ee418 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml @@ -21,7 +21,7 @@ @Html.Raw(@T("Plugins.Feed.Froogle.AdminInstruction"))
    - @Html.Action("InfoProfile", "Export", new { systemName = GoogleMerchantCenterFeedPlugin.SystemName, returnUrl = Request.RawUrl, area = "admin" }) + @Html.Action("InfoProfile", "Export", new { systemName = "Feeds.GoogleMerchantCenterProductXml", returnUrl = Request.RawUrl, area = "admin" })
    diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 218cf8bd82..58dc4a00e9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -846,7 +846,8 @@ public ActionResult InfoProfile(string systemName, string returnUrl) var model = new ProfileInfoForProviderModel { - ReturnUrl = returnUrl + ReturnUrl = returnUrl, + SystemName = systemName }; model.Profiles = profiles diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ProfileInfoForProviderModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ProfileInfoForProviderModel.cs index 49c21536d7..79923f3e9e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ProfileInfoForProviderModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ProfileInfoForProviderModel.cs @@ -6,6 +6,8 @@ namespace SmartStore.Admin.Models.DataExchange { public class ProfileInfoForProviderModel : ModelBase { + public string SystemName { get; set; } + [SmartResourceDisplayName("Admin.DataExchange.Export.ProfileForProvider")] public List Profiles { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml index 533931f14a..24b066d505 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml @@ -8,7 +8,7 @@ { - @Html.Raw(T("Admin.DataExchange.Export.NoProfilesForProvider", Url.Action("List", "Export", new { area = "Admin" }))) + @Html.Raw(T("Admin.DataExchange.Export.NoProfilesForProvider", Model.SystemName, Url.Action("List", "Export", new { area = "Admin" }))) } @@ -21,7 +21,6 @@ @profile.Name - From 262c825dbd4475c8573596972bc7163a8e1570a7 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 18 Sep 2015 20:48:48 +0200 Subject: [PATCH 077/874] Export profile protocol button in profile list --- .../Controllers/ExportController.cs | 2 +- .../Administration/Views/Export/Edit.cshtml | 16 ++++++++++++---- .../Administration/Views/Export/List.cshtml | 7 +++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 58dc4a00e9..4aed25b29c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -114,6 +114,7 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile model.ScheduleTaskName = profile.ScheduleTask.Name.NaIfEmpty(); model.IsTaskRunning = profile.ScheduleTask.IsRunning; model.IsTaskEnabled = profile.ScheduleTask.Enabled; + model.LogFileExists = System.IO.File.Exists(profile.GetExportLogFilePath()); model.Provider = new ExportProfileModel.ProviderModel { @@ -158,7 +159,6 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile model.CompletedEmailAddresses = profile.CompletedEmailAddresses; model.CreateZipArchive = profile.CreateZipArchive; model.Cleanup = profile.Cleanup; - model.LogFileExists = System.IO.File.Exists(profile.GetExportLogFilePath()); model.FileNamePatternExample = profile.ResolveFileNamePattern(_services.StoreContext.CurrentStore, 1, _dataExchangeSettings.MaxFileNameLength); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 30b0fbce96..62517543a4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -17,13 +17,21 @@ @title - @Model.Name @Html.ActionLink("(" + T("Admin.Common.BackToList") + ")", "List")
    -  @T("Admin.Common.Preview") + +  @T("Admin.Common.Preview") + @if (Model.LogFileExists) { -  @T("Admin.Configuration.ActivityLog") + +  @T("Admin.Configuration.ActivityLog") + } - - + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 5db3e3f3d2..a44d075c56 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -80,6 +80,13 @@ data-task-id="@profile.ScheduleTaskId" onclick="return confirm('@T("Admin.Common.AreYouSure")');"> @T("Common.Cancel") + + @if (!profile.IsTaskRunning && profile.LogFileExists) + { + +  @T("Admin.Configuration.ActivityLog") + + } } From 0c9ec3679e2cfdbe8129c940db0b90b4938aaebc Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 21 Sep 2015 15:27:27 +0200 Subject: [PATCH 078/874] Export framework further implementation --- .gitignore | 2 +- .../Domain/DataExchange/ExportCore.cs | 3 +- .../201509150931528_ExportFramework2.cs | 48 +++++++++++++++++++ .../DataExchange/ExpandoExtensions.cs | 27 +++++++++-- .../DataExchange/ExportExecuteContext.cs | 42 ++++++++++------ .../DataExchange/ExportProfileTask.cs | 28 ++++++++--- .../DataExchange/ExportService.cs | 2 +- .../Providers/ProductExportXmlProvider.cs | 5 +- .../Administration/Views/Export/Create.cshtml | 24 +++++----- .../Administration/Views/Export/List.cshtml | 26 +++++----- 10 files changed, 155 insertions(+), 52 deletions(-) diff --git a/.gitignore b/.gitignore index 2efc9a1e1a..3b997b1c64 100644 --- a/.gitignore +++ b/.gitignore @@ -207,7 +207,7 @@ Kopie von* # Import/Export /src/Presentation/SmartStore.Web/Content/files/[Ee]xport[Ii]mport/*.* -/src/Presentation/SmartStore.Web/[Ee]xchange/*.* +/src/Presentation/SmartStore.Web/[Ee]xchange/* # Media /src/Presentation/SmartStore.Web/Media/[Tt]humbs diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs index 36cbb3e7ed..67cd684c5d 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs @@ -71,7 +71,8 @@ public enum ExportProjectionSupport ShippingCosts, AttributeCombinationAsProduct, OldPrice, - SpecialPrice + SpecialPrice, + CreateInitialPublicDeployment } /// diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index 9ae5a7d564..b269da1f21 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -25,10 +25,12 @@ public void Seed(SmartObjectContext context) context.MigrateLocaleResources(MigrateLocaleResources); context.Execute("DELETE FROM [dbo].[ScheduleTask] WHERE [Type] = 'SmartStore.GoogleMerchantCenter.StaticFileGenerationTask, SmartStore.GoogleMerchantCenter'"); + context.Execute("DELETE FROM [dbo].[ScheduleTask] WHERE [Type] = 'SmartStore.BMEcat.StaticFileGenerationTask, SmartStore.BMEcat'"); context.MigrateSettings(x => { x.DeleteGroup("FroogleSettings"); + x.DeleteGroup("BMEcatExportSettings"); }); } @@ -88,6 +90,52 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Plugins.Feed.Froogle.General", "Plugins.Feed.Froogle.ProductData" ); + + builder.Delete( + "Plugins.Feed.BMEcat.TaskEnabled", + "Plugins.Feed.BMEcat.TaskEnabled.Hint", + "Plugins.Feed.BMEcat.StaticFileUrl", + "Plugins.Feed.BMEcat.StaticFileUrl.Hint", + "Plugins.Feed.BMEcat.GenerateStaticFileEachMinutes", + "Plugins.Feed.BMEcat.GenerateStaticFileEachMinutes.Hint", + "Plugins.Feed.BMEcat.UseOwnProductNo", + "Plugins.Feed.BMEcat.UseOwnProductNo.Hint", + "Plugins.Feed.BMEcat.ShippingCostAustria", + "Plugins.Feed.BMEcat.ShippingCostAustria.Hint", + "Plugins.Feed.BMEcat.Currency", + "Plugins.Feed.BMEcat.Currency.Hint", + "Plugins.Feed.BMEcat.ProductPictureSize", + "Plugins.Feed.BMEcat.ProductPictureSize.Hint", + "Plugins.Feed.BMEcat.AppendDescriptionText", + "Plugins.Feed.BMEcat.AppendDescriptionText.Hint", + "Plugins.Feed.BMEcat.BuildDescription", + "Plugins.Feed.BMEcat.BuildDescription.Hint", + "Plugins.Feed.BMEcat.Automatic", + "Plugins.Feed.BMEcat.DescShort", + "Plugins.Feed.BMEcat.DescLong", + "Plugins.Feed.BMEcat.DescTitleAndShort", + "Plugins.Feed.BMEcat.DescTitleAndLong", + "Plugins.Feed.BMEcat.DescManuAndTitleAndShort", + "Plugins.Feed.BMEcat.DescManuAndTitleAndLong", + "Plugins.Feed.BMEcat.UseOwnProductNo", + "Plugins.Feed.BMEcat.UseOwnProductNo.Hint", + "Plugins.Feed.BMEcat.DescriptionToPlainText", + "Plugins.Feed.BMEcat.DescriptionToPlainText.Hint", + "Plugins.Feed.BMEcat.ShippingCost", + "Plugins.Feed.BMEcat.ShippingCost.Hint", + "Plugins.Feed.BMEcat.ShippingTime", + "Plugins.Feed.BMEcat.ShippingTime.Hint", + "Plugins.Feed.BMEcat.Brand", + "Plugins.Feed.BMEcat.Brand.Hint", + "Plugins.Feed.BMEcat.Store", + "Plugins.Feed.BMEcat.Store.Hint", + "Plugins.Feed.BMEcat.ConvertNetToGrossPrices", + "Plugins.Feed.BMEcat.ConvertNetToGrossPrices.Hint", + "Plugins.Feed.BMEcat.LanguageId", + "Plugins.Feed.BMEcat.LanguageId.Hint", + "Plugins.Feed.BMEcat.Generate", + "Plugins.Feed.BMEcat.ConfigSaveNote" + ); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs index 76f76a1df0..0a8e29a04a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs @@ -1,10 +1,10 @@ -using System.Collections.Generic; -using System.Dynamic; +using System.Dynamic; using System.Linq; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Stores; @@ -40,6 +40,27 @@ public static ExpandoObject ToExpando(this Currency currency, int languageId) return expando as ExpandoObject; } + public static ExpandoObject ToExpando(this Language language) + { + if (language == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = language; + + expando.Id = language.Id; + expando.Name = language.Name; + expando.LanguageCulture = language.LanguageCulture; + expando.UniqueSeoCode = language.UniqueSeoCode; + expando.FlagImageFileName = language.FlagImageFileName; + expando.Rtl = language.Rtl; + expando.LimitedToStores = language.LimitedToStores; + expando.Published = language.Published; + expando.DisplayOrder = language.DisplayOrder; + + return expando as ExpandoObject; + } + public static ExpandoObject ToExpando(this Country country, int languageId) { if (country == null) @@ -192,7 +213,7 @@ public static ExpandoObject ToExpando(this Picture picture, IPictureService pict expando._Entity = picture; expando.Id = picture.Id; - expando.SeoFileName = picture.SeoFilename; + expando.SeoFilename = picture.SeoFilename; expando.MimeType = picture.MimeType; expando._ThumbImageUrl = pictureService.GetPictureUrl(picture, thumbPictureSize, false, store.Url); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs index fe442552d5..da0bb443f7 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs @@ -28,6 +28,11 @@ public interface IExportExecuteContext /// dynamic Currency { get; } + /// + /// The language context to be used for the export + /// + dynamic Language { get; } + /// /// Projection data /// @@ -43,13 +48,14 @@ public interface IExportExecuteContext /// ExportAbortion Abort { get; set; } + /// /// The maximum allowed file name length /// int MaxFileNameLength { get; } /// - /// The path of the export folder + /// The path of the export content folder /// string Folder { get; } @@ -63,10 +69,22 @@ public interface IExportExecuteContext /// string FilePath { get; } + + /// + /// Whether the profile has a public deployment into "Exchange" folder + /// + bool HasPublicDeployment { get; } + + /// + /// The local path to the public export folder "Exchange". null if the profile has no public deployment. + /// + string PublicFolderPath { get; } + /// - /// The public URL of the export file (accessible through the internet) + /// The public URL of the export file (accessible through the internet). null if the profile has no public deployment. /// - string FilePublicUrl { get; } + string PublicFileUrl { get; } + /// /// Provider specific configuration data @@ -122,6 +140,7 @@ internal 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; } @@ -154,18 +173,11 @@ public bool IsMaxFailures public string Folder { get; private set; } public string FileName { get; internal set; } public string FileExtension { get; internal set; } - public string FilePath - { - get { return Path.Combine(Folder, FileName.EmptyNull()); } - } - public string FilePublicUrl - { - get - { - var url = string.Concat(((string)Store.Url).EnsureEndsWith("/"), ExportProfileTask.PublicFolder.EnsureEndsWith("/"), FileName.EmptyNull()); - return url; - } - } + public string FilePath { get; internal 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; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs index 0173bb0dc8..04522794bf 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs @@ -19,6 +19,7 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Messages; using SmartStore.Core.Domain.Orders; @@ -72,6 +73,7 @@ public class ExportProfileTask : ITask private IAddressService _addressesService; private ICountryService _countryService; private IRepository _orderRepository; + private ILanguageService _languageService; private void InitDependencies(TaskExecutionContext context) { @@ -100,6 +102,7 @@ private void InitDependencies(TaskExecutionContext context) _addressesService = context.Resolve(); _countryService = context.Resolve(); _orderRepository = context.Resolve>(); + _languageService = context.Resolve(); } #endregion @@ -947,6 +950,11 @@ private List Init(ExportProfileTaskContext ctx) else ctx.ProjectionCustomer = _services.WorkContext.CurrentCustomer; + if (ctx.Projection.LanguageId.HasValue) + ctx.ProjectionLanguage = _languageService.GetLanguageById(ctx.Projection.LanguageId.Value); + else + ctx.ProjectionLanguage = _services.WorkContext.WorkingLanguage; + ctx.Stores = _services.StoreService.GetAllStores().ToDictionary(x => x.Id, x => x); if (!ctx.IsPreview && ctx.Profile.PerStore) @@ -1018,10 +1026,11 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.MaxFileNameLength = _dataExchangeSettings.MaxFileNameLength; - if (ctx.Provider.Value.FileExtension.HasValue()) - { - ctx.Export.FileExtension = ctx.Provider.Value.FileExtension.ToLower().EnsureStartsWith("."); - } + ctx.Export.FileExtension = (ctx.Provider.Value.FileExtension.HasValue() ? ctx.Provider.Value.FileExtension.ToLower().EnsureStartsWith(".") : ""); + + ctx.Export.HasPublicDeployment = ctx.Profile.Deployments.Any(x => x.IsPublic && x.DeploymentType == ExportDeploymentType.FileSystem); + + ctx.Export.PublicFolderPath = (ctx.Export.HasPublicDeployment ? Path.Combine(HttpRuntime.AppDomainAppPath, PublicFolder) : null); var totalCount = ctx.RecordsPerStore.First(x => x.Key == ctx.Store.Id).Value; @@ -1062,13 +1071,18 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) { var resolvedPattern = ctx.Profile.ResolveFileNamePattern(ctx.Store, ctx.Export.Data.FileIndex + 1, ctx.Export.MaxFileNameLength); - ctx.Export.FileName = resolvedPattern + ctx.Export.FileExtension.EmptyNull(); + ctx.Export.FileName = resolvedPattern + ctx.Export.FileExtension; + ctx.Export.FilePath = Path.Combine(ctx.Export.Folder, ctx.Export.FileName); + + if (ctx.Export.HasPublicDeployment) + ctx.Export.PublicFileUrl = ctx.Store.Url.EnsureEndsWith("/") + PublicFolder.EnsureEndsWith("/") + ctx.Export.FileName; } ctx.Provider.Value.Execute(ctx.Export); ctx.Log.Information("Provider reports {0} successful exported record(s)".FormatInvariant(ctx.Export.RecordsSucceeded)); + // create info for deployment list in profile edit if (ctx.IsFileBasedExport && File.Exists(ctx.Export.FilePath)) { ctx.ResultInfo.Files.Add(new ExportResultFileInfo @@ -1165,7 +1179,8 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) var stores = Init(ctx); ctx.Export.Customer = ctx.ProjectionCustomer.ToExpando(); - ctx.Export.Currency = ctx.ProjectionCurrency.ToExpando(ctx.Projection.LanguageId ?? 0); + ctx.Export.Currency = ctx.ProjectionCurrency.ToExpando(ctx.ProjectionLanguage.Id); + ctx.Export.Language = ctx.ProjectionLanguage.ToExpando(); stores.ForEach(x => ExportCoreInner(ctx, x)); } @@ -1443,6 +1458,7 @@ public bool IsPreview public ExportProjection Projection { get; private set; } public Currency ProjectionCurrency { get; set; } public Customer ProjectionCustomer { get; set; } + public Language ProjectionLanguage { get; set; } public TraceLogger Log { get; set; } public Store Store { get; set; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index 232e4a44cd..d73d95a239 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -105,7 +105,7 @@ public virtual ExportProfile InsertExportProfile(Provider provi { if (cloneProfile == null) { - if (systemName.StartsWith("Feeds.")) + if (provider.Supports(ExportProjectionSupport.CreateInitialPublicDeployment)) { profile.Deployments.Add(new ExportDeployment { diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs index 4d818bc993..273ac19b96 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs @@ -20,12 +20,13 @@ namespace SmartStore.GoogleMerchantCenter.Providers [FriendlyName("Google Merchant Center XML product feed")] [DisplayOrder(1)] [ExportProjectionSupport( + ExportProjectionSupport.CreateInitialPublicDeployment, + ExportProjectionSupport.AttributeCombinationAsProduct, ExportProjectionSupport.Description, ExportProjectionSupport.UseOwnProductNo, ExportProjectionSupport.Brand, ExportProjectionSupport.MainPictureUrl, - ExportProjectionSupport.SpecialPrice, - ExportProjectionSupport.AttributeCombinationAsProduct)] + ExportProjectionSupport.SpecialPrice)] public class ProductExportXmlProvider : IExportProvider { private const string _googleNamespace = "http://base.google.com/ns/1.0"; diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml index 306cb37df2..b6baf86c64 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml @@ -4,7 +4,7 @@ Layout = null; } -
    +
    @if(Model.Provider.AvailableExportProviders.Count > 0) {
    @@ -17,17 +17,6 @@ @Html.DropDownListFor(model => model.Provider.SystemName, Model.Provider.AvailableExportProviders, new { style = "width: 500px;" }) - - -   - - - @foreach (var item in Model.Provider.ProviderDescriptions) - { - @item.Value - } - - @if(Model.AvailableProfiles.Count > 0) { @@ -39,6 +28,17 @@ } + + +   + + + @foreach (var item in Model.Provider.ProviderDescriptions) + { + @item.Value + } + +
    } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index a44d075c56..9dd3d451c5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -81,12 +81,10 @@ @T("Common.Cancel") - @if (!profile.IsTaskRunning && profile.LogFileExists) - { - -  @T("Admin.Configuration.ActivityLog") - - } + +  @T("Admin.Configuration.ActivityLog") + } @@ -204,11 +202,17 @@ else defaultProgressMessage: '@T("Admin.System.ScheduleTasks.RunNow.IsRunning")', pollUrl: '@Url.Action("GetRunningTasks", "ScheduleTask")', onTaskStarted: function (task, el) { - el.closest('tr').find('.btn-run-task').addClass('hide').next().removeClass('hide'); - el.closest('tr').find('.next-run-info').addClass('hide'); + var tr = el.closest('tr'); + tr.find('.btn-run-task').addClass('hide'); + tr.find('.btn-cancel-task').removeClass('hide'); + tr.find('.btn-log').addClass('hide'); + tr.find('.next-run-info').addClass('hide'); }, onTaskCompleted: function (taskId, el) { - el.closest('tr').find('.btn-run-task').removeClass('hide').next().addClass('hide'); + var tr = el.closest('tr'); + tr.find('.btn-run-task').removeClass('hide'); + tr.find('.btn-cancel-task').addClass('hide'); + tr.find('.btn-log').removeClass('hide'); $.ajax({ cache: false, global: false, @@ -216,8 +220,8 @@ else url: '@Url.Action("GetTaskRunInfo", "ScheduleTask")', data: { id: taskId }, success: function (data) { - el.closest('tr').find('.next-run-info').html(data.nextRunHtml).removeClass("hide"); - el.closest('tr').find('.last-run-info').html(data.lastRunHtml); + tr.find('.next-run-info').html(data.nextRunHtml).removeClass("hide"); + tr.find('.last-run-info').html(data.lastRunHtml); } }); } From 7efa417a7ee26085b00ce31ee3505a32c74f7ce4 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 22 Sep 2015 16:05:47 +0200 Subject: [PATCH 079/874] Closes #780 Cart: Base price gets multiplied with quantity unit instead of unit price --- .../SmartStore.Web/Controllers/BlogController.cs | 2 +- .../SmartStore.Web/Controllers/BoardsController.cs | 2 +- .../SmartStore.Web/Controllers/ShoppingCartController.cs | 6 +++++- .../SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml | 3 --- .../SmartStore.Web/Views/Customer/ChangePassword.cshtml | 2 +- .../Views/Customer/DownloadableProducts.cshtml | 1 - src/Presentation/SmartStore.Web/Views/Customer/Login.cshtml | 3 +-- .../SmartStore.Web/Views/Customer/Register.cshtml | 1 - 8 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/BlogController.cs b/src/Presentation/SmartStore.Web/Controllers/BlogController.cs index db52b792b0..ad683bda9f 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BlogController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BlogController.cs @@ -118,7 +118,7 @@ protected void PrepareBlogPostModel(BlogPostModel model, BlogPost blogPost, bool model.Title = blogPost.Title; model.Body = blogPost.Body; model.AllowComments = blogPost.AllowComments; - model.AvatarPictureSize = _mediaSettings.AvatarPictureSize; // codehint: sm-add + model.AvatarPictureSize = _mediaSettings.AvatarPictureSize; model.CreatedOn = _dateTimeHelper.ConvertToUserTime(blogPost.CreatedOnUtc, DateTimeKind.Utc); model.Tags = blogPost.ParseTags().ToList(); model.NumberOfComments = blogPost.ApprovedCommentCount; diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 6e2dd867cf..441d1a6b9d 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -318,7 +318,7 @@ public ActionResult Forum(int id, int page = 1) if (forumSubscription != null) { model.WatchForumText = _localizationService.GetResource("Forum.UnwatchForum"); - model.WatchForumSubscribed = true; // codehint: sm-add + model.WatchForumSubscribed = true; } } diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index 91f158310b..215e1d0e6a 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -368,7 +368,11 @@ private ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartItemModel(Org model.Discount = _priceFormatter.FormatPrice(shoppingCartItemDiscount); } - model.BasePrice = product.GetBasePriceInfo(_localizationService, _priceFormatter, (product.Price - shoppingCartItemSubTotalWithDiscount) * (-1)); + model.BasePrice = product.GetBasePriceInfo( + _localizationService, + _priceFormatter, + (product.Price - _taxService.GetProductPrice(product, _priceCalculationService.GetUnitPrice(sci, true), out taxRate)) * (-1) + ); } //picture diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml index 9ce481ed74..6a4d18680b 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml @@ -8,9 +8,6 @@ //title Html.AddTitleParts(T("PageTitle.Checkout").Text); - //string termsLink = ""; - //string disclaimerLink = ""; - string termsLink = ""; string disclaimerLink = ""; string terms = string.Format(T("Checkout.TermsOfService.IAccept"), termsLink, "", disclaimerLink); diff --git a/src/Presentation/SmartStore.Web/Views/Customer/ChangePassword.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/ChangePassword.cshtml index a215bea2dd..3d038d3cab 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/ChangePassword.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/ChangePassword.cshtml @@ -10,7 +10,7 @@ { @Html.Partial("MyAccountNavigation", Model.NavigationModel, new ViewDataDictionary()) } -@*codehint: sm-edit*@ + @using (Html.BeginForm("ChangePassword", "Customer", FormMethod.Post, new { @class = "form-horizontal" })) { @Html.AntiForgeryToken() diff --git a/src/Presentation/SmartStore.Web/Views/Customer/DownloadableProducts.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/DownloadableProducts.cshtml index 761173e21b..db3dfebd6b 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/DownloadableProducts.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/DownloadableProducts.cshtml @@ -19,7 +19,6 @@
    @if (Model.Items.Count > 0) { - //codehint: sm-edit diff --git a/src/Presentation/SmartStore.Web/Views/Customer/Login.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/Login.cshtml index c3e9bde7d6..419a2c94db 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/Login.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/Login.cshtml @@ -1,5 +1,4 @@ -@*codehint: sm-refactored-completly *@ -@model LoginModel +@model LoginModel @using SmartStore.Web.Models.Customer; @using SmartStore.Core; @using SmartStore.Core.Infrastructure; diff --git a/src/Presentation/SmartStore.Web/Views/Customer/Register.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/Register.cshtml index a91d81c4c2..9a937d320e 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/Register.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/Register.cshtml @@ -2,7 +2,6 @@ @using SmartStore.Core.Infrastructure; @using SmartStore.Core.Infrastructure.DependencyManagement; -@*codehint: sm-refactored-completly *@ @model RegisterModel @using SmartStore.Web.Models.Customer; @{ From 57d6f215b29115afe6ba060e0b077cf713ab3a7e Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 22 Sep 2015 16:40:07 +0200 Subject: [PATCH 080/874] PayPal: corrected route description in admin instruction --- .../SmartStore.PayPal/Localization/resources.de-de.xml | 8 ++++---- .../SmartStore.PayPal/Localization/resources.en-us.xml | 8 ++++---- .../SmartStore.PayPal/Providers/PayPalStandardProvider.cs | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index fcb196c6b0..d9f8e940a1 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -80,7 +80,7 @@
  • Auf den Register Mein Profil klicken.
  • Sofortige Zahlungsbestätigung klicken.
  • Einstellungen für sofortige Zahlungsbestätigungen wählen klicken.
  • -
  • Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (http://www.yourStore.com/Plugins/PayPalExpress/IPNHandler) eingeben.
  • +
  • Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (http://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalExpress/IPNHandler) eingeben.
  • Speichern klicken. Danach sollten Sie eine Nachricht über die erfolgreiche Aktivierung von IPN erhalten.
  • ]]> @@ -175,7 +175,7 @@
  • Auf den Register Mein Profil klicken.
  • Sofortige Zahlungsbestätigung klicken.
  • Einstellungen für sofortige Zahlungsbestätigungen wählen klicken.
  • -
  • Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (http://www.yourStore.com/Plugins/PaymentPayPalDirect/IPNHandler) eingeben.
  • +
  • Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (http://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalDirect/IPNHandler) eingeben.
  • Speichern klicken. Danach sollten Sie eine Nachricht über die erfolgreiche Aktivierung von IPN erhalten.
  • ]]> @@ -227,7 +227,7 @@
  • Klicken Sie auf den Register Mein Profil.
  • Klicken Sie unter Verkäufer/Händler auf Website-Einstellungen.
  • Aktivieren Sie Automatische Rückleitung.
  • -
  • Als Rückleitungs-URL geben Sie bitte die Seite Ihres Shops ein, die die Transaktions-ID von PayPal nach einer Kundenzahlung empfängt (http://www.yourStore.com/Plugins/PaymentPayPalStandard/PDTHandler).
  • +
  • Als Rückleitungs-URL geben Sie bitte die Seite Ihres Shops ein, die die Transaktions-ID von PayPal nach einer Kundenzahlung empfängt (http://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalStandard/PDTHandler).
  • Aktivieren Sie Übertragung der Zahlungsdaten.
  • Klicken Sie auf Speichern.
  • Klicken Sie unter Verkäufer/Händler auf Website-Einstellungen.
  • @@ -243,7 +243,7 @@
  • Auf den Register Mein Profil klicken.
  • Sofortige Zahlungsbestätigung klicken.
  • Einstellungen für sofortige Zahlungsbestätigungen wählen klicken.
  • -
  • Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (http://www.yourStore.com/Plugins/PaymentPayPalStandard/IPNHandler) eingeben.
  • +
  • Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (http://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalStandard/IPNHandler) eingeben.
  • Speichern klicken. Danach sollten Sie eine Nachricht über die erfolgreiche Aktivierung von IPN erhalten.
  • diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index 673d01faaa..3aebd55408 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -30,7 +30,7 @@
  • Click the Profile subtab.
  • Click Instant Payment Notification in the Selling Preferences column.
  • Click the Edit IPN Settings button to update your settings.
  • -
  • Select Receive IPN messages (enabled) and enter the URL of your IPN handler (http://www.yourStore.com/Plugins/PayPalExpress/IPNHandler).
  • +
  • Select Receive IPN messages (enabled) and enter the URL of your IPN handler (http://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalExpress/IPNHandler).
  • Click Save, and you should get a message that you have successfully activated IPN.
  • ]]> @@ -167,7 +167,7 @@
  • Click the Profile subtab.
  • Click Instant Payment Notification in the Selling Preferences column.
  • Click the Edit IPN Settings button to update your settings.
  • -
  • Select Receive IPN messages (enabled) and enter the URL of your IPN handler (http://www.yourStore.com/Plugins/PaymentPayPalDirect/IPNHandler).
  • +
  • Select Receive IPN messages (enabled) and enter the URL of your IPN handler (http://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalDirect/IPNHandler).
  • Click Save, and you should get a message that you have successfully activated IPN.
  • ]]> @@ -260,7 +260,7 @@
  • Click the Profile subtab.
  • Click Website Payment Preferences in the Seller Preferences column.
  • Under Auto Return for Website Payments, click the On radio button.
  • -
  • For the Return URL, enter the URL on your site that will receive the transaction ID posted by PayPal after a customer payment (http://www.yourStore.com/Plugins/PaymentPayPalStandard/PDTHandler).
  • +
  • For the Return URL, enter the URL on your site that will receive the transaction ID posted by PayPal after a customer payment (http://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalStandard/PDTHandler).
  • Under Payment Data Transfer, click the On radio button.
  • Click Save.
  • Click Website Payment Preferences in the Seller Preferences column.
  • @@ -276,7 +276,7 @@
  • Click the Profile subtab.
  • Click Instant Payment Notification in the Selling Preferences column.
  • Click the Edit IPN Settings button to update your settings.
  • -
  • Select Receive IPN messages (Enabled) and enter the URL of your IPN handler (http://www.yourStore.com/Plugins/PaymentPayPalStandard/IPNHandler).
  • +
  • Select Receive IPN messages (Enabled) and enter the URL of your IPN handler (http://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalStandard/IPNHandler).
  • Click Save, and you should get a message that you have successfully activated IPN.
  • diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs index f6e333e66c..7f60018419 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs @@ -230,8 +230,8 @@ public override void PostProcessPayment(PostProcessPaymentRequest postProcessPay builder.AppendFormat("&no_shipping=1", new object[0]); } - string returnUrl = _services.WebHelper.GetStoreLocation(false) + "Plugins/PaymentPayPalStandard/PDTHandler"; - string cancelReturnUrl = _services.WebHelper.GetStoreLocation(false) + "Plugins/PaymentPayPalStandard/CancelOrder"; + string returnUrl = _services.WebHelper.GetStoreLocation(false) + "Plugins/SmartStore.PayPal/PayPalStandard/PDTHandler"; + string cancelReturnUrl = _services.WebHelper.GetStoreLocation(false) + "Plugins/SmartStore.PayPal/PayPalStandard/CancelOrder"; builder.AppendFormat("&return={0}&cancel_return={1}", HttpUtility.UrlEncode(returnUrl), HttpUtility.UrlEncode(cancelReturnUrl)); //Instant Payment Notification (server to server message) @@ -239,7 +239,7 @@ public override void PostProcessPayment(PostProcessPaymentRequest postProcessPay { string ipnUrl; if (String.IsNullOrWhiteSpace(settings.IpnUrl)) - ipnUrl = _services.WebHelper.GetStoreLocation(false) + "Plugins/PaymentPayPalStandard/IPNHandler"; + ipnUrl = _services.WebHelper.GetStoreLocation(false) + "Plugins/SmartStore.PayPal/PayPalStandard/IPNHandler"; else ipnUrl = settings.IpnUrl; builder.AppendFormat("¬ify_url={0}", ipnUrl); From cde165d36cf6b43304838393a779448e62fc2b15 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 24 Sep 2015 17:18:32 +0200 Subject: [PATCH 081/874] Tested order entity export and export via volatile export profile --- .../Domain/DataExchange/ExportResultInfo.cs | 29 -- .../SmartStore.Core/SmartStore.Core.csproj | 1 - .../201509021536425_ExportFramework1.cs | 2 +- .../201509150931528_ExportFramework2.cs | 3 + .../Common/AddressService.cs | 2 +- .../DataExchange/ExpandoExtensions.cs | 32 +- .../DataExchange/ExportConfigurationInfo.cs | 25 ++ .../DataExchange/ExportExecuteResult.cs | 54 ++++ .../DataExchange/ExportExtensions.cs | 47 ++- .../DataExchange/ExportService.cs | 51 +++- .../ExportOrderDataContext.cs | 4 +- .../ExportProductDataContext.cs | 4 +- .../{ => ExportTask}/ExportProfileTask.cs | 280 +++++------------- .../ExportTask/ExportProfileTaskContext.cs | 167 +++++++++++ .../DataExchange/IExportProvider.cs | 11 +- .../DataExchange/IExportService.cs | 12 +- .../SmartStore.Services.csproj | 9 +- .../FeedGoogleMerchantCenterController.cs | 2 +- .../Providers/ProductExportXmlProvider.cs | 30 +- .../FeedGoogleMerchantCenter/Configure.cshtml | 3 +- .../Controllers/ExportController.cs | 40 +-- .../Models/DataExchange/ExportPreviewModel.cs | 2 + .../Administration/Views/Export/Edit.cshtml | 4 +- .../Administration/Views/Export/List.cshtml | 5 +- .../Views/Export/Preview.cshtml | 6 +- .../Views/Export/_Tab.Deployment.cshtml | 67 +++-- 26 files changed, 553 insertions(+), 339 deletions(-) delete mode 100644 src/Libraries/SmartStore.Core/Domain/DataExchange/ExportResultInfo.cs create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportConfigurationInfo.cs create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportExecuteResult.cs rename src/Libraries/SmartStore.Services/DataExchange/{ => ExportTask}/ExportOrderDataContext.cs (96%) rename src/Libraries/SmartStore.Services/DataExchange/{ => ExportTask}/ExportProductDataContext.cs (94%) rename src/Libraries/SmartStore.Services/DataExchange/{ => ExportTask}/ExportProfileTask.cs (86%) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportResultInfo.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportResultInfo.cs deleted file mode 100644 index b45b3d766c..0000000000 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportResultInfo.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace SmartStore.Core.Domain.DataExchange -{ - [Serializable] - public class ExportResultInfo - { - /// - /// Files created by last export - /// - public List Files { get; set; } - } - - - [Serializable] - public class ExportResultFileInfo - { - /// - /// Store identifier - /// - public int StoreId { get; set; } - - /// - /// Name of file - /// - public string FileName { get; set; } - } -} diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index 17477a6b83..a3ab21c916 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -160,7 +160,6 @@ - diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs index e5ffff62d3..b163b3cbea 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs @@ -185,7 +185,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.DataExchange.Export.NoProfilesForProvider", "There was no export profile of type \"{0}\" found. Create now a new export profile.", - "Es wurde kein Exportprofil vom Type \"{0}\" gefunden. Jetzt ein neues Exportprofil anlegen."); + "Es wurde kein Exportprofil vom Typ \"{0}\" gefunden. Jetzt ein neues Exportprofil anlegen."); builder.AddOrUpdate("Admin.DataExchange.Export.ProfileForProvider", "Export profile", diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index b269da1f21..d45d606feb 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -31,6 +31,7 @@ public void Seed(SmartObjectContext context) { x.DeleteGroup("FroogleSettings"); x.DeleteGroup("BMEcatExportSettings"); + x.DeleteGroup("OpenTransSettings"); }); } @@ -136,6 +137,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Plugins.Feed.BMEcat.Generate", "Plugins.Feed.BMEcat.ConfigSaveNote" ); + + builder.Delete("Plugins.Widgets.OpenTrans.IsLexwareCompatibe"); } } } diff --git a/src/Libraries/SmartStore.Services/Common/AddressService.cs b/src/Libraries/SmartStore.Services/Common/AddressService.cs index a03e9d3c4b..6ba5546ccb 100644 --- a/src/Libraries/SmartStore.Services/Common/AddressService.cs +++ b/src/Libraries/SmartStore.Services/Common/AddressService.cs @@ -121,7 +121,7 @@ public virtual IList
    GetAddressByIds(int[] addressIds) Guard.ArgumentNotNull(() => addressIds); var query = - from x in _addressRepository.TableUntracked.Expand(x => x.Country) + from x in _addressRepository.TableUntracked.Expand(x => x.Country).Expand(x => x.StateProvince) where addressIds.Contains(x.Id) select x; diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs index 0a8e29a04a..baddabd869 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs @@ -84,6 +84,24 @@ public static ExpandoObject ToExpando(this Country country, int languageId) return expando as ExpandoObject; } + public static ExpandoObject ToExpando(this StateProvince stateProvince, int languageId) + { + if (stateProvince == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = stateProvince; + + expando.Id = stateProvince.Id; + expando.CountryId = stateProvince.CountryId; + expando.Name = stateProvince.GetLocalized(x => x.Name, languageId, true, false); + expando.Abbreviation = stateProvince.Abbreviation; + expando.Published = stateProvince.Published; + expando.DisplayOrder = stateProvince.DisplayOrder; + + return expando as ExpandoObject; + } + public static ExpandoObject ToExpando(this Address address, int languageId) { if (address == null) @@ -108,11 +126,12 @@ public static ExpandoObject ToExpando(this Address address, int languageId) expando.CreatedOnUtc = address.CreatedOnUtc; expando.Country = address.Country.ToExpando(languageId); + expando.StateProvince = address.StateProvince.ToExpando(languageId); return expando as ExpandoObject; } - - public static ExpandoObject ToExpando(this Customer customer) + + public static ExpandoObject ToExpando(this Customer customer, int languageId) { if (customer == null) return null; @@ -124,7 +143,7 @@ public static ExpandoObject ToExpando(this Customer customer) expando.CustomerGuid = customer.CustomerGuid; expando.Username = customer.Username; expando.Email = customer.Email; - //Password... we not provide that data + //Password... we do not provide that data expando.PasswordFormatId = customer.PasswordFormatId; expando.PasswordFormat = customer.PasswordFormat; expando.AdminComment = customer.AdminComment; @@ -139,6 +158,9 @@ public static ExpandoObject ToExpando(this Customer customer) expando.LastLoginDateUtc = customer.LastLoginDateUtc; expando.LastActivityDateUtc = customer.LastActivityDateUtc; + expando.BillingAddress = customer.BillingAddress.ToExpando(languageId); + expando.ShippingAddress = customer.ShippingAddress.ToExpando(languageId); + return expando as ExpandoObject; } @@ -557,6 +579,10 @@ public static ExpandoObject ToExpando(this Order order, int languageId, ILocaliz expando.CustomerTaxDisplayType = order.CustomerTaxDisplayType; expando.TaxRatesDictionary = order.TaxRatesDictionary; + expando.BillingAddress = null; + expando.ShippingAddress = null; + expando.Store = null; + return expando as ExpandoObject; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportConfigurationInfo.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportConfigurationInfo.cs new file mode 100644 index 0000000000..4816d9e2ab --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportConfigurationInfo.cs @@ -0,0 +1,25 @@ +using System; + +namespace SmartStore.Services.DataExchange +{ + /// + /// Serves information about export provider specific configuration + /// + public class ExportConfigurationInfo + { + /// + /// The partial view name for the configuration + /// + public string PartialViewName { get; set; } + + /// + /// Type of the view model + /// + public Type ModelType { get; set; } + + /// + /// Callback to initialize the view model. Can be null. + /// + public Action Initialize { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteResult.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteResult.cs new file mode 100644 index 0000000000..ead41dbccf --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteResult.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace SmartStore.Services.DataExchange +{ + [Serializable] + public class ExportExecuteResult + { + public ExportExecuteResult() + { + Files = new List(); + } + + /// + /// Whether the export succeeded + /// + public bool Succeeded + { + get { return LastError.IsEmpty(); } + } + + /// + /// Last error + /// + [XmlIgnore] + public string LastError { get; set; } + + /// + /// Files created by last export + /// + public List Files { get; set; } + + /// + /// The path of the folder with the export files + /// + [XmlIgnore] + public string FileFolder { get; set; } + + [Serializable] + public class ExportFileInfo + { + /// + /// Store identifier + /// + public int StoreId { get; set; } + + /// + /// Name of file + /// + public string FileName { get; set; } + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs index eee5d64533..a79efba103 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs @@ -1,9 +1,13 @@ -using System.IO; +using System; +using System.IO; using System.Linq; +using System.Web; +using System.Web.Caching; using SmartStore.Core.Domain; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Plugins; +using SmartStore.Services.Localization; using SmartStore.Utilities; namespace SmartStore.Services.DataExchange @@ -33,6 +37,21 @@ public static bool Supports(this Provider provider, ExportProje return false; } + /// + /// Gets the localized friendly name or the system name as fallback + /// + /// Export provider + /// Localization service + /// Provider name + public static string GetName(this Provider provider, ILocalizationService localizationService) + { + var systemName = provider.Metadata.SystemName; + var resourceName = provider.Metadata.ResourceKeyPattern.FormatInvariant(systemName, "FriendlyName"); + var name = localizationService.GetResource(resourceName, 0, false, systemName, true); + + return (name.IsEmpty() ? systemName : name); + } + /// /// Get temporary folder for an export profile /// @@ -77,5 +96,31 @@ public static string ResolveFileNamePattern(this ExportProfile profile, Store st return result; } + + /// + /// Gets the cache key for selected entity identifiers + /// + /// Export profile + /// Cache key for selected entity identifiers + public static string GetSelectedEntityIdsCacheKey(this ExportProfile profile) + { + // do not use profile.Id because it can be 0 + return "ExportProfile_SelectedEntityIds_" + profile.ProviderSystemName; + } + + /// + /// Caches selected entity identifiers to be considered during following export + /// + /// Export profile + /// Comma separated entity identifiers + public static void CacheSelectedEntityIds(this ExportProfile profile, string selectedIds) + { + var selectedIdsCacheKey = profile.GetSelectedEntityIdsCacheKey(); + + if (selectedIds.HasValue()) + HttpRuntime.Cache.Add(selectedIdsCacheKey, selectedIds, null, DateTime.UtcNow.AddMinutes(5), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); + else + HttpRuntime.Cache.Remove(selectedIdsCacheKey); + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index d73d95a239..9548737959 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -8,6 +8,8 @@ using SmartStore.Core.Domain.Tasks; using SmartStore.Core.Events; using SmartStore.Core.Plugins; +using SmartStore.Services.DataExchange.ExportTask; +using SmartStore.Services.Localization; using SmartStore.Services.Tasks; using SmartStore.Utilities; @@ -15,12 +17,15 @@ namespace SmartStore.Services.DataExchange { public partial class ExportService : IExportService { + private const string _defaultFileNamePattern = "%Store.Id%-%ExportProfile.Id%-%Misc.FileNumber%-%ExportProfile.SeoName%"; + private readonly IRepository _exportProfileRepository; private readonly IRepository _exportDeploymentRepository; private readonly IEventPublisher _eventPublisher; private readonly IScheduleTaskService _scheduleTaskService; private readonly IProviderManager _providerManager; private readonly DataExchangeSettings _dataExchangeSettings; + private readonly ILocalizationService _localizationService; public ExportService( IRepository exportProfileRepository, @@ -28,7 +33,8 @@ public ExportService( IEventPublisher eventPublisher, IScheduleTaskService scheduleTaskService, IProviderManager providerManager, - DataExchangeSettings dataExchangeSettings) + DataExchangeSettings dataExchangeSettings, + ILocalizationService localizationService) { _exportProfileRepository = exportProfileRepository; _exportDeploymentRepository = exportDeploymentRepository; @@ -36,24 +42,51 @@ public ExportService( _scheduleTaskService = scheduleTaskService; _providerManager = providerManager; _dataExchangeSettings = dataExchangeSettings; + _localizationService = localizationService; } #region Export profiles - public virtual ExportProfile InsertExportProfile(Provider provider, string name, int cloneFromProfileId = 0) + public virtual ExportProfile CreateVolatileProfile(Provider provider, string providerConfigData, int storeId) + { + var name = provider.GetName(_localizationService); + var seoName = SeoHelper.GetSeName(name, true, false).Replace("/", "").Replace("-", ""); + + var profile = new ExportProfile + { + Id = 0, + Name = name, + FolderName = seoName.ToValidPath().Truncate(_dataExchangeSettings.MaxFileNameLength), + FileNamePattern = _defaultFileNamePattern, + ProviderSystemName = provider.Metadata.SystemName, + Enabled = true, + SchedulingTaskId = 0, + ProviderConfigData = providerConfigData, + PerStore = false, + CreateZipArchive = false, + Cleanup = false, + ScheduleTask = null, + Deployments = new List() + }; + + if (storeId != 0) + { + profile.Filtering = XmlHelper.Serialize(new ExportFilter { StoreId = storeId }); + } + + return profile; + } + + public virtual ExportProfile InsertExportProfile(Provider provider, int cloneFromProfileId = 0) { if (provider == null) throw new ArgumentNullException("provider"); var cloneProfile = GetExportProfileById(cloneFromProfileId); - var systemName = provider.Metadata.SystemName; - - if (name.IsEmpty()) - name = systemName; - ScheduleTask task = null; ExportProfile profile = null; + var name = provider.GetName(_localizationService); var seoName = SeoHelper.GetSeName(name, true, false).Replace("/", "").Replace("-", ""); if (cloneProfile == null) @@ -81,7 +114,7 @@ public virtual ExportProfile InsertExportProfile(Provider provi { profile = new ExportProfile { - FileNamePattern = "%Store.Id%-%ExportProfile.Id%-%Misc.FileNumber%-%ExportProfile.SeoName%", + FileNamePattern = _defaultFileNamePattern, Filtering = XmlHelper.Serialize(new ExportFilter()), Projection = XmlHelper.Serialize(new ExportProjection()) }; @@ -93,7 +126,7 @@ public virtual ExportProfile InsertExportProfile(Provider provi profile.Name = name; profile.FolderName = seoName.ToValidPath().Truncate(_dataExchangeSettings.MaxFileNameLength); - profile.ProviderSystemName = systemName; + profile.ProviderSystemName = provider.Metadata.SystemName; profile.SchedulingTaskId = task.Id; _exportProfileRepository.Insert(profile); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportOrderDataContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportOrderDataContext.cs similarity index 96% rename from src/Libraries/SmartStore.Services/DataExchange/ExportOrderDataContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportOrderDataContext.cs index 0abf0f16b9..f4e6848a45 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportOrderDataContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportOrderDataContext.cs @@ -6,9 +6,9 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Orders; -namespace SmartStore.Services.DataExchange +namespace SmartStore.Services.DataExchange.ExportTask { - public class ExportOrderDataContext + internal class ExportOrderDataContext { protected List _orderIds; protected List _customerIds; diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProductDataContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProductDataContext.cs similarity index 94% rename from src/Libraries/SmartStore.Services/DataExchange/ExportProductDataContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProductDataContext.cs index e149f3f756..9a8ffb27d0 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProductDataContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProductDataContext.cs @@ -4,9 +4,9 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Services.Catalog; -namespace SmartStore.Services.DataExchange +namespace SmartStore.Services.DataExchange.ExportTask { - public class ExportProductDataContext : PriceCalculationContext + internal class ExportProductDataContext : PriceCalculationContext { private Func> _funcProductManufacturers; private Func> _funcProductPictures; diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs similarity index 86% rename from src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs rename to src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 04522794bf..8399284327 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -10,16 +10,11 @@ using System.Threading; using System.Web; using Autofac; -using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain; using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Messages; using SmartStore.Core.Domain.Orders; @@ -42,7 +37,8 @@ using SmartStore.Services.Tax; using SmartStore.Utilities; -namespace SmartStore.Services.DataExchange +// note: namespace persisted in ScheduleTask.Type +namespace SmartStore.Services.DataExchange.ExportTask { public class ExportProfileTask : ITask { @@ -296,7 +292,7 @@ private void GetDeliveryTimeAndQuantityUnit(ExportProfileTaskContext ctx, dynami private void SetProgress(ExportProfileTaskContext ctx, int loadedRecords) { - if (!ctx.IsPreview && loadedRecords > 0) + if (!ctx.IsPreview && loadedRecords > 0 && ctx.TaskContext.ScheduleTask != null) { int totalRecords = ctx.RecordsPerStore.Sum(x => x.Value); @@ -894,43 +890,47 @@ private List ToExpando(ExportProfileTaskContext ctx, Order order) ctx.OrderDataContext.Addresses.Collect(order.ShippingAddressId ?? 0); - var customers = ctx.OrderDataContext.Customers.Load(order.CustomerId); var addresses = ctx.OrderDataContext.Addresses.Load(order.BillingAddressId); + var customers = ctx.OrderDataContext.Customers.Load(order.CustomerId); var orderItems = ctx.OrderDataContext.OrderItems.Load(order.Id); dynamic expando = order.ToExpando(languageId, _services.Localization); expando.Customer = customers .FirstOrDefault(x => x.Id == order.CustomerId) - .ToExpando(); + .ToExpando(languageId); expando.BillingAddress = addresses .FirstOrDefault(x => x.Id == order.BillingAddressId) .ToExpando(languageId); if (order.ShippingAddressId.HasValue) - expando.ShippingAddress = addresses.FirstOrDefault(x => x.Id == order.ShippingAddressId.Value).ToExpando(languageId); - else - expando.ShippingAddress = null; + { + expando.ShippingAddress = addresses + .FirstOrDefault(x => x.Id == order.ShippingAddressId.Value) + .ToExpando(languageId); + } if (ctx.Stores.ContainsKey(order.StoreId)) - expando.Store = ctx.Stores[order.StoreId].ToExpando(languageId); - else - expando.Store = null; + { + expando.Store = ctx.Stores[order.StoreId] + .ToExpando(languageId); + } expando.OrderItems = orderItems .Select(e => { dynamic exp = e.ToExpando(languageId); - exp._BasePriceInfo = e.Product.GetBasePriceInfo(_services.Localization, _priceFormatter, decimal.Zero, true); + exp.Product._BasePriceInfo = e.Product.GetBasePriceInfo(_services.Localization, _priceFormatter, decimal.Zero, true); - GetDeliveryTimeAndQuantityUnit(ctx, exp, e.Product.DeliveryTimeId, e.Product.QuantityUnitId); + GetDeliveryTimeAndQuantityUnit(ctx, exp.Product, e.Product.DeliveryTimeId, e.Product.QuantityUnitId); return exp as ExpandoObject; }) .ToList(); + result.Add(expando); return result; } @@ -1078,18 +1078,27 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.PublicFileUrl = ctx.Store.Url.EnsureEndsWith("/") + PublicFolder.EnsureEndsWith("/") + ctx.Export.FileName; } - ctx.Provider.Value.Execute(ctx.Export); + try + { + ctx.Provider.Value.Execute(ctx.Export); - ctx.Log.Information("Provider reports {0} successful exported record(s)".FormatInvariant(ctx.Export.RecordsSucceeded)); + ctx.Log.Information("Provider reports {0} successful exported record(s)".FormatInvariant(ctx.Export.RecordsSucceeded)); - // create info for deployment list in profile edit - if (ctx.IsFileBasedExport && File.Exists(ctx.Export.FilePath)) - { - ctx.ResultInfo.Files.Add(new ExportResultFileInfo + // create info for deployment list in profile edit + if (ctx.IsFileBasedExport && File.Exists(ctx.Export.FilePath)) { - StoreId = ctx.Store.Id, - FileName = ctx.Export.FileName - }); + ctx.Result.Files.Add(new ExportExecuteResult.ExportFileInfo + { + StoreId = ctx.Store.Id, + FileName = ctx.Export.FileName + }); + } + } + catch (Exception exc) + { + ctx.Export.Abort = ExportAbortion.Hard; + ctx.Log.Error("The provider failed to execute the export: " + exc.ToAllMessages(), exc); + ctx.Result.LastError = exc.ToString(); } } else if (ctx.Export.Data.ReadNextSegment()) @@ -1151,12 +1160,10 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) if (ctx.Profile.ProviderConfigData.HasValue()) { - string partialName; - Type dataType; - Action initialize; - if (ctx.Provider.Value.RequiresConfiguration(out partialName, out dataType, out initialize)) + var configInfo = ctx.Provider.Value.ConfigurationInfo; + if (configInfo != null) { - ctx.Export.ConfigurationData = XmlHelper.Deserialize(ctx.Profile.ProviderConfigData, dataType); + ctx.Export.ConfigurationData = XmlHelper.Deserialize(ctx.Profile.ProviderConfigData, configInfo.ModelType); } } @@ -1178,7 +1185,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) var stores = Init(ctx); - ctx.Export.Customer = ctx.ProjectionCustomer.ToExpando(); + ctx.Export.Customer = ctx.ProjectionCustomer.ToExpando(ctx.ProjectionLanguage.Id); ctx.Export.Currency = ctx.ProjectionCurrency.ToExpando(ctx.ProjectionLanguage.Id); ctx.Export.Language = ctx.ProjectionLanguage.ToExpando(); @@ -1230,14 +1237,15 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) catch (Exception exc) { logger.Error(exc); + ctx.Result.LastError = exc.ToString(); } finally { try { - if (!ctx.IsPreview) + if (!ctx.IsPreview && ctx.Profile.Id != 0) { - ctx.Profile.ResultInfo = XmlHelper.Serialize(ctx.ResultInfo); + ctx.Profile.ResultInfo = XmlHelper.Serialize(ctx.Result); _exportService.UpdateExportProfile(ctx.Profile); } @@ -1309,6 +1317,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) catch (Exception exc) { logger.Error(exc); + ctx.Result.LastError = exc.ToString(); } } } @@ -1319,32 +1328,51 @@ public static string PublicFolder get { return "Exchange"; } } - public void Execute(TaskExecutionContext context) + public void Execute(TaskExecutionContext taskContext) { - InitDependencies(context); + InitDependencies(taskContext); - var id = context.ScheduleTask.Alias.ToInt(); - var profile = _exportService.GetExportProfileById(id); + var profileId = taskContext.ScheduleTask.Alias.ToInt(); + var profile = _exportService.GetExportProfileById(profileId); - var selectedIdsCacheKey = "ExportTaskSelectedIds" + id.ToString(); - var selectedIds = HttpRuntime.Cache[selectedIdsCacheKey] as string; + var selectedIdsCacheKey = profile.GetSelectedEntityIdsCacheKey(); + var selectedEntityIds = HttpRuntime.Cache[selectedIdsCacheKey] as string; - var ctx = new ExportProfileTaskContext(context, profile, _exportService.LoadProvider(profile.ProviderSystemName), selectedIds); + var ctx = new ExportProfileTaskContext(taskContext, profile, _exportService.LoadProvider(profile.ProviderSystemName), selectedEntityIds); HttpRuntime.Cache.Remove(selectedIdsCacheKey); ExportCoreOuter(ctx); - context.CancellationToken.ThrowIfCancellationRequested(); + taskContext.CancellationToken.ThrowIfCancellationRequested(); } - public void Preview(ExportProfile profile, IComponentContext context, int pageIndex, int pageSize, int totalRecords, Action previewData) + public ExportExecuteResult Execute(string providerSystemName, IComponentContext context, CancellationToken cancellationToken, + string providerConfigData = null, string selectedEntityIds = null, int storeId = 0) { - if (profile == null) - throw new ArgumentNullException("profile"); + Guard.ArgumentNotEmpty(() => providerSystemName); + Guard.ArgumentNotNull(() => context); + + var taskContext = new TaskExecutionContext(context, null); + taskContext.CancellationToken = cancellationToken; + + InitDependencies(taskContext); + + var provider = _exportService.LoadProvider(providerSystemName); + + var profile = _exportService.CreateVolatileProfile(provider, providerConfigData, storeId); + + var ctx = new ExportProfileTaskContext(taskContext, profile, provider, selectedEntityIds); + + ExportCoreOuter(ctx); + + return ctx.Result; + } - if (context == null) - throw new ArgumentNullException("context"); + public void Preview(ExportProfile profile, IComponentContext context, int pageIndex, int pageSize, int totalRecords, Action previewData) + { + Guard.ArgumentNotNull(() => profile); + Guard.ArgumentNotNull(() => context); var taskContext = new TaskExecutionContext(context, null); var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(5.0)); @@ -1361,14 +1389,9 @@ public void Preview(ExportProfile profile, IComponentContext context, int pageIn public int GetRecordCount(ExportProfile profile, Provider provider, IComponentContext context) { - if (profile == null) - throw new ArgumentNullException("profile"); - - if (provider == null) - throw new ArgumentNullException("provider"); - - if (context == null) - throw new ArgumentNullException("context"); + Guard.ArgumentNotNull(() => profile); + Guard.ArgumentNotNull(() => provider); + Guard.ArgumentNotNull(() => context); var taskContext = new TaskExecutionContext(context, null); var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(5.0)); @@ -1386,151 +1409,4 @@ public int GetRecordCount(ExportProfile profile, Provider provi return result; } } - - - internal class ExportProfileTaskContext - { - private ExportProductDataContext _productDataContext; - private ExportOrderDataContext _orderDataContext; - - public ExportProfileTaskContext( - TaskExecutionContext taskContext, - ExportProfile profile, - Provider provider, - string selectedIds = null, - int pageIndex = 0, - int pageSize = 100, - int? totalRecords = null, - Action previewData = null) - { - TaskContext = taskContext; - Profile = profile; - Provider = provider; - Filter = XmlHelper.Deserialize(profile.Filtering); - Projection = XmlHelper.Deserialize(profile.Projection); - EntityIdsSelected = selectedIds.SplitSafe(",").Select(x => x.ToInt()).ToList(); - PageIndex = pageIndex; - PageSize = pageSize; - TotalRecords = totalRecords; - PreviewData = previewData; - - FolderContent = FileSystemHelper.TempDir(@"Profile\Export\{0}\Content".FormatInvariant(profile.FolderName)); - FolderRoot = System.IO.Directory.GetParent(FolderContent).FullName; - - Categories = new Dictionary(); - CategoryPathes = new Dictionary(); - Countries = new Dictionary(); - - Export = new ExportExecuteContext(TaskContext.CancellationToken, FolderContent); - Export.Projection = XmlHelper.Deserialize(profile.Projection); - - RecordsPerStore = new Dictionary(); - EntityIdsLoaded = new List(); - - ResultInfo = new ExportResultInfo - { - Files = new List() - }; - } - - public List EntityIdsSelected { get; private set; } - public List EntityIdsLoaded { get; set; } - - public int PageIndex { get; private set; } - public int PageSize { get; private set; } - public int? TotalRecords { get; set; } - public int RecordCount { get; set; } - public Dictionary RecordsPerStore { get; set; } - public string ProgressInfo { get; set; } - - public Action PreviewData { get; private set; } - public bool IsPreview - { - get { return PreviewData != null; } - } - - public TaskExecutionContext TaskContext { get; private set; } - public ExportProfile Profile { get; private set; } - public Provider Provider { get; private set; } - public ExportResultInfo ResultInfo { get; set; } - - public ExportFilter Filter { get; private set; } - public ExportProjection Projection { get; private set; } - public Currency ProjectionCurrency { get; set; } - public Customer ProjectionCustomer { get; set; } - public Language ProjectionLanguage { get; set; } - - public TraceLogger Log { get; set; } - public Store Store { get; set; } - - public string FolderRoot { get; private set; } - public string FolderContent { get; private set; } - public string ZipName - { - get { return Profile.FolderName + ".zip"; } - } - public string ZipPath - { - get { return Path.Combine(FolderRoot, ZipName); } - } - public string LogPath - { - get { return Path.Combine(FolderRoot, "log.txt"); } - } - - public bool IsFileBasedExport - { - get { return Provider.Value.FileExtension.HasValue(); } - } - public string[] GetDeploymentFiles(ExportDeployment deployment) - { - if (!IsFileBasedExport) - return new string[0]; - - if (deployment.CreateZip) - return new string[] { ZipPath }; - - return System.IO.Directory.GetFiles(FolderContent, "*.*", SearchOption.AllDirectories); - } - - // data loaded once per export - public Dictionary Categories { get; set; } - public Dictionary CategoryPathes { get; set; } - public Dictionary DeliveryTimes { get; set; } - public Dictionary QuantityUnits { get; set; } - public Dictionary Stores { get; set; } - public Dictionary Countries { get; set; } - - // data loaded once per page - public ExportProductDataContext ProductDataContext - { - get - { - return _productDataContext; - } - set - { - if (_productDataContext != null) - _productDataContext.Clear(); - - _productDataContext = value; - } - } - public ExportOrderDataContext OrderDataContext - { - get - { - return _orderDataContext; - } - set - { - if (_orderDataContext != null) - _orderDataContext.Clear(); - - _orderDataContext = value; - } - } - - public ExportExecuteContext Export { get; set; } - } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs new file mode 100644 index 0000000000..7c50102105 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using SmartStore.Core; +using SmartStore.Core.Domain; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; +using SmartStore.Services.Tasks; +using SmartStore.Utilities; + +namespace SmartStore.Services.DataExchange.ExportTask +{ + internal class ExportProfileTaskContext + { + private ExportProductDataContext _productDataContext; + private ExportOrderDataContext _orderDataContext; + + public ExportProfileTaskContext( + TaskExecutionContext taskContext, + ExportProfile profile, + Provider provider, + string selectedIds = null, + int pageIndex = 0, + int pageSize = 100, + int? totalRecords = null, + Action previewData = null) + { + TaskContext = taskContext; + Profile = profile; + Provider = provider; + Filter = XmlHelper.Deserialize(profile.Filtering); + Projection = XmlHelper.Deserialize(profile.Projection); + EntityIdsSelected = selectedIds.SplitSafe(",").Select(x => x.ToInt()).ToList(); + PageIndex = pageIndex; + PageSize = pageSize; + TotalRecords = totalRecords; + PreviewData = previewData; + + FolderContent = FileSystemHelper.TempDir(@"Profile\Export\{0}\Content".FormatInvariant(profile.FolderName)); + FolderRoot = System.IO.Directory.GetParent(FolderContent).FullName; + + Categories = new Dictionary(); + CategoryPathes = new Dictionary(); + Countries = new Dictionary(); + + RecordsPerStore = new Dictionary(); + EntityIdsLoaded = new List(); + + Export = new ExportExecuteContext(TaskContext.CancellationToken, FolderContent); + Export.Projection = XmlHelper.Deserialize(profile.Projection); + + Result = new ExportExecuteResult(); + + if (IsFileBasedExport) + { + Result.FileFolder = FolderContent; + } + } + + public List EntityIdsSelected { get; private set; } + public List EntityIdsLoaded { get; set; } + + public int PageIndex { get; private set; } + public int PageSize { get; private set; } + public int? TotalRecords { get; set; } + public int RecordCount { get; set; } + public Dictionary RecordsPerStore { get; set; } + public string ProgressInfo { get; set; } + + public Action PreviewData { get; private set; } + public bool IsPreview + { + get { return PreviewData != null; } + } + + public TaskExecutionContext TaskContext { get; private set; } + public ExportProfile Profile { get; private set; } + public Provider Provider { get; private set; } + + public ExportFilter Filter { get; private set; } + public ExportProjection Projection { get; private set; } + public Currency ProjectionCurrency { get; set; } + public Customer ProjectionCustomer { get; set; } + public Language ProjectionLanguage { get; set; } + + public TraceLogger Log { get; set; } + public Store Store { get; set; } + + public string FolderRoot { get; private set; } + public string FolderContent { get; private set; } + public string ZipName + { + get { return Profile.FolderName + ".zip"; } + } + public string ZipPath + { + get { return Path.Combine(FolderRoot, ZipName); } + } + public string LogPath + { + get { return Path.Combine(FolderRoot, "log.txt"); } + } + + public bool IsFileBasedExport + { + get { return Provider.Value.FileExtension.HasValue(); } + } + public string[] GetDeploymentFiles(ExportDeployment deployment) + { + if (!IsFileBasedExport) + return new string[0]; + + if (deployment.CreateZip) + return new string[] { ZipPath }; + + return System.IO.Directory.GetFiles(FolderContent, "*.*", SearchOption.AllDirectories); + } + + // data loaded once per export + public Dictionary Categories { get; set; } + public Dictionary CategoryPathes { get; set; } + public Dictionary DeliveryTimes { get; set; } + public Dictionary QuantityUnits { get; set; } + public Dictionary Stores { get; set; } + public Dictionary Countries { get; set; } + + // data loaded once per page + public ExportProductDataContext ProductDataContext + { + get + { + return _productDataContext; + } + set + { + if (_productDataContext != null) + _productDataContext.Clear(); + + _productDataContext = value; + } + } + public ExportOrderDataContext OrderDataContext + { + get + { + return _orderDataContext; + } + set + { + if (_orderDataContext != null) + _orderDataContext.Clear(); + + _orderDataContext = value; + } + } + + public ExportExecuteContext Export { get; set; } + public ExportExecuteResult Result { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs index 74c4cef39f..c0e683a0e8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs @@ -1,5 +1,4 @@ -using System; -using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Plugins; namespace SmartStore.Services.DataExchange @@ -17,13 +16,9 @@ public partial interface IExportProvider : IProvider, IUserEditable string FileExtension { get; } /// - /// Get configuration information + /// Get provider specific configuration information. Return null when no provider specific configuration is required. /// - /// The partial view name for the configuration - /// Type of the view model - /// Callback to initialize the view model. Can be null. - /// Whether configuration is required - bool RequiresConfiguration(out string partialViewName, out Type modelType, out Action initialize); + ExportConfigurationInfo ConfigurationInfo { get; } /// /// Export data to a file diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs index c3aeafb28f..098701b0a3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs @@ -7,14 +7,22 @@ namespace SmartStore.Services.DataExchange { public interface IExportService { + /// + /// Creates a volatile export project + /// + /// Export provider + /// Provider configuration data + /// Store identifier + /// New export profile + ExportProfile CreateVolatileProfile(Provider provider, string providerConfigData, int storeId); + /// /// Inserts an export profile /// /// Export provider - /// Name of the profile /// Identifier of a profile the settings should be copied from /// New export profile - ExportProfile InsertExportProfile(Provider provider, string name, int cloneFromProfileId = 0); + ExportProfile InsertExportProfile(Provider provider, int cloneFromProfileId = 0); /// /// Updates an export profile diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 17b90fd0b4..f4e555ad04 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -169,14 +169,17 @@ + + + - - + + - + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs index 2dd0107819..86eb437175 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs @@ -61,7 +61,7 @@ public ActionResult ProductEditTab(int productId) ViewBag.DefaultAgeGroup = T("Common.Auto"); // we do not have export profile context here, so we simply use the first profile - var profile = _exportService.GetExportProfilesBySystemName("Feeds.GoogleMerchantCenterProductXml").FirstOrDefault(); + var profile = _exportService.GetExportProfilesBySystemName(ProductExportXmlProvider.SystemName).FirstOrDefault(); if (profile != null) { var config = XmlHelper.Deserialize(profile.ProviderConfigData, typeof(ProfileConfigurationModel)) as ProfileConfigurationModel; diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs index 273ac19b96..82918f9e14 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs @@ -121,24 +121,32 @@ private bool BasePriceSupported(int baseAmount, string unit) return false; } + public static string SystemName + { + get { return "Feeds.GoogleMerchantCenterProductXml"; } + } + public static string Unspecified { get { return "__nospec__"; } } - public bool RequiresConfiguration(out string partialViewName, out Type modelType, out Action initialize) + public ExportConfigurationInfo ConfigurationInfo { - partialViewName = "~/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml"; - modelType = typeof(ProfileConfigurationModel); - - initialize = obj => + get { - var model = (obj as ProfileConfigurationModel); - - model.AvailableGoogleCategories = _googleFeedService.GetTaxonomyList(); - }; + return new ExportConfigurationInfo + { + PartialViewName = "~/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml", + ModelType = typeof(ProfileConfigurationModel), + Initialize = obj => + { + var model = (obj as ProfileConfigurationModel); - return true; + model.AvailableGoogleCategories = _googleFeedService.GetTaxonomyList(); + } + }; + } } public ExportEntityType EntityType @@ -377,7 +385,7 @@ public void Execute(IExportExecuteContext context) } catch (Exception exc) { - context.Log.Error("Error while processing product with id {0}: {1}".FormatInvariant(productId, exc.Message), exc); + context.Log.Error("Error while processing product with id {0}: {1}".FormatInvariant(productId, exc.ToAllMessages()), exc); ++context.RecordsFailed; } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml index 32f56ee418..8d01b7baeb 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml @@ -1,6 +1,7 @@ @model FeedGoogleMerchantCenterModel @using SmartStore.GoogleMerchantCenter; @using SmartStore.GoogleMerchantCenter.Models; +@using SmartStore.GoogleMerchantCenter.Providers; @using SmartStore.Web.Framework; @using SmartStore.Web.Framework.Plugins; @using Telerik.Web.Mvc.UI; @@ -21,7 +22,7 @@ @Html.Raw(@T("Plugins.Feed.Froogle.AdminInstruction"))
    - @Html.Action("InfoProfile", "Export", new { systemName = "Feeds.GoogleMerchantCenterProductXml", returnUrl = Request.RawUrl, area = "admin" }) + @Html.Action("InfoProfile", "Export", new { systemName = ProductExportXmlProvider.SystemName, returnUrl = Request.RawUrl, area = "admin" })
    diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 4aed25b29c..384be3bddf 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.Web; -using System.Web.Caching; using System.Web.Mvc; using Autofac; using SmartStore.Admin.Extensions; @@ -22,6 +21,7 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Customers; using SmartStore.Services.DataExchange; +using SmartStore.Services.DataExchange.ExportTask; using SmartStore.Services.Directory; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; @@ -126,7 +126,7 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile model.Provider.ThumbnailUrl = GetThumbnailUrl(provider); model.Provider.Url = provider.Metadata.PluginDescriptor.Url; - model.Provider.ConfigurationUrl = Url.Action("ConfigurePlugin", "Plugin", new { systemName = provider.Metadata.SystemName, area = "Admin" }); + model.Provider.ConfigurationUrl = Url.Action("ConfigurePlugin", "Plugin", new { systemName = provider.Metadata.PluginDescriptor.SystemName, area = "Admin" }); model.Provider.FriendlyName = _pluginMediator.GetLocalizedFriendlyName(provider.Metadata); model.Provider.Author = provider.Metadata.PluginDescriptor.Author; model.Provider.Version = provider.Metadata.PluginDescriptor.Version.ToString(); @@ -175,20 +175,18 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile try { - string partialName; - Type dataType; - Action initialize; - if (provider.Value.RequiresConfiguration(out partialName, out dataType, out initialize)) + var configInfo = provider.Value.ConfigurationInfo; + if (configInfo != null) { - model.Provider.ConfigPartialViewName = partialName; - model.Provider.ConfigDataType = dataType; - model.Provider.ConfigData = XmlHelper.Deserialize(profile.ProviderConfigData, dataType); + model.Provider.ConfigPartialViewName = configInfo.PartialViewName; + model.Provider.ConfigDataType = configInfo.ModelType; + model.Provider.ConfigData = XmlHelper.Deserialize(profile.ProviderConfigData, configInfo.ModelType); - if (initialize != null) + if (configInfo.Initialize != null) { try { - initialize(model.Provider.ConfigData); + configInfo.Initialize(model.Provider.ConfigData); } catch (Exception exc) { @@ -331,7 +329,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile try { var publicFolder = Path.Combine(HttpRuntime.AppDomainAppPath, ExportProfileTask.PublicFolder); - var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); + var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); if (resultInfo != null && resultInfo.Files != null) { @@ -533,7 +531,7 @@ public ActionResult Create(ExportProfileModel model) var provider = _exportService.LoadProvider(model.Provider.SystemName); if (provider != null) { - var profile = _exportService.InsertExportProfile(provider, _pluginMediator.GetLocalizedFriendlyName(provider.Metadata), model.CloneProfileId ?? 0); + var profile = _exportService.InsertExportProfile(provider, model.CloneProfileId ?? 0); return RedirectToAction("Edit", new { id = profile.Id }); } @@ -660,12 +658,10 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) profile.ProviderConfigData = null; try { - string partialName; - Type dataType; - Action initialize; - if (provider.Value.RequiresConfiguration(out partialName, out dataType, out initialize) && model.CustomProperties.ContainsKey("ProviderConfigData")) + var configInfo = provider.Value.ConfigurationInfo; + if (configInfo != null && model.CustomProperties.ContainsKey("ProviderConfigData")) { - profile.ProviderConfigData = XmlHelper.Serialize(model.CustomProperties["ProviderConfigData"], dataType); + profile.ProviderConfigData = XmlHelper.Serialize(model.CustomProperties["ProviderConfigData"], configInfo.ModelType); } } catch (Exception exc) @@ -781,6 +777,7 @@ public ActionResult PreviewList(GridCommand command, int id, int totalRecords) var om = new ExportPreviewOrderModel { Id = x.Id, + HasNewPaymentNotification = x.HasNewPaymentNotification, OrderNumber = x.OrderNumber, OrderStatus = x.OrderStatus, PaymentStatus = x.PaymentStatus, @@ -827,12 +824,7 @@ public ActionResult Execute(int id, string selectedIds, bool exportAll) if (profile == null) return RedirectToAction("List"); - var selectedIdsCacheKey = "ExportTaskSelectedIds" + id.ToString(); - - if (selectedIds.HasValue()) - HttpRuntime.Cache.Add(selectedIdsCacheKey, selectedIds, null, DateTime.UtcNow.AddMinutes(5), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); - else - HttpRuntime.Cache.Remove(selectedIdsCacheKey); + profile.CacheSelectedEntityIds(selectedIds); var returnUrl = Url.Action("List", "Export", new { area = "admin" }); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportPreviewModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportPreviewModel.cs index ed19372a01..4dbcde6c6a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportPreviewModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportPreviewModel.cs @@ -45,6 +45,8 @@ public class ExportPreviewProductModel : EntityModelBase public class ExportPreviewOrderModel : EntityModelBase { + public bool HasNewPaymentNotification { get; set; } + [SmartResourceDisplayName("Admin.Orders.Fields.OrderNumber")] public string OrderNumber { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 62517543a4..a290c681d2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -95,8 +95,8 @@
    @if (Model.Provider.Description.HasValue()) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 9dd3d451c5..19e9921237 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -48,7 +48,8 @@ @profile.Name + + @foreach (var deployment in Model.Deployments) + { + + + + + + + } + +
    - @Model.Provider.FriendlyName.NaIfEmpty() (@(Model.Provider.SystemName.NaIfEmpty())) - + @Model.Provider.FriendlyName.NaIfEmpty() + (@(Model.Provider.SystemName.NaIfEmpty()))
    - @profile.Provider.FriendlyName.NaIfEmpty() (@profile.Provider.SystemName) + @profile.Provider.FriendlyName.NaIfEmpty() +
    @profile.Provider.SystemName
    @Html.SymbolForBool(profile.Enabled) @@ -181,7 +182,7 @@ else } function toggleProviderDescription() { - var val = dialog.find('select[name="Provider.SystemName"]').val().replace('.', ''); + var val = dialog.find('select[name="Provider.SystemName"]').val().replace(/\./g, ''); dialog.find('.export-provider-description').hide(); $('#ExportProviderDescription' + val).fadeIn('fast'); } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml index 414464f115..c0e06aca48 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml @@ -134,7 +134,7 @@ .EnableCustomBinding(true)) } -else if (Model.EntityType == ExportEntityType.Product) +else if (Model.EntityType == ExportEntityType.Order) {
    @(Html.Telerik().Grid() @@ -160,7 +160,9 @@ else if (Model.EntityType == ExportEntityType.Product) columns.Bound(x => x.CustomerEmail); columns.Bound(x => x.StoreName); columns.Bound(x => x.CreatedOn); - columns.Bound(x => x.OrderTotal); + columns.Bound(x => x.OrderTotal) + .Format("{0:0.00}") + .RightAlign(); }) .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) 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 5217919650..3e8d295898 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml @@ -83,39 +83,42 @@  @T("Common.AddNew") -
    - - - - - - - - - - - @foreach (var deployment in Model.Deployments) - { - - - - - + if (Model.Deployments.Count > 0) + { +
    +
    @T("Admin.DataExchange.Export.Deployment.Name")@T("Common.Enabled")@T("Admin.DataExchange.Export.Deployment.DeploymentType")@T("Admin.Common.Actions")
    - @deployment.Name - - @Html.SymbolForBool(deployment.Enabled) - - @Html.Partial("InfoDeployment", deployment) - - - @T("Common.Delete") - -
    + + + + + + - } - -
    @T("Admin.DataExchange.Export.Deployment.Name")@T("Common.Enabled")@T("Admin.DataExchange.Export.Deployment.DeploymentType")@T("Admin.Common.Actions")
    -
    +
    + @deployment.Name + + @Html.SymbolForBool(deployment.Enabled) + + @Html.Partial("InfoDeployment", deployment) + + + @T("Common.Delete") + +
    +
    + } } else { From 33b2d5230f169e03015323b7a0d45c70a8384c7c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 24 Sep 2015 18:09:55 +0200 Subject: [PATCH 082/874] Maintenance also deletes export profile temp folders --- .../201509150931528_ExportFramework2.cs | 9 ++++++++ .../Controllers/CommonController.cs | 22 +++++++++++++++---- .../Models/Common/MaintenanceModel.cs | 5 ++--- .../Views/Common/Maintenance.cshtml | 13 ++++++++--- 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index d45d606feb..f180e7b0c5 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -39,6 +39,14 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) { builder.AddOrUpdate("Common.Example", "Example", "Beispiel"); + builder.AddOrUpdate("Admin.Common.FilesDeleted", + "{0} files were deleted", + "{0} Dateien wurden gelscht"); + + builder.AddOrUpdate("Admin.Common.FoldersDeleted", + "{0} folders were deleted", + "{0} Verzeichnisse wurden gelscht"); + builder.AddOrUpdate("Admin.DataExchange.Export.CloneProfile", "Apply settings from", "Einstellungen bernehmen von", @@ -139,6 +147,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) ); builder.Delete("Plugins.Widgets.OpenTrans.IsLexwareCompatibe"); + builder.Delete("Admin.System.Maintenance.DeleteExportedFolders.TotalDeleted"); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs index d64752d555..9c4c7b4424 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs @@ -842,20 +842,22 @@ public ActionResult MaintenanceDeleteFiles(MaintenanceModel model) return AccessDeniedView(); DateTime? startDateValue = (model.DeleteExportedFiles.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.Value.ConvertToUtcTime(model.DeleteExportedFiles.StartDate.Value, _dateTimeHelper.Value.CurrentTimeZone); + : (DateTime?)_dateTimeHelper.Value.ConvertToUtcTime(model.DeleteExportedFiles.StartDate.Value, _dateTimeHelper.Value.CurrentTimeZone); DateTime? endDateValue = (model.DeleteExportedFiles.EndDate == null) ? null - : (DateTime?)_dateTimeHelper.Value.ConvertToUtcTime(model.DeleteExportedFiles.EndDate.Value, _dateTimeHelper.Value.CurrentTimeZone).AddDays(1); + : (DateTime?)_dateTimeHelper.Value.ConvertToUtcTime(model.DeleteExportedFiles.EndDate.Value, _dateTimeHelper.Value.CurrentTimeZone).AddDays(1); model.DeleteExportedFiles.NumberOfDeletedFiles = 0; + model.DeleteExportedFiles.NumberOfDeletedFolders = 0; var appPath = this.Request.PhysicalApplicationPath; string[] paths = new string[] { - string.Concat(appPath, "Content\\files\\exportimport\\"), - string.Concat(appPath, "Exchange\\"), + appPath + @"Content\files\exportimport\", + appPath + @"Exchange\", + appPath + @"App_Data\_temp\Profile\Export\" }; foreach (var path in paths) @@ -885,6 +887,18 @@ public ActionResult MaintenanceDeleteFiles(MaintenanceModel model) NotifyError(exc, false); } } + + var dir = new DirectoryInfo(path); + + foreach (var dirInfo in dir.GetDirectories()) + { + if ((!startDateValue.HasValue || startDateValue.Value < dirInfo.LastWriteTimeUtc) && + (!endDateValue.HasValue || dirInfo.LastWriteTimeUtc < endDateValue.Value)) + { + FileSystemHelper.ClearDirectory(dirInfo.FullName, true); + ++model.DeleteExportedFiles.NumberOfDeletedFolders; + } + } } return View(model); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Common/MaintenanceModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Common/MaintenanceModel.cs index 49e85dd97a..9781b22bb4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Common/MaintenanceModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Common/MaintenanceModel.cs @@ -1,5 +1,4 @@ using System; -using System.ComponentModel.DataAnnotations; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Mvc; @@ -21,7 +20,6 @@ public MaintenanceModel() [SmartResourceDisplayName("Admin.System.Maintenance.SqlQuery")] public string SqlQuery { get; set; } - #region Nested classes public class DeleteGuestsModel : ModelBase @@ -47,9 +45,9 @@ public class DeleteExportedFilesModel : ModelBase public DateTime? EndDate { get; set; } public int? NumberOfDeletedFiles { get; set; } + public int? NumberOfDeletedFolders { get; set; } } - // codehint: sm-add public class DeleteImageCacheModel : ModelBase { [SmartResourceDisplayName("Admin.System.Maintenance.DeleteImageCache.FileCount")] @@ -58,6 +56,7 @@ public class DeleteImageCacheModel : ModelBase [SmartResourceDisplayName("Admin.System.Maintenance.DeleteImageCache.TotalSize")] public string TotalSize { get; set; } } + #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Common/Maintenance.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Common/Maintenance.cshtml index d152ec5e21..3689f6e979 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Common/Maintenance.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Common/Maintenance.cshtml @@ -103,12 +103,19 @@
    @T("Admin.System.Maintenance.DeleteExportedFiles")
    - @if (Model.DeleteExportedFiles.NumberOfDeletedFiles.HasValue) + @if (Model.DeleteExportedFiles.NumberOfDeletedFiles.HasValue || Model.DeleteExportedFiles.NumberOfDeletedFolders.HasValue) {
    - @string.Format(T("Admin.System.Maintenance.DeleteExportedFiles.TotalDeleted").Text, Model.DeleteExportedFiles.NumberOfDeletedFiles.Value) + @if (Model.DeleteExportedFiles.NumberOfDeletedFiles.HasValue) + { +
    @T("Admin.Common.FilesDeleted", Model.DeleteExportedFiles.NumberOfDeletedFiles.Value).
    + } + @if (Model.DeleteExportedFiles.NumberOfDeletedFolders.HasValue) + { +
    @T("Admin.Common.FoldersDeleted", Model.DeleteExportedFiles.NumberOfDeletedFolders.Value).
    + }
    @@ -132,7 +139,7 @@   - From df1384012f5188c139efc12dd1666863c3382f57 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 24 Sep 2015 21:03:51 +0200 Subject: [PATCH 083/874] Removed obsolete FeedPluginHelper --- .../Plugins/FeedPluginCore.cs | 114 --- .../Plugins/FeedPluginHelper.cs | 698 ------------------ .../SmartStore.Web.Framework.csproj | 2 - 3 files changed, 814 deletions(-) delete mode 100644 src/Presentation/SmartStore.Web.Framework/Plugins/FeedPluginCore.cs delete mode 100644 src/Presentation/SmartStore.Web.Framework/Plugins/FeedPluginHelper.cs diff --git a/src/Presentation/SmartStore.Web.Framework/Plugins/FeedPluginCore.cs b/src/Presentation/SmartStore.Web.Framework/Plugins/FeedPluginCore.cs deleted file mode 100644 index 8da827cebb..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/Plugins/FeedPluginCore.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Web.Mvc; -using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Logging; -using SmartStore.Web.Framework.Mvc; - -namespace SmartStore.Web.Framework.Plugins -{ - public class PromotionFeedSettings - { - public PromotionFeedSettings() - { - PageSize = 100; - } - - public int ProductPictureSize { get; set; } - public int CurrencyId { get; set; } - public string StaticFileName { get; set; } - public string BuildDescription { get; set; } - public bool DescriptionToPlainText { get; set; } - public bool AdditionalImages { get; set; } - public string Availability { get; set; } - public decimal ShippingCost { get; set; } - public string ShippingTime { get; set; } - public string Brand { get; set; } - public bool UseOwnProductNo { get; set; } - public int StoreId { get; set; } - public string ExportFormat { get; set; } - public bool ConvertNetToGrossPrices { get; set; } - public int LanguageId { get; set; } - public int PageSize { get; set; } - public decimal? FreeShippingThreshold { get; set; } - } - - - public class PromotionFeedConfigModel - { - public PromotionFeedConfigModel() - { - GeneratedFiles = new List(); - } - - public FeedPluginHelper Helper { get; set; } - public bool IsRunning { get; set; } - public string ProcessInfo { get; set; } - public string GenerateFeedUrl { get; set; } - public string GenerateFeedProgressUrl { get; set; } - public string DeleteFilesUrl { get; set; } - - [SmartResourceDisplayName("Admin.PromotionFeeds")] - public List GeneratedFiles { get; set; } - public List AvailableStores { get; set; } - public List AvailableLanguages { get; set; } - } - - - public class FeedFileData : ModelBase - { - public int StoreId { get; set; } - public string StoreName { get; set; } - public string FileTempPath { get; set; } - public string FilePath { get; set; } - public string FileUrl { get; set; } - public string LogPath { get; set; } - public string LogUrl { get; set; } - public string LastWriteTime { get; set; } - } - - - public class FeedFileCreationContext - { - public FileStream Stream { get; set; } - public TraceLogger Logger { get; set; } - public Store Store { get; set; } - public int StoreCount { get; set; } - public int TotalRecords { get; set; } - public int TotalProcessed { get; set; } - public string FeedFileUrl { get; set; } - public string SecondFilePath { get; set; } - public string ErrorMessage { get; set; } - public IProgress Progress { get; set; } - - public void Report() - { - if (Progress != null) - { - Progress.Report(new FeedFileCreationProgress - { - TotalRecords = TotalRecords, - TotalProcessed = ++TotalProcessed - }); - } - } - } - - - public class FeedFileCreationProgress - { - public int TotalRecords { get; set; } - public int TotalProcessed { get; set; } - public double ProcessedPercent - { - get - { - if (TotalRecords == 0) - return 0; - - return ((double)TotalProcessed / (double)TotalRecords) * 100; - } - } - } -} diff --git a/src/Presentation/SmartStore.Web.Framework/Plugins/FeedPluginHelper.cs b/src/Presentation/SmartStore.Web.Framework/Plugins/FeedPluginHelper.cs deleted file mode 100644 index 15385e0fae..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/Plugins/FeedPluginHelper.cs +++ /dev/null @@ -1,698 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Web; -using System.Web.Mvc; -using Autofac; -using SmartStore.Core; -using SmartStore.Core.Async; -using SmartStore.Core.Data; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Domain.Tasks; -using SmartStore.Core.Html; -using SmartStore.Core.Logging; -using SmartStore.Services.Catalog; -using SmartStore.Services.Directory; -using SmartStore.Services.Localization; -using SmartStore.Services.Media; -using SmartStore.Services.Seo; -using SmartStore.Services.Stores; -using SmartStore.Services.Tasks; -using SmartStore.Services.Tax; -using SmartStore.Utilities; - -namespace SmartStore.Web.Framework.Plugins -{ - public partial class FeedPluginHelper : PluginHelper - { - private readonly string _namespace; - private ScheduleTask _scheduleTask; - private IDictionary _cachedCategories; - private IDictionary _cachedPathes; - private Func _settingsFunc; - - public FeedPluginHelper(IComponentContext componentContext, string systemName, string rootNamespace, Func settings, string providerResRootKey = null /* Legacy */) : - base(componentContext, systemName, providerResRootKey) - { - _namespace = rootNamespace; - _settingsFunc = settings; - } - - #region Dependencies - - private IScheduleTaskService _scheduleTaskService; - private IScheduleTaskService ScheduleTaskService - { - get { return _scheduleTaskService ?? (_scheduleTaskService = _ctx.Resolve()); } - } - - private IPictureService _pictureService; - private IPictureService PictureService - { - get { return _pictureService ?? (_pictureService = _ctx.Resolve()); } - } - - private IProductService _productService; - private IProductService ProductService - { - get { return _productService ?? (_productService = _ctx.Resolve()); } - } - - private ICategoryService _categoryService; - private ICategoryService CategoryService - { - get { return _categoryService ?? (_categoryService = _ctx.Resolve()); } - } - - private INotifier _notifier; - private INotifier Notifier - { - get { return _notifier ?? (_notifier = _ctx.Resolve()); } - } - - private ILogger _logger; - private ILogger Logger - { - get { return _logger ?? (_logger = _ctx.Resolve()); } - } - - private IUrlRecordService _urlRecordService; - private IUrlRecordService UrlRecordService - { - get { return _urlRecordService ?? (_urlRecordService = _ctx.Resolve()); } - } - - private ILanguageService _languageService; - private ILanguageService LanguageService - { - get { return _languageService ?? (_languageService = _ctx.Resolve()); } - } - - private IPriceCalculationService _priceCalculationService; - private IPriceCalculationService PriceCalculationService - { - get { return _priceCalculationService ?? (_priceCalculationService = _ctx.Resolve()); } - } - - private ITaxService _taxService; - private ITaxService TaxService - { - get { return _taxService ?? (_taxService = _ctx.Resolve()); } - } - - private IWorkContext _workContext; - private IWorkContext WorkContext - { - get { return _workContext ?? (_workContext = _ctx.Resolve()); } - } - - private ICurrencyService _currencyService; - private ICurrencyService CurrencyService - { - get { return _currencyService ?? (_currencyService = _ctx.Resolve()); } - } - - #endregion - - private PromotionFeedSettings BaseSettings - { - get { return _settingsFunc(); } - } - - private string ScheduleTaskType - { - get - { - return "{0}.StaticFileGenerationTask, {0}".FormatWith(_namespace); - } - } - - public ScheduleTask ScheduleTask - { - get - { - if (_scheduleTask == null) - _scheduleTask = ScheduleTaskService.GetTaskByType(ScheduleTaskType); - - return _scheduleTask; - } - } - - public string RemoveInvalidFeedChars(string input, bool isHtmlEncoded) - { - if (String.IsNullOrWhiteSpace(input)) - return input; - - //Microsoft uses a proprietary encoding (called CP-1252) for the bullet symbol and some other special characters, - //whereas most websites and data feeds use UTF-8. When you copy-paste from a Microsoft product into a website, - //some characters may appear as junk. Our system generates data feeds in the UTF-8 character encoding, - //which many shopping engines now require. - - //http://www.atensoftware.com/p90.php?q=182 - - if (isHtmlEncoded) - input = HttpUtility.HtmlDecode(input); - - input = input.Replace("¼", ""); - input = input.Replace("½", ""); - input = input.Replace("¾", ""); - - if (isHtmlEncoded) - input = HttpUtility.HtmlEncode(input); - - return input; - } - - public string BuildProductDescription(string productName, string shortDescription, string fullDescription, string manufacturer, Func updateResult = null) - { - if (BaseSettings.BuildDescription.IsCaseInsensitiveEqual(NotSpecified)) - return ""; - - string description = ""; - - if (BaseSettings.BuildDescription.IsEmpty()) - { - description = fullDescription; - - if (description.IsEmpty()) - description = shortDescription; - if (description.IsEmpty()) - description = productName; - } - else if (BaseSettings.BuildDescription.IsCaseInsensitiveEqual("short")) - { - description = shortDescription; - } - else if (BaseSettings.BuildDescription.IsCaseInsensitiveEqual("long")) - { - description = fullDescription; - } - else if (BaseSettings.BuildDescription.IsCaseInsensitiveEqual("titleAndShort")) - { - description = productName.Grow(shortDescription, " "); - } - else if (BaseSettings.BuildDescription.IsCaseInsensitiveEqual("titleAndLong")) - { - description = productName.Grow(fullDescription, " "); - } - else if (BaseSettings.BuildDescription.IsCaseInsensitiveEqual("manuAndTitleAndShort")) - { - description = manufacturer.Grow(productName, " "); - description = description.Grow(shortDescription, " "); - } - else if (BaseSettings.BuildDescription.IsCaseInsensitiveEqual("manuAndTitleAndLong")) - { - description = manufacturer.Grow(productName, " "); - description = description.Grow(fullDescription, " "); - } - - if (updateResult != null) - { - description = updateResult(description); - } - - try - { - if (BaseSettings.DescriptionToPlainText) - { - //Regex reg = new Regex("<[^>]+>", RegexOptions.IgnoreCase); - //description = HttpUtility.HtmlDecode(reg.Replace(description, "")); - - description = HtmlUtils.ConvertHtmlToPlainText(description); - description = HtmlUtils.StripTags(HttpUtility.HtmlDecode(description)); - - description = RemoveInvalidFeedChars(description, false); - } - else - { - description = RemoveInvalidFeedChars(description, true); - } - } - catch (Exception exc) - { - exc.Dump(); - } - - return description; - } - - public string GetManufacturerPartNumber(Product product) - { - if (product.ManufacturerPartNumber.HasValue()) - return product.ManufacturerPartNumber; - - if (BaseSettings.UseOwnProductNo) - return product.Sku; - - return ""; - } - - public string GetShippingCost(Product product, decimal productPrice, decimal? shippingCost = null) - { - if (product.IsFreeShipping) - return "0"; - - if (BaseSettings.FreeShippingThreshold.HasValue && productPrice >= BaseSettings.FreeShippingThreshold.Value) - return "0"; - - decimal cost = shippingCost ?? BaseSettings.ShippingCost; - - if (decimal.Compare(cost, decimal.Zero) == 0) - return ""; - - return cost.FormatInvariant(); - } - - public string GetProductDetailUrl(Store store, Product product) - { - return "{0}{1}".FormatWith(store.Url, product.GetSeName(Language.Id, UrlRecordService, LanguageService)); - } - - public decimal GetProductPrice(Product product, Currency currency, Store store) - { - decimal priceBase = PriceCalculationService.GetPreselectedPrice(product, null); - - if (BaseSettings.ConvertNetToGrossPrices) - { - decimal taxRate; - priceBase = TaxService.GetProductPrice(product, priceBase, true, WorkContext.CurrentCustomer, out taxRate); - } - - decimal price = CurrencyService.ConvertFromPrimaryStoreCurrency(priceBase, currency, store); - return price; - } - - public decimal? GetOldPrice(Product product, Currency currency, Store store) - { - if (!decimal.Equals(product.OldPrice, decimal.Zero) && !decimal.Equals(product.OldPrice, product.Price) && - !(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)) - { - decimal price = product.OldPrice; - - if (BaseSettings.ConvertNetToGrossPrices) - { - decimal taxRate; - price = TaxService.GetProductPrice(product, price, true, WorkContext.CurrentCustomer, out taxRate); - } - - return CurrencyService.ConvertFromPrimaryStoreCurrency(price, currency, store); - } - return null; - } - - public FeedFileData GetFeedFileByStore(Store store, string secondFileName = null, string extension = null) - { - if (store == null) - return null; - - string dirTemp = FileSystemHelper.TempDir(); - string ext = extension ?? BaseSettings.ExportFormat; - string dir = Path.Combine(HttpRuntime.AppDomainAppPath, "Content\\files\\exportimport"); - string fileName = "{0}_{1}".FormatWith(store.Id, BaseSettings.StaticFileName); - string logName = Path.GetFileNameWithoutExtension(fileName) + ".txt"; - - if (ext.HasValue()) - fileName = Path.GetFileNameWithoutExtension(fileName) + (ext.StartsWith(".") ? "" : ".") + ext; - - string url = "{0}content/files/exportimport/".FormatWith(store.Url.EnsureEndsWith("/")); - - if (!(url.StartsWith("http://") || url.StartsWith("https://"))) - url = "http://" + url; - - if (!Directory.Exists(dir)) - Directory.CreateDirectory(dir); - - var feedFile = new FeedFileData - { - StoreId = store.Id, - StoreName = store.Name, - FileTempPath = Path.Combine(dirTemp, fileName), - FilePath = Path.Combine(dir, fileName), - FileUrl = url + fileName, - LogPath = Path.Combine(dir, logName), - LogUrl = url + logName - }; - - try - { - feedFile.LastWriteTime = File.GetLastWriteTimeUtc(feedFile.FilePath).RelativeFormat(true, null); - } - catch (Exception) - { - feedFile.LastWriteTime = feedFile.LastWriteTime.NaIfEmpty(); - } - - if (secondFileName.HasValue()) - { - string fname2 = store.Id + "_" + secondFileName; - feedFile.CustomProperties.Add("SecondFileTempPath", Path.Combine(dirTemp, fname2)); - feedFile.CustomProperties.Add("SecondFilePath", Path.Combine(dir, fname2)); - feedFile.CustomProperties.Add("SecondFileUrl", url + fname2); - } - return feedFile; - } - - public List GetFeedFiles(List stores, string secondFileName = null, string extension = null) - { - var lst = new List(); - - foreach (var store in stores) - { - var feedFile = GetFeedFileByStore(store, secondFileName, extension); - - if (feedFile != null && File.Exists(feedFile.FilePath)) - lst.Add(feedFile); - } - return lst; - } - - public void StartCreatingFeeds(Func createFeed, string secondFileName = null) - { - try - { - using (var scope = new DbContextScope(autoDetectChanges: false, validateOnSave: false, forceNoTracking: true)) - { - _cachedPathes = null; - _cachedCategories = null; - - var storeService = _ctx.Resolve(); - var stores = new List(); - - if (BaseSettings.StoreId != 0) - { - var storeById = storeService.GetStoreById(BaseSettings.StoreId); - if (storeById != null) - stores.Add(storeById); - } - - if (stores.Count == 0) - { - stores.AddRange(storeService.GetAllStores()); - } - - var context = new FeedFileCreationContext - { - StoreCount = stores.Count, - Progress = new Progress(x => - { - AsyncState.Current.Set(x, SystemName); - }) - }; - - foreach (var store in stores) - { - var feedFile = GetFeedFileByStore(store, secondFileName); - if (feedFile != null) - { - FileSystemHelper.Delete(feedFile.FileTempPath); - - if (secondFileName.HasValue()) - FileSystemHelper.Delete(feedFile.CustomProperties["SecondFileTempPath"] as string); - - using (var stream = new FileStream(feedFile.FileTempPath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var logger = new TraceLogger(feedFile.LogPath)) - { - context.Stream = stream; - context.Logger = logger; - context.Store = store; - context.FeedFileUrl = feedFile.FileUrl; - - if (secondFileName.HasValue()) - context.SecondFilePath = feedFile.CustomProperties["SecondFileTempPath"] as string; - - if (!createFeed(context)) - break; - } - - FileSystemHelper.Copy(feedFile.FileTempPath, feedFile.FilePath); - - if (secondFileName.HasValue()) - FileSystemHelper.Copy(context.SecondFilePath, feedFile.CustomProperties["SecondFilePath"] as string); - } - } - } - } - finally - { - AsyncState.Current.Remove(SystemName); - } - } - - public void DeleteFeedFiles(string secondFileName = null) - { - try - { - var extensions = new List() { "xml", "csv" }; - - if (!extensions.Contains(BaseSettings.ExportFormat)) - extensions.Add(BaseSettings.ExportFormat); - - var storeService = _ctx.Resolve(); - var stores = storeService.GetAllStores(); - - foreach (var store in stores) - { - foreach (var ext in extensions) - { - var feedFile = GetFeedFileByStore(store, secondFileName, ext); - if (feedFile != null) - { - FileSystemHelper.Delete(feedFile.FileTempPath); - FileSystemHelper.Delete(feedFile.FilePath); - FileSystemHelper.Delete(feedFile.LogPath); - - if (secondFileName.HasValue()) - { - FileSystemHelper.Delete(feedFile.CustomProperties["SecondFilePath"] as string); - FileSystemHelper.Delete(feedFile.CustomProperties["SecondFileTempPath"] as string); - } - } - } - } - } - catch (Exception exc) - { - Notifier.Error(exc.Message); - } - } - - public string GetMainProductImageUrl(Store store, Product product) - { - string url; - var pictureService = PictureService; - var picture = product.GetDefaultProductPicture(pictureService); - - // always use HTTP when getting image URL - if (picture != null) - url = pictureService.GetPictureUrl(picture, BaseSettings.ProductPictureSize, storeLocation: store.Url); - else - url = pictureService.GetDefaultPictureUrl(BaseSettings.ProductPictureSize, storeLocation: store.Url); - - return url; - } - - public List GetAdditionalProductImages(Store store, Product product, string mainImageUrl, int maxImages = 10) - { - var urls = new List(); - - if (BaseSettings.AdditionalImages) - { - var pictureService = PictureService; - var pics = pictureService.GetPicturesByProductId(product.Id, 0); - - foreach (var pic in pics) - { - if (pic != null) - { - string url = pictureService.GetPictureUrl(pic, BaseSettings.ProductPictureSize, storeLocation: store.Url); - - if (url.HasValue() && (mainImageUrl.IsEmpty() || !mainImageUrl.IsCaseInsensitiveEqual(url))) - { - urls.Add(url); - if (urls.Count >= maxImages) - break; - } - } - } - } - return urls; - } - - internal string LookupCategoryPath(int id) - { - if (_cachedPathes.ContainsKey(id)) - { - return _cachedPathes[id]; - } - return null; - } - - internal void AddPathToCache(int id, string value) - { - _cachedPathes[id] = value; - } - - internal Category LookupCategory(int id) - { - if (_cachedCategories.ContainsKey(id)) - { - return _cachedCategories[id]; - } - return null; - } - - public string GetCategoryPath(Product product) - { - var categoryService = CategoryService; - - if (_cachedPathes == null) - { - _cachedPathes = new Dictionary(); - } - - if (_cachedCategories == null) - { - var allCategories = categoryService.GetAllCategories(showHidden: true, applyNavigationFilters: false); - _cachedCategories = allCategories.ToDictionary(x => x.Id); - } - - string path = categoryService.GetCategoryPath(product, null, LookupCategoryPath, AddPathToCache, (i) => LookupCategory(i) ?? categoryService.GetCategoryById(i)); - - return path; - } - - public void UpdateScheduleTask(bool enabled, string cronExpression = "0 */6 * * *" /* every 6 hrs */) - { - var task = ScheduleTask; - if (task != null) - { - task.Enabled = enabled; - task.CronExpression = cronExpression; - - ScheduleTaskService.UpdateTask(task); - } - } - - public void InsertScheduleTask(string cronExpression = "0 */6 * * *" /* every 6 hrs */) - { - var task = ScheduleTask; - if (task == null) - { - ScheduleTaskService.InsertTask(new ScheduleTask - { - Name = "{0} feed file generation".FormatWith(SystemName), - CronExpression = cronExpression, - Type = ScheduleTaskType, - Enabled = false, - StopOnError = false, - }); - } - } - - public void DeleteScheduleTask() - { - var task = ScheduleTask; - if (task != null) - ScheduleTaskService.DeleteTask(task); - } - - public bool RunScheduleTask() - { - if (ScheduleTask.IsRunning) - { - Notifier.Information(GetProgressInfo()); - return true; - } - - var taskScheduler = _ctx.Resolve(); - taskScheduler.RunSingleTask(ScheduleTask.Id); - - Notifier.Information(GetResource("Admin.System.ScheduleTasks.RunNow.Progress")); - - return true; - } - - public string GetProgressInfo(bool checkIfRunnning = false) - { - if (checkIfRunnning) - { - var task = ScheduleTask; - if (task != null && !task.IsRunning) - return ""; - } - - string result = GetResource("Admin.System.ScheduleTasks.RunNow.Progress"); - - try - { - var progress = AsyncState.Current.Get(SystemName); - - if (progress != null) - { - int percent = (int)progress.ProcessedPercent; - - if (percent > 0 && percent <= 100) - result = "{0}... {1}%".FormatWith(result, percent); - } - } - catch (Exception) { } - - return result; - } - - public void SetupConfigModel(PromotionFeedConfigModel model, string controller, string secondFileName = null, string[] extensions = null) - { - var storeService = _ctx.Resolve(); - var stores = storeService.GetAllStores().ToList(); - - var urlHelper = new UrlHelper(HttpContext.Current.Request.RequestContext); - var routeValues = new { /*Namespaces = _namespace + ".Controllers",*/ area = _namespace }; - - model.GenerateFeedUrl = urlHelper.Action("GenerateFeed", controller, routeValues); - model.GenerateFeedProgressUrl = urlHelper.Action("GenerateFeedProgress", controller, routeValues); - model.DeleteFilesUrl = urlHelper.Action("DeleteFiles", controller, routeValues); - - model.Helper = this; - model.AvailableStores = new List(); - model.AvailableStores.Add(new SelectListItem() { Text = GetResource("Admin.Common.All"), Value = "0" }); - model.AvailableStores.AddRange(stores.ToSelectListItems()); - - model.AvailableLanguages = new List(); - model.AvailableLanguages.Add(new SelectListItem() { Text = GetResource("Admin.Common.Standard"), Value = "0" }); - - foreach (var language in LanguageService.GetAllLanguages()) - { - model.AvailableLanguages.Add(new SelectListItem() - { - Text = language.Name, - Value = language.Id.ToString() - }); - } - - if (!model.IsRunning) - model.IsRunning = (ScheduleTask != null && ScheduleTask.IsRunning); - - if (model.IsRunning) - { - model.ProcessInfo = GetProgressInfo(); - } - else - { - if (extensions != null) - { - foreach (var ext in extensions) - model.GeneratedFiles.AddRange(GetFeedFiles(stores, null, ext)); - } - else - { - model.GeneratedFiles = GetFeedFiles(stores, secondFileName); - } - } - } - } -} diff --git a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj index 5ff292a492..42eaebe5df 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj +++ b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj @@ -276,8 +276,6 @@ - - From d27238eeb745879be48d40b868e246f73e719515 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 25 Sep 2015 09:39:36 +0200 Subject: [PATCH 084/874] Refactoring and removing PluginHelper --- .../Controllers/PayPalDirectController.cs | 38 ++- .../Controllers/PayPalExpressController.cs | 42 ++-- .../Providers/PayPalDirectProvider.cs | 8 +- .../Providers/PayPalExpressProvider.cs | 8 +- .../Providers/PayPalProviderBase.cs | 30 +-- .../Services/PayPalHelper.cs | 8 +- .../Plugins/PluginHelper.cs | 226 ------------------ .../SmartStore.Web.Framework.csproj | 1 - 8 files changed, 53 insertions(+), 308 deletions(-) delete mode 100644 src/Presentation/SmartStore.Web.Framework/Plugins/PluginHelper.cs diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs index d0a5d8434d..def2724b96 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Text; using System.Web.Mvc; -using Autofac; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Logging; @@ -16,51 +15,46 @@ using SmartStore.Services; using SmartStore.Services.Orders; using SmartStore.Services.Payments; -using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Plugins; using SmartStore.Web.Framework.Settings; namespace SmartStore.PayPal.Controllers { public class PayPalDirectController : PaymentControllerBase { - private readonly PluginHelper _helper; private readonly IPaymentService _paymentService; private readonly IOrderService _orderService; private readonly IOrderProcessingService _orderProcessingService; private readonly ICommonServices _services; - private readonly IStoreService _storeService; public PayPalDirectController( - IPaymentService paymentService, IOrderService orderService, + IPaymentService paymentService, + IOrderService orderService, IOrderProcessingService orderProcessingService, PaymentSettings paymentSettings, - IComponentContext ctx, ICommonServices services, - IStoreService storeService) + ICommonServices services) { _paymentService = paymentService; _orderService = orderService; _orderProcessingService = orderProcessingService; _services = services; - _storeService = storeService; - _helper = new PluginHelper(ctx, "SmartStore.PayPal", "Plugins.Payments.PayPalDirect"); } private SelectList TransactModeValues(TransactMode selected) { - return new SelectList(new List() + return new SelectList(new List { - new { ID = (int)TransactMode.Authorize, Name = _helper.GetResource("ModeAuth") }, - new { ID = (int)TransactMode.AuthorizeAndCapture, Name = _helper.GetResource("ModeAuthAndCapture") } - }, "ID", "Name", (int)selected); + new { ID = (int)TransactMode.Authorize, Name = T("Plugins.Payments.PayPalDirect.ModeAuth") }, + new { ID = (int)TransactMode.AuthorizeAndCapture, Name = T("Plugins.Payments.PayPalDirect.ModeAuthAndCapture") } + }, + "ID", "Name", (int)selected); } [AdminAuthorize, ChildActionOnly] public ActionResult Configure() { var model = new PayPalDirectConfigurationModel(); - int storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _services.WorkContext); + int storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); var settings = _services.Settings.LoadSetting(storeScope); model.Copy(settings, true); @@ -82,7 +76,7 @@ public ActionResult Configure(PayPalDirectConfigurationModel model, FormCollecti ModelState.Clear(); var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - int storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _services.WorkContext); + int storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); var settings = _services.Settings.LoadSetting(storeScope); model.Copy(settings, false); @@ -223,7 +217,7 @@ public ActionResult IPNHandler() var provider = _paymentService.LoadPaymentMethodBySystemName("Payments.PayPalDirect", true); var processor = provider != null ? provider.Value as PayPalDirectProvider : null; if (processor == null) - throw new SmartException(_helper.GetResource("NoModuleLoading")); + throw new SmartException(T("Plugins.Payments.PayPalDirect.NoModuleLoading")); if (processor.VerifyIPN(strRequest, out values)) { @@ -270,7 +264,7 @@ public ActionResult IPNHandler() } var newPaymentStatus = PayPalHelper.GetPaymentStatus(payment_status, pending_reason); - sb.AppendLine("{0}: {1}".FormatWith(_helper.GetResource("NewPaymentStatus"), newPaymentStatus)); + sb.AppendLine("{0}: {1}".FormatInvariant(T("Plugins.Payments.PayPalDirect.NewPaymentStatus"), newPaymentStatus)); switch (txn_type) { @@ -324,11 +318,11 @@ public ActionResult IPNHandler() } //this.OrderService.InsertOrderNote(newOrder.OrderId, sb.ToString(), DateTime.UtcNow); - Logger.Information(_helper.GetResource("IpnLogInfo"), new SmartException(sb.ToString())); + Logger.Information(T("Plugins.Payments.PayPalDirect.IpnLogInfo"), new SmartException(sb.ToString())); } else { - Logger.Error(_helper.GetResource("IpnOrderNotFound"), new SmartException(sb.ToString())); + Logger.Error(T("Plugins.Payments.PayPalDirect.IpnOrderNotFound"), new SmartException(sb.ToString())); } } #endregion @@ -405,7 +399,7 @@ public ActionResult IPNHandler() } else { - Logger.Error(_helper.GetResource("IpnOrderNotFound"), new SmartException(sb.ToString())); + Logger.Error(T("Plugins.Payments.PayPalDirect.IpnOrderNotFound"), new SmartException(sb.ToString())); } } #endregion @@ -414,7 +408,7 @@ public ActionResult IPNHandler() } else { - Logger.Error(_helper.GetResource("IpnFailed"), new SmartException(strRequest)); + Logger.Error(T("Plugins.Payments.PayPalDirect.IpnFailed"), new SmartException(strRequest)); } //nothing should be rendered to visitor diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs index 0318a9e097..739bffca93 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs @@ -3,7 +3,6 @@ using System.Globalization; using System.Text; using System.Web.Mvc; -using Autofac; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Discounts; @@ -11,7 +10,6 @@ using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Domain.Shipping; -using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.PayPal.Models; using SmartStore.PayPal.PayPalSvc; @@ -22,25 +20,20 @@ using SmartStore.Services.Common; using SmartStore.Services.Customers; using SmartStore.Services.Directory; -using SmartStore.Services.Localization; using SmartStore.Services.Orders; using SmartStore.Services.Payments; -using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Plugins; using SmartStore.Web.Framework.Settings; namespace SmartStore.PayPal.Controllers { public class PayPalExpressController : PaymentControllerBase { - private readonly PluginHelper _helper; private readonly IPaymentService _paymentService; private readonly IOrderService _orderService; private readonly IOrderProcessingService _orderProcessingService; private readonly ILogger _logger; private readonly PaymentSettings _paymentSettings; - private readonly ILocalizationService _localizationService; private readonly OrderSettings _orderSettings; private readonly ICurrencyService _currencyService; private readonly CurrencySettings _currencySettings; @@ -48,26 +41,26 @@ public class PayPalExpressController : PaymentControllerBase private readonly ICustomerService _customerService; private readonly IGenericAttributeService _genericAttributeService; private readonly ICommonServices _services; - private readonly IStoreService _storeService; public PayPalExpressController( - IPaymentService paymentService, IOrderService orderService, + IPaymentService paymentService, + IOrderService orderService, IOrderProcessingService orderProcessingService, ILogger logger, - PaymentSettings paymentSettings, ILocalizationService localizationService, + PaymentSettings paymentSettings, OrderSettings orderSettings, - ICurrencyService currencyService, CurrencySettings currencySettings, - IOrderTotalCalculationService orderTotalCalculationService, ICustomerService customerService, + ICurrencyService currencyService, + CurrencySettings currencySettings, + IOrderTotalCalculationService orderTotalCalculationService, + ICustomerService customerService, IGenericAttributeService genericAttributeService, - IComponentContext ctx, ICommonServices services, - IStoreService storeService) + ICommonServices services) { _paymentService = paymentService; _orderService = orderService; _orderProcessingService = orderProcessingService; _logger = logger; _paymentSettings = paymentSettings; - _localizationService = localizationService; _orderSettings = orderSettings; _currencyService = currencyService; _currencySettings = currencySettings; @@ -75,24 +68,23 @@ public PayPalExpressController( _customerService = customerService; _genericAttributeService = genericAttributeService; _services = services; - _storeService = storeService; - - _helper = new PluginHelper(ctx, "SmartStore.PayPal", "Plugins.Payments.PayPalExpress"); } public SelectList TransactModeValues(TransactMode selected) { - return new SelectList(new List() { - new { ID = (int)TransactMode.Authorize, Name = _helper.GetResource("ModeAuth") }, - new { ID = (int)TransactMode.AuthorizeAndCapture, Name = _helper.GetResource("ModeAuthAndCapture") } - }, "ID", "Name", (int)selected); + return new SelectList(new List + { + new { ID = (int)TransactMode.Authorize, Name = T("Plugins.Payments.PayPalExpress.ModeAuth") }, + new { ID = (int)TransactMode.AuthorizeAndCapture, Name = T("Plugins.Payments.PayPalExpress.ModeAuthAndCapture") } + }, + "ID", "Name", (int)selected); } [AdminAuthorize, ChildActionOnly] public ActionResult Configure() { var model = new PayPalExpressConfigurationModel(); - int storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _services.WorkContext); + int storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); var settings = _services.Settings.LoadSetting(storeScope); model.Copy(settings, true); @@ -114,7 +106,7 @@ public ActionResult Configure(PayPalExpressConfigurationModel model, FormCollect ModelState.Clear(); var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - int storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _services.WorkContext); + int storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); var settings = _services.Settings.LoadSetting(storeScope); model.Copy(settings, false); @@ -546,7 +538,7 @@ public override IList ValidatePaymentForm(FormCollection form) var warnings = new List(); //validate - var validator = new PayPalExpressPaymentInfoValidator(_localizationService); + var validator = new PayPalExpressPaymentInfoValidator(_services.Localization); var model = new PayPalExpressPaymentInfoModel() { diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalDirectProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalDirectProvider.cs index ec49fe64e7..c3b0a29b1e 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalDirectProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalDirectProvider.cs @@ -138,7 +138,7 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces DoDirectPaymentResponseType response = service.DoDirectPayment(req); string error = ""; - bool success = PayPalHelper.CheckSuccess(Helper, response, out error); + bool success = PayPalHelper.CheckSuccess(Services.Localization, response, out error); if (success) { result.AvsResult = response.AVSCode; @@ -220,7 +220,7 @@ public override ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentReque //schedule details.ScheduleDetails = new ScheduleDetailsType(); - details.ScheduleDetails.Description = Helper.GetResource("RecurringPayment"); + details.ScheduleDetails.Description = T("Plugins.Payments.PayPalDirect.RecurringPayment"); details.ScheduleDetails.PaymentPeriod = new BillingPeriodDetailsType(); details.ScheduleDetails.PaymentPeriod.Amount = new BasicAmountType(); details.ScheduleDetails.PaymentPeriod.Amount.Value = Math.Round(processPaymentRequest.OrderTotal, 2).ToString("N", new CultureInfo("en-us")); @@ -241,7 +241,7 @@ public override ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentReque details.ScheduleDetails.PaymentPeriod.BillingPeriod = BillingPeriodType.Year; break; default: - throw new SmartException(Helper.GetResource("NotSupportedPeriod")); + throw new SmartException(T("Plugins.Payments.PayPalDirect.NotSupportedPeriod")); } details.ScheduleDetails.PaymentPeriod.TotalBillingCycles = processPaymentRequest.RecurringTotalCycles; details.ScheduleDetails.PaymentPeriod.TotalBillingCyclesSpecified = true; @@ -253,7 +253,7 @@ public override ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentReque CreateRecurringPaymentsProfileResponseType response = service.CreateRecurringPaymentsProfile(req); string error = ""; - bool success = PayPalHelper.CheckSuccess(Helper, response, out error); + bool success = PayPalHelper.CheckSuccess(Services.Localization, response, out error); if (success) { result.NewPaymentStatus = PaymentStatus.Pending; diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs index 2df564d924..69c325af14 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs @@ -14,6 +14,7 @@ using SmartStore.PayPal.PayPalSvc; using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; +using SmartStore.Services; using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; @@ -60,7 +61,6 @@ public PayPalExpress( _customerService = customerService; _countryService = countryService; _httpContext = httpContext; - } protected override string GetResourceRootKey() @@ -119,6 +119,7 @@ public override CapturePaymentResult Capture(CapturePaymentRequest capturePaymen { var result = new DoCaptureResponseType(); var settings = Services.Settings.LoadSetting(capturePaymentRequest.Order.StoreId); + var currencyCode = Services.WorkContext.WorkingCurrency.CurrencyCode ?? "EUR"; // build the request var req = new DoCaptureReq @@ -127,9 +128,10 @@ public override CapturePaymentResult Capture(CapturePaymentRequest capturePaymen { Version = PayPalHelper.GetApiVersion(), AuthorizationID = capturePaymentRequest.Order.CaptureTransactionId, - Amount = new BasicAmountType { + Amount = new BasicAmountType + { Value = Math.Round(capturePaymentRequest.Order.OrderTotal, 2).ToString("N", new CultureInfo("en-us")), - currencyID = (CurrencyCodeType)Enum.Parse(typeof(CurrencyCodeType), Helper.CurrencyCode, true) + currencyID = (CurrencyCodeType)Enum.Parse(typeof(CurrencyCodeType), currencyCode, true) }, CompleteType = CompleteCodeType.NotComplete } diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalProviderBase.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalProviderBase.cs index 1cd701b319..7aea49dbd3 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalProviderBase.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalProviderBase.cs @@ -6,7 +6,6 @@ using System.Text; using System.Web; using System.Web.Routing; -using Autofac; using SmartStore.Core.Configuration; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; @@ -18,7 +17,6 @@ using SmartStore.Services; using SmartStore.Services.Orders; using SmartStore.Services.Payments; -using SmartStore.Web.Framework.Plugins; namespace SmartStore.PayPal { @@ -37,23 +35,8 @@ protected PayPalProviderBase() public IOrderTotalCalculationService OrderTotalCalculationService { get; set; } - public IComponentContext ComponentContext { get; set; } - protected abstract string GetResourceRootKey(); - private PluginHelper _helper; - public PluginHelper Helper - { - get - { - if (_helper == null) - { - _helper = new PluginHelper(this.ComponentContext, "SmartStore.PayPal", GetResourceRootKey()); - } - return _helper; - } - } - /// /// Verifies IPN /// @@ -130,7 +113,8 @@ public override CapturePaymentResult Capture(CapturePaymentRequest capturePaymen }; var settings = Services.Settings.LoadSetting(capturePaymentRequest.Order.StoreId); - string authorizationId = capturePaymentRequest.Order.AuthorizationTransactionId; + var authorizationId = capturePaymentRequest.Order.AuthorizationTransactionId; + var currencyCode = Services.WorkContext.WorkingCurrency.CurrencyCode ?? "EUR"; var req = new DoCaptureReq(); req.DoCaptureRequest = new DoCaptureRequestType(); @@ -138,7 +122,7 @@ public override CapturePaymentResult Capture(CapturePaymentRequest capturePaymen req.DoCaptureRequest.AuthorizationID = authorizationId; req.DoCaptureRequest.Amount = new BasicAmountType(); req.DoCaptureRequest.Amount.Value = Math.Round(capturePaymentRequest.Order.OrderTotal, 2).ToString("N", new CultureInfo("en-us")); - req.DoCaptureRequest.Amount.currencyID = (CurrencyCodeType)Enum.Parse(typeof(CurrencyCodeType), Helper.CurrencyCode, true); + req.DoCaptureRequest.Amount.currencyID = (CurrencyCodeType)Enum.Parse(typeof(CurrencyCodeType), currencyCode, true); req.DoCaptureRequest.CompleteType = CompleteCodeType.Complete; using (var service = new PayPalAPIAASoapBinding()) @@ -148,7 +132,7 @@ public override CapturePaymentResult Capture(CapturePaymentRequest capturePaymen DoCaptureResponseType response = service.DoCapture(req); string error = ""; - bool success = PayPalHelper.CheckSuccess(Helper, response, out error); + bool success = PayPalHelper.CheckSuccess(Services.Localization, response, out error); if (success) { result.NewPaymentStatus = PaymentStatus.Paid; @@ -193,7 +177,7 @@ public override RefundPaymentResult Refund(RefundPaymentRequest request) RefundTransactionResponseType response = service.RefundTransaction(req); string error = string.Empty; - bool Success = PayPalHelper.CheckSuccess(Helper, response, out error); + bool Success = PayPalHelper.CheckSuccess(Services.Localization, response, out error); if (Success) { result.NewPaymentStatus = PaymentStatus.Refunded; @@ -239,7 +223,7 @@ public override VoidPaymentResult Void(VoidPaymentRequest request) DoVoidResponseType response = service.DoVoid(req); string error = ""; - bool success = PayPalHelper.CheckSuccess(Helper, response, out error); + bool success = PayPalHelper.CheckSuccess(Services.Localization, response, out error); if (success) { result.NewPaymentStatus = PaymentStatus.Voided; @@ -281,7 +265,7 @@ public override CancelRecurringPaymentResult CancelRecurringPayment(CancelRecurr var response = service.ManageRecurringPaymentsProfileStatus(req); string error = ""; - if (!PayPalHelper.CheckSuccess(Helper, response, out error)) + if (!PayPalHelper.CheckSuccess(Services.Localization, response, out error)) { result.AddError(error); } diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalHelper.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalHelper.cs index fd1c3a32de..98fcf58a94 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalHelper.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalHelper.cs @@ -6,7 +6,7 @@ using SmartStore.Core.Domain.Payments; using SmartStore.PayPal.PayPalSvc; using SmartStore.PayPal.Settings; -using SmartStore.Web.Framework.Plugins; +using SmartStore.Services.Localization; namespace SmartStore.PayPal.Services { @@ -71,7 +71,7 @@ public static PaymentStatus GetPaymentStatus(string paymentStatus, string pendin /// response /// Error message if exists /// True - response OK; otherwise, false - public static bool CheckSuccess(PluginHelper helper, AbstractResponseType abstractResponse, out string errorMsg) + public static bool CheckSuccess(ILocalizationService localization, AbstractResponseType abstractResponse, out string errorMsg) { bool success = false; StringBuilder sb = new StringBuilder(); @@ -92,8 +92,8 @@ public static bool CheckSuccess(PluginHelper helper, AbstractResponseType abstra { sb.Append(Environment.NewLine); } - sb.AppendLine("{0}: {1}".FormatWith(helper.GetResource("Admin.System.Log.Fields.FullMessage"), errorType.LongMessage)); - sb.AppendLine("{0}: {1}".FormatWith(helper.GetResource("Admin.System.Log.Fields.ShortMessage"), errorType.ShortMessage)); + sb.AppendLine("{0}: {1}".FormatWith(localization.GetResource("Admin.System.Log.Fields.FullMessage"), errorType.LongMessage)); + sb.AppendLine("{0}: {1}".FormatWith(localization.GetResource("Admin.System.Log.Fields.ShortMessage"), errorType.ShortMessage)); sb.Append("Code: ").Append(errorType.ErrorCode).Append(Environment.NewLine); } } diff --git a/src/Presentation/SmartStore.Web.Framework/Plugins/PluginHelper.cs b/src/Presentation/SmartStore.Web.Framework/Plugins/PluginHelper.cs deleted file mode 100644 index f18077c900..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/Plugins/PluginHelper.cs +++ /dev/null @@ -1,226 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Web.Mvc; -using System.Xml; -using Autofac; -using SmartStore.Core; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Plugins; -using SmartStore.Services.Directory; -using SmartStore.Services.Localization; -using SmartStore.Utilities; - -namespace SmartStore.Web.Framework.Plugins -{ - public partial class PluginHelper - { - protected readonly IComponentContext _ctx; - private PluginDescriptor _plugin; - private string _interfaceVersion; - private string _pluginResRootKey; - private string _providerResRootKey; - private Language _language; - private string _currencyCode; - private Currency _euroCurrency; - private Dictionary _resMap = new Dictionary(); - - public PluginHelper(IComponentContext componentContext, string systemName, string providerResRootKey = null /* Legacy */) - { - Guard.ArgumentNotEmpty(() => systemName); - - _ctx = componentContext; - SystemName = systemName; - _providerResRootKey = providerResRootKey.NullEmpty(); - } - - public static string NotSpecified - { - get - { - return "__nospec__"; // explicitly do not set a field - } - } - - public string SystemName { get; set; } - - public PluginDescriptor Plugin - { - get - { - if (_plugin == null) - { - _plugin = _ctx.Resolve().GetPluginDescriptorBySystemName(SystemName); - - if (_plugin == null) - { - var provider = _ctx.Resolve().GetProvider(SystemName); - if (provider != null) - { - _plugin = provider.Metadata.PluginDescriptor; - } - } - } - return _plugin; - } - } - - public string InterfaceVersion - { - get - { - if (_interfaceVersion == null) - { - _interfaceVersion = "{0}_v{1}".FormatWith(CommonHelper.GetAppSetting("sm:ApplicationName"), Plugin.Version); - } - return _interfaceVersion; - } - } - - public Language Language - { - get - { - if (_language == null) - { - _language = _ctx.Resolve().WorkingLanguage; - } - return _language; - } - } - - public bool IsLanguageGerman - { - get - { - return Language.UniqueSeoCode.IsCaseInsensitiveEqual("DE"); - } - } - - public string CurrencyCode - { - get - { - try - { - if (_currencyCode == null) - { - _currencyCode = _ctx.Resolve().WorkingCurrency.CurrencyCode; - } - } - catch (Exception) - { - } - return _currencyCode ?? "EUR"; - } - } - - public Currency EuroCurrency - { - get - { - if (_euroCurrency == null) - { - _euroCurrency = _ctx.Resolve().GetCurrencyByCode("EUR"); - } - return _euroCurrency; - } - } - - public string GetResource(string keyOrShortKey) - { - string res = ""; - - try - { - if (keyOrShortKey.HasValue()) - { - var key = keyOrShortKey; - var isFullExpr = key.Contains('.'); - var isProvider = !isFullExpr && _providerResRootKey != null; - - if (_pluginResRootKey == null) - { - _pluginResRootKey = Plugin.ResourceRootKey.HasValue() ? Plugin.ResourceRootKey : "Plugins.{0}".FormatWith(SystemName); - } - - if (!isFullExpr) - { - key = "{0}.{1}".FormatWith(_providerResRootKey ?? _pluginResRootKey, key); - } - - if (_resMap.ContainsKey(key)) - { - return _resMap[key]; - } - - var loc = _ctx.Resolve(); - - res = loc.GetResource(key, returnEmptyIfNotFound: true).NullEmpty(); - - if (res == null && isProvider) - { - // No match with provider root key! Try it again with plugin root key as fallback. - res = loc.GetResource("{0}.{1}".FormatWith(_pluginResRootKey, keyOrShortKey), returnEmptyIfNotFound: true).NullEmpty(); - } - - if (res == null) - res = key; - - _resMap[keyOrShortKey] = res; - } - } - catch (Exception ex) - { - ex.Dump(); - } - - return res; - } - - public XmlDocument CreateXmlDocument(Func content) - { - XmlDocument doc = null; - XmlWriterSettings settings = new XmlWriterSettings(); - settings.Encoding = new UTF8Encoding(false); - - using (MemoryStream ms = new MemoryStream()) - { - using (XmlWriter xw = XmlWriter.Create(ms, settings)) - { - if (content(xw)) - { - xw.Flush(); - - doc = new XmlDocument(); - doc.LoadXml(Encoding.UTF8.GetString(ms.ToArray())); - } - - xw.Close(); - ms.Close(); - return doc; - } - } - } - - public List AvailableCurrencies() - { - var lst = new List(); - var allCurrencies = _ctx.Resolve().GetAllCurrencies(false); - - foreach (var c in allCurrencies) - { - lst.Add(new SelectListItem() - { - Text = c.Name, - Value = c.Id.ToString() - }); - } - return lst; - } - - } -} diff --git a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj index 42eaebe5df..dba5ac1aaf 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj +++ b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj @@ -276,7 +276,6 @@ - From 9fdbf2e49c509b2bbfead60d738cc19f89d28f4c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 25 Sep 2015 10:54:05 +0200 Subject: [PATCH 085/874] Minor CSS refactoring --- .../Views/AmazonPay/Configure.cshtml | 28 +++++++++---------- .../Views/PayPalDirect/Configure.cshtml | 28 +++++++++---------- .../Views/PayPalExpress/Configure.cshtml | 28 +++++++++---------- .../Views/PayPalStandard/Configure.cshtml | 28 +++++++++---------- .../Administration/Content/admin.less | 19 ------------- 5 files changed, 52 insertions(+), 79 deletions(-) diff --git a/src/Plugins/SmartStore.AmazonPay/Views/AmazonPay/Configure.cshtml b/src/Plugins/SmartStore.AmazonPay/Views/AmazonPay/Configure.cshtml index f8193c3c87..3d77fb69e4 100644 --- a/src/Plugins/SmartStore.AmazonPay/Views/AmazonPay/Configure.cshtml +++ b/src/Plugins/SmartStore.AmazonPay/Views/AmazonPay/Configure.cshtml @@ -9,21 +9,19 @@ Html.AddScriptParts(true, Url.Content("~/Plugins/SmartStore.AmazonPay/Scripts/smartstore.amazonpay.js")); } - - - - - -
    -
    - - @Html.Raw(@T("Plugins.Payments.AmazonPay.AdminInstruction")) -
    -
    - - - -
    +
    +
    +
    + + @Html.Raw(@T("Plugins.Payments.AmazonPay.AdminInstruction")) +
    +
    +
    + + + +
    +
    @Html.Action("StoreScopeConfiguration", "Setting", new { area = "Admin" }) diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/Configure.cshtml index febf02c057..dc3e1dbfbd 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/Configure.cshtml @@ -6,21 +6,19 @@ Html.AddCssFileParts(true, "~/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css"); } - - - - - -
    -
    - - @Html.Raw(T("Plugins.Payments.PayPalDirect.AdminInstruction")) -
    -
    - - - -
    +
    +
    +
    + + @Html.Raw(T("Plugins.Payments.PayPalDirect.AdminInstruction")) +
    +
    +
    + + + +
    +
    @Html.Action("StoreScopeConfiguration", "Setting", new { area = "Admin" }) diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/Configure.cshtml index 3ab559894a..b4f4d7cd37 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/Configure.cshtml @@ -7,21 +7,19 @@ Html.AddCssFileParts(true, "~/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css"); } - - - - - -
    -
    - - @Html.Raw(T("Plugins.Payments.PayPalExpress.AdminInstruction")) -
    -
    - - - -
    +
    +
    +
    + + @Html.Raw(T("Plugins.Payments.PayPalExpress.AdminInstruction")) +
    +
    +
    + + + +
    +
    @Html.Action("StoreScopeConfiguration", "Setting", new { area = "Admin" }) diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml index 561f35a72d..c4b1c4b0eb 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml @@ -5,21 +5,19 @@ Layout = ""; } - - - - - -
    -
    - - @Html.Raw(T("Plugins.Payments.PayPalStandard.AdminInstruction")) -
    -
    - - - -
    +
    +
    +
    + + @Html.Raw(T("Plugins.Payments.PayPalStandard.AdminInstruction")) +
    +
    +
    + + + +
    +
    @Html.Action("StoreScopeConfiguration", "Setting", new { area = "Admin" }) diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index 8ce3a53e84..895e5f636b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -821,25 +821,6 @@ tr.adminSeparator hr { padding: 0; } -/* obsolete, use plugin-configuration instead */ -.plugin-intro-table { - width: 100%; - clear: both; - - td { - vertical-align: top; - } - - td:first-child { - width: 80%; - } - - td:last-child { - float: right; - padding-left: 30px; - } -} - .plugin-configuration { display: table; width: 100%; From 98d361b5a1d870fc1da1ec66a0dff7758e026ec1 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 25 Sep 2015 18:42:17 +0200 Subject: [PATCH 086/874] Minor fixes --- src/Libraries/SmartStore.Core/Utilities/Inflector.cs | 2 +- src/Libraries/SmartStore.Core/Utilities/SeoHelper.cs | 10 ---------- src/Plugins/SmartStore.DevTools/AdminMenu.cs | 2 +- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Utilities/Inflector.cs b/src/Libraries/SmartStore.Core/Utilities/Inflector.cs index 02f505f7b6..f02ecb3784 100644 --- a/src/Libraries/SmartStore.Core/Utilities/Inflector.cs +++ b/src/Libraries/SmartStore.Core/Utilities/Inflector.cs @@ -6,7 +6,7 @@ namespace SmartStore.Utilities { /// - /// Implementation of the Infelctor in Ruby that transforms words from singular to plural, + /// Implementation of the Inflector in Ruby that transforms words from singular to plural, /// class names to table names, modularized class names to ones without, and class names to foreign keys /// // TODO: Inflector ist leider Englisch! Irgend 'ne Chance das zu lokalisieren??!! diff --git a/src/Libraries/SmartStore.Core/Utilities/SeoHelper.cs b/src/Libraries/SmartStore.Core/Utilities/SeoHelper.cs index 0db2eadde7..0c71d1d6ba 100644 --- a/src/Libraries/SmartStore.Core/Utilities/SeoHelper.cs +++ b/src/Libraries/SmartStore.Core/Utilities/SeoHelper.cs @@ -41,16 +41,6 @@ public static string GetSeName(string name, bool convertNonWesternChars, bool al c2 = _seoCharacterTable[c2]; } - // codehint: sm-edit - //if (allowUnicodeCharsInUrls) - //{ - // if (char.IsLetterOrDigit(c) || okChars.Contains(c2)) - // sb.Append(c2); - //} - //else if (okChars.Contains(c2)) - //{ - // sb.Append(c2); - //} switch (c2) { case "ä": diff --git a/src/Plugins/SmartStore.DevTools/AdminMenu.cs b/src/Plugins/SmartStore.DevTools/AdminMenu.cs index d00117376f..25ae68e381 100644 --- a/src/Plugins/SmartStore.DevTools/AdminMenu.cs +++ b/src/Plugins/SmartStore.DevTools/AdminMenu.cs @@ -12,7 +12,7 @@ protected override void BuildMenuCore(TreeNode pluginsNode) .Icon("code") .Action("ConfigurePlugin", "Plugin", new { systemName = "SmartStore.DevTools", area = "Admin" }) .ToItem(); - + pluginsNode.Prepend(menuItem); // uncomment to add to admin menu (see plugin sub-menu) From 0edcab1e7dbd37575824aaae420870ce61c80e00 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 25 Sep 2015 18:44:32 +0200 Subject: [PATCH 087/874] SEO: H1 instead of H2 in product reviews page --- src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml b/src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml index 60a130bf72..2419235e89 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/Reviews.cshtml @@ -10,9 +10,9 @@

    @T("Reviews.ProductReviewsFor")

    From 31cf44f64569d3fcc791f94cf508c1faa4ab16f3 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 28 Sep 2015 10:13:01 +0200 Subject: [PATCH 088/874] Convert order XML export to support new export framework --- .../Plugins/Providers/IsHiddenAttribute.cs | 15 + .../Plugins/Providers/ProviderMetadata.cs | 5 + .../SmartStore.Core/SmartStore.Core.csproj | 1 + .../DataExchange/ExpandoExtensions.cs | 44 +- .../ExportProvider/OrderExportXmlProvider.cs | 519 ++++++++++++++++++ .../DataExchange/ExportService.cs | 12 +- .../ExportTask/ExportOrderDataContext.cs | 21 +- .../ExportTask/ExportProfileTask.cs | 116 +++- .../ExportTask/ExportProfileTaskContext.cs | 10 +- .../DataExchange/IExportService.cs | 3 +- .../ExportImport/ExportManager.cs | 144 ----- .../ExportImport/IExportManager.cs | 7 - .../Shipping/IShipmentService.cs | 10 +- .../Shipping/ShipmentService.cs | 19 + .../SmartStore.Services.csproj | 1 + .../DependencyRegistrar.cs | 13 + .../Controllers/ExportController.cs | 2 +- .../Controllers/OrderController.cs | 44 +- 18 files changed, 794 insertions(+), 192 deletions(-) create mode 100644 src/Libraries/SmartStore.Core/Plugins/Providers/IsHiddenAttribute.cs create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXmlProvider.cs diff --git a/src/Libraries/SmartStore.Core/Plugins/Providers/IsHiddenAttribute.cs b/src/Libraries/SmartStore.Core/Plugins/Providers/IsHiddenAttribute.cs new file mode 100644 index 0000000000..6ba32b9d5a --- /dev/null +++ b/src/Libraries/SmartStore.Core/Plugins/Providers/IsHiddenAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace SmartStore.Core.Plugins +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + public sealed class IsHiddenAttribute : Attribute + { + public IsHiddenAttribute(bool isHidden) + { + IsHidden = isHidden; + } + + public bool IsHidden { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs b/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs index 4423d1a073..a579f72977 100644 --- a/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs +++ b/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs @@ -68,6 +68,11 @@ public class ProviderMetadata /// public bool IsEditable { get; set; } + /// + /// Gets or sets a value indicating whether the provider is hidden (by decorating with ) + /// + public bool IsHidden { get; set; } + /// /// Gets or sets an array of values that reflects what export projection is supported by a provider /// diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index a3ab21c916..ab5fd94130 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -188,6 +188,7 @@ + diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs index baddabd869..39f6126ea9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs @@ -7,6 +7,7 @@ using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Stores; using SmartStore.Services.Localization; using SmartStore.Services.Media; @@ -145,7 +146,6 @@ public static ExpandoObject ToExpando(this Customer customer, int languageId) expando.Email = customer.Email; //Password... we do not provide that data expando.PasswordFormatId = customer.PasswordFormatId; - expando.PasswordFormat = customer.PasswordFormat; expando.AdminComment = customer.AdminComment; expando.IsTaxExempt = customer.IsTaxExempt; expando.AffiliateId = customer.AffiliateId; @@ -433,7 +433,7 @@ public static ExpandoObject ToExpando(this Product product, int languageId) expando.UnlimitedDownloads = product.UnlimitedDownloads; expando.MaxNumberOfDownloads = product.MaxNumberOfDownloads; expando.DownloadExpirationDays = product.DownloadExpirationDays; - expando.DownloadActivationType = product.DownloadActivationType; + expando.DownloadActivationTypeId = product.DownloadActivationTypeId; expando.HasSampleDownload = product.HasSampleDownload; expando.SampleDownloadId = product.SampleDownloadId; expando.HasUserAgreement = product.HasUserAgreement; @@ -576,12 +576,11 @@ public static ExpandoObject ToExpando(this Order order, int languageId, ILocaliz expando.OrderStatus = order.OrderStatus.GetLocalizedEnum(localization, languageId); expando.PaymentStatus = order.PaymentStatus.GetLocalizedEnum(localization, languageId); expando.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(localization, languageId); - expando.CustomerTaxDisplayType = order.CustomerTaxDisplayType; - expando.TaxRatesDictionary = order.TaxRatesDictionary; expando.BillingAddress = null; expando.ShippingAddress = null; expando.Store = null; + expando.Shipments = null; return expando as ExpandoObject; } @@ -619,5 +618,42 @@ public static ExpandoObject ToExpando(this OrderItem orderItem, int languageId) return expando as ExpandoObject; } + + public static ExpandoObject ToExpando(this Shipment shipment) + { + if (shipment == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = shipment; + + expando.Id = shipment.Id; + expando.OrderId = shipment.OrderId; + expando.TrackingNumber = shipment.TrackingNumber; + expando.TotalWeight = shipment.TotalWeight; + expando.ShippedDateUtc = shipment.ShippedDateUtc; + expando.DeliveryDateUtc = shipment.DeliveryDateUtc; + expando.CreatedOnUtc = shipment.CreatedOnUtc; + + expando.ShipmentItems = null; + + return expando as ExpandoObject; + } + + public static ExpandoObject ToExpando(this ShipmentItem shipmentItem) + { + if (shipmentItem == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = shipmentItem; + + expando.Id = shipmentItem.Id; + expando.ShipmentId = shipmentItem.ShipmentId; + expando.OrderItemId = shipmentItem.OrderItemId; + expando.Quantity = shipmentItem.Quantity; + + return expando as ExpandoObject; + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXmlProvider.cs new file mode 100644 index 0000000000..ce6d921e5e --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXmlProvider.cs @@ -0,0 +1,519 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using System.Xml; +using SmartStore.Core; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.DataExchange.ExportProvider +{ + [SystemName("Exports.SmartStoreNetOrderXml")] + [FriendlyName("SmartStore.NET order export")] + [IsHidden(true)] + public class OrderExportXmlProvider : IExportProvider + { + private void WriteAddress(XmlWriter writer, string node, dynamic address, CultureInfo culture) + { + if (address == null) + return; + + int? countryId = address.CountryId; + int? stateProvinceId = address.StateProvinceId; + + writer.WriteStartElement(node); + + writer.WriteElementString("Id", ((int)address.Id).ToString()); + writer.WriteElementString("FirstName", (string)address.FirstName); + writer.WriteElementString("LastName", (string)address.LastName); + writer.WriteElementString("Email", (string)address.Email); + writer.WriteElementString("Company", (string)address.Company); + writer.WriteElementString("CountryId", countryId.HasValue ? countryId.Value.ToString() : ""); + writer.WriteElementString("StateProvinceId", stateProvinceId.HasValue ? stateProvinceId.Value.ToString() : ""); + writer.WriteElementString("City", (string)address.City); + writer.WriteElementString("Address1", (string)address.Address1); + writer.WriteElementString("Address2", (string)address.Address2); + writer.WriteElementString("ZipPostalCode", (string)address.ZipPostalCode); + writer.WriteElementString("PhoneNumber", (string)address.PhoneNumber); + writer.WriteElementString("FaxNumber", (string)address.FaxNumber); + writer.WriteElementString("CreatedOnUtc", ((DateTime)address.CreatedOnUtc).ToString(culture)); + + if (address.Country != null) + { + dynamic country = address.Country; + + writer.WriteStartElement("Country"); + writer.WriteElementString("Id", ((int)country.Id).ToString()); + writer.WriteElementString("Name", (string)country.Name); + writer.WriteElementString("AllowsBilling", ((bool)country.AllowsBilling).ToString()); + writer.WriteElementString("AllowsShipping", ((bool)country.AllowsShipping).ToString()); + writer.WriteElementString("TwoLetterIsoCode", (string)country.TwoLetterIsoCode); + writer.WriteElementString("ThreeLetterIsoCode", (string)country.ThreeLetterIsoCode); + writer.WriteElementString("NumericIsoCode", ((int)country.NumericIsoCode).ToString()); + writer.WriteElementString("SubjectToVat", ((bool)country.SubjectToVat).ToString()); + writer.WriteElementString("Published", ((bool)country.Published).ToString()); + writer.WriteElementString("DisplayOrder", ((int)country.DisplayOrder).ToString()); + writer.WriteElementString("LimitedToStores", ((bool)country.LimitedToStores).ToString()); + writer.WriteEndElement(); // Country + } + + if (address.StateProvince != null) + { + dynamic stateProvince = address.StateProvince; + + writer.WriteStartElement("StateProvince"); + writer.WriteElementString("Id", ((int)stateProvince.Id).ToString()); + writer.WriteElementString("CountryId", ((int)stateProvince.CountryId).ToString()); + writer.WriteElementString("Name", (string)stateProvince.Name); + writer.WriteElementString("Abbreviation", (string)stateProvince.Abbreviation); + writer.WriteElementString("Published", ((bool)stateProvince.Published).ToString()); + writer.WriteElementString("DisplayOrder", ((int)stateProvince.DisplayOrder).ToString()); + writer.WriteEndElement(); // StateProvince + } + + writer.WriteEndElement(); // node + } + + private void WriteCurrency(XmlWriter writer, string node, dynamic currency, CultureInfo culture) + { + if (currency == null) + return; + + writer.WriteStartElement(node); + + writer.WriteElementString("Id", ((int)currency.Id).ToString()); + writer.WriteElementString("Name", (string)currency.Name); + writer.WriteElementString("CurrencyCode", (string)currency.CurrencyCode); + writer.WriteElementString("Rate", ((decimal)currency.Rate).ToString(culture)); + writer.WriteElementString("DisplayLocale", (string)currency.DisplayLocale); + writer.WriteElementString("CustomFormatting", (string)currency.CustomFormatting); + writer.WriteElementString("LimitedToStores", ((bool)currency.LimitedToStores).ToString()); + writer.WriteElementString("Published", ((bool)currency.Published).ToString()); + writer.WriteElementString("DisplayOrder", ((int)currency.DisplayOrder).ToString()); + writer.WriteElementString("CreatedOnUtc", ((DateTime)currency.CreatedOnUtc).ToString(culture)); + writer.WriteElementString("UpdatedOnUtc", ((DateTime)currency.UpdatedOnUtc).ToString(culture)); + writer.WriteElementString("DomainEndings", (string)currency.DomainEndings); + + writer.WriteEndElement(); // node + } + + private void WriteProduct(XmlWriter writer, string node, dynamic product, CultureInfo culture) + { + if (product == null) + return; + + int? downloadExpirationDays = product.DownloadExpirationDays; + int? sampleDownloadId = product.SampleDownloadId; + decimal? specialPrice = product.SpecialPrice; + DateTime? specialPriceStartDateTimeUtc = product.SpecialPriceStartDateTimeUtc; + DateTime? specialPriceEndDateTimeUtc = product.SpecialPriceEndDateTimeUtc; + DateTime? availableStartDateTimeUtc = product.AvailableStartDateTimeUtc; + DateTime? availableEndDateTimeUtc = product.AvailableEndDateTimeUtc; + decimal? basePriceAmount = product.BasePriceAmount; + int? basePriceBaseAmount = product.BasePriceBaseAmount; + decimal? lowestAttributeCombinationPrice = product.LowestAttributeCombinationPrice; + + writer.WriteStartElement(node); + + writer.WriteElementString("Id", ((int)product.Id).ToString()); + writer.WriteElementString("Name", ((string)product.Name).RemoveInvalidXmlChars()); + writer.WriteElementString("SeName", (string)product.SeName); + writer.WriteElementString("ShortDescription", ((string)product.ShortDescription).RemoveInvalidXmlChars()); + writer.WriteElementString("FullDescription", ((string)product.FullDescription).RemoveInvalidXmlChars()); + writer.WriteElementString("AdminComment", ((string)product.AdminComment).RemoveInvalidXmlChars()); + writer.WriteElementString("ProductTemplateId", ((int)product.ProductTemplateId).ToString()); + writer.WriteElementString("ProductTemplateViewPath", (string)product._ProductTemplateViewPath); + writer.WriteElementString("ShowOnHomePage", ((bool)product.ShowOnHomePage).ToString()); + writer.WriteElementString("HomePageDisplayOrder", ((int)product.HomePageDisplayOrder).ToString()); + writer.WriteElementString("MetaKeywords", ((string)product.MetaKeywords).RemoveInvalidXmlChars()); + writer.WriteElementString("MetaDescription", ((string)product.MetaDescription).RemoveInvalidXmlChars()); + writer.WriteElementString("MetaTitle", ((string)product.MetaTitle).RemoveInvalidXmlChars()); + writer.WriteElementString("AllowCustomerReviews", ((bool)product.AllowCustomerReviews).ToString()); + writer.WriteElementString("ApprovedRatingSum", ((int)product.ApprovedRatingSum).ToString()); + writer.WriteElementString("NotApprovedRatingSum", ((int)product.NotApprovedRatingSum).ToString()); + writer.WriteElementString("ApprovedTotalReviews", ((int)product.ApprovedTotalReviews).ToString()); + writer.WriteElementString("NotApprovedTotalReviews", ((int)product.NotApprovedTotalReviews).ToString()); + writer.WriteElementString("Published", ((bool)product.Published).ToString()); + writer.WriteElementString("CreatedOnUtc", ((DateTime)product.CreatedOnUtc).ToString(culture)); + writer.WriteElementString("UpdatedOnUtc", ((DateTime)product.UpdatedOnUtc).ToString(culture)); + writer.WriteElementString("SubjectToAcl", ((bool)product.SubjectToAcl).ToString()); + writer.WriteElementString("LimitedToStores", ((bool)product.LimitedToStores).ToString()); + writer.WriteElementString("ProductTypeId", ((int)product.ProductTypeId).ToString()); + writer.WriteElementString("ParentGroupedProductId", ((int)product.ParentGroupedProductId).ToString()); + writer.WriteElementString("Sku", (string)product.Sku); + writer.WriteElementString("ManufacturerPartNumber", (string)product.ManufacturerPartNumber); + writer.WriteElementString("Gtin", (string)product.Gtin); + writer.WriteElementString("IsGiftCard", ((bool)product.IsGiftCard).ToString()); + writer.WriteElementString("GiftCardTypeId", ((int)product.GiftCardTypeId).ToString()); + writer.WriteElementString("RequireOtherProducts", ((bool)product.RequireOtherProducts).ToString()); + writer.WriteElementString("RequiredProductIds", (string)product.RequiredProductIds); + writer.WriteElementString("AutomaticallyAddRequiredProducts", ((bool)product.AutomaticallyAddRequiredProducts).ToString()); + writer.WriteElementString("IsDownload", ((bool)product.IsDownload).ToString()); + writer.WriteElementString("DownloadId", ((int)product.DownloadId).ToString()); + writer.WriteElementString("UnlimitedDownloads", ((bool)product.UnlimitedDownloads).ToString()); + writer.WriteElementString("MaxNumberOfDownloads", ((int)product.MaxNumberOfDownloads).ToString()); + writer.WriteElementString("DownloadExpirationDays", downloadExpirationDays.HasValue ? downloadExpirationDays.Value.ToString() : ""); + writer.WriteElementString("DownloadActivationTypeId", ((int)product.DownloadActivationTypeId).ToString()); + writer.WriteElementString("HasSampleDownload", ((bool)product.HasSampleDownload).ToString()); + writer.WriteElementString("SampleDownloadId", sampleDownloadId.HasValue ? sampleDownloadId.Value.ToString() : ""); + writer.WriteElementString("HasUserAgreement", ((bool)product.HasUserAgreement).ToString()); + writer.WriteElementString("UserAgreementText", ((string)product.UserAgreementText).RemoveInvalidXmlChars()); + writer.WriteElementString("IsRecurring", ((bool)product.IsRecurring).ToString()); + writer.WriteElementString("RecurringCycleLength", ((int)product.RecurringCycleLength).ToString()); + writer.WriteElementString("RecurringCyclePeriodId", ((int)product.RecurringCyclePeriodId).ToString()); + writer.WriteElementString("RecurringTotalCycles", ((int)product.RecurringTotalCycles).ToString()); + writer.WriteElementString("IsShipEnabled", ((bool)product.IsShipEnabled).ToString()); + writer.WriteElementString("IsFreeShipping", ((bool)product.IsFreeShipping).ToString()); + writer.WriteElementString("AdditionalShippingCharge", ((decimal)product.AdditionalShippingCharge).ToString(culture)); + writer.WriteElementString("IsTaxExempt", ((bool)product.IsTaxExempt).ToString()); + writer.WriteElementString("TaxCategoryId", ((int)product.TaxCategoryId).ToString()); + writer.WriteElementString("ManageInventoryMethodId", ((int)product.ManageInventoryMethodId).ToString()); + writer.WriteElementString("StockQuantity", ((int)product.StockQuantity).ToString()); + writer.WriteElementString("DisplayStockAvailability", ((bool)product.DisplayStockAvailability).ToString()); + writer.WriteElementString("DisplayStockQuantity", ((bool)product.DisplayStockQuantity).ToString()); + writer.WriteElementString("MinStockQuantity", ((int)product.MinStockQuantity).ToString()); + writer.WriteElementString("LowStockActivityId", ((int)product.LowStockActivityId).ToString()); + writer.WriteElementString("NotifyAdminForQuantityBelow", ((int)product.NotifyAdminForQuantityBelow).ToString()); + writer.WriteElementString("BackorderModeId", ((int)product.BackorderModeId).ToString()); + writer.WriteElementString("AllowBackInStockSubscriptions", ((bool)product.AllowBackInStockSubscriptions).ToString()); + writer.WriteElementString("OrderMinimumQuantity", ((int)product.OrderMinimumQuantity).ToString()); + writer.WriteElementString("OrderMaximumQuantity", ((int)product.OrderMaximumQuantity).ToString()); + writer.WriteElementString("AllowedQuantities", (string)product.AllowedQuantities); + writer.WriteElementString("DisableBuyButton", ((bool)product.DisableBuyButton).ToString()); + writer.WriteElementString("DisableWishlistButton", ((bool)product.DisableWishlistButton).ToString()); + writer.WriteElementString("AvailableForPreOrder", ((bool)product.AvailableForPreOrder).ToString()); + writer.WriteElementString("CallForPrice", ((bool)product.CallForPrice).ToString()); + writer.WriteElementString("Price", ((decimal)product.Price).ToString(culture)); + writer.WriteElementString("OldPrice", ((decimal)product.OldPrice).ToString(culture)); + writer.WriteElementString("ProductCost", ((decimal)product.ProductCost).ToString(culture)); + writer.WriteElementString("SpecialPrice", specialPrice.HasValue ? specialPrice.Value.ToString(culture) : ""); + writer.WriteElementString("SpecialPriceStartDateTimeUtc", specialPriceStartDateTimeUtc.HasValue ? specialPriceStartDateTimeUtc.Value.ToString(culture) : ""); + writer.WriteElementString("SpecialPriceEndDateTimeUtc", specialPriceEndDateTimeUtc.HasValue ? specialPriceEndDateTimeUtc.Value.ToString(culture) : ""); + writer.WriteElementString("CustomerEntersPrice", ((bool)product.CustomerEntersPrice).ToString()); + writer.WriteElementString("MinimumCustomerEnteredPrice", ((decimal)product.MinimumCustomerEnteredPrice).ToString(culture)); + writer.WriteElementString("MaximumCustomerEnteredPrice", ((decimal)product.MaximumCustomerEnteredPrice).ToString(culture)); + writer.WriteElementString("HasTierPrices", ((bool)product.HasTierPrices).ToString()); + writer.WriteElementString("HasDiscountsApplied", ((bool)product.HasDiscountsApplied).ToString()); + writer.WriteElementString("Weight", ((decimal)product.Weight).ToString(culture)); + writer.WriteElementString("Length", ((decimal)product.Length).ToString(culture)); + writer.WriteElementString("Width", ((decimal)product.Width).ToString(culture)); + writer.WriteElementString("Height", ((decimal)product.Height).ToString(culture)); + writer.WriteElementString("AvailableStartDateTimeUtc", availableStartDateTimeUtc.HasValue ? availableStartDateTimeUtc.Value.ToString(culture) : ""); + writer.WriteElementString("AvailableEndDateTimeUtc", availableEndDateTimeUtc.HasValue ? availableEndDateTimeUtc.Value.ToString(culture) : ""); + writer.WriteElementString("BasePriceEnabled", ((bool)product.BasePriceEnabled).ToString()); + writer.WriteElementString("BasePriceMeasureUnit", (string)product.BasePriceMeasureUnit); + writer.WriteElementString("BasePriceAmount", basePriceAmount.HasValue ? basePriceAmount.Value.ToString(culture) : ""); + writer.WriteElementString("BasePriceBaseAmount", basePriceBaseAmount.HasValue ? basePriceBaseAmount.Value.ToString() : ""); + writer.WriteElementString("BasePriceHasValue", ((bool)product.BasePriceHasValue).ToString()); + writer.WriteElementString("BasePriceInfo", (string)product._BasePriceInfo); + writer.WriteElementString("VisibleIndividually", ((bool)product.VisibleIndividually).ToString()); + writer.WriteElementString("DisplayOrder", ((int)product.DisplayOrder).ToString()); + writer.WriteElementString("BundleTitleText", ((string)product.BundleTitleText).RemoveInvalidXmlChars()); + writer.WriteElementString("BundlePerItemPricing", ((bool)product.BundlePerItemPricing).ToString()); + writer.WriteElementString("BundlePerItemShipping", ((bool)product.BundlePerItemShipping).ToString()); + writer.WriteElementString("BundlePerItemShoppingCart", ((bool)product.BundlePerItemShoppingCart).ToString()); + writer.WriteElementString("LowestAttributeCombinationPrice", lowestAttributeCombinationPrice.HasValue ? lowestAttributeCombinationPrice.Value.ToString(culture) : ""); + writer.WriteElementString("IsEsd", ((bool)product.IsEsd).ToString()); + + if (product.DeliveryTime != null) + { + writer.WriteStartElement("DeliveryTime"); + writer.WriteElementString("Id", ((int)product.DeliveryTime.Id).ToString()); + writer.WriteElementString("Name", ((string)product.DeliveryTime.Name).RemoveInvalidXmlChars()); + writer.WriteElementString("DisplayLocale", ((string)product.DeliveryTime.DisplayLocale).RemoveInvalidXmlChars()); + writer.WriteElementString("ColorHexValue", (string)product.DeliveryTime.ColorHexValue); + writer.WriteElementString("DisplayOrder", ((int)product.DeliveryTime.DisplayOrder).ToString()); + writer.WriteEndElement(); // DeliveryTime + } + + if (product.QuantityUnit != null) + { + writer.WriteStartElement("QuantityUnit"); + writer.WriteElementString("Id", ((int)product.QuantityUnit.Id).ToString()); + writer.WriteElementString("Name", ((string)product.QuantityUnit.Name).RemoveInvalidXmlChars()); + writer.WriteElementString("Description", ((string)product.QuantityUnit.Description).RemoveInvalidXmlChars()); + writer.WriteElementString("DisplayLocale", ((string)product.QuantityUnit.DisplayLocale).RemoveInvalidXmlChars()); + writer.WriteElementString("DisplayOrder", ((int)product.QuantityUnit.DisplayOrder).ToString()); + writer.WriteElementString("IsDefault", ((bool)product.QuantityUnit.IsDefault).ToString()); + writer.WriteEndElement(); // QuantityUnit + } + + writer.WriteEndElement(); // node + } + + public static string SystemName + { + get { return "Exports.SmartStoreNetOrderXml"; } + } + + public ExportConfigurationInfo ConfigurationInfo + { + get { return null; } + } + + public ExportEntityType EntityType + { + get { return ExportEntityType.Order; } + } + + public string FileExtension + { + get { return "XML"; } + } + + public void Execute(IExportExecuteContext context) + { + var settings = new XmlWriterSettings + { + Encoding = Encoding.UTF8, + CheckCharacters = false, + Indent = true, + IndentChars = "\t" + }; + + var path = context.FilePath; + var invariantCulture = CultureInfo.InvariantCulture; + + context.Log.Information("Creating file " + path); + + using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + using (var writer = XmlWriter.Create(stream, settings)) + { + writer.WriteStartDocument(); + writer.WriteStartElement("Orders"); + writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); + + while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + { + var segment = context.Data.CurrentSegment; + + foreach (dynamic order in segment) + { + if (context.Abort != ExportAbortion.None) + break; + + int orderId = order.Id; + dynamic customer = order.Customer; + dynamic store = order.Store; + + writer.WriteStartElement("Order"); + + try + { + int? shippingAddressId = order.ShippingAddressId; + DateTime? paidDateUtc = order.PaidDateUtc; + int? rewardPointsRemaining = order.RewardPointsRemaining; + + writer.WriteElementString("Id", orderId.ToString()); + writer.WriteElementString("OrderNumber", (string)order.OrderNumber); + writer.WriteElementString("OrderGuid", ((Guid)order.OrderGuid).ToString()); + writer.WriteElementString("StoreId", ((int)order.StoreId).ToString()); + writer.WriteElementString("CustomerId", ((int)order.CustomerId).ToString()); + writer.WriteElementString("BillingAddressId", ((int)order.BillingAddressId).ToString()); + writer.WriteElementString("ShippingAddressId", shippingAddressId.HasValue ? shippingAddressId.Value.ToString() : ""); + writer.WriteElementString("OrderStatusId", ((int)order.OrderStatusId).ToString()); + writer.WriteElementString("ShippingStatusId", ((int)order.ShippingStatusId).ToString()); + writer.WriteElementString("PaymentStatusId", ((int)order.PaymentStatusId).ToString()); + writer.WriteElementString("PaymentMethodSystemName", (string)order.PaymentMethodSystemName); + writer.WriteElementString("CustomerCurrencyCode", (string)order.CustomerCurrencyCode); + writer.WriteElementString("CurrencyRate", ((decimal)order.CurrencyRate).ToString(invariantCulture)); + writer.WriteElementString("CustomerTaxDisplayTypeId", ((int)order.CustomerTaxDisplayTypeId).ToString()); + writer.WriteElementString("VatNumber", (string)order.VatNumber); + writer.WriteElementString("OrderSubtotalInclTax", ((decimal)order.OrderSubtotalInclTax).ToString(invariantCulture)); + writer.WriteElementString("OrderSubtotalExclTax", ((decimal)order.OrderSubtotalExclTax).ToString(invariantCulture)); + writer.WriteElementString("OrderSubTotalDiscountInclTax", ((decimal)order.OrderSubTotalDiscountInclTax).ToString(invariantCulture)); + writer.WriteElementString("OrderSubTotalDiscountExclTax", ((decimal)order.OrderSubTotalDiscountExclTax).ToString(invariantCulture)); + writer.WriteElementString("OrderShippingInclTax", ((decimal)order.OrderShippingInclTax).ToString(invariantCulture)); + writer.WriteElementString("OrderShippingExclTax", ((decimal)order.OrderShippingExclTax).ToString(invariantCulture)); + writer.WriteElementString("OrderShippingTaxRate", ((decimal)order.OrderShippingTaxRate).ToString(invariantCulture)); + writer.WriteElementString("PaymentMethodAdditionalFeeInclTax", ((decimal)order.PaymentMethodAdditionalFeeInclTax).ToString(invariantCulture)); + writer.WriteElementString("PaymentMethodAdditionalFeeExclTax", ((decimal)order.PaymentMethodAdditionalFeeExclTax).ToString(invariantCulture)); + writer.WriteElementString("PaymentMethodAdditionalFeeTaxRate", ((decimal)order.PaymentMethodAdditionalFeeTaxRate).ToString(invariantCulture)); + writer.WriteElementString("TaxRates", (string)order.TaxRates); + writer.WriteElementString("OrderTax", ((decimal)order.OrderTax).ToString(invariantCulture)); + writer.WriteElementString("OrderDiscount", ((decimal)order.OrderDiscount).ToString(invariantCulture)); + writer.WriteElementString("OrderTotal", ((decimal)order.OrderTotal).ToString(invariantCulture)); + writer.WriteElementString("RefundedAmount", ((decimal)order.RefundedAmount).ToString(invariantCulture)); + writer.WriteElementString("RewardPointsWereAdded", ((bool)order.RewardPointsWereAdded).ToString()); + writer.WriteElementString("CheckoutAttributeDescription", ((string)order.CheckoutAttributeDescription).RemoveInvalidXmlChars()); + writer.WriteElementString("CheckoutAttributesXml", ((string)order.CheckoutAttributesXml).RemoveInvalidXmlChars()); + writer.WriteElementString("CustomerLanguageId", ((int)order.CustomerLanguageId).ToString()); + writer.WriteElementString("AffiliateId", ((int)order.AffiliateId).ToString()); + writer.WriteElementString("CustomerIp", (string)order.CustomerIp); + writer.WriteElementString("AllowStoringCreditCardNumber", ((bool)order.AllowStoringCreditCardNumber).ToString()); + writer.WriteElementString("CardType", (string)order.CardType); + writer.WriteElementString("CardName", (string)order.CardName); + writer.WriteElementString("CardNumber", (string)order.CardNumber); + writer.WriteElementString("MaskedCreditCardNumber", (string)order.MaskedCreditCardNumber); + writer.WriteElementString("CardCvv2", (string)order.CardCvv2); + writer.WriteElementString("CardExpirationMonth", (string)order.CardExpirationMonth); + writer.WriteElementString("CardExpirationYear", (string)order.CardExpirationYear); + writer.WriteElementString("AllowStoringDirectDebit", ((bool)order.AllowStoringDirectDebit).ToString()); + writer.WriteElementString("DirectDebitAccountHolder", (string)order.DirectDebitAccountHolder); + writer.WriteElementString("DirectDebitAccountNumber", (string)order.DirectDebitAccountNumber); + writer.WriteElementString("DirectDebitBankCode", (string)order.DirectDebitBankCode); + writer.WriteElementString("DirectDebitBankName", (string)order.DirectDebitBankName); + writer.WriteElementString("DirectDebitBIC", (string)order.DirectDebitBIC); + writer.WriteElementString("DirectDebitCountry", (string)order.DirectDebitCountry); + writer.WriteElementString("DirectDebitIban", (string)order.DirectDebitIban); + writer.WriteElementString("CustomerOrderComment", ((string)order.CustomerOrderComment).RemoveInvalidXmlChars()); + writer.WriteElementString("AuthorizationTransactionId", (string)order.AuthorizationTransactionId); + writer.WriteElementString("AuthorizationTransactionCode", (string)order.AuthorizationTransactionCode); + writer.WriteElementString("AuthorizationTransactionResult", (string)order.AuthorizationTransactionResult); + writer.WriteElementString("CaptureTransactionId", (string)order.CaptureTransactionId); + writer.WriteElementString("CaptureTransactionResult", (string)order.CaptureTransactionResult); + writer.WriteElementString("SubscriptionTransactionId", (string)order.SubscriptionTransactionId); + writer.WriteElementString("PurchaseOrderNumber", (string)order.PurchaseOrderNumber); + writer.WriteElementString("PaidDateUtc", paidDateUtc.HasValue ? paidDateUtc.Value.ToString(invariantCulture) : ""); + writer.WriteElementString("ShippingMethod", (string)order.ShippingMethod); + writer.WriteElementString("ShippingRateComputationMethodSystemName", (string)order.ShippingRateComputationMethodSystemName); + writer.WriteElementString("Deleted", ((bool)order.Deleted).ToString()); + writer.WriteElementString("CreatedOnUtc", ((DateTime)order.CreatedOnUtc).ToString(invariantCulture)); + writer.WriteElementString("UpdatedOnUtc", ((DateTime)order.UpdatedOnUtc).ToString(invariantCulture)); + writer.WriteElementString("RewardPointsRemaining", rewardPointsRemaining.HasValue ? rewardPointsRemaining.Value.ToString() : ""); + writer.WriteElementString("HasNewPaymentNotification", ((bool)order.HasNewPaymentNotification).ToString()); + writer.WriteElementString("OrderStatus", (string)order.OrderStatus); + writer.WriteElementString("PaymentStatus", (string)order.PaymentStatus); + writer.WriteElementString("ShippingStatus", (string)order.ShippingStatus); + + if (customer != null) + { + DateTime? lastLoginDateUtc = customer.LastLoginDateUtc; + + writer.WriteStartElement("Customer"); + writer.WriteElementString("Id", ((int)customer.Id).ToString()); + writer.WriteElementString("CustomerGuid", ((Guid)customer.CustomerGuid).ToString()); + writer.WriteElementString("Username", (string)customer.Username); + writer.WriteElementString("Email", (string)customer.Email); + writer.WriteElementString("PasswordFormatId", ((int)customer.PasswordFormatId).ToString()); + writer.WriteElementString("AdminComment", ((string)customer.AdminComment).RemoveInvalidXmlChars()); + writer.WriteElementString("IsTaxExempt", ((bool)customer.IsTaxExempt).ToString()); + writer.WriteElementString("AffiliateId", ((int)customer.AffiliateId).ToString()); + writer.WriteElementString("Active", ((bool)customer.Active).ToString()); + writer.WriteElementString("Deleted", ((bool)customer.Deleted).ToString()); + writer.WriteElementString("IsSystemAccount", ((bool)customer.IsSystemAccount).ToString()); + writer.WriteElementString("SystemName", (string)customer.SystemName); + writer.WriteElementString("LastIpAddress", (string)customer.LastIpAddress); + writer.WriteElementString("CreatedOnUtc", ((DateTime)customer.CreatedOnUtc).ToString(invariantCulture)); + writer.WriteElementString("LastLoginDateUtc", lastLoginDateUtc.HasValue ? lastLoginDateUtc.Value.ToString(invariantCulture) : ""); + writer.WriteElementString("LastActivityDateUtc", ((DateTime)customer.LastActivityDateUtc).ToString(invariantCulture)); + + WriteAddress(writer, "BillingAddress", customer.BillingAddress, invariantCulture); + WriteAddress(writer, "ShippingAddress", customer.ShippingAddress, invariantCulture); + writer.WriteEndElement(); // Customer + } + + WriteAddress(writer, "BillingAddress", order.BillingAddress, invariantCulture); + WriteAddress(writer, "ShippingAddress", order.ShippingAddress, invariantCulture); + + if (store != null) + { + writer.WriteStartElement("Store"); + writer.WriteElementString("Id", ((int)store.Id).ToString()); + writer.WriteElementString("Name", (string)store.Name); + writer.WriteElementString("Url", (string)store.Url); + writer.WriteElementString("SslEnabled", ((bool)store.SslEnabled).ToString()); + writer.WriteElementString("SecureUrl", (string)store.SecureUrl); + writer.WriteElementString("Hosts", (string)store.Hosts); + writer.WriteElementString("LogoPictureId", ((int)store.LogoPictureId).ToString()); + writer.WriteElementString("DisplayOrder", ((int)store.DisplayOrder).ToString()); + writer.WriteElementString("HtmlBodyId", (string)store.HtmlBodyId); + writer.WriteElementString("ContentDeliveryNetwork", (string)store.ContentDeliveryNetwork); + writer.WriteElementString("PrimaryStoreCurrencyId", ((int)store.PrimaryStoreCurrencyId).ToString()); + writer.WriteElementString("PrimaryExchangeRateCurrencyId", ((int)store.PrimaryExchangeRateCurrencyId).ToString()); + + WriteCurrency(writer, "PrimaryStoreCurrency", store.PrimaryStoreCurrency, invariantCulture); + WriteCurrency(writer, "PrimaryExchangeRateCurrency", store.PrimaryExchangeRateCurrency, invariantCulture); + writer.WriteEndElement(); // Store + } + + writer.WriteStartElement("OrderItems"); + foreach (dynamic orderItem in order.OrderItems) + { + int? licenseDownloadId = orderItem.LicenseDownloadId; + decimal? itemWeight = orderItem.ItemWeight; + + writer.WriteStartElement("OrderItem"); + writer.WriteElementString("Id", ((int)orderItem.Id).ToString()); + writer.WriteElementString("OrderItemGuid", ((Guid)orderItem.OrderItemGuid).ToString()); + writer.WriteElementString("OrderId", ((int)orderItem.OrderId).ToString()); + writer.WriteElementString("ProductId", ((int)orderItem.ProductId).ToString()); + writer.WriteElementString("Quantity", ((int)orderItem.Quantity).ToString()); + writer.WriteElementString("UnitPriceInclTax", ((decimal)orderItem.UnitPriceInclTax).ToString(invariantCulture)); + writer.WriteElementString("UnitPriceExclTax", ((decimal)orderItem.UnitPriceExclTax).ToString(invariantCulture)); + writer.WriteElementString("PriceInclTax", ((decimal)orderItem.PriceInclTax).ToString(invariantCulture)); + writer.WriteElementString("PriceExclTax", ((decimal)orderItem.PriceExclTax).ToString(invariantCulture)); + writer.WriteElementString("TaxRate", ((decimal)orderItem.TaxRate).ToString(invariantCulture)); + writer.WriteElementString("DiscountAmountInclTax", ((decimal)orderItem.DiscountAmountInclTax).ToString(invariantCulture)); + writer.WriteElementString("DiscountAmountExclTax", ((decimal)orderItem.DiscountAmountExclTax).ToString(invariantCulture)); + writer.WriteElementString("AttributeDescription", ((string)orderItem.AttributeDescription).RemoveInvalidXmlChars()); + writer.WriteElementString("AttributesXml", ((string)orderItem.AttributesXml).RemoveInvalidXmlChars()); + writer.WriteElementString("DownloadCount", ((int)orderItem.DownloadCount).ToString()); + writer.WriteElementString("IsDownloadActivated", ((bool)orderItem.IsDownloadActivated).ToString()); + writer.WriteElementString("LicenseDownloadId", licenseDownloadId.HasValue ? licenseDownloadId.Value.ToString() : ""); + writer.WriteElementString("ItemWeight", itemWeight.HasValue ? itemWeight.Value.ToString(invariantCulture) : ""); + writer.WriteElementString("BundleData", ((string)orderItem.BundleData).RemoveInvalidXmlChars()); + writer.WriteElementString("ProductCost", ((decimal)orderItem.ProductCost).ToString(invariantCulture)); + + WriteProduct(writer, "Product", orderItem.Product, invariantCulture); + writer.WriteEndElement(); // OrderItem + } + writer.WriteEndElement(); // OrderItems + + writer.WriteStartElement("Shipments"); + foreach (dynamic shipment in order.Shipments) + { + decimal? totalWeight = shipment.TotalWeight; + DateTime? shippedDateUtc = shipment.ShippedDateUtc; + DateTime? deliveryDateUtc = shipment.DeliveryDateUtc; + + writer.WriteStartElement("Shipment"); + writer.WriteElementString("Id", ((int)shipment.Id).ToString()); + writer.WriteElementString("OrderId", ((int)shipment.OrderId).ToString()); + writer.WriteElementString("TrackingNumber", (string)shipment.TrackingNumber); + writer.WriteElementString("TotalWeight", totalWeight.HasValue ? totalWeight.Value.ToString(invariantCulture) : ""); + writer.WriteElementString("ShippedDateUtc", shippedDateUtc.HasValue ? shippedDateUtc.Value.ToString(invariantCulture) : ""); + writer.WriteElementString("DeliveryDateUtc", deliveryDateUtc.HasValue ? deliveryDateUtc.Value.ToString(invariantCulture) : ""); + writer.WriteElementString("CreatedOnUtc", ((DateTime)shipment.CreatedOnUtc).ToString(invariantCulture)); + + writer.WriteStartElement("ShipmentItems"); + foreach (dynamic shipmentItem in shipment.ShipmentItems) + { + writer.WriteStartElement("ShipmentItem"); + writer.WriteElementString("Id", ((int)shipmentItem.Id).ToString()); + writer.WriteElementString("ShipmentId", ((int)shipmentItem.ShipmentId).ToString()); + writer.WriteElementString("OrderItemId", ((int)shipmentItem.OrderItemId).ToString()); + writer.WriteElementString("Quantity", ((int)shipmentItem.Quantity).ToString()); + writer.WriteEndElement(); // ShipmentItem + } + writer.WriteEndElement(); // ShipmentItems + + writer.WriteEndElement(); // Shipment + } + writer.WriteEndElement(); // Shipments + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.Log.Error("Error while processing order with id {0}: {1}".FormatInvariant(orderId, exc.ToAllMessages()), exc); + ++context.RecordsFailed; + } + + writer.WriteEndElement(); // Order + } + } + + writer.WriteEndElement(); // Orders + writer.WriteEndDocument(); + } + } + + public void ExecuteEnded(IExportExecuteContext context) + { + // nothing to do + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index 9548737959..64d9f40193 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -210,7 +210,11 @@ from x in _exportProfileRepository.Table.Expand(x => x.ScheduleTask) query = query.Where(x => x.Enabled == enabled.Value); } - return query.OrderBy(x => x.Name); + query = query + .OrderBy(x => x.ProviderSystemName) + .ThenBy(x => x.Name); + + return query; } public virtual ExportProfile GetExportProfileById(int id) @@ -241,10 +245,12 @@ public virtual IList GetExportProfilesBySystemName(string systemN } - public virtual IEnumerable> LoadAllExportProviders(int storeId = 0) + public virtual IEnumerable> LoadAllExportProviders(int storeId = 0, bool showHidden = true) { var allProviders = _providerManager.GetAllProviders(storeId) - .Where(x => x.IsValid()); + .Where(x => x.IsValid() && (showHidden || !x.Metadata.IsHidden)) + .OrderBy(x => x.Metadata.SystemName) + .ThenBy(x => x.Metadata.FriendlyName); return allProviders; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportOrderDataContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportOrderDataContext.cs index f4e6848a45..b172165a1c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportOrderDataContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportOrderDataContext.cs @@ -5,6 +5,7 @@ using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Shipping; namespace SmartStore.Services.DataExchange.ExportTask { @@ -17,15 +18,18 @@ internal class ExportOrderDataContext private Func> _funcCustomers; private Func> _funcAddresses; private Func> _funcOrderItems; + private Func> _funcShipments; private LazyMultimap _customers; private LazyMultimap
    _addresses; private LazyMultimap _orderItems; + private LazyMultimap _shipments; public ExportOrderDataContext(IEnumerable orders, Func> customers, Func> addresses, - Func> orderItems) + Func> orderItems, + Func> shipments) { if (orders == null) { @@ -48,6 +52,7 @@ public ExportOrderDataContext(IEnumerable orders, _funcCustomers = customers; _funcAddresses = addresses; _funcOrderItems = orderItems; + _funcShipments = shipments; } public void Clear() @@ -58,6 +63,8 @@ public void Clear() _addresses.Clear(); if (_orderItems != null) _orderItems.Clear(); + if (_shipments != null) + _shipments.Clear(); } public LazyMultimap Customers @@ -95,5 +102,17 @@ public LazyMultimap OrderItems return _orderItems; } } + + public LazyMultimap Shipments + { + get + { + if (_shipments == null) + { + _shipments = new LazyMultimap(keys => _funcShipments(keys), _orderIds); + } + return _shipments; + } + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 8399284327..ca95e08181 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -8,9 +8,12 @@ using System.Net.Http; using System.Text; using System.Threading; +using System.Threading.Tasks; using System.Web; +using System.Web.Mvc; using Autofac; using SmartStore.Core; +using SmartStore.Core.Async; using SmartStore.Core.Data; using SmartStore.Core.Domain; using SmartStore.Core.Domain.Catalog; @@ -22,6 +25,7 @@ using SmartStore.Core.Email; using SmartStore.Core.Html; using SmartStore.Core.IO; +using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Catalog; @@ -33,6 +37,7 @@ using SmartStore.Services.Media; using SmartStore.Services.Messages; using SmartStore.Services.Orders; +using SmartStore.Services.Shipping; using SmartStore.Services.Tasks; using SmartStore.Services.Tax; using SmartStore.Utilities; @@ -70,6 +75,8 @@ public class ExportProfileTask : ITask private ICountryService _countryService; private IRepository _orderRepository; private ILanguageService _languageService; + private IShipmentService _shipmentService; + private IProductTemplateService _productTemplateService; private void InitDependencies(TaskExecutionContext context) { @@ -99,6 +106,8 @@ private void InitDependencies(TaskExecutionContext context) _countryService = context.Resolve(); _orderRepository = context.Resolve>(); _languageService = context.Resolve(); + _shipmentService = context.Resolve(); + _productTemplateService = context.Resolve(); } #endregion @@ -624,7 +633,8 @@ private List GetOrders(ExportProfileTaskContext ctx, int pageIndex, int p ctx.OrderDataContext = new ExportOrderDataContext(result, x => _customerService.GetCustomersByIds(x), x => _addressesService.GetAddressByIds(x), - x => _orderService.GetOrderItemsByOrderIds(x) + x => _orderService.GetOrderItemsByOrderIds(x), + x => _shipmentService.GetShipmentsByOrderIds(x) ); SetProgress(ctx, orders.Count); @@ -652,9 +662,12 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod var productCategories = ctx.ProductDataContext.ProductCategories.Load(product.Id); var productAttributes = ctx.ProductDataContext.Attributes.Load(product.Id); var productAttributeCombinations = ctx.ProductDataContext.AttributeCombinations.Load(product.Id); + var productTemplate = ctx.ProductTemplates.FirstOrDefault(x => x.Key == product.ProductTemplateId); dynamic expando = product.ToExpando(languageId); + expando._ProductTemplateViewPath = (productTemplate.Value == null ? "" : productTemplate.Value.ViewPath); + expando._DetailUrl = ctx.Store.Url + expando.SeName; expando._CategoryName = null; @@ -893,6 +906,7 @@ private List ToExpando(ExportProfileTaskContext ctx, Order order) var addresses = ctx.OrderDataContext.Addresses.Load(order.BillingAddressId); var customers = ctx.OrderDataContext.Customers.Load(order.CustomerId); var orderItems = ctx.OrderDataContext.OrderItems.Load(order.Id); + var shipments = ctx.OrderDataContext.Shipments.Load(order.Id); dynamic expando = order.ToExpando(languageId, _services.Localization); @@ -921,6 +935,9 @@ private List ToExpando(ExportProfileTaskContext ctx, Order order) .Select(e => { dynamic exp = e.ToExpando(languageId); + var productTemplate = ctx.ProductTemplates.FirstOrDefault(x => x.Key == e.Product.ProductTemplateId); + + exp.Product._ProductTemplateViewPath = (productTemplate.Value == null ? "" : productTemplate.Value.ViewPath); exp.Product._BasePriceInfo = e.Product.GetBasePriceInfo(_services.Localization, _priceFormatter, decimal.Zero, true); @@ -930,6 +947,19 @@ private List ToExpando(ExportProfileTaskContext ctx, Order order) }) .ToList(); + expando.Shipments = shipments + .Select(e => + { + dynamic exp = e.ToExpando(); + + exp.ShipmentItems = e.ShipmentItems + .Select(x => x.ToExpando()) + .ToList(); + + return exp as ExpandoObject; + }) + .ToList(); + result.Add(expando); return result; } @@ -1171,6 +1201,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) { ctx.DeliveryTimes = _deliveryTimeService.GetAllDeliveryTimes().ToDictionary(x => x.Id, x => x); ctx.QuantityUnits = _quantityUnitService.GetAllQuantityUnits().ToDictionary(x => x.Id, x => x); + ctx.ProductTemplates = _productTemplateService.GetAllProductTemplates().ToDictionary(x => x.Id, x => x); if (ctx.Provider.Value.EntityType == ExportEntityType.Product) { @@ -1263,6 +1294,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) try { + ctx.ProductTemplates.Clear(); ctx.Countries.Clear(); ctx.Stores.Clear(); ctx.QuantityUnits.Clear(); @@ -1323,11 +1355,67 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) } } + + /// + /// The name of the public export folder + /// public static string PublicFolder { get { return "Exchange"; } } + /// + /// Exports with a volatile profile + /// + /// Provider system name + /// Entity identifiers of entities to be exported. Can be null to export all entities. + /// Last error + /// File stream result of the first export data file + public static FileStreamResult Export(string providerSystemName, string selectedEntityIds, string downloadFileName, out string error) + { + Guard.ArgumentNotEmpty(() => providerSystemName); + + error = null; + var cancellation = new CancellationTokenSource(TimeSpan.FromHours(3.0)); + + var task = AsyncRunner.Run((container, ct) => + { + var exportTask = new ExportProfileTask(); + return exportTask.Execute(providerSystemName, container, ct, null, selectedEntityIds); + }, + cancellation.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); + + task.Wait(); + + if (task.Result != null && task.Result.Succeeded && task.Result.FileFolder.HasValue() && task.Result.Files.Count > 0) + { + var fileName = task.Result.Files.First().FileName; + var filePath = Path.Combine(task.Result.FileFolder, fileName); + + var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); + var result = new FileStreamResult(stream, MimeTypes.MapNameToMimeType(fileName)); + + if (downloadFileName.IsEmpty()) + downloadFileName = providerSystemName; + + if (selectedEntityIds.HasValue() && !selectedEntityIds.Contains(",")) + downloadFileName = string.Concat(downloadFileName, "-", selectedEntityIds); + + result.FileDownloadName = downloadFileName.ToValidFileName() + Path.GetExtension(filePath); + + return result; + } + + if (task.Result != null) + error = task.Result.LastError; + + return null; + } + + /// + /// Executes the export profile task + /// + /// public void Execute(TaskExecutionContext taskContext) { InitDependencies(taskContext); @@ -1347,6 +1435,16 @@ public void Execute(TaskExecutionContext taskContext) taskContext.CancellationToken.ThrowIfCancellationRequested(); } + /// + /// Executes the export profile task with a volatile profile + /// + /// Provider system name + /// Component context + /// Cancellation token + /// Provider specific configuration data. Cane be null. + /// Entity identifiers of entities to be exported. Can be null to export all entities. + /// Store identifier + /// Export execute result public ExportExecuteResult Execute(string providerSystemName, IComponentContext context, CancellationToken cancellationToken, string providerConfigData = null, string selectedEntityIds = null, int storeId = 0) { @@ -1369,6 +1467,15 @@ public ExportExecuteResult Execute(string providerSystemName, IComponentContext return ctx.Result; } + /// + /// Executes the export profile task to get preview data + /// + /// Export profile + /// Component context + /// Page index + /// Page size + /// Number of total records + /// Action to process preview data public void Preview(ExportProfile profile, IComponentContext context, int pageIndex, int pageSize, int totalRecords, Action previewData) { Guard.ArgumentNotNull(() => profile); @@ -1387,6 +1494,13 @@ public void Preview(ExportProfile profile, IComponentContext context, int pageIn ExportCoreOuter(ctx); } + /// + /// Get the number of total records + /// + /// Export profile + /// Export provider + /// Component context + /// Number of total records public int GetRecordCount(ExportProfile profile, Provider provider, IComponentContext context) { Guard.ArgumentNotNull(() => profile); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs index 7c50102105..2fefe8e4d6 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs @@ -49,6 +49,7 @@ public ExportProfileTaskContext( Categories = new Dictionary(); CategoryPathes = new Dictionary(); Countries = new Dictionary(); + ProductTemplates = new Dictionary(); RecordsPerStore = new Dictionary(); EntityIdsLoaded = new List(); @@ -56,12 +57,10 @@ public ExportProfileTaskContext( Export = new ExportExecuteContext(TaskContext.CancellationToken, FolderContent); Export.Projection = XmlHelper.Deserialize(profile.Projection); - Result = new ExportExecuteResult(); - - if (IsFileBasedExport) + Result = new ExportExecuteResult { - Result.FileFolder = FolderContent; - } + FileFolder = (IsFileBasedExport ? FolderContent : null) + }; } public List EntityIdsSelected { get; private set; } @@ -130,6 +129,7 @@ public string[] GetDeploymentFiles(ExportDeployment deployment) public Dictionary QuantityUnits { get; set; } public Dictionary Stores { get; set; } public Dictionary Countries { get; set; } + public Dictionary ProductTemplates { get; set; } // data loaded once per page public ExportProductDataContext ProductDataContext diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs index 098701b0a3..7ce264f229 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs @@ -62,8 +62,9 @@ public interface IExportService /// Load all export providers /// /// Store identifier + /// Whether to load hidden providers /// Export providers - IEnumerable> LoadAllExportProviders(int storeId = 0); + IEnumerable> LoadAllExportProviders(int storeId = 0, bool showHidden = true); /// /// Load export provider by system name diff --git a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs index 430a71ebad..91fca3863f 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs @@ -1270,150 +1270,6 @@ public virtual void ExportProductsToXlsx(Stream stream, IList products) GC.Collect(); } - /// - /// Export order list to xml - /// - /// Orders - /// Result in XML format - public virtual string ExportOrdersToXml(IList orders) - { - var sb = new StringBuilder(); - var stringWriter = new StringWriter(sb); - var xmlWriter = new XmlTextWriter(stringWriter); - xmlWriter.WriteStartDocument(); - xmlWriter.WriteStartElement("Orders"); - xmlWriter.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - - - foreach (var order in orders) - { - xmlWriter.WriteStartElement("Order"); - - xmlWriter.WriteElementString("OrderId", null, order.GetOrderNumber()); - xmlWriter.WriteElementString("OrderGuid", null, order.OrderGuid.ToString()); - xmlWriter.WriteElementString("StoreId", null, order.StoreId.ToString()); - xmlWriter.WriteElementString("CustomerId", null, order.CustomerId.ToString()); - xmlWriter.WriteElementString("CustomerLanguageId", null, order.CustomerLanguageId.ToString()); - xmlWriter.WriteElementString("CustomerTaxDisplayTypeId", null, order.CustomerTaxDisplayTypeId.ToString()); - xmlWriter.WriteElementString("CustomerIp", null, order.CustomerIp); - xmlWriter.WriteElementString("OrderSubtotalInclTax", null, order.OrderSubtotalInclTax.ToString()); - xmlWriter.WriteElementString("OrderSubtotalExclTax", null, order.OrderSubtotalExclTax.ToString()); - xmlWriter.WriteElementString("OrderSubTotalDiscountInclTax", null, order.OrderSubTotalDiscountInclTax.ToString()); - xmlWriter.WriteElementString("OrderSubTotalDiscountExclTax", null, order.OrderSubTotalDiscountExclTax.ToString()); - xmlWriter.WriteElementString("OrderShippingInclTax", null, order.OrderShippingInclTax.ToString()); - xmlWriter.WriteElementString("OrderShippingExclTax", null, order.OrderShippingExclTax.ToString()); - xmlWriter.WriteElementString("PaymentMethodAdditionalFeeInclTax", null, order.PaymentMethodAdditionalFeeInclTax.ToString()); - xmlWriter.WriteElementString("PaymentMethodAdditionalFeeExclTax", null, order.PaymentMethodAdditionalFeeExclTax.ToString()); - xmlWriter.WriteElementString("TaxRates", null, order.TaxRates); - xmlWriter.WriteElementString("OrderTax", null, order.OrderTax.ToString()); - xmlWriter.WriteElementString("OrderTotal", null, order.OrderTotal.ToString()); - xmlWriter.WriteElementString("RefundedAmount", null, order.RefundedAmount.ToString()); - xmlWriter.WriteElementString("OrderDiscount", null, order.OrderDiscount.ToString()); - xmlWriter.WriteElementString("CurrencyRate", null, order.CurrencyRate.ToString()); - xmlWriter.WriteElementString("CustomerCurrencyCode", null, order.CustomerCurrencyCode); - xmlWriter.WriteElementString("AffiliateId", null, order.AffiliateId.ToString()); - xmlWriter.WriteElementString("OrderStatusId", null, order.OrderStatusId.ToString()); - xmlWriter.WriteElementString("AllowStoringCreditCardNumber", null, order.AllowStoringCreditCardNumber.ToString()); - xmlWriter.WriteElementString("CardType", null, order.CardType); - xmlWriter.WriteElementString("CardName", null, order.CardName); - xmlWriter.WriteElementString("CardNumber", null, order.CardNumber); - xmlWriter.WriteElementString("MaskedCreditCardNumber", null, order.MaskedCreditCardNumber); - xmlWriter.WriteElementString("CardCvv2", null, order.CardCvv2); - xmlWriter.WriteElementString("CardExpirationMonth", null, order.CardExpirationMonth); - xmlWriter.WriteElementString("CardExpirationYear", null, order.CardExpirationYear); - - xmlWriter.WriteElementString("DirectDebitAccountHolder", null, order.DirectDebitAccountHolder); - xmlWriter.WriteElementString("DirectDebitAccountHolder", null, order.DirectDebitAccountNumber); - xmlWriter.WriteElementString("DirectDebitAccountHolder", null, order.DirectDebitBankCode); - xmlWriter.WriteElementString("DirectDebitAccountHolder", null, order.DirectDebitBankName); - xmlWriter.WriteElementString("DirectDebitAccountHolder", null, order.DirectDebitBIC); - xmlWriter.WriteElementString("DirectDebitAccountHolder", null, order.DirectDebitCountry); - xmlWriter.WriteElementString("DirectDebitAccountHolder", null, order.DirectDebitIban); - - xmlWriter.WriteElementString("PaymentMethodSystemName", null, order.PaymentMethodSystemName); - xmlWriter.WriteElementString("AuthorizationTransactionId", null, order.AuthorizationTransactionId); - xmlWriter.WriteElementString("AuthorizationTransactionCode", null, order.AuthorizationTransactionCode); - xmlWriter.WriteElementString("AuthorizationTransactionResult", null, order.AuthorizationTransactionResult); - xmlWriter.WriteElementString("CaptureTransactionId", null, order.CaptureTransactionId); - xmlWriter.WriteElementString("CaptureTransactionResult", null, order.CaptureTransactionResult); - xmlWriter.WriteElementString("SubscriptionTransactionId", null, order.SubscriptionTransactionId); - xmlWriter.WriteElementString("PurchaseOrderNumber", null, order.PurchaseOrderNumber); - xmlWriter.WriteElementString("PaymentStatusId", null, order.PaymentStatusId.ToString()); - xmlWriter.WriteElementString("PaidDateUtc", null, (order.PaidDateUtc == null) ? string.Empty : order.PaidDateUtc.Value.ToString()); - xmlWriter.WriteElementString("ShippingStatusId", null, order.ShippingStatusId.ToString()); - xmlWriter.WriteElementString("ShippingMethod", null, order.ShippingMethod); - xmlWriter.WriteElementString("ShippingRateComputationMethodSystemName", null, order.ShippingRateComputationMethodSystemName); - xmlWriter.WriteElementString("VatNumber", null, order.VatNumber); - xmlWriter.WriteElementString("Deleted", null, order.Deleted.ToString()); - xmlWriter.WriteElementString("CreatedOnUtc", null, order.CreatedOnUtc.ToString()); - xmlWriter.WriteElementString("UpdatedOnUtc", null, order.UpdatedOnUtc.ToString()); - xmlWriter.WriteElementString("RewardPointsUsed", null, order.RedeemedRewardPointsEntry != null && order.RedeemedRewardPointsEntry.Points != 0 ? (order.RedeemedRewardPointsEntry.Points * (-1)).ToString() : ""); - var remainingRewardPoints = order.Customer.GetRewardPointsBalance(); - xmlWriter.WriteElementString("RewardPointsRemaining", null, remainingRewardPoints > 0 ? remainingRewardPoints.ToString() : ""); - xmlWriter.WriteElementString("HasNewPaymentNotification", null, order.HasNewPaymentNotification.ToString()); - - //products - var orderItems = order.OrderItems; - if (orderItems.Count > 0) - { - xmlWriter.WriteStartElement("OrderItems"); - foreach (var orderItem in orderItems) - { - xmlWriter.WriteStartElement("OrderItem"); - xmlWriter.WriteElementString("Id", null, orderItem.Id.ToString()); - xmlWriter.WriteElementString("OrderItemGuid", null, orderItem.OrderItemGuid.ToString()); - xmlWriter.WriteElementString("ProductId", null, orderItem.ProductId.ToString()); - - xmlWriter.WriteElementString("ProductName", null, orderItem.Product.Name); - xmlWriter.WriteElementString("UnitPriceInclTax", null, orderItem.UnitPriceInclTax.ToString()); - xmlWriter.WriteElementString("UnitPriceExclTax", null, orderItem.UnitPriceExclTax.ToString()); - xmlWriter.WriteElementString("PriceInclTax", null, orderItem.PriceInclTax.ToString()); - xmlWriter.WriteElementString("PriceExclTax", null, orderItem.PriceExclTax.ToString()); - xmlWriter.WriteElementString("AttributeDescription", null, orderItem.AttributeDescription); - xmlWriter.WriteElementString("AttributesXml", null, orderItem.AttributesXml); - xmlWriter.WriteElementString("Quantity", null, orderItem.Quantity.ToString()); - xmlWriter.WriteElementString("DiscountAmountInclTax", null, orderItem.DiscountAmountInclTax.ToString()); - xmlWriter.WriteElementString("DiscountAmountExclTax", null, orderItem.DiscountAmountExclTax.ToString()); - xmlWriter.WriteElementString("DownloadCount", null, orderItem.DownloadCount.ToString()); - xmlWriter.WriteElementString("IsDownloadActivated", null, orderItem.IsDownloadActivated.ToString()); - xmlWriter.WriteElementString("LicenseDownloadId", null, orderItem.LicenseDownloadId.ToString()); - xmlWriter.WriteElementString("BundleData", null, orderItem.BundleData); - xmlWriter.WriteElementString("ProductCost", null, orderItem.ProductCost.ToString()); - xmlWriter.WriteEndElement(); - } - xmlWriter.WriteEndElement(); - } - - //shipments - var shipments = order.Shipments.OrderBy(x => x.CreatedOnUtc).ToList(); - if (shipments.Count > 0) - { - xmlWriter.WriteStartElement("Shipments"); - foreach (var shipment in shipments) - { - xmlWriter.WriteStartElement("Shipment"); - xmlWriter.WriteElementString("ShipmentId", null, shipment.Id.ToString()); - xmlWriter.WriteElementString("TrackingNumber", null, shipment.TrackingNumber); - xmlWriter.WriteElementString("TotalWeight", null, shipment.TotalWeight.HasValue ? shipment.TotalWeight.Value.ToString() : ""); - - xmlWriter.WriteElementString("ShippedDateUtc", null, shipment.ShippedDateUtc.HasValue ? - shipment.ShippedDateUtc.ToString() : ""); - xmlWriter.WriteElementString("DeliveryDateUtc", null, shipment.DeliveryDateUtc.HasValue ? - shipment.DeliveryDateUtc.Value.ToString() : ""); - xmlWriter.WriteElementString("CreatedOnUtc", null, shipment.CreatedOnUtc.ToString()); - xmlWriter.WriteEndElement(); - } - xmlWriter.WriteEndElement(); - } - xmlWriter.WriteEndElement(); - } - - xmlWriter.WriteEndElement(); - xmlWriter.WriteEndDocument(); - xmlWriter.Close(); - return stringWriter.ToString(); - } - /// /// Export orders to XLSX /// diff --git a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs index 0c8afdbcfd..1ef709e855 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs @@ -48,13 +48,6 @@ public interface IExportManager /// Products void ExportProductsToXlsx(Stream stream, IList products); - /// - /// Export order list to xml - /// - /// Orders - /// Result in XML format - string ExportOrdersToXml(IList orders); - /// /// Export orders to XLSX /// diff --git a/src/Libraries/SmartStore.Services/Shipping/IShipmentService.cs b/src/Libraries/SmartStore.Services/Shipping/IShipmentService.cs index ac79efe3dc..0a544651ad 100644 --- a/src/Libraries/SmartStore.Services/Shipping/IShipmentService.cs +++ b/src/Libraries/SmartStore.Services/Shipping/IShipmentService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain.Shipping; @@ -29,12 +30,19 @@ IPagedList GetAllShipments(string trackingNumber, DateTime? createdFro int pageIndex, int pageSize); /// - /// Get shipment by identifiers + /// Get shipments by identifiers /// /// Shipment identifiers /// Shipments IList GetShipmentsByIds(int[] shipmentIds); + /// + /// Get shipments by order identifiers + /// + /// Order identifiers + /// Shipments + Multimap GetShipmentsByOrderIds(int[] orderIds); + /// /// Gets a shipment /// diff --git a/src/Libraries/SmartStore.Services/Shipping/ShipmentService.cs b/src/Libraries/SmartStore.Services/Shipping/ShipmentService.cs index 7afbb90cbb..301122a7fc 100644 --- a/src/Libraries/SmartStore.Services/Shipping/ShipmentService.cs +++ b/src/Libraries/SmartStore.Services/Shipping/ShipmentService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Orders; @@ -123,6 +124,24 @@ where shipmentIds.Contains(o.Id) return sortedOrders; } + public virtual Multimap GetShipmentsByOrderIds(int[] orderIds) + { + Guard.ArgumentNotNull(() => orderIds); + + var query = + from x in _shipmentRepository.TableUntracked.Expand(x => x.ShipmentItems) + where orderIds.Contains(x.OrderId) + select x; + + var map = query + .OrderBy(x => x.OrderId) + .ThenBy(x => x.CreatedOnUtc) + .ToList() + .ToMultimap(x => x.OrderId, x => x); + + return map; + } + /// /// Gets a shipment /// diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index f4e555ad04..43c779951f 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -170,6 +170,7 @@ + diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index 515eb4db21..749e07c047 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -773,6 +773,7 @@ protected override void Load(ContainerBuilder builder) var settingPattern = (pluginDescriptor != null ? "Plugins" : "Providers") + ".{0}.{1}"; // e.g. Plugins.MySystemName.DisplayOrder var isConfigurable = typeof(IConfigurable).IsAssignableFrom(type); var isEditable = typeof(IUserEditable).IsAssignableFrom(type); + var isHidden = GetIsHidden(type); var exportProjectionSupport = GetExportProjectionSupport(type); var registration = builder.RegisterType(type).Named(systemName).InstancePerRequest().PropertiesAutowired(PropertyWiringOptions.None); @@ -789,6 +790,7 @@ protected override void Load(ContainerBuilder builder) m.For(em => em.DependentWidgets, dependentWidgets); m.For(em => em.IsConfigurable, isConfigurable); m.For(em => em.IsEditable, isEditable); + m.For(em => em.IsHidden, isHidden); m.For(em => em.ExportProjectionSupport, exportProjectionSupport); }); @@ -856,6 +858,17 @@ private int GetDisplayOrder(Type type, PluginDescriptor descriptor) return 0; } + private bool GetIsHidden(Type type) + { + var attr = type.GetAttribute(false); + if (attr != null) + { + return attr.IsHidden; + } + + return false; + } + private ExportProjectionSupport[] GetExportProjectionSupport(Type type) { var attr = type.GetAttribute(false); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 384be3bddf..5501bf0acd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -485,7 +485,7 @@ public ActionResult Create() model.Provider = new ExportProfileModel.ProviderModel(); model.Provider.ProviderDescriptions = new Dictionary(); - model.Provider.AvailableExportProviders = _exportService.LoadAllExportProviders() + model.Provider.AvailableExportProviders = _exportService.LoadAllExportProviders(0, false) .Select(x => { var item = new SelectListItem diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs index 6ca9491a59..da2ee2c221 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs @@ -21,6 +21,8 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; +using SmartStore.Services.DataExchange.ExportProvider; +using SmartStore.Services.DataExchange.ExportTask; using SmartStore.Services.Directory; using SmartStore.Services.ExportImport; using SmartStore.Services.Helpers; @@ -874,44 +876,38 @@ public ActionResult GoToOrderId(OrderListModel model) #region Export / Import + [Compress] public ActionResult ExportXmlAll() { if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - try - { - var orders = _orderService.SearchOrders(0, 0, null, null, null, - null, null, null, null, null, 0, int.MaxValue); + string error = null; + var fileStreamResult = ExportProfileTask.Export(OrderExportXmlProvider.SystemName, null, "smnet-orders", out error); - var xml = _exportManager.ExportOrdersToXml(orders); - return new XmlDownloadResult(xml, "orders.xml"); - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } + if (fileStreamResult != null) + return fileStreamResult; + + NotifyError(string.Concat("

    ", T("Admin.Common.UnknownError"), "

    ", error.NaIfEmpty())); + + return RedirectToAction("List"); } - [HttpPost] + [HttpPost, Compress] public ActionResult ExportXmlSelected(string selectedIds) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - var orders = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - orders.AddRange(_orderService.GetOrdersByIds(ids)); - } + string error = null; + var fileStreamResult = ExportProfileTask.Export(OrderExportXmlProvider.SystemName, selectedIds, "smnet-orders", out error); + + if (fileStreamResult != null) + return fileStreamResult; + + NotifyError(string.Concat("

    ", T("Admin.Common.UnknownError"), "

    ", error.NaIfEmpty())); - var xml = _exportManager.ExportOrdersToXml(orders); - return new XmlDownloadResult(xml, "orders.xml"); + return RedirectToAction("List"); } public ActionResult ExportExcelAll() From 9cf071fbc8388263545740919c60832990c1748c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 28 Sep 2015 14:08:10 +0200 Subject: [PATCH 089/874] Convert order XLSX export to support new export framework --- .../Customers/CustomerService.cs | 27 +- .../Customers/ICustomerService.cs | 8 + .../DataExchange/ExpandoExtensions.cs | 24 ++ .../ExportProvider/OrderExportXlsxProvider.cs | 258 +++++++++++++++ .../ExportProvider/OrderExportXmlProvider.cs | 27 +- .../ExportTask/ExportOrderDataContext.cs | 18 ++ .../ExportTask/ExportProfileTask.cs | 15 + .../ExportImport/ExportManager.cs | 300 ------------------ .../ExportImport/IExportManager.cs | 7 - .../SmartStore.Services.csproj | 1 + .../Extensions/HtmlExtensions.cs | 2 +- .../Controllers/OrderController.cs | 74 ++--- .../CustomerRegistrationServiceTests.cs | 4 +- 13 files changed, 396 insertions(+), 369 deletions(-) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXlsxProvider.cs diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerService.cs b/src/Libraries/SmartStore.Services/Customers/CustomerService.cs index e597bf8f12..cd2e68d0cf 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerService.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerService.cs @@ -4,20 +4,17 @@ using System.Globalization; using System.Linq; using System.Linq.Expressions; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; -using SmartStore.Core.Domain.Blogs; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; -using SmartStore.Core.Domain.News; using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Domain.Polls; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Events; -using SmartStore.Core.Infrastructure; using SmartStore.Core.Localization; using SmartStore.Services.Common; using SmartStore.Services.Localization; @@ -42,6 +39,7 @@ public partial class CustomerService : ICustomerService private readonly IRepository _customerRepository; private readonly IRepository _customerRoleRepository; private readonly IRepository _gaRepository; + private readonly IRepository _rewardPointsHistoryRepository; private readonly IGenericAttributeService _genericAttributeService; private readonly ICacheManager _cacheManager; private readonly IEventPublisher _eventPublisher; @@ -55,6 +53,7 @@ public CustomerService(ICacheManager cacheManager, IRepository customerRepository, IRepository customerRoleRepository, IRepository gaRepository, + IRepository rewardPointsHistoryRepository, IGenericAttributeService genericAttributeService, IEventPublisher eventPublisher, RewardPointsSettings rewardPointsSettings) @@ -63,6 +62,7 @@ public CustomerService(ICacheManager cacheManager, this._customerRepository = customerRepository; this._customerRoleRepository = customerRoleRepository; this._gaRepository = gaRepository; + this._rewardPointsHistoryRepository = rewardPointsHistoryRepository; this._genericAttributeService = genericAttributeService; this._eventPublisher = eventPublisher; this._rewardPointsSettings = rewardPointsSettings; @@ -650,6 +650,25 @@ public virtual void RewardPointsForProductReview(Customer customer, Product prod } } + public virtual Multimap GetRewardPointsHistoriesByCustomerIds(int[] customerIds) + { + Guard.ArgumentNotNull(() => customerIds); + + var query = + from x in _rewardPointsHistoryRepository.TableUntracked + where customerIds.Contains(x.CustomerId) + select x; + + var map = query + .OrderBy(x => x.CustomerId) + .ThenByDescending(x => x.CreatedOnUtc) + .ThenByDescending(x => x.Id) + .ToList() + .ToMultimap(x => x.CustomerId, x => x); + + return map; + } + #endregion Reward points #endregion diff --git a/src/Libraries/SmartStore.Services/Customers/ICustomerService.cs b/src/Libraries/SmartStore.Services/Customers/ICustomerService.cs index d5d3ce672c..ec4b470074 100644 --- a/src/Libraries/SmartStore.Services/Customers/ICustomerService.cs +++ b/src/Libraries/SmartStore.Services/Customers/ICustomerService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; @@ -212,6 +213,13 @@ void ResetCheckoutData(Customer customer, int storeId, /// Whether to add or remove points void RewardPointsForProductReview(Customer customer, Product product, bool add); + /// + /// Gets reward points histories + /// + /// Customer identifiers + /// Reward points histories + Multimap GetRewardPointsHistoriesByCustomerIds(int[] customerIds); + #endregion Reward points } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs index 39f6126ea9..0c9ee2eedc 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs @@ -132,6 +132,25 @@ public static ExpandoObject ToExpando(this Address address, int languageId) return expando as ExpandoObject; } + public static ExpandoObject ToExpando(this RewardPointsHistory history) + { + if (history == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = history; + + expando.Id = history.Id; + expando.CustomerId = history.CustomerId; + expando.Points = history.Points; + expando.PointsBalance = history.PointsBalance; + expando.UsedAmount = history.UsedAmount; + expando.Message = history.Message; + expando.CreatedOnUtc = history.CreatedOnUtc; + + return expando as ExpandoObject; + } + public static ExpandoObject ToExpando(this Customer customer, int languageId) { if (customer == null) @@ -161,6 +180,9 @@ public static ExpandoObject ToExpando(this Customer customer, int languageId) expando.BillingAddress = customer.BillingAddress.ToExpando(languageId); expando.ShippingAddress = customer.ShippingAddress.ToExpando(languageId); + expando.RewardPointsHistory = null; + expando._RewardPointsBalance = 0; + return expando as ExpandoObject; } @@ -577,10 +599,12 @@ public static ExpandoObject ToExpando(this Order order, int languageId, ILocaliz expando.PaymentStatus = order.PaymentStatus.GetLocalizedEnum(localization, languageId); expando.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(localization, languageId); + expando.Customer = null; expando.BillingAddress = null; expando.ShippingAddress = null; expando.Store = null; expando.Shipments = null; + expando.RedeemedRewardPointsEntry = order.RedeemedRewardPointsEntry.ToExpando(); return expando as ExpandoObject; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXlsxProvider.cs new file mode 100644 index 0000000000..5fb8bd5fd9 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXlsxProvider.cs @@ -0,0 +1,258 @@ +using System; +using System.Drawing; +using System.IO; +using OfficeOpenXml; +using OfficeOpenXml.Style; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.DataExchange.ExportProvider +{ + [SystemName("Exports.SmartStoreNetOrderXlsx")] + [FriendlyName("SmartStore.NET Excel order export")] + [IsHidden(true)] + public class OrderExportXlsxProvider : IExportProvider + { + private string[] Properties + { + get + { + return new string[] + { + "OrderId", + "OrderGuid", + "CustomerId", + "OrderSubtotalInclTax", + "OrderSubtotalExclTax", + "OrderSubTotalDiscountInclTax", + "OrderSubTotalDiscountExclTax", + "OrderShippingInclTax", + "OrderShippingExclTax", + "PaymentMethodAdditionalFeeInclTax", + "PaymentMethodAdditionalFeeExclTax", + "TaxRates", + "OrderTax", + "OrderTotal", + "RefundedAmount", + "OrderDiscount", + "CurrencyRate", + "CustomerCurrencyCode", + "AffiliateId", + "OrderStatusId", + "PaymentMethodSystemName", + "PurchaseOrderNumber", + "PaymentStatusId", + "ShippingStatusId", + "ShippingMethod", + "ShippingRateComputationMethodSystemName", + "VatNumber", + "CreatedOnUtc", + "UpdatedOnUtc", + "RewardPointsUsed", + "RewardPointsRemaining", + "HasNewPaymentNotification", + //billing address + "BillingFirstName", + "BillingLastName", + "BillingEmail", + "BillingCompany", + "BillingCountry", + "BillingStateProvince", + "BillingCity", + "BillingAddress1", + "BillingAddress2", + "BillingZipPostalCode", + "BillingPhoneNumber", + "BillingFaxNumber", + //shipping address + "ShippingFirstName", + "ShippingLastName", + "ShippingEmail", + "ShippingCompany", + "ShippingCountry", + "ShippingStateProvince", + "ShippingCity", + "ShippingAddress1", + "ShippingAddress2", + "ShippingZipPostalCode", + "ShippingPhoneNumber", + "ShippingFaxNumber" + }; + } + } + + private void WriteCell(ExcelWorksheet worksheet, int row, ref int column, object value) + { + worksheet.Cells[row, column].Value = value; + ++column; + } + + public static string SystemName + { + get { return "Exports.SmartStoreNetOrderXlsx"; } + } + + public ExportConfigurationInfo ConfigurationInfo + { + get { return null; } + } + + public ExportEntityType EntityType + { + get { return ExportEntityType.Order; } + } + + public string FileExtension + { + get { return "XLSX"; } + } + + public void Execute(IExportExecuteContext context) + { + var path = context.FilePath; + + context.Log.Information("Creating file " + path); + + using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + using (var xlPackage = new ExcelPackage(stream)) + { + // uncomment this line if you want the XML written out to the outputDir + //xlPackage.DebugMode = true; + + // get handle to the existing worksheet + var worksheet = xlPackage.Workbook.Worksheets.Add("Orders"); + + // create headers and format them + string[] properties = Properties; + + for (int i = 0; i < properties.Length; ++i) + { + worksheet.Cells[1, i + 1].Value = properties[i]; + worksheet.Cells[1, i + 1].Style.Fill.PatternType = ExcelFillStyle.Solid; + worksheet.Cells[1, i + 1].Style.Fill.BackgroundColor.SetColor(Color.FromArgb(184, 204, 228)); + worksheet.Cells[1, i + 1].Style.Font.Bold = true; + } + + int row = 2; + + while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + { + var segment = context.Data.CurrentSegment; + + foreach (dynamic order in segment) + { + if (context.Abort != ExportAbortion.None) + break; + + int column = 1; + int orderId = order.Id; + dynamic customer = order.Customer; + dynamic billingAddress = order.BillingAddress; + dynamic shippingAddress = order.ShippingAddress; + + try + { + int rewardPointsUsed = 0; + int rewardPointsRemaining = customer._RewardPointsBalance; + + if (order.RedeemedRewardPointsEntry != null && (int)order.RedeemedRewardPointsEntry.Points != 0) + rewardPointsUsed = (-1 * (int)order.RedeemedRewardPointsEntry.Points); + + WriteCell(worksheet, row, ref column, (string)order.OrderNumber); + WriteCell(worksheet, row, ref column, (Guid)order.OrderGuid); + WriteCell(worksheet, row, ref column, (int)order.CustomerId); + WriteCell(worksheet, row, ref column, (decimal)order.OrderSubtotalInclTax); + WriteCell(worksheet, row, ref column, (decimal)order.OrderSubtotalExclTax); + WriteCell(worksheet, row, ref column, (decimal)order.OrderSubTotalDiscountInclTax); + WriteCell(worksheet, row, ref column, (decimal)order.OrderSubTotalDiscountExclTax); + WriteCell(worksheet, row, ref column, (decimal)order.OrderShippingInclTax); + WriteCell(worksheet, row, ref column, (decimal)order.OrderShippingExclTax); + WriteCell(worksheet, row, ref column, (decimal)order.PaymentMethodAdditionalFeeInclTax); + WriteCell(worksheet, row, ref column, (decimal)order.PaymentMethodAdditionalFeeExclTax); + WriteCell(worksheet, row, ref column, (string)order.TaxRates); + WriteCell(worksheet, row, ref column, (decimal)order.OrderTax); + WriteCell(worksheet, row, ref column, (decimal)order.OrderTotal); + WriteCell(worksheet, row, ref column, (decimal)order.RefundedAmount); + WriteCell(worksheet, row, ref column, (decimal)order.OrderDiscount); + WriteCell(worksheet, row, ref column, (decimal)order.CurrencyRate); + WriteCell(worksheet, row, ref column, (string)order.CustomerCurrencyCode); + WriteCell(worksheet, row, ref column, (int)order.AffiliateId); + WriteCell(worksheet, row, ref column, (int)order.OrderStatusId); + WriteCell(worksheet, row, ref column, (string)order.PaymentMethodSystemName); + WriteCell(worksheet, row, ref column, (string)order.PurchaseOrderNumber); + WriteCell(worksheet, row, ref column, (int)order.PaymentStatusId); + WriteCell(worksheet, row, ref column, (int)order.ShippingStatusId); + WriteCell(worksheet, row, ref column, (string)order.ShippingMethod); + WriteCell(worksheet, row, ref column, (string)order.ShippingRateComputationMethodSystemName); + WriteCell(worksheet, row, ref column, (string)order.VatNumber); + WriteCell(worksheet, row, ref column, ((DateTime)order.CreatedOnUtc).ToOADate()); + WriteCell(worksheet, row, ref column, ((DateTime)order.UpdatedOnUtc).ToOADate()); + WriteCell(worksheet, row, ref column, rewardPointsUsed > 0 ? rewardPointsUsed.ToString() : ""); + WriteCell(worksheet, row, ref column, rewardPointsRemaining > 0 ? rewardPointsRemaining.ToString() : ""); + WriteCell(worksheet, row, ref column, (bool)order.HasNewPaymentNotification); + + WriteCell(worksheet, row, ref column, billingAddress != null ? (string)billingAddress.FirstName : ""); + WriteCell(worksheet, row, ref column, billingAddress != null ? (string)billingAddress.LastName : ""); + WriteCell(worksheet, row, ref column, billingAddress != null ? (string)billingAddress.Email : ""); + WriteCell(worksheet, row, ref column, billingAddress != null ? (string)billingAddress.Company : ""); + WriteCell(worksheet, row, ref column, billingAddress != null && billingAddress.Country != null ? (string)billingAddress.Country.Name : ""); + WriteCell(worksheet, row, ref column, billingAddress != null && billingAddress.StateProvince != null ? (string)billingAddress.StateProvince.Name : ""); + WriteCell(worksheet, row, ref column, billingAddress != null ? (string)billingAddress.City : ""); + WriteCell(worksheet, row, ref column, billingAddress != null ? (string)billingAddress.Address1 : ""); + WriteCell(worksheet, row, ref column, billingAddress != null ? (string)billingAddress.Address2 : ""); + WriteCell(worksheet, row, ref column, billingAddress != null ? (string)billingAddress.ZipPostalCode : ""); + WriteCell(worksheet, row, ref column, billingAddress != null ? (string)billingAddress.PhoneNumber : ""); + WriteCell(worksheet, row, ref column, billingAddress != null ? (string)billingAddress.FaxNumber : ""); + + WriteCell(worksheet, row, ref column, shippingAddress != null ? (string)shippingAddress.FirstName : ""); + WriteCell(worksheet, row, ref column, shippingAddress != null ? (string)shippingAddress.LastName : ""); + WriteCell(worksheet, row, ref column, shippingAddress != null ? (string)shippingAddress.Email : ""); + WriteCell(worksheet, row, ref column, shippingAddress != null ? (string)shippingAddress.Company : ""); + WriteCell(worksheet, row, ref column, shippingAddress != null && shippingAddress.Country != null ? (string)shippingAddress.Country.Name : ""); + WriteCell(worksheet, row, ref column, shippingAddress != null && shippingAddress.StateProvince != null ? (string)shippingAddress.StateProvince.Name : ""); + WriteCell(worksheet, row, ref column, shippingAddress != null ? (string)shippingAddress.City : ""); + WriteCell(worksheet, row, ref column, shippingAddress != null ? (string)shippingAddress.Address1 : ""); + WriteCell(worksheet, row, ref column, shippingAddress != null ? (string)shippingAddress.Address2 : ""); + WriteCell(worksheet, row, ref column, shippingAddress != null ? (string)shippingAddress.ZipPostalCode : ""); + WriteCell(worksheet, row, ref column, shippingAddress != null ? (string)shippingAddress.PhoneNumber : ""); + WriteCell(worksheet, row, ref column, shippingAddress != null ? (string)shippingAddress.FaxNumber : ""); + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.Log.Error("Error while processing order with id {0}: {1}".FormatInvariant(orderId, exc.ToAllMessages()), exc); + ++context.RecordsFailed; + } + + ++row; + } + } + + // we had better add some document properties to the spreadsheet + // set some core property values + //var storeName = _storeInformationSettings.StoreName; + //var storeUrl = _storeInformationSettings.StoreUrl; + //xlPackage.Workbook.Properties.Title = string.Format("{0} orders", storeName); + //xlPackage.Workbook.Properties.Author = storeName; + //xlPackage.Workbook.Properties.Subject = string.Format("{0} orders", storeName); + //xlPackage.Workbook.Properties.Keywords = string.Format("{0} orders", storeName); + //xlPackage.Workbook.Properties.Category = "Orders"; + //xlPackage.Workbook.Properties.Comments = string.Format("{0} orders", storeName); + + // set some extended property values + //xlPackage.Workbook.Properties.Company = storeName; + //xlPackage.Workbook.Properties.HyperlinkBase = new Uri(storeUrl); + + // save the new spreadsheet + xlPackage.Save(); + } + } + + public void ExecuteEnded(IExportExecuteContext context) + { + // nothing to do + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXmlProvider.cs index ce6d921e5e..0f06f6d611 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXmlProvider.cs @@ -11,7 +11,7 @@ namespace SmartStore.Services.DataExchange.ExportProvider { [SystemName("Exports.SmartStoreNetOrderXml")] - [FriendlyName("SmartStore.NET order export")] + [FriendlyName("SmartStore.NET XML order export")] [IsHidden(true)] public class OrderExportXmlProvider : IExportProvider { @@ -243,6 +243,29 @@ private void WriteProduct(XmlWriter writer, string node, dynamic product, Cultur writer.WriteEndElement(); // node } + private void WriteRewardPointsHistory(XmlWriter writer, string node, dynamic rewardPoints, CultureInfo culture) + { + if (rewardPoints == null) + return; + + writer.WriteStartElement(node); + + foreach (dynamic rewardPoint in rewardPoints) + { + writer.WriteStartElement("RewardPoint"); + writer.WriteElementString("Id", ((int)rewardPoint.Id).ToString()); + writer.WriteElementString("CustomerId", ((int)rewardPoint.CustomerId).ToString()); + writer.WriteElementString("Points", ((int)rewardPoint.Points).ToString()); + writer.WriteElementString("PointsBalance", ((int)rewardPoint.PointsBalance).ToString()); + writer.WriteElementString("UsedAmount", ((decimal)rewardPoint.UsedAmount).ToString(culture)); + writer.WriteElementString("Message", (string)rewardPoint.Message); + writer.WriteElementString("CreatedOnUtc", ((DateTime)rewardPoint.CreatedOnUtc).ToString(culture)); + writer.WriteEndElement(); // RewardPoint + } + + writer.WriteEndElement(); // node + } + public static string SystemName { get { return "Exports.SmartStoreNetOrderXml"; } @@ -399,7 +422,9 @@ public void Execute(IExportExecuteContext context) writer.WriteElementString("CreatedOnUtc", ((DateTime)customer.CreatedOnUtc).ToString(invariantCulture)); writer.WriteElementString("LastLoginDateUtc", lastLoginDateUtc.HasValue ? lastLoginDateUtc.Value.ToString(invariantCulture) : ""); writer.WriteElementString("LastActivityDateUtc", ((DateTime)customer.LastActivityDateUtc).ToString(invariantCulture)); + writer.WriteElementString("RewardPointsBalance", ((int)customer._RewardPointsBalance).ToString()); + WriteRewardPointsHistory(writer, "RewardPointsHistory", customer.RewardPointsHistory, invariantCulture); WriteAddress(writer, "BillingAddress", customer.BillingAddress, invariantCulture); WriteAddress(writer, "ShippingAddress", customer.ShippingAddress, invariantCulture); writer.WriteEndElement(); // Customer diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportOrderDataContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportOrderDataContext.cs index b172165a1c..150e6884c7 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportOrderDataContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportOrderDataContext.cs @@ -16,17 +16,20 @@ internal class ExportOrderDataContext protected List _addressIds; private Func> _funcCustomers; + private Func> _funcRewardPointsHistories; private Func> _funcAddresses; private Func> _funcOrderItems; private Func> _funcShipments; private LazyMultimap _customers; + private LazyMultimap _rewardPointsHistories; private LazyMultimap
    _addresses; private LazyMultimap _orderItems; private LazyMultimap _shipments; public ExportOrderDataContext(IEnumerable orders, Func> customers, + Func> rewardPointsHistory, Func> addresses, Func> orderItems, Func> shipments) @@ -50,6 +53,7 @@ public ExportOrderDataContext(IEnumerable orders, } _funcCustomers = customers; + _funcRewardPointsHistories = rewardPointsHistory; _funcAddresses = addresses; _funcOrderItems = orderItems; _funcShipments = shipments; @@ -59,6 +63,8 @@ public void Clear() { if (_customers != null) _customers.Clear(); + if (_rewardPointsHistories != null) + _rewardPointsHistories.Clear(); if (_addresses != null) _addresses.Clear(); if (_orderItems != null) @@ -79,6 +85,18 @@ public LazyMultimap Customers } } + public LazyMultimap RewardPointsHistories + { + get + { + if (_rewardPointsHistories == null) + { + _rewardPointsHistories = new LazyMultimap(keys => _funcRewardPointsHistories(keys), _customerIds); + } + return _rewardPointsHistories; + } + } + public LazyMultimap
    Addresses { get diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index ca95e08181..3a3032f845 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -632,6 +632,7 @@ private List GetOrders(ExportProfileTaskContext ctx, int pageIndex, int p { ctx.OrderDataContext = new ExportOrderDataContext(result, x => _customerService.GetCustomersByIds(x), + x => _customerService.GetRewardPointsHistoriesByCustomerIds(x), x => _addressesService.GetAddressByIds(x), x => _orderService.GetOrderItemsByOrderIds(x), x => _shipmentService.GetShipmentsByOrderIds(x) @@ -905,6 +906,7 @@ private List ToExpando(ExportProfileTaskContext ctx, Order order) var addresses = ctx.OrderDataContext.Addresses.Load(order.BillingAddressId); var customers = ctx.OrderDataContext.Customers.Load(order.CustomerId); + var rewardPointsHistories = ctx.OrderDataContext.RewardPointsHistories.Load(order.CustomerId); var orderItems = ctx.OrderDataContext.OrderItems.Load(order.Id); var shipments = ctx.OrderDataContext.Shipments.Load(order.Id); @@ -914,6 +916,19 @@ private List ToExpando(ExportProfileTaskContext ctx, Order order) .FirstOrDefault(x => x.Id == order.CustomerId) .ToExpando(languageId); + expando.Customer.RewardPointsHistory = rewardPointsHistories + .Select(x => x.ToExpando()) + .ToList(); + + if (rewardPointsHistories.Count > 0) + { + expando.Customer._RewardPointsBalance = rewardPointsHistories + .OrderByDescending(x => x.CreatedOnUtc) + .ThenByDescending(x => x.Id) + .FirstOrDefault() + .PointsBalance; + } + expando.BillingAddress = addresses .FirstOrDefault(x => x.Id == order.BillingAddressId) .ToExpando(languageId); diff --git a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs index 91fca3863f..6d731cb6d3 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs @@ -1270,306 +1270,6 @@ public virtual void ExportProductsToXlsx(Stream stream, IList products) GC.Collect(); } - /// - /// Export orders to XLSX - /// - /// Stream - /// Orders - public virtual void ExportOrdersToXlsx(Stream stream, IList orders) - { - if (stream == null) - throw new ArgumentNullException("stream"); - - // ok, we can run the real code of the sample now - using (var xlPackage = new ExcelPackage(stream)) - { - // uncomment this line if you want the XML written out to the outputDir - //xlPackage.DebugMode = true; - - // get handle to the existing worksheet - var worksheet = xlPackage.Workbook.Worksheets.Add("Orders"); - // Create Headers and format them - var properties = new string[] - { - //order properties - "OrderId", - "OrderGuid", - "CustomerId", - "OrderSubtotalInclTax", - "OrderSubtotalExclTax", - "OrderSubTotalDiscountInclTax", - "OrderSubTotalDiscountExclTax", - "OrderShippingInclTax", - "OrderShippingExclTax", - "PaymentMethodAdditionalFeeInclTax", - "PaymentMethodAdditionalFeeExclTax", - "TaxRates", - "OrderTax", - "OrderTotal", - "RefundedAmount", - "OrderDiscount", - "CurrencyRate", - "CustomerCurrencyCode", - "AffiliateId", - "OrderStatusId", - "PaymentMethodSystemName", - "PurchaseOrderNumber", - "PaymentStatusId", - "ShippingStatusId", - "ShippingMethod", - "ShippingRateComputationMethodSystemName", - "VatNumber", - "CreatedOnUtc", - "UpdatedOnUtc", - "RewardPointsUsed", - "RewardPointsRemaining", - "HasNewPaymentNotification", - //billing address - "BillingFirstName", - "BillingLastName", - "BillingEmail", - "BillingCompany", - "BillingCountry", - "BillingStateProvince", - "BillingCity", - "BillingAddress1", - "BillingAddress2", - "BillingZipPostalCode", - "BillingPhoneNumber", - "BillingFaxNumber", - //shipping address - "ShippingFirstName", - "ShippingLastName", - "ShippingEmail", - "ShippingCompany", - "ShippingCountry", - "ShippingStateProvince", - "ShippingCity", - "ShippingAddress1", - "ShippingAddress2", - "ShippingZipPostalCode", - "ShippingPhoneNumber", - "ShippingFaxNumber", - }; - for (int i = 0; i < properties.Length; i++) - { - worksheet.Cells[1, i + 1].Value = properties[i]; - worksheet.Cells[1, i + 1].Style.Fill.PatternType = ExcelFillStyle.Solid; - worksheet.Cells[1, i + 1].Style.Fill.BackgroundColor.SetColor(Color.FromArgb(184, 204, 228)); - worksheet.Cells[1, i + 1].Style.Font.Bold = true; - } - - - int row = 2; - foreach (var order in orders) - { - int col = 1; - - //order properties - worksheet.Cells[row, col].Value = order.GetOrderNumber(); - col++; - - worksheet.Cells[row, col].Value = order.OrderGuid; - col++; - - worksheet.Cells[row, col].Value = order.CustomerId; - col++; - - worksheet.Cells[row, col].Value = order.OrderSubtotalInclTax; - col++; - - worksheet.Cells[row, col].Value = order.OrderSubtotalExclTax; - col++; - - worksheet.Cells[row, col].Value = order.OrderSubTotalDiscountInclTax; - col++; - - worksheet.Cells[row, col].Value = order.OrderSubTotalDiscountExclTax; - col++; - - worksheet.Cells[row, col].Value = order.OrderShippingInclTax; - col++; - - worksheet.Cells[row, col].Value = order.OrderShippingExclTax; - col++; - - worksheet.Cells[row, col].Value = order.PaymentMethodAdditionalFeeInclTax; - col++; - - worksheet.Cells[row, col].Value = order.PaymentMethodAdditionalFeeExclTax; - col++; - - worksheet.Cells[row, col].Value = order.TaxRates; - col++; - - worksheet.Cells[row, col].Value = order.OrderTax; - col++; - - worksheet.Cells[row, col].Value = order.OrderTotal; - col++; - - worksheet.Cells[row, col].Value = order.RefundedAmount; - col++; - - worksheet.Cells[row, col].Value = order.OrderDiscount; - col++; - - worksheet.Cells[row, col].Value = order.CurrencyRate; - col++; - - worksheet.Cells[row, col].Value = order.CustomerCurrencyCode; - col++; - - worksheet.Cells[row, col].Value = order.AffiliateId; - col++; - - worksheet.Cells[row, col].Value = order.OrderStatusId; - col++; - - worksheet.Cells[row, col].Value = order.PaymentMethodSystemName; - col++; - - worksheet.Cells[row, col].Value = order.PurchaseOrderNumber; - col++; - - worksheet.Cells[row, col].Value = order.PaymentStatusId; - col++; - - worksheet.Cells[row, col].Value = order.ShippingStatusId; - col++; - - worksheet.Cells[row, col].Value = order.ShippingMethod; - col++; - - worksheet.Cells[row, col].Value = order.ShippingRateComputationMethodSystemName; - col++; - - worksheet.Cells[row, col].Value = order.VatNumber; - col++; - - worksheet.Cells[row, col].Value = order.CreatedOnUtc.ToOADate(); - col++; - - worksheet.Cells[row, col].Value = order.UpdatedOnUtc.ToOADate(); - col++; - - worksheet.Cells[row, col].Value = order.RedeemedRewardPointsEntry != null ? (order.RedeemedRewardPointsEntry.Points != 0 ? (order.RedeemedRewardPointsEntry.Points * (-1)).ToString() : "") : ""; - col++; - - var remainingRewardPoints = order.Customer.GetRewardPointsBalance(); - worksheet.Cells[row, col].Value = (remainingRewardPoints > 0 ? remainingRewardPoints.ToString() : ""); - col++; - - worksheet.Cells[row, col].Value = order.HasNewPaymentNotification; - col++; - - - //billing address - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.FirstName : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.LastName : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.Email : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.Company : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null && order.BillingAddress.Country != null ? order.BillingAddress.Country.Name : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null && order.BillingAddress.StateProvince != null ? order.BillingAddress.StateProvince.Name : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.City : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.Address1 : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.Address2 : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.ZipPostalCode : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.PhoneNumber : ""; - col++; - - worksheet.Cells[row, col].Value = order.BillingAddress != null ? order.BillingAddress.FaxNumber : ""; - col++; - - //shipping address - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.FirstName : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.LastName : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.Email : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.Company : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null && order.ShippingAddress.Country != null ? order.ShippingAddress.Country.Name : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null && order.ShippingAddress.StateProvince != null ? order.ShippingAddress.StateProvince.Name : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.City : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.Address1 : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.Address2 : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.ZipPostalCode : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.PhoneNumber : ""; - col++; - - worksheet.Cells[row, col].Value = order.ShippingAddress != null ? order.ShippingAddress.FaxNumber : ""; - col++; - - //next row - row++; - } - - - - - - - - - // we had better add some document properties to the spreadsheet - - // set some core property values - //var storeName = _storeInformationSettings.StoreName; - //var storeUrl = _storeInformationSettings.StoreUrl; - //xlPackage.Workbook.Properties.Title = string.Format("{0} orders", storeName); - //xlPackage.Workbook.Properties.Author = storeName; - //xlPackage.Workbook.Properties.Subject = string.Format("{0} orders", storeName); - //xlPackage.Workbook.Properties.Keywords = string.Format("{0} orders", storeName); - //xlPackage.Workbook.Properties.Category = "Orders"; - //xlPackage.Workbook.Properties.Comments = string.Format("{0} orders", storeName); - - // set some extended property values - //xlPackage.Workbook.Properties.Company = storeName; - //xlPackage.Workbook.Properties.HyperlinkBase = new Uri(storeUrl); - - // save the new spreadsheet - xlPackage.Save(); - } - } - /// /// Export customer list to XLSX /// diff --git a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs index 1ef709e855..27937c87e8 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs @@ -48,13 +48,6 @@ public interface IExportManager /// Products void ExportProductsToXlsx(Stream stream, IList products); - /// - /// Export orders to XLSX - /// - /// Stream - /// Orders - void ExportOrdersToXlsx(Stream stream, IList orders); - /// /// Export customer list to XLSX /// diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 43c779951f..71fba3dc47 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -170,6 +170,7 @@ + diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs index 801231aa57..266013ca30 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlExtensions.cs @@ -640,7 +640,7 @@ public static MvcHtmlString IconForFileExtension(this HtmlHelper helper, string { result = ""; } - else if (fileExtension.IsCaseInsensitiveEqual("xls")) + else if (fileExtension.IsCaseInsensitiveEqual("xls") || fileExtension.IsCaseInsensitiveEqual("xlsx")) { result = ""; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs index da2ee2c221..add4ae97aa 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Web.Mvc; using System.Web.Routing; @@ -774,6 +773,19 @@ private void PrepareOrderAddressModel(OrderAddressModel model, Address address) } } + private ActionResult Export(string providerSystemName, string selectedIds) + { + string error = null; + var fileStreamResult = ExportProfileTask.Export(providerSystemName, selectedIds, "smnet-orders", out error); + + if (fileStreamResult != null) + return fileStreamResult; + + NotifyError(string.Concat("

    ", T("Admin.Common.UnknownError"), "

    ", error.NaIfEmpty())); + + return RedirectToAction("List"); + } + #endregion #region Order list @@ -882,15 +894,7 @@ public ActionResult ExportXmlAll() if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - string error = null; - var fileStreamResult = ExportProfileTask.Export(OrderExportXmlProvider.SystemName, null, "smnet-orders", out error); - - if (fileStreamResult != null) - return fileStreamResult; - - NotifyError(string.Concat("

    ", T("Admin.Common.UnknownError"), "

    ", error.NaIfEmpty())); - - return RedirectToAction("List"); + return Export(OrderExportXmlProvider.SystemName, null); } [HttpPost, Compress] @@ -899,65 +903,25 @@ public ActionResult ExportXmlSelected(string selectedIds) if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - string error = null; - var fileStreamResult = ExportProfileTask.Export(OrderExportXmlProvider.SystemName, selectedIds, "smnet-orders", out error); - - if (fileStreamResult != null) - return fileStreamResult; - - NotifyError(string.Concat("

    ", T("Admin.Common.UnknownError"), "

    ", error.NaIfEmpty())); - - return RedirectToAction("List"); + return Export(OrderExportXmlProvider.SystemName, selectedIds); } + [Compress] public ActionResult ExportExcelAll() { if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - try - { - var orders = _orderService.SearchOrders(0, 0, null, null, null, - null, null, null, null, null, 0, int.MaxValue); - - byte[] bytes = null; - using (var stream = new MemoryStream()) - { - _exportManager.ExportOrdersToXlsx(stream, orders); - bytes = stream.ToArray(); - } - return File(bytes, "text/xls", "orders.xlsx"); - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } + return Export(OrderExportXlsxProvider.SystemName, null); } - [HttpPost] + [HttpPost, Compress] public ActionResult ExportExcelSelected(string selectedIds) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - var orders = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - orders.AddRange(_orderService.GetOrdersByIds(ids)); - } - - byte[] bytes = null; - using (var stream = new MemoryStream()) - { - _exportManager.ExportOrdersToXlsx(stream, orders); - bytes = stream.ToArray(); - } - return File(bytes, "text/xls", "orders.xlsx"); + return Export(OrderExportXlsxProvider.SystemName, selectedIds); } public ActionResult ExportPdf(bool all, string selectedIds = null) diff --git a/src/Tests/SmartStore.Services.Tests/Customers/CustomerRegistrationServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Customers/CustomerRegistrationServiceTests.cs index 83c77806ea..c3dfb206de 100644 --- a/src/Tests/SmartStore.Services.Tests/Customers/CustomerRegistrationServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Customers/CustomerRegistrationServiceTests.cs @@ -24,6 +24,7 @@ public class CustomerRegistrationServiceTests : ServiceTest IRepository _customerRepo; IRepository _customerRoleRepo; IRepository _genericAttributeRepo; + IRepository _rewardPointsHistoryRepo; IGenericAttributeService _genericAttributeService; IEncryptionService _encryptionService; ICustomerService _customerService; @@ -111,6 +112,7 @@ public class CustomerRegistrationServiceTests : ServiceTest _customerRoleRepo = MockRepository.GenerateMock>(); _genericAttributeRepo = MockRepository.GenerateMock>(); + _rewardPointsHistoryRepo = MockRepository.GenerateMock>(); _genericAttributeService = MockRepository.GenerateMock(); _newsLetterSubscriptionService = MockRepository.GenerateMock(); @@ -119,7 +121,7 @@ public class CustomerRegistrationServiceTests : ServiceTest _storeContext = MockRepository.GenerateMock(); _customerService = new CustomerService(new NullCache(), _customerRepo, _customerRoleRepo, - _genericAttributeRepo, _genericAttributeService, _eventPublisher, _rewardPointsSettings); + _genericAttributeRepo, _rewardPointsHistoryRepo, _genericAttributeService, _eventPublisher, _rewardPointsSettings); _customerRegistrationService = new CustomerRegistrationService(_customerService, _encryptionService, _newsLetterSubscriptionService, _localizationService, From 5430156787299b289d8573c77e37cf1f0f102b9a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 28 Sep 2015 15:27:14 +0200 Subject: [PATCH 090/874] Check attached entities during export --- .../Extensions/DbContextExtensions.cs | 12 ++++++++++++ .../ExportTask/ExportProfileTask.cs | 19 ++++++++----------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Extensions/DbContextExtensions.cs b/src/Libraries/SmartStore.Data/Extensions/DbContextExtensions.cs index 2dbbc2721f..6a0aba947f 100644 --- a/src/Libraries/SmartStore.Data/Extensions/DbContextExtensions.cs +++ b/src/Libraries/SmartStore.Data/Extensions/DbContextExtensions.cs @@ -1,6 +1,8 @@ using System.Linq; using System.Data.Entity; using System.Data.Entity.Infrastructure; +using SmartStore.Core; +using System.Diagnostics; namespace SmartStore { @@ -81,6 +83,16 @@ public static int Execute(this DbContext context, string sql, params object[] pa return context.Database.ExecuteSqlCommand(sql, parameters); } + [Conditional("DEBUG")] + [DebuggerStepThrough] + public static void DumpAttachedEntities(this DbContext context) + { + context.ChangeTracker.Entries() + .Where(x => x.State != System.Data.Entity.EntityState.Detached) + .ToList() + .ForEach(x => "{0} {1} {2}".FormatInvariant((x.Entity as BaseEntity).Id, x.State.ToString(), x.Entity.GetType().Name).Dump()); + } + internal static SqlServerInfo GetSqlServerInfo(this DbContext context) { string sql = @"SELECT diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 3a3032f845..a25afe714e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Data.Entity; using System.Dynamic; using System.IO; using System.IO.Compression; @@ -644,10 +645,6 @@ private List GetOrders(ExportProfileTaskContext ctx, int pageIndex, int p try { _services.DbContext.DetachEntities(result); - - // TODO: examine remaining attached entities - //foreach (var item in orderItems) - // _services.DbContext.DetachEntity(item.Product); } catch { } @@ -912,6 +909,12 @@ private List ToExpando(ExportProfileTaskContext ctx, Order order) dynamic expando = order.ToExpando(languageId, _services.Localization); + if (ctx.Stores.ContainsKey(order.StoreId)) + { + expando.Store = ctx.Stores[order.StoreId] + .ToExpando(languageId); + } + expando.Customer = customers .FirstOrDefault(x => x.Id == order.CustomerId) .ToExpando(languageId); @@ -940,12 +943,6 @@ private List ToExpando(ExportProfileTaskContext ctx, Order order) .ToExpando(languageId); } - if (ctx.Stores.ContainsKey(order.StoreId)) - { - expando.Store = ctx.Stores[order.StoreId] - .ToExpando(languageId); - } - expando.OrderItems = orderItems .Select(e => { @@ -1231,9 +1228,9 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) var stores = Init(ctx); + ctx.Export.Language = ctx.ProjectionLanguage.ToExpando(); ctx.Export.Customer = ctx.ProjectionCustomer.ToExpando(ctx.ProjectionLanguage.Id); ctx.Export.Currency = ctx.ProjectionCurrency.ToExpando(ctx.ProjectionLanguage.Id); - ctx.Export.Language = ctx.ProjectionLanguage.ToExpando(); stores.ForEach(x => ExportCoreInner(ctx, x)); } From ebee94a7ad8192950a914ad5213fa16697394536 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 29 Sep 2015 11:12:03 +0200 Subject: [PATCH 091/874] Convert product XML export to support new export framework (part 1) --- .../DataExchange/ExpandoExtensions.cs | 22 +- ...Provider.cs => ExportOrderXlsxProvider.cs} | 2 +- .../ExportProvider/ExportOrderXmlProvider.cs | 298 ++++++++++ .../ExportProductXmlProvider.cs | 97 ++++ .../ExportProvider/OrderExportXmlProvider.cs | 544 ------------------ .../DataExchange/ExportXmlHelper.cs | 299 ++++++++++ .../SmartStore.Services.csproj | 6 +- .../Controllers/OrderController.cs | 8 +- 8 files changed, 714 insertions(+), 562 deletions(-) rename src/Libraries/SmartStore.Services/DataExchange/ExportProvider/{OrderExportXlsxProvider.cs => ExportOrderXlsxProvider.cs} (99%) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs delete mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXmlProvider.cs create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs index 0c9ee2eedc..510d451260 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs @@ -132,21 +132,21 @@ public static ExpandoObject ToExpando(this Address address, int languageId) return expando as ExpandoObject; } - public static ExpandoObject ToExpando(this RewardPointsHistory history) + public static ExpandoObject ToExpando(this RewardPointsHistory points) { - if (history == null) + if (points == null) return null; dynamic expando = new ExpandoObject(); - expando._Entity = history; - - expando.Id = history.Id; - expando.CustomerId = history.CustomerId; - expando.Points = history.Points; - expando.PointsBalance = history.PointsBalance; - expando.UsedAmount = history.UsedAmount; - expando.Message = history.Message; - expando.CreatedOnUtc = history.CreatedOnUtc; + expando._Entity = points; + + expando.Id = points.Id; + expando.CustomerId = points.CustomerId; + expando.Points = points.Points; + expando.PointsBalance = points.PointsBalance; + expando.UsedAmount = points.UsedAmount; + expando.Message = points.Message; + expando.CreatedOnUtc = points.CreatedOnUtc; return expando as ExpandoObject; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs similarity index 99% rename from src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXlsxProvider.cs rename to src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs index 5fb8bd5fd9..12fb46ac15 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs @@ -12,7 +12,7 @@ namespace SmartStore.Services.DataExchange.ExportProvider [SystemName("Exports.SmartStoreNetOrderXlsx")] [FriendlyName("SmartStore.NET Excel order export")] [IsHidden(true)] - public class OrderExportXlsxProvider : IExportProvider + public class ExportOrderXlsxProvider : IExportProvider { private string[] Properties { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs new file mode 100644 index 0000000000..7a608dd976 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs @@ -0,0 +1,298 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using System.Xml; +using SmartStore.Core; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.DataExchange.ExportProvider +{ + [SystemName("Exports.SmartStoreNetOrderXml")] + [FriendlyName("SmartStore.NET XML order export")] + [IsHidden(true)] + public class ExportOrderXmlProvider : IExportProvider + { + public static string SystemName + { + get { return "Exports.SmartStoreNetOrderXml"; } + } + + public ExportConfigurationInfo ConfigurationInfo + { + get { return null; } + } + + public ExportEntityType EntityType + { + get { return ExportEntityType.Order; } + } + + public string FileExtension + { + get { return "XML"; } + } + + public void Execute(IExportExecuteContext context) + { + var settings = new XmlWriterSettings + { + Encoding = Encoding.UTF8, + CheckCharacters = false, + Indent = true, + IndentChars = "\t" + }; + + var path = context.FilePath; + var invariantCulture = CultureInfo.InvariantCulture; + + context.Log.Information("Creating file " + path); + + using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + using (var writer = XmlWriter.Create(stream, settings)) + { + var xmlHelper = new ExportXmlHelper(writer, invariantCulture); + + writer.WriteStartDocument(); + writer.WriteStartElement("Orders"); + writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); + + while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + { + var segment = context.Data.CurrentSegment; + + foreach (dynamic order in segment) + { + if (context.Abort != ExportAbortion.None) + break; + + int orderId = order.Id; + dynamic customer = order.Customer; + dynamic store = order.Store; + + writer.WriteStartElement("Order"); + + try + { + int? shippingAddressId = order.ShippingAddressId; + DateTime? paidDateUtc = order.PaidDateUtc; + int? rewardPointsRemaining = order.RewardPointsRemaining; + + writer.Write("Id", orderId.ToString()); + writer.Write("OrderNumber", (string)order.OrderNumber); + writer.Write("OrderGuid", ((Guid)order.OrderGuid).ToString()); + writer.Write("StoreId", ((int)order.StoreId).ToString()); + writer.Write("CustomerId", ((int)order.CustomerId).ToString()); + writer.Write("BillingAddressId", ((int)order.BillingAddressId).ToString()); + writer.Write("ShippingAddressId", shippingAddressId.HasValue ? shippingAddressId.Value.ToString() : ""); + writer.Write("OrderStatusId", ((int)order.OrderStatusId).ToString()); + writer.Write("ShippingStatusId", ((int)order.ShippingStatusId).ToString()); + writer.Write("PaymentStatusId", ((int)order.PaymentStatusId).ToString()); + writer.Write("PaymentMethodSystemName", (string)order.PaymentMethodSystemName); + writer.Write("CustomerCurrencyCode", (string)order.CustomerCurrencyCode); + writer.Write("CurrencyRate", ((decimal)order.CurrencyRate).ToString(invariantCulture)); + writer.Write("CustomerTaxDisplayTypeId", ((int)order.CustomerTaxDisplayTypeId).ToString()); + writer.Write("VatNumber", (string)order.VatNumber); + writer.Write("OrderSubtotalInclTax", ((decimal)order.OrderSubtotalInclTax).ToString(invariantCulture)); + writer.Write("OrderSubtotalExclTax", ((decimal)order.OrderSubtotalExclTax).ToString(invariantCulture)); + writer.Write("OrderSubTotalDiscountInclTax", ((decimal)order.OrderSubTotalDiscountInclTax).ToString(invariantCulture)); + writer.Write("OrderSubTotalDiscountExclTax", ((decimal)order.OrderSubTotalDiscountExclTax).ToString(invariantCulture)); + writer.Write("OrderShippingInclTax", ((decimal)order.OrderShippingInclTax).ToString(invariantCulture)); + writer.Write("OrderShippingExclTax", ((decimal)order.OrderShippingExclTax).ToString(invariantCulture)); + writer.Write("OrderShippingTaxRate", ((decimal)order.OrderShippingTaxRate).ToString(invariantCulture)); + writer.Write("PaymentMethodAdditionalFeeInclTax", ((decimal)order.PaymentMethodAdditionalFeeInclTax).ToString(invariantCulture)); + writer.Write("PaymentMethodAdditionalFeeExclTax", ((decimal)order.PaymentMethodAdditionalFeeExclTax).ToString(invariantCulture)); + writer.Write("PaymentMethodAdditionalFeeTaxRate", ((decimal)order.PaymentMethodAdditionalFeeTaxRate).ToString(invariantCulture)); + writer.Write("TaxRates", (string)order.TaxRates); + writer.Write("OrderTax", ((decimal)order.OrderTax).ToString(invariantCulture)); + writer.Write("OrderDiscount", ((decimal)order.OrderDiscount).ToString(invariantCulture)); + writer.Write("OrderTotal", ((decimal)order.OrderTotal).ToString(invariantCulture)); + writer.Write("RefundedAmount", ((decimal)order.RefundedAmount).ToString(invariantCulture)); + writer.Write("RewardPointsWereAdded", ((bool)order.RewardPointsWereAdded).ToString()); + writer.Write("CheckoutAttributeDescription", (string)order.CheckoutAttributeDescription); + writer.Write("CheckoutAttributesXml", (string)order.CheckoutAttributesXml); + writer.Write("CustomerLanguageId", ((int)order.CustomerLanguageId).ToString()); + writer.Write("AffiliateId", ((int)order.AffiliateId).ToString()); + writer.Write("CustomerIp", (string)order.CustomerIp); + writer.Write("AllowStoringCreditCardNumber", ((bool)order.AllowStoringCreditCardNumber).ToString()); + writer.Write("CardType", (string)order.CardType); + writer.Write("CardName", (string)order.CardName); + writer.Write("CardNumber", (string)order.CardNumber); + writer.Write("MaskedCreditCardNumber", (string)order.MaskedCreditCardNumber); + writer.Write("CardCvv2", (string)order.CardCvv2); + writer.Write("CardExpirationMonth", (string)order.CardExpirationMonth); + writer.Write("CardExpirationYear", (string)order.CardExpirationYear); + writer.Write("AllowStoringDirectDebit", ((bool)order.AllowStoringDirectDebit).ToString()); + writer.Write("DirectDebitAccountHolder", (string)order.DirectDebitAccountHolder); + writer.Write("DirectDebitAccountNumber", (string)order.DirectDebitAccountNumber); + writer.Write("DirectDebitBankCode", (string)order.DirectDebitBankCode); + writer.Write("DirectDebitBankName", (string)order.DirectDebitBankName); + writer.Write("DirectDebitBIC", (string)order.DirectDebitBIC); + writer.Write("DirectDebitCountry", (string)order.DirectDebitCountry); + writer.Write("DirectDebitIban", (string)order.DirectDebitIban); + writer.Write("CustomerOrderComment", (string)order.CustomerOrderComment); + writer.Write("AuthorizationTransactionId", (string)order.AuthorizationTransactionId); + writer.Write("AuthorizationTransactionCode", (string)order.AuthorizationTransactionCode); + writer.Write("AuthorizationTransactionResult", (string)order.AuthorizationTransactionResult); + writer.Write("CaptureTransactionId", (string)order.CaptureTransactionId); + writer.Write("CaptureTransactionResult", (string)order.CaptureTransactionResult); + writer.Write("SubscriptionTransactionId", (string)order.SubscriptionTransactionId); + writer.Write("PurchaseOrderNumber", (string)order.PurchaseOrderNumber); + writer.Write("PaidDateUtc", paidDateUtc.HasValue ? paidDateUtc.Value.ToString(invariantCulture) : ""); + writer.Write("ShippingMethod", (string)order.ShippingMethod); + writer.Write("ShippingRateComputationMethodSystemName", (string)order.ShippingRateComputationMethodSystemName); + writer.Write("Deleted", ((bool)order.Deleted).ToString()); + writer.Write("CreatedOnUtc", ((DateTime)order.CreatedOnUtc).ToString(invariantCulture)); + writer.Write("UpdatedOnUtc", ((DateTime)order.UpdatedOnUtc).ToString(invariantCulture)); + writer.Write("RewardPointsRemaining", rewardPointsRemaining.HasValue ? rewardPointsRemaining.Value.ToString() : ""); + writer.Write("HasNewPaymentNotification", ((bool)order.HasNewPaymentNotification).ToString()); + writer.Write("OrderStatus", (string)order.OrderStatus); + writer.Write("PaymentStatus", (string)order.PaymentStatus); + writer.Write("ShippingStatus", (string)order.ShippingStatus); + + if (customer != null) + { + DateTime? lastLoginDateUtc = customer.LastLoginDateUtc; + + writer.WriteStartElement("Customer"); + writer.Write("Id", ((int)customer.Id).ToString()); + writer.Write("CustomerGuid", ((Guid)customer.CustomerGuid).ToString()); + writer.Write("Username", (string)customer.Username); + writer.Write("Email", (string)customer.Email); + writer.Write("PasswordFormatId", ((int)customer.PasswordFormatId).ToString()); + writer.Write("AdminComment", (string)customer.AdminComment); + writer.Write("IsTaxExempt", ((bool)customer.IsTaxExempt).ToString()); + writer.Write("AffiliateId", ((int)customer.AffiliateId).ToString()); + writer.Write("Active", ((bool)customer.Active).ToString()); + writer.Write("Deleted", ((bool)customer.Deleted).ToString()); + writer.Write("IsSystemAccount", ((bool)customer.IsSystemAccount).ToString()); + writer.Write("SystemName", (string)customer.SystemName); + writer.Write("LastIpAddress", (string)customer.LastIpAddress); + writer.Write("CreatedOnUtc", ((DateTime)customer.CreatedOnUtc).ToString(invariantCulture)); + writer.Write("LastLoginDateUtc", lastLoginDateUtc.HasValue ? lastLoginDateUtc.Value.ToString(invariantCulture) : ""); + writer.Write("LastActivityDateUtc", ((DateTime)customer.LastActivityDateUtc).ToString(invariantCulture)); + writer.Write("RewardPointsBalance", ((int)customer._RewardPointsBalance).ToString()); + + xmlHelper.WriteRewardPointsHistory(customer.RewardPointsHistory, "RewardPointsHistories"); + xmlHelper.WriteAddress(customer.BillingAddress, "BillingAddress"); + xmlHelper.WriteAddress(customer.ShippingAddress, "ShippingAddress"); + + writer.WriteEndElement(); // Customer + } + + xmlHelper.WriteAddress(order.BillingAddress, "BillingAddress"); + xmlHelper.WriteAddress(order.ShippingAddress, "ShippingAddress"); + + if (store != null) + { + writer.WriteStartElement("Store"); + writer.Write("Id", ((int)store.Id).ToString()); + writer.Write("Name", (string)store.Name); + writer.Write("Url", (string)store.Url); + writer.Write("SslEnabled", ((bool)store.SslEnabled).ToString()); + writer.Write("SecureUrl", (string)store.SecureUrl); + writer.Write("Hosts", (string)store.Hosts); + writer.Write("LogoPictureId", ((int)store.LogoPictureId).ToString()); + writer.Write("DisplayOrder", ((int)store.DisplayOrder).ToString()); + writer.Write("HtmlBodyId", (string)store.HtmlBodyId); + writer.Write("ContentDeliveryNetwork", (string)store.ContentDeliveryNetwork); + writer.Write("PrimaryStoreCurrencyId", ((int)store.PrimaryStoreCurrencyId).ToString()); + writer.Write("PrimaryExchangeRateCurrencyId", ((int)store.PrimaryExchangeRateCurrencyId).ToString()); + + xmlHelper.WriteCurrency(store.PrimaryStoreCurrency, "PrimaryStoreCurrency"); + xmlHelper.WriteCurrency(store.PrimaryExchangeRateCurrency, "PrimaryExchangeRateCurrency"); + + writer.WriteEndElement(); // Store + } + + writer.WriteStartElement("OrderItems"); + foreach (dynamic orderItem in order.OrderItems) + { + int? licenseDownloadId = orderItem.LicenseDownloadId; + decimal? itemWeight = orderItem.ItemWeight; + + writer.WriteStartElement("OrderItem"); + writer.Write("Id", ((int)orderItem.Id).ToString()); + writer.Write("OrderItemGuid", ((Guid)orderItem.OrderItemGuid).ToString()); + writer.Write("OrderId", ((int)orderItem.OrderId).ToString()); + writer.Write("ProductId", ((int)orderItem.ProductId).ToString()); + writer.Write("Quantity", ((int)orderItem.Quantity).ToString()); + writer.Write("UnitPriceInclTax", ((decimal)orderItem.UnitPriceInclTax).ToString(invariantCulture)); + writer.Write("UnitPriceExclTax", ((decimal)orderItem.UnitPriceExclTax).ToString(invariantCulture)); + writer.Write("PriceInclTax", ((decimal)orderItem.PriceInclTax).ToString(invariantCulture)); + writer.Write("PriceExclTax", ((decimal)orderItem.PriceExclTax).ToString(invariantCulture)); + writer.Write("TaxRate", ((decimal)orderItem.TaxRate).ToString(invariantCulture)); + writer.Write("DiscountAmountInclTax", ((decimal)orderItem.DiscountAmountInclTax).ToString(invariantCulture)); + writer.Write("DiscountAmountExclTax", ((decimal)orderItem.DiscountAmountExclTax).ToString(invariantCulture)); + writer.Write("AttributeDescription", (string)orderItem.AttributeDescription); + writer.Write("AttributesXml", (string)orderItem.AttributesXml); + writer.Write("DownloadCount", ((int)orderItem.DownloadCount).ToString()); + writer.Write("IsDownloadActivated", ((bool)orderItem.IsDownloadActivated).ToString()); + writer.Write("LicenseDownloadId", licenseDownloadId.HasValue ? licenseDownloadId.Value.ToString() : ""); + writer.Write("ItemWeight", itemWeight.HasValue ? itemWeight.Value.ToString(invariantCulture) : ""); + writer.Write("BundleData", (string)orderItem.BundleData); + writer.Write("ProductCost", ((decimal)orderItem.ProductCost).ToString(invariantCulture)); + + xmlHelper.WriteProduct(orderItem.Product, "Product"); + + writer.WriteEndElement(); // OrderItem + } + writer.WriteEndElement(); // OrderItems + + writer.WriteStartElement("Shipments"); + foreach (dynamic shipment in order.Shipments) + { + decimal? totalWeight = shipment.TotalWeight; + DateTime? shippedDateUtc = shipment.ShippedDateUtc; + DateTime? deliveryDateUtc = shipment.DeliveryDateUtc; + + writer.WriteStartElement("Shipment"); + writer.Write("Id", ((int)shipment.Id).ToString()); + writer.Write("OrderId", ((int)shipment.OrderId).ToString()); + writer.Write("TrackingNumber", (string)shipment.TrackingNumber); + writer.Write("TotalWeight", totalWeight.HasValue ? totalWeight.Value.ToString(invariantCulture) : ""); + writer.Write("ShippedDateUtc", shippedDateUtc.HasValue ? shippedDateUtc.Value.ToString(invariantCulture) : ""); + writer.Write("DeliveryDateUtc", deliveryDateUtc.HasValue ? deliveryDateUtc.Value.ToString(invariantCulture) : ""); + writer.Write("CreatedOnUtc", ((DateTime)shipment.CreatedOnUtc).ToString(invariantCulture)); + + writer.WriteStartElement("ShipmentItems"); + foreach (dynamic shipmentItem in shipment.ShipmentItems) + { + writer.WriteStartElement("ShipmentItem"); + writer.Write("Id", ((int)shipmentItem.Id).ToString()); + writer.Write("ShipmentId", ((int)shipmentItem.ShipmentId).ToString()); + writer.Write("OrderItemId", ((int)shipmentItem.OrderItemId).ToString()); + writer.Write("Quantity", ((int)shipmentItem.Quantity).ToString()); + writer.WriteEndElement(); // ShipmentItem + } + writer.WriteEndElement(); // ShipmentItems + + writer.WriteEndElement(); // Shipment + } + writer.WriteEndElement(); // Shipments + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.Log.Error("Error while processing order with id {0}: {1}".FormatInvariant(orderId, exc.ToAllMessages()), exc); + ++context.RecordsFailed; + } + + writer.WriteEndElement(); // Order + } + } + + writer.WriteEndElement(); // Orders + writer.WriteEndDocument(); + } + } + + public void ExecuteEnded(IExportExecuteContext context) + { + // nothing to do + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs new file mode 100644 index 0000000000..f81b0fe66b --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs @@ -0,0 +1,97 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using System.Xml; +using SmartStore.Core; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.DataExchange.ExportProvider +{ + [SystemName("Exports.SmartStoreNetProductXml")] + [FriendlyName("SmartStore.NET XML product export")] + [IsHidden(true)] + public class ExportProductXmlProvider : IExportProvider + { + public static string SystemName + { + get { return "Exports.SmartStoreNetProductXml"; } + } + + public ExportConfigurationInfo ConfigurationInfo + { + get { return null; } + } + + public ExportEntityType EntityType + { + get { return ExportEntityType.Product; } + } + + public string FileExtension + { + get { return "XML"; } + } + + public void Execute(IExportExecuteContext context) + { + var settings = new XmlWriterSettings + { + Encoding = Encoding.UTF8, + CheckCharacters = false, + Indent = true, + IndentChars = "\t" + }; + + var path = context.FilePath; + var invariantCulture = CultureInfo.InvariantCulture; + + context.Log.Information("Creating file " + path); + + using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + using (var writer = XmlWriter.Create(stream, settings)) + { + var xmlHelper = new ExportXmlHelper(writer, invariantCulture); + + writer.WriteStartDocument(); + writer.WriteStartElement("Products"); + writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); + + while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + { + var segment = context.Data.CurrentSegment; + + foreach (dynamic product in segment) + { + if (context.Abort != ExportAbortion.None) + break; + + int productId = product.Id; + + try + { + xmlHelper.WriteProduct(product, "Product"); + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.Log.Error("Error while processing product with id {0}: {1}".FormatInvariant(productId, exc.ToAllMessages()), exc); + ++context.RecordsFailed; + } + } + } + + writer.WriteEndElement(); // Products + writer.WriteEndDocument(); + } + } + + public void ExecuteEnded(IExportExecuteContext context) + { + // nothing to do + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXmlProvider.cs deleted file mode 100644 index 0f06f6d611..0000000000 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/OrderExportXmlProvider.cs +++ /dev/null @@ -1,544 +0,0 @@ -using System; -using System.Globalization; -using System.IO; -using System.Text; -using System.Xml; -using SmartStore.Core; -using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Logging; -using SmartStore.Core.Plugins; - -namespace SmartStore.Services.DataExchange.ExportProvider -{ - [SystemName("Exports.SmartStoreNetOrderXml")] - [FriendlyName("SmartStore.NET XML order export")] - [IsHidden(true)] - public class OrderExportXmlProvider : IExportProvider - { - private void WriteAddress(XmlWriter writer, string node, dynamic address, CultureInfo culture) - { - if (address == null) - return; - - int? countryId = address.CountryId; - int? stateProvinceId = address.StateProvinceId; - - writer.WriteStartElement(node); - - writer.WriteElementString("Id", ((int)address.Id).ToString()); - writer.WriteElementString("FirstName", (string)address.FirstName); - writer.WriteElementString("LastName", (string)address.LastName); - writer.WriteElementString("Email", (string)address.Email); - writer.WriteElementString("Company", (string)address.Company); - writer.WriteElementString("CountryId", countryId.HasValue ? countryId.Value.ToString() : ""); - writer.WriteElementString("StateProvinceId", stateProvinceId.HasValue ? stateProvinceId.Value.ToString() : ""); - writer.WriteElementString("City", (string)address.City); - writer.WriteElementString("Address1", (string)address.Address1); - writer.WriteElementString("Address2", (string)address.Address2); - writer.WriteElementString("ZipPostalCode", (string)address.ZipPostalCode); - writer.WriteElementString("PhoneNumber", (string)address.PhoneNumber); - writer.WriteElementString("FaxNumber", (string)address.FaxNumber); - writer.WriteElementString("CreatedOnUtc", ((DateTime)address.CreatedOnUtc).ToString(culture)); - - if (address.Country != null) - { - dynamic country = address.Country; - - writer.WriteStartElement("Country"); - writer.WriteElementString("Id", ((int)country.Id).ToString()); - writer.WriteElementString("Name", (string)country.Name); - writer.WriteElementString("AllowsBilling", ((bool)country.AllowsBilling).ToString()); - writer.WriteElementString("AllowsShipping", ((bool)country.AllowsShipping).ToString()); - writer.WriteElementString("TwoLetterIsoCode", (string)country.TwoLetterIsoCode); - writer.WriteElementString("ThreeLetterIsoCode", (string)country.ThreeLetterIsoCode); - writer.WriteElementString("NumericIsoCode", ((int)country.NumericIsoCode).ToString()); - writer.WriteElementString("SubjectToVat", ((bool)country.SubjectToVat).ToString()); - writer.WriteElementString("Published", ((bool)country.Published).ToString()); - writer.WriteElementString("DisplayOrder", ((int)country.DisplayOrder).ToString()); - writer.WriteElementString("LimitedToStores", ((bool)country.LimitedToStores).ToString()); - writer.WriteEndElement(); // Country - } - - if (address.StateProvince != null) - { - dynamic stateProvince = address.StateProvince; - - writer.WriteStartElement("StateProvince"); - writer.WriteElementString("Id", ((int)stateProvince.Id).ToString()); - writer.WriteElementString("CountryId", ((int)stateProvince.CountryId).ToString()); - writer.WriteElementString("Name", (string)stateProvince.Name); - writer.WriteElementString("Abbreviation", (string)stateProvince.Abbreviation); - writer.WriteElementString("Published", ((bool)stateProvince.Published).ToString()); - writer.WriteElementString("DisplayOrder", ((int)stateProvince.DisplayOrder).ToString()); - writer.WriteEndElement(); // StateProvince - } - - writer.WriteEndElement(); // node - } - - private void WriteCurrency(XmlWriter writer, string node, dynamic currency, CultureInfo culture) - { - if (currency == null) - return; - - writer.WriteStartElement(node); - - writer.WriteElementString("Id", ((int)currency.Id).ToString()); - writer.WriteElementString("Name", (string)currency.Name); - writer.WriteElementString("CurrencyCode", (string)currency.CurrencyCode); - writer.WriteElementString("Rate", ((decimal)currency.Rate).ToString(culture)); - writer.WriteElementString("DisplayLocale", (string)currency.DisplayLocale); - writer.WriteElementString("CustomFormatting", (string)currency.CustomFormatting); - writer.WriteElementString("LimitedToStores", ((bool)currency.LimitedToStores).ToString()); - writer.WriteElementString("Published", ((bool)currency.Published).ToString()); - writer.WriteElementString("DisplayOrder", ((int)currency.DisplayOrder).ToString()); - writer.WriteElementString("CreatedOnUtc", ((DateTime)currency.CreatedOnUtc).ToString(culture)); - writer.WriteElementString("UpdatedOnUtc", ((DateTime)currency.UpdatedOnUtc).ToString(culture)); - writer.WriteElementString("DomainEndings", (string)currency.DomainEndings); - - writer.WriteEndElement(); // node - } - - private void WriteProduct(XmlWriter writer, string node, dynamic product, CultureInfo culture) - { - if (product == null) - return; - - int? downloadExpirationDays = product.DownloadExpirationDays; - int? sampleDownloadId = product.SampleDownloadId; - decimal? specialPrice = product.SpecialPrice; - DateTime? specialPriceStartDateTimeUtc = product.SpecialPriceStartDateTimeUtc; - DateTime? specialPriceEndDateTimeUtc = product.SpecialPriceEndDateTimeUtc; - DateTime? availableStartDateTimeUtc = product.AvailableStartDateTimeUtc; - DateTime? availableEndDateTimeUtc = product.AvailableEndDateTimeUtc; - decimal? basePriceAmount = product.BasePriceAmount; - int? basePriceBaseAmount = product.BasePriceBaseAmount; - decimal? lowestAttributeCombinationPrice = product.LowestAttributeCombinationPrice; - - writer.WriteStartElement(node); - - writer.WriteElementString("Id", ((int)product.Id).ToString()); - writer.WriteElementString("Name", ((string)product.Name).RemoveInvalidXmlChars()); - writer.WriteElementString("SeName", (string)product.SeName); - writer.WriteElementString("ShortDescription", ((string)product.ShortDescription).RemoveInvalidXmlChars()); - writer.WriteElementString("FullDescription", ((string)product.FullDescription).RemoveInvalidXmlChars()); - writer.WriteElementString("AdminComment", ((string)product.AdminComment).RemoveInvalidXmlChars()); - writer.WriteElementString("ProductTemplateId", ((int)product.ProductTemplateId).ToString()); - writer.WriteElementString("ProductTemplateViewPath", (string)product._ProductTemplateViewPath); - writer.WriteElementString("ShowOnHomePage", ((bool)product.ShowOnHomePage).ToString()); - writer.WriteElementString("HomePageDisplayOrder", ((int)product.HomePageDisplayOrder).ToString()); - writer.WriteElementString("MetaKeywords", ((string)product.MetaKeywords).RemoveInvalidXmlChars()); - writer.WriteElementString("MetaDescription", ((string)product.MetaDescription).RemoveInvalidXmlChars()); - writer.WriteElementString("MetaTitle", ((string)product.MetaTitle).RemoveInvalidXmlChars()); - writer.WriteElementString("AllowCustomerReviews", ((bool)product.AllowCustomerReviews).ToString()); - writer.WriteElementString("ApprovedRatingSum", ((int)product.ApprovedRatingSum).ToString()); - writer.WriteElementString("NotApprovedRatingSum", ((int)product.NotApprovedRatingSum).ToString()); - writer.WriteElementString("ApprovedTotalReviews", ((int)product.ApprovedTotalReviews).ToString()); - writer.WriteElementString("NotApprovedTotalReviews", ((int)product.NotApprovedTotalReviews).ToString()); - writer.WriteElementString("Published", ((bool)product.Published).ToString()); - writer.WriteElementString("CreatedOnUtc", ((DateTime)product.CreatedOnUtc).ToString(culture)); - writer.WriteElementString("UpdatedOnUtc", ((DateTime)product.UpdatedOnUtc).ToString(culture)); - writer.WriteElementString("SubjectToAcl", ((bool)product.SubjectToAcl).ToString()); - writer.WriteElementString("LimitedToStores", ((bool)product.LimitedToStores).ToString()); - writer.WriteElementString("ProductTypeId", ((int)product.ProductTypeId).ToString()); - writer.WriteElementString("ParentGroupedProductId", ((int)product.ParentGroupedProductId).ToString()); - writer.WriteElementString("Sku", (string)product.Sku); - writer.WriteElementString("ManufacturerPartNumber", (string)product.ManufacturerPartNumber); - writer.WriteElementString("Gtin", (string)product.Gtin); - writer.WriteElementString("IsGiftCard", ((bool)product.IsGiftCard).ToString()); - writer.WriteElementString("GiftCardTypeId", ((int)product.GiftCardTypeId).ToString()); - writer.WriteElementString("RequireOtherProducts", ((bool)product.RequireOtherProducts).ToString()); - writer.WriteElementString("RequiredProductIds", (string)product.RequiredProductIds); - writer.WriteElementString("AutomaticallyAddRequiredProducts", ((bool)product.AutomaticallyAddRequiredProducts).ToString()); - writer.WriteElementString("IsDownload", ((bool)product.IsDownload).ToString()); - writer.WriteElementString("DownloadId", ((int)product.DownloadId).ToString()); - writer.WriteElementString("UnlimitedDownloads", ((bool)product.UnlimitedDownloads).ToString()); - writer.WriteElementString("MaxNumberOfDownloads", ((int)product.MaxNumberOfDownloads).ToString()); - writer.WriteElementString("DownloadExpirationDays", downloadExpirationDays.HasValue ? downloadExpirationDays.Value.ToString() : ""); - writer.WriteElementString("DownloadActivationTypeId", ((int)product.DownloadActivationTypeId).ToString()); - writer.WriteElementString("HasSampleDownload", ((bool)product.HasSampleDownload).ToString()); - writer.WriteElementString("SampleDownloadId", sampleDownloadId.HasValue ? sampleDownloadId.Value.ToString() : ""); - writer.WriteElementString("HasUserAgreement", ((bool)product.HasUserAgreement).ToString()); - writer.WriteElementString("UserAgreementText", ((string)product.UserAgreementText).RemoveInvalidXmlChars()); - writer.WriteElementString("IsRecurring", ((bool)product.IsRecurring).ToString()); - writer.WriteElementString("RecurringCycleLength", ((int)product.RecurringCycleLength).ToString()); - writer.WriteElementString("RecurringCyclePeriodId", ((int)product.RecurringCyclePeriodId).ToString()); - writer.WriteElementString("RecurringTotalCycles", ((int)product.RecurringTotalCycles).ToString()); - writer.WriteElementString("IsShipEnabled", ((bool)product.IsShipEnabled).ToString()); - writer.WriteElementString("IsFreeShipping", ((bool)product.IsFreeShipping).ToString()); - writer.WriteElementString("AdditionalShippingCharge", ((decimal)product.AdditionalShippingCharge).ToString(culture)); - writer.WriteElementString("IsTaxExempt", ((bool)product.IsTaxExempt).ToString()); - writer.WriteElementString("TaxCategoryId", ((int)product.TaxCategoryId).ToString()); - writer.WriteElementString("ManageInventoryMethodId", ((int)product.ManageInventoryMethodId).ToString()); - writer.WriteElementString("StockQuantity", ((int)product.StockQuantity).ToString()); - writer.WriteElementString("DisplayStockAvailability", ((bool)product.DisplayStockAvailability).ToString()); - writer.WriteElementString("DisplayStockQuantity", ((bool)product.DisplayStockQuantity).ToString()); - writer.WriteElementString("MinStockQuantity", ((int)product.MinStockQuantity).ToString()); - writer.WriteElementString("LowStockActivityId", ((int)product.LowStockActivityId).ToString()); - writer.WriteElementString("NotifyAdminForQuantityBelow", ((int)product.NotifyAdminForQuantityBelow).ToString()); - writer.WriteElementString("BackorderModeId", ((int)product.BackorderModeId).ToString()); - writer.WriteElementString("AllowBackInStockSubscriptions", ((bool)product.AllowBackInStockSubscriptions).ToString()); - writer.WriteElementString("OrderMinimumQuantity", ((int)product.OrderMinimumQuantity).ToString()); - writer.WriteElementString("OrderMaximumQuantity", ((int)product.OrderMaximumQuantity).ToString()); - writer.WriteElementString("AllowedQuantities", (string)product.AllowedQuantities); - writer.WriteElementString("DisableBuyButton", ((bool)product.DisableBuyButton).ToString()); - writer.WriteElementString("DisableWishlistButton", ((bool)product.DisableWishlistButton).ToString()); - writer.WriteElementString("AvailableForPreOrder", ((bool)product.AvailableForPreOrder).ToString()); - writer.WriteElementString("CallForPrice", ((bool)product.CallForPrice).ToString()); - writer.WriteElementString("Price", ((decimal)product.Price).ToString(culture)); - writer.WriteElementString("OldPrice", ((decimal)product.OldPrice).ToString(culture)); - writer.WriteElementString("ProductCost", ((decimal)product.ProductCost).ToString(culture)); - writer.WriteElementString("SpecialPrice", specialPrice.HasValue ? specialPrice.Value.ToString(culture) : ""); - writer.WriteElementString("SpecialPriceStartDateTimeUtc", specialPriceStartDateTimeUtc.HasValue ? specialPriceStartDateTimeUtc.Value.ToString(culture) : ""); - writer.WriteElementString("SpecialPriceEndDateTimeUtc", specialPriceEndDateTimeUtc.HasValue ? specialPriceEndDateTimeUtc.Value.ToString(culture) : ""); - writer.WriteElementString("CustomerEntersPrice", ((bool)product.CustomerEntersPrice).ToString()); - writer.WriteElementString("MinimumCustomerEnteredPrice", ((decimal)product.MinimumCustomerEnteredPrice).ToString(culture)); - writer.WriteElementString("MaximumCustomerEnteredPrice", ((decimal)product.MaximumCustomerEnteredPrice).ToString(culture)); - writer.WriteElementString("HasTierPrices", ((bool)product.HasTierPrices).ToString()); - writer.WriteElementString("HasDiscountsApplied", ((bool)product.HasDiscountsApplied).ToString()); - writer.WriteElementString("Weight", ((decimal)product.Weight).ToString(culture)); - writer.WriteElementString("Length", ((decimal)product.Length).ToString(culture)); - writer.WriteElementString("Width", ((decimal)product.Width).ToString(culture)); - writer.WriteElementString("Height", ((decimal)product.Height).ToString(culture)); - writer.WriteElementString("AvailableStartDateTimeUtc", availableStartDateTimeUtc.HasValue ? availableStartDateTimeUtc.Value.ToString(culture) : ""); - writer.WriteElementString("AvailableEndDateTimeUtc", availableEndDateTimeUtc.HasValue ? availableEndDateTimeUtc.Value.ToString(culture) : ""); - writer.WriteElementString("BasePriceEnabled", ((bool)product.BasePriceEnabled).ToString()); - writer.WriteElementString("BasePriceMeasureUnit", (string)product.BasePriceMeasureUnit); - writer.WriteElementString("BasePriceAmount", basePriceAmount.HasValue ? basePriceAmount.Value.ToString(culture) : ""); - writer.WriteElementString("BasePriceBaseAmount", basePriceBaseAmount.HasValue ? basePriceBaseAmount.Value.ToString() : ""); - writer.WriteElementString("BasePriceHasValue", ((bool)product.BasePriceHasValue).ToString()); - writer.WriteElementString("BasePriceInfo", (string)product._BasePriceInfo); - writer.WriteElementString("VisibleIndividually", ((bool)product.VisibleIndividually).ToString()); - writer.WriteElementString("DisplayOrder", ((int)product.DisplayOrder).ToString()); - writer.WriteElementString("BundleTitleText", ((string)product.BundleTitleText).RemoveInvalidXmlChars()); - writer.WriteElementString("BundlePerItemPricing", ((bool)product.BundlePerItemPricing).ToString()); - writer.WriteElementString("BundlePerItemShipping", ((bool)product.BundlePerItemShipping).ToString()); - writer.WriteElementString("BundlePerItemShoppingCart", ((bool)product.BundlePerItemShoppingCart).ToString()); - writer.WriteElementString("LowestAttributeCombinationPrice", lowestAttributeCombinationPrice.HasValue ? lowestAttributeCombinationPrice.Value.ToString(culture) : ""); - writer.WriteElementString("IsEsd", ((bool)product.IsEsd).ToString()); - - if (product.DeliveryTime != null) - { - writer.WriteStartElement("DeliveryTime"); - writer.WriteElementString("Id", ((int)product.DeliveryTime.Id).ToString()); - writer.WriteElementString("Name", ((string)product.DeliveryTime.Name).RemoveInvalidXmlChars()); - writer.WriteElementString("DisplayLocale", ((string)product.DeliveryTime.DisplayLocale).RemoveInvalidXmlChars()); - writer.WriteElementString("ColorHexValue", (string)product.DeliveryTime.ColorHexValue); - writer.WriteElementString("DisplayOrder", ((int)product.DeliveryTime.DisplayOrder).ToString()); - writer.WriteEndElement(); // DeliveryTime - } - - if (product.QuantityUnit != null) - { - writer.WriteStartElement("QuantityUnit"); - writer.WriteElementString("Id", ((int)product.QuantityUnit.Id).ToString()); - writer.WriteElementString("Name", ((string)product.QuantityUnit.Name).RemoveInvalidXmlChars()); - writer.WriteElementString("Description", ((string)product.QuantityUnit.Description).RemoveInvalidXmlChars()); - writer.WriteElementString("DisplayLocale", ((string)product.QuantityUnit.DisplayLocale).RemoveInvalidXmlChars()); - writer.WriteElementString("DisplayOrder", ((int)product.QuantityUnit.DisplayOrder).ToString()); - writer.WriteElementString("IsDefault", ((bool)product.QuantityUnit.IsDefault).ToString()); - writer.WriteEndElement(); // QuantityUnit - } - - writer.WriteEndElement(); // node - } - - private void WriteRewardPointsHistory(XmlWriter writer, string node, dynamic rewardPoints, CultureInfo culture) - { - if (rewardPoints == null) - return; - - writer.WriteStartElement(node); - - foreach (dynamic rewardPoint in rewardPoints) - { - writer.WriteStartElement("RewardPoint"); - writer.WriteElementString("Id", ((int)rewardPoint.Id).ToString()); - writer.WriteElementString("CustomerId", ((int)rewardPoint.CustomerId).ToString()); - writer.WriteElementString("Points", ((int)rewardPoint.Points).ToString()); - writer.WriteElementString("PointsBalance", ((int)rewardPoint.PointsBalance).ToString()); - writer.WriteElementString("UsedAmount", ((decimal)rewardPoint.UsedAmount).ToString(culture)); - writer.WriteElementString("Message", (string)rewardPoint.Message); - writer.WriteElementString("CreatedOnUtc", ((DateTime)rewardPoint.CreatedOnUtc).ToString(culture)); - writer.WriteEndElement(); // RewardPoint - } - - writer.WriteEndElement(); // node - } - - public static string SystemName - { - get { return "Exports.SmartStoreNetOrderXml"; } - } - - public ExportConfigurationInfo ConfigurationInfo - { - get { return null; } - } - - public ExportEntityType EntityType - { - get { return ExportEntityType.Order; } - } - - public string FileExtension - { - get { return "XML"; } - } - - public void Execute(IExportExecuteContext context) - { - var settings = new XmlWriterSettings - { - Encoding = Encoding.UTF8, - CheckCharacters = false, - Indent = true, - IndentChars = "\t" - }; - - var path = context.FilePath; - var invariantCulture = CultureInfo.InvariantCulture; - - context.Log.Information("Creating file " + path); - - using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var writer = XmlWriter.Create(stream, settings)) - { - writer.WriteStartDocument(); - writer.WriteStartElement("Orders"); - writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - - while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) - { - var segment = context.Data.CurrentSegment; - - foreach (dynamic order in segment) - { - if (context.Abort != ExportAbortion.None) - break; - - int orderId = order.Id; - dynamic customer = order.Customer; - dynamic store = order.Store; - - writer.WriteStartElement("Order"); - - try - { - int? shippingAddressId = order.ShippingAddressId; - DateTime? paidDateUtc = order.PaidDateUtc; - int? rewardPointsRemaining = order.RewardPointsRemaining; - - writer.WriteElementString("Id", orderId.ToString()); - writer.WriteElementString("OrderNumber", (string)order.OrderNumber); - writer.WriteElementString("OrderGuid", ((Guid)order.OrderGuid).ToString()); - writer.WriteElementString("StoreId", ((int)order.StoreId).ToString()); - writer.WriteElementString("CustomerId", ((int)order.CustomerId).ToString()); - writer.WriteElementString("BillingAddressId", ((int)order.BillingAddressId).ToString()); - writer.WriteElementString("ShippingAddressId", shippingAddressId.HasValue ? shippingAddressId.Value.ToString() : ""); - writer.WriteElementString("OrderStatusId", ((int)order.OrderStatusId).ToString()); - writer.WriteElementString("ShippingStatusId", ((int)order.ShippingStatusId).ToString()); - writer.WriteElementString("PaymentStatusId", ((int)order.PaymentStatusId).ToString()); - writer.WriteElementString("PaymentMethodSystemName", (string)order.PaymentMethodSystemName); - writer.WriteElementString("CustomerCurrencyCode", (string)order.CustomerCurrencyCode); - writer.WriteElementString("CurrencyRate", ((decimal)order.CurrencyRate).ToString(invariantCulture)); - writer.WriteElementString("CustomerTaxDisplayTypeId", ((int)order.CustomerTaxDisplayTypeId).ToString()); - writer.WriteElementString("VatNumber", (string)order.VatNumber); - writer.WriteElementString("OrderSubtotalInclTax", ((decimal)order.OrderSubtotalInclTax).ToString(invariantCulture)); - writer.WriteElementString("OrderSubtotalExclTax", ((decimal)order.OrderSubtotalExclTax).ToString(invariantCulture)); - writer.WriteElementString("OrderSubTotalDiscountInclTax", ((decimal)order.OrderSubTotalDiscountInclTax).ToString(invariantCulture)); - writer.WriteElementString("OrderSubTotalDiscountExclTax", ((decimal)order.OrderSubTotalDiscountExclTax).ToString(invariantCulture)); - writer.WriteElementString("OrderShippingInclTax", ((decimal)order.OrderShippingInclTax).ToString(invariantCulture)); - writer.WriteElementString("OrderShippingExclTax", ((decimal)order.OrderShippingExclTax).ToString(invariantCulture)); - writer.WriteElementString("OrderShippingTaxRate", ((decimal)order.OrderShippingTaxRate).ToString(invariantCulture)); - writer.WriteElementString("PaymentMethodAdditionalFeeInclTax", ((decimal)order.PaymentMethodAdditionalFeeInclTax).ToString(invariantCulture)); - writer.WriteElementString("PaymentMethodAdditionalFeeExclTax", ((decimal)order.PaymentMethodAdditionalFeeExclTax).ToString(invariantCulture)); - writer.WriteElementString("PaymentMethodAdditionalFeeTaxRate", ((decimal)order.PaymentMethodAdditionalFeeTaxRate).ToString(invariantCulture)); - writer.WriteElementString("TaxRates", (string)order.TaxRates); - writer.WriteElementString("OrderTax", ((decimal)order.OrderTax).ToString(invariantCulture)); - writer.WriteElementString("OrderDiscount", ((decimal)order.OrderDiscount).ToString(invariantCulture)); - writer.WriteElementString("OrderTotal", ((decimal)order.OrderTotal).ToString(invariantCulture)); - writer.WriteElementString("RefundedAmount", ((decimal)order.RefundedAmount).ToString(invariantCulture)); - writer.WriteElementString("RewardPointsWereAdded", ((bool)order.RewardPointsWereAdded).ToString()); - writer.WriteElementString("CheckoutAttributeDescription", ((string)order.CheckoutAttributeDescription).RemoveInvalidXmlChars()); - writer.WriteElementString("CheckoutAttributesXml", ((string)order.CheckoutAttributesXml).RemoveInvalidXmlChars()); - writer.WriteElementString("CustomerLanguageId", ((int)order.CustomerLanguageId).ToString()); - writer.WriteElementString("AffiliateId", ((int)order.AffiliateId).ToString()); - writer.WriteElementString("CustomerIp", (string)order.CustomerIp); - writer.WriteElementString("AllowStoringCreditCardNumber", ((bool)order.AllowStoringCreditCardNumber).ToString()); - writer.WriteElementString("CardType", (string)order.CardType); - writer.WriteElementString("CardName", (string)order.CardName); - writer.WriteElementString("CardNumber", (string)order.CardNumber); - writer.WriteElementString("MaskedCreditCardNumber", (string)order.MaskedCreditCardNumber); - writer.WriteElementString("CardCvv2", (string)order.CardCvv2); - writer.WriteElementString("CardExpirationMonth", (string)order.CardExpirationMonth); - writer.WriteElementString("CardExpirationYear", (string)order.CardExpirationYear); - writer.WriteElementString("AllowStoringDirectDebit", ((bool)order.AllowStoringDirectDebit).ToString()); - writer.WriteElementString("DirectDebitAccountHolder", (string)order.DirectDebitAccountHolder); - writer.WriteElementString("DirectDebitAccountNumber", (string)order.DirectDebitAccountNumber); - writer.WriteElementString("DirectDebitBankCode", (string)order.DirectDebitBankCode); - writer.WriteElementString("DirectDebitBankName", (string)order.DirectDebitBankName); - writer.WriteElementString("DirectDebitBIC", (string)order.DirectDebitBIC); - writer.WriteElementString("DirectDebitCountry", (string)order.DirectDebitCountry); - writer.WriteElementString("DirectDebitIban", (string)order.DirectDebitIban); - writer.WriteElementString("CustomerOrderComment", ((string)order.CustomerOrderComment).RemoveInvalidXmlChars()); - writer.WriteElementString("AuthorizationTransactionId", (string)order.AuthorizationTransactionId); - writer.WriteElementString("AuthorizationTransactionCode", (string)order.AuthorizationTransactionCode); - writer.WriteElementString("AuthorizationTransactionResult", (string)order.AuthorizationTransactionResult); - writer.WriteElementString("CaptureTransactionId", (string)order.CaptureTransactionId); - writer.WriteElementString("CaptureTransactionResult", (string)order.CaptureTransactionResult); - writer.WriteElementString("SubscriptionTransactionId", (string)order.SubscriptionTransactionId); - writer.WriteElementString("PurchaseOrderNumber", (string)order.PurchaseOrderNumber); - writer.WriteElementString("PaidDateUtc", paidDateUtc.HasValue ? paidDateUtc.Value.ToString(invariantCulture) : ""); - writer.WriteElementString("ShippingMethod", (string)order.ShippingMethod); - writer.WriteElementString("ShippingRateComputationMethodSystemName", (string)order.ShippingRateComputationMethodSystemName); - writer.WriteElementString("Deleted", ((bool)order.Deleted).ToString()); - writer.WriteElementString("CreatedOnUtc", ((DateTime)order.CreatedOnUtc).ToString(invariantCulture)); - writer.WriteElementString("UpdatedOnUtc", ((DateTime)order.UpdatedOnUtc).ToString(invariantCulture)); - writer.WriteElementString("RewardPointsRemaining", rewardPointsRemaining.HasValue ? rewardPointsRemaining.Value.ToString() : ""); - writer.WriteElementString("HasNewPaymentNotification", ((bool)order.HasNewPaymentNotification).ToString()); - writer.WriteElementString("OrderStatus", (string)order.OrderStatus); - writer.WriteElementString("PaymentStatus", (string)order.PaymentStatus); - writer.WriteElementString("ShippingStatus", (string)order.ShippingStatus); - - if (customer != null) - { - DateTime? lastLoginDateUtc = customer.LastLoginDateUtc; - - writer.WriteStartElement("Customer"); - writer.WriteElementString("Id", ((int)customer.Id).ToString()); - writer.WriteElementString("CustomerGuid", ((Guid)customer.CustomerGuid).ToString()); - writer.WriteElementString("Username", (string)customer.Username); - writer.WriteElementString("Email", (string)customer.Email); - writer.WriteElementString("PasswordFormatId", ((int)customer.PasswordFormatId).ToString()); - writer.WriteElementString("AdminComment", ((string)customer.AdminComment).RemoveInvalidXmlChars()); - writer.WriteElementString("IsTaxExempt", ((bool)customer.IsTaxExempt).ToString()); - writer.WriteElementString("AffiliateId", ((int)customer.AffiliateId).ToString()); - writer.WriteElementString("Active", ((bool)customer.Active).ToString()); - writer.WriteElementString("Deleted", ((bool)customer.Deleted).ToString()); - writer.WriteElementString("IsSystemAccount", ((bool)customer.IsSystemAccount).ToString()); - writer.WriteElementString("SystemName", (string)customer.SystemName); - writer.WriteElementString("LastIpAddress", (string)customer.LastIpAddress); - writer.WriteElementString("CreatedOnUtc", ((DateTime)customer.CreatedOnUtc).ToString(invariantCulture)); - writer.WriteElementString("LastLoginDateUtc", lastLoginDateUtc.HasValue ? lastLoginDateUtc.Value.ToString(invariantCulture) : ""); - writer.WriteElementString("LastActivityDateUtc", ((DateTime)customer.LastActivityDateUtc).ToString(invariantCulture)); - writer.WriteElementString("RewardPointsBalance", ((int)customer._RewardPointsBalance).ToString()); - - WriteRewardPointsHistory(writer, "RewardPointsHistory", customer.RewardPointsHistory, invariantCulture); - WriteAddress(writer, "BillingAddress", customer.BillingAddress, invariantCulture); - WriteAddress(writer, "ShippingAddress", customer.ShippingAddress, invariantCulture); - writer.WriteEndElement(); // Customer - } - - WriteAddress(writer, "BillingAddress", order.BillingAddress, invariantCulture); - WriteAddress(writer, "ShippingAddress", order.ShippingAddress, invariantCulture); - - if (store != null) - { - writer.WriteStartElement("Store"); - writer.WriteElementString("Id", ((int)store.Id).ToString()); - writer.WriteElementString("Name", (string)store.Name); - writer.WriteElementString("Url", (string)store.Url); - writer.WriteElementString("SslEnabled", ((bool)store.SslEnabled).ToString()); - writer.WriteElementString("SecureUrl", (string)store.SecureUrl); - writer.WriteElementString("Hosts", (string)store.Hosts); - writer.WriteElementString("LogoPictureId", ((int)store.LogoPictureId).ToString()); - writer.WriteElementString("DisplayOrder", ((int)store.DisplayOrder).ToString()); - writer.WriteElementString("HtmlBodyId", (string)store.HtmlBodyId); - writer.WriteElementString("ContentDeliveryNetwork", (string)store.ContentDeliveryNetwork); - writer.WriteElementString("PrimaryStoreCurrencyId", ((int)store.PrimaryStoreCurrencyId).ToString()); - writer.WriteElementString("PrimaryExchangeRateCurrencyId", ((int)store.PrimaryExchangeRateCurrencyId).ToString()); - - WriteCurrency(writer, "PrimaryStoreCurrency", store.PrimaryStoreCurrency, invariantCulture); - WriteCurrency(writer, "PrimaryExchangeRateCurrency", store.PrimaryExchangeRateCurrency, invariantCulture); - writer.WriteEndElement(); // Store - } - - writer.WriteStartElement("OrderItems"); - foreach (dynamic orderItem in order.OrderItems) - { - int? licenseDownloadId = orderItem.LicenseDownloadId; - decimal? itemWeight = orderItem.ItemWeight; - - writer.WriteStartElement("OrderItem"); - writer.WriteElementString("Id", ((int)orderItem.Id).ToString()); - writer.WriteElementString("OrderItemGuid", ((Guid)orderItem.OrderItemGuid).ToString()); - writer.WriteElementString("OrderId", ((int)orderItem.OrderId).ToString()); - writer.WriteElementString("ProductId", ((int)orderItem.ProductId).ToString()); - writer.WriteElementString("Quantity", ((int)orderItem.Quantity).ToString()); - writer.WriteElementString("UnitPriceInclTax", ((decimal)orderItem.UnitPriceInclTax).ToString(invariantCulture)); - writer.WriteElementString("UnitPriceExclTax", ((decimal)orderItem.UnitPriceExclTax).ToString(invariantCulture)); - writer.WriteElementString("PriceInclTax", ((decimal)orderItem.PriceInclTax).ToString(invariantCulture)); - writer.WriteElementString("PriceExclTax", ((decimal)orderItem.PriceExclTax).ToString(invariantCulture)); - writer.WriteElementString("TaxRate", ((decimal)orderItem.TaxRate).ToString(invariantCulture)); - writer.WriteElementString("DiscountAmountInclTax", ((decimal)orderItem.DiscountAmountInclTax).ToString(invariantCulture)); - writer.WriteElementString("DiscountAmountExclTax", ((decimal)orderItem.DiscountAmountExclTax).ToString(invariantCulture)); - writer.WriteElementString("AttributeDescription", ((string)orderItem.AttributeDescription).RemoveInvalidXmlChars()); - writer.WriteElementString("AttributesXml", ((string)orderItem.AttributesXml).RemoveInvalidXmlChars()); - writer.WriteElementString("DownloadCount", ((int)orderItem.DownloadCount).ToString()); - writer.WriteElementString("IsDownloadActivated", ((bool)orderItem.IsDownloadActivated).ToString()); - writer.WriteElementString("LicenseDownloadId", licenseDownloadId.HasValue ? licenseDownloadId.Value.ToString() : ""); - writer.WriteElementString("ItemWeight", itemWeight.HasValue ? itemWeight.Value.ToString(invariantCulture) : ""); - writer.WriteElementString("BundleData", ((string)orderItem.BundleData).RemoveInvalidXmlChars()); - writer.WriteElementString("ProductCost", ((decimal)orderItem.ProductCost).ToString(invariantCulture)); - - WriteProduct(writer, "Product", orderItem.Product, invariantCulture); - writer.WriteEndElement(); // OrderItem - } - writer.WriteEndElement(); // OrderItems - - writer.WriteStartElement("Shipments"); - foreach (dynamic shipment in order.Shipments) - { - decimal? totalWeight = shipment.TotalWeight; - DateTime? shippedDateUtc = shipment.ShippedDateUtc; - DateTime? deliveryDateUtc = shipment.DeliveryDateUtc; - - writer.WriteStartElement("Shipment"); - writer.WriteElementString("Id", ((int)shipment.Id).ToString()); - writer.WriteElementString("OrderId", ((int)shipment.OrderId).ToString()); - writer.WriteElementString("TrackingNumber", (string)shipment.TrackingNumber); - writer.WriteElementString("TotalWeight", totalWeight.HasValue ? totalWeight.Value.ToString(invariantCulture) : ""); - writer.WriteElementString("ShippedDateUtc", shippedDateUtc.HasValue ? shippedDateUtc.Value.ToString(invariantCulture) : ""); - writer.WriteElementString("DeliveryDateUtc", deliveryDateUtc.HasValue ? deliveryDateUtc.Value.ToString(invariantCulture) : ""); - writer.WriteElementString("CreatedOnUtc", ((DateTime)shipment.CreatedOnUtc).ToString(invariantCulture)); - - writer.WriteStartElement("ShipmentItems"); - foreach (dynamic shipmentItem in shipment.ShipmentItems) - { - writer.WriteStartElement("ShipmentItem"); - writer.WriteElementString("Id", ((int)shipmentItem.Id).ToString()); - writer.WriteElementString("ShipmentId", ((int)shipmentItem.ShipmentId).ToString()); - writer.WriteElementString("OrderItemId", ((int)shipmentItem.OrderItemId).ToString()); - writer.WriteElementString("Quantity", ((int)shipmentItem.Quantity).ToString()); - writer.WriteEndElement(); // ShipmentItem - } - writer.WriteEndElement(); // ShipmentItems - - writer.WriteEndElement(); // Shipment - } - writer.WriteEndElement(); // Shipments - - ++context.RecordsSucceeded; - } - catch (Exception exc) - { - context.Log.Error("Error while processing order with id {0}: {1}".FormatInvariant(orderId, exc.ToAllMessages()), exc); - ++context.RecordsFailed; - } - - writer.WriteEndElement(); // Order - } - } - - writer.WriteEndElement(); // Orders - writer.WriteEndDocument(); - } - } - - public void ExecuteEnded(IExportExecuteContext context) - { - // nothing to do - } - } -} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs new file mode 100644 index 0000000000..f3db20bfe0 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs @@ -0,0 +1,299 @@ +using System; +using System.Globalization; +using System.Xml; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.DataExchange; + +namespace SmartStore.Services.DataExchange +{ + public class ExportXmlHelper + { + private XmlWriter _writer; + private CultureInfo _culture; + + public ExportXmlHelper(XmlWriter writer, CultureInfo culture) + { + _writer = writer; + _culture = culture; + } + + public void WriteAddress(dynamic address, string node) + { + if (address == null) + return; + + int? countryId = address.CountryId; + int? stateProvinceId = address.StateProvinceId; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", ((int)address.Id).ToString()); + _writer.Write("FirstName", (string)address.FirstName); + _writer.Write("LastName", (string)address.LastName); + _writer.Write("Email", (string)address.Email); + _writer.Write("Company", (string)address.Company); + _writer.Write("CountryId", countryId.HasValue ? countryId.Value.ToString() : ""); + _writer.Write("StateProvinceId", stateProvinceId.HasValue ? stateProvinceId.Value.ToString() : ""); + _writer.Write("City", (string)address.City); + _writer.Write("Address1", (string)address.Address1); + _writer.Write("Address2", (string)address.Address2); + _writer.Write("ZipPostalCode", (string)address.ZipPostalCode); + _writer.Write("PhoneNumber", (string)address.PhoneNumber); + _writer.Write("FaxNumber", (string)address.FaxNumber); + _writer.Write("CreatedOnUtc", ((DateTime)address.CreatedOnUtc).ToString(_culture)); + + if (address.Country != null) + { + dynamic country = address.Country; + + _writer.WriteStartElement("Country"); + _writer.Write("Id", ((int)country.Id).ToString()); + _writer.Write("Name", (string)country.Name); + _writer.Write("AllowsBilling", ((bool)country.AllowsBilling).ToString()); + _writer.Write("AllowsShipping", ((bool)country.AllowsShipping).ToString()); + _writer.Write("TwoLetterIsoCode", (string)country.TwoLetterIsoCode); + _writer.Write("ThreeLetterIsoCode", (string)country.ThreeLetterIsoCode); + _writer.Write("NumericIsoCode", ((int)country.NumericIsoCode).ToString()); + _writer.Write("SubjectToVat", ((bool)country.SubjectToVat).ToString()); + _writer.Write("Published", ((bool)country.Published).ToString()); + _writer.Write("DisplayOrder", ((int)country.DisplayOrder).ToString()); + _writer.Write("LimitedToStores", ((bool)country.LimitedToStores).ToString()); + _writer.WriteEndElement(); // Country + } + + if (address.StateProvince != null) + { + dynamic stateProvince = address.StateProvince; + + _writer.WriteStartElement("StateProvince"); + _writer.Write("Id", ((int)stateProvince.Id).ToString()); + _writer.Write("CountryId", ((int)stateProvince.CountryId).ToString()); + _writer.Write("Name", (string)stateProvince.Name); + _writer.Write("Abbreviation", (string)stateProvince.Abbreviation); + _writer.Write("Published", ((bool)stateProvince.Published).ToString()); + _writer.Write("DisplayOrder", ((int)stateProvince.DisplayOrder).ToString()); + _writer.WriteEndElement(); // StateProvince + } + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WriteCurrency(dynamic currency, string node) + { + if (currency == null) + return; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", ((int)currency.Id).ToString()); + _writer.Write("Name", (string)currency.Name); + _writer.Write("CurrencyCode", (string)currency.CurrencyCode); + _writer.Write("Rate", ((decimal)currency.Rate).ToString(_culture)); + _writer.Write("DisplayLocale", (string)currency.DisplayLocale); + _writer.Write("CustomFormatting", (string)currency.CustomFormatting); + _writer.Write("LimitedToStores", ((bool)currency.LimitedToStores).ToString()); + _writer.Write("Published", ((bool)currency.Published).ToString()); + _writer.Write("DisplayOrder", ((int)currency.DisplayOrder).ToString()); + _writer.Write("CreatedOnUtc", ((DateTime)currency.CreatedOnUtc).ToString(_culture)); + _writer.Write("UpdatedOnUtc", ((DateTime)currency.UpdatedOnUtc).ToString(_culture)); + _writer.Write("DomainEndings", (string)currency.DomainEndings); + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WriteRewardPointsHistory(dynamic rewardPoints, string node) + { + if (rewardPoints == null) + return; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + foreach (dynamic rewardPoint in rewardPoints) + { + _writer.WriteStartElement("RewardPointsHistory"); + _writer.Write("Id", ((int)rewardPoint.Id).ToString()); + _writer.Write("CustomerId", ((int)rewardPoint.CustomerId).ToString()); + _writer.Write("Points", ((int)rewardPoint.Points).ToString()); + _writer.Write("PointsBalance", ((int)rewardPoint.PointsBalance).ToString()); + _writer.Write("UsedAmount", ((decimal)rewardPoint.UsedAmount).ToString(_culture)); + _writer.Write("Message", (string)rewardPoint.Message); + _writer.Write("CreatedOnUtc", ((DateTime)rewardPoint.CreatedOnUtc).ToString(_culture)); + _writer.WriteEndElement(); // RewardPointsHistory + } + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WriteProduct(dynamic product, string node) + { + if (product == null) + return; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + int? downloadExpirationDays = product.DownloadExpirationDays; + int? sampleDownloadId = product.SampleDownloadId; + decimal? specialPrice = product.SpecialPrice; + DateTime? specialPriceStartDateTimeUtc = product.SpecialPriceStartDateTimeUtc; + DateTime? specialPriceEndDateTimeUtc = product.SpecialPriceEndDateTimeUtc; + DateTime? availableStartDateTimeUtc = product.AvailableStartDateTimeUtc; + DateTime? availableEndDateTimeUtc = product.AvailableEndDateTimeUtc; + decimal? basePriceAmount = product.BasePriceAmount; + int? basePriceBaseAmount = product.BasePriceBaseAmount; + decimal? lowestAttributeCombinationPrice = product.LowestAttributeCombinationPrice; + + _writer.Write("Id", ((int)product.Id).ToString()); + _writer.Write("Name", (string)product.Name); + _writer.Write("SeName", (string)product.SeName); + _writer.Write("ShortDescription", (string)product.ShortDescription); + _writer.Write("FullDescription", (string)product.FullDescription); + _writer.Write("AdminComment", (string)product.AdminComment); + _writer.Write("ProductTemplateId", ((int)product.ProductTemplateId).ToString()); + _writer.Write("ProductTemplateViewPath", (string)product._ProductTemplateViewPath); + _writer.Write("ShowOnHomePage", ((bool)product.ShowOnHomePage).ToString()); + _writer.Write("HomePageDisplayOrder", ((int)product.HomePageDisplayOrder).ToString()); + _writer.Write("MetaKeywords", (string)product.MetaKeywords); + _writer.Write("MetaDescription", (string)product.MetaDescription); + _writer.Write("MetaTitle", (string)product.MetaTitle); + _writer.Write("AllowCustomerReviews", ((bool)product.AllowCustomerReviews).ToString()); + _writer.Write("ApprovedRatingSum", ((int)product.ApprovedRatingSum).ToString()); + _writer.Write("NotApprovedRatingSum", ((int)product.NotApprovedRatingSum).ToString()); + _writer.Write("ApprovedTotalReviews", ((int)product.ApprovedTotalReviews).ToString()); + _writer.Write("NotApprovedTotalReviews", ((int)product.NotApprovedTotalReviews).ToString()); + _writer.Write("Published", ((bool)product.Published).ToString()); + _writer.Write("CreatedOnUtc", ((DateTime)product.CreatedOnUtc).ToString(_culture)); + _writer.Write("UpdatedOnUtc", ((DateTime)product.UpdatedOnUtc).ToString(_culture)); + _writer.Write("SubjectToAcl", ((bool)product.SubjectToAcl).ToString()); + _writer.Write("LimitedToStores", ((bool)product.LimitedToStores).ToString()); + _writer.Write("ProductTypeId", ((int)product.ProductTypeId).ToString()); + _writer.Write("ParentGroupedProductId", ((int)product.ParentGroupedProductId).ToString()); + _writer.Write("Sku", (string)product.Sku); + _writer.Write("ManufacturerPartNumber", (string)product.ManufacturerPartNumber); + _writer.Write("Gtin", (string)product.Gtin); + _writer.Write("IsGiftCard", ((bool)product.IsGiftCard).ToString()); + _writer.Write("GiftCardTypeId", ((int)product.GiftCardTypeId).ToString()); + _writer.Write("RequireOtherProducts", ((bool)product.RequireOtherProducts).ToString()); + _writer.Write("RequiredProductIds", (string)product.RequiredProductIds); + _writer.Write("AutomaticallyAddRequiredProducts", ((bool)product.AutomaticallyAddRequiredProducts).ToString()); + _writer.Write("IsDownload", ((bool)product.IsDownload).ToString()); + _writer.Write("DownloadId", ((int)product.DownloadId).ToString()); + _writer.Write("UnlimitedDownloads", ((bool)product.UnlimitedDownloads).ToString()); + _writer.Write("MaxNumberOfDownloads", ((int)product.MaxNumberOfDownloads).ToString()); + _writer.Write("DownloadExpirationDays", downloadExpirationDays.HasValue ? downloadExpirationDays.Value.ToString() : ""); + _writer.Write("DownloadActivationTypeId", ((int)product.DownloadActivationTypeId).ToString()); + _writer.Write("HasSampleDownload", ((bool)product.HasSampleDownload).ToString()); + _writer.Write("SampleDownloadId", sampleDownloadId.HasValue ? sampleDownloadId.Value.ToString() : ""); + _writer.Write("HasUserAgreement", ((bool)product.HasUserAgreement).ToString()); + _writer.Write("UserAgreementText", (string)product.UserAgreementText); + _writer.Write("IsRecurring", ((bool)product.IsRecurring).ToString()); + _writer.Write("RecurringCycleLength", ((int)product.RecurringCycleLength).ToString()); + _writer.Write("RecurringCyclePeriodId", ((int)product.RecurringCyclePeriodId).ToString()); + _writer.Write("RecurringTotalCycles", ((int)product.RecurringTotalCycles).ToString()); + _writer.Write("IsShipEnabled", ((bool)product.IsShipEnabled).ToString()); + _writer.Write("IsFreeShipping", ((bool)product.IsFreeShipping).ToString()); + _writer.Write("AdditionalShippingCharge", ((decimal)product.AdditionalShippingCharge).ToString(_culture)); + _writer.Write("IsTaxExempt", ((bool)product.IsTaxExempt).ToString()); + _writer.Write("TaxCategoryId", ((int)product.TaxCategoryId).ToString()); + _writer.Write("ManageInventoryMethodId", ((int)product.ManageInventoryMethodId).ToString()); + _writer.Write("StockQuantity", ((int)product.StockQuantity).ToString()); + _writer.Write("DisplayStockAvailability", ((bool)product.DisplayStockAvailability).ToString()); + _writer.Write("DisplayStockQuantity", ((bool)product.DisplayStockQuantity).ToString()); + _writer.Write("MinStockQuantity", ((int)product.MinStockQuantity).ToString()); + _writer.Write("LowStockActivityId", ((int)product.LowStockActivityId).ToString()); + _writer.Write("NotifyAdminForQuantityBelow", ((int)product.NotifyAdminForQuantityBelow).ToString()); + _writer.Write("BackorderModeId", ((int)product.BackorderModeId).ToString()); + _writer.Write("AllowBackInStockSubscriptions", ((bool)product.AllowBackInStockSubscriptions).ToString()); + _writer.Write("OrderMinimumQuantity", ((int)product.OrderMinimumQuantity).ToString()); + _writer.Write("OrderMaximumQuantity", ((int)product.OrderMaximumQuantity).ToString()); + _writer.Write("AllowedQuantities", (string)product.AllowedQuantities); + _writer.Write("DisableBuyButton", ((bool)product.DisableBuyButton).ToString()); + _writer.Write("DisableWishlistButton", ((bool)product.DisableWishlistButton).ToString()); + _writer.Write("AvailableForPreOrder", ((bool)product.AvailableForPreOrder).ToString()); + _writer.Write("CallForPrice", ((bool)product.CallForPrice).ToString()); + _writer.Write("Price", ((decimal)product.Price).ToString(_culture)); + _writer.Write("OldPrice", ((decimal)product.OldPrice).ToString(_culture)); + _writer.Write("ProductCost", ((decimal)product.ProductCost).ToString(_culture)); + _writer.Write("SpecialPrice", specialPrice.HasValue ? specialPrice.Value.ToString(_culture) : ""); + _writer.Write("SpecialPriceStartDateTimeUtc", specialPriceStartDateTimeUtc.HasValue ? specialPriceStartDateTimeUtc.Value.ToString(_culture) : ""); + _writer.Write("SpecialPriceEndDateTimeUtc", specialPriceEndDateTimeUtc.HasValue ? specialPriceEndDateTimeUtc.Value.ToString(_culture) : ""); + _writer.Write("CustomerEntersPrice", ((bool)product.CustomerEntersPrice).ToString()); + _writer.Write("MinimumCustomerEnteredPrice", ((decimal)product.MinimumCustomerEnteredPrice).ToString(_culture)); + _writer.Write("MaximumCustomerEnteredPrice", ((decimal)product.MaximumCustomerEnteredPrice).ToString(_culture)); + _writer.Write("HasTierPrices", ((bool)product.HasTierPrices).ToString()); + _writer.Write("HasDiscountsApplied", ((bool)product.HasDiscountsApplied).ToString()); + _writer.Write("Weight", ((decimal)product.Weight).ToString(_culture)); + _writer.Write("Length", ((decimal)product.Length).ToString(_culture)); + _writer.Write("Width", ((decimal)product.Width).ToString(_culture)); + _writer.Write("Height", ((decimal)product.Height).ToString(_culture)); + _writer.Write("AvailableStartDateTimeUtc", availableStartDateTimeUtc.HasValue ? availableStartDateTimeUtc.Value.ToString(_culture) : ""); + _writer.Write("AvailableEndDateTimeUtc", availableEndDateTimeUtc.HasValue ? availableEndDateTimeUtc.Value.ToString(_culture) : ""); + _writer.Write("BasePriceEnabled", ((bool)product.BasePriceEnabled).ToString()); + _writer.Write("BasePriceMeasureUnit", (string)product.BasePriceMeasureUnit); + _writer.Write("BasePriceAmount", basePriceAmount.HasValue ? basePriceAmount.Value.ToString(_culture) : ""); + _writer.Write("BasePriceBaseAmount", basePriceBaseAmount.HasValue ? basePriceBaseAmount.Value.ToString() : ""); + _writer.Write("BasePriceHasValue", ((bool)product.BasePriceHasValue).ToString()); + _writer.Write("BasePriceInfo", (string)product._BasePriceInfo); + _writer.Write("VisibleIndividually", ((bool)product.VisibleIndividually).ToString()); + _writer.Write("DisplayOrder", ((int)product.DisplayOrder).ToString()); + _writer.Write("BundleTitleText", (string)product.BundleTitleText); + _writer.Write("BundlePerItemPricing", ((bool)product.BundlePerItemPricing).ToString()); + _writer.Write("BundlePerItemShipping", ((bool)product.BundlePerItemShipping).ToString()); + _writer.Write("BundlePerItemShoppingCart", ((bool)product.BundlePerItemShoppingCart).ToString()); + _writer.Write("LowestAttributeCombinationPrice", lowestAttributeCombinationPrice.HasValue ? lowestAttributeCombinationPrice.Value.ToString(_culture) : ""); + _writer.Write("IsEsd", ((bool)product.IsEsd).ToString()); + + if (product.DeliveryTime != null) + { + _writer.WriteStartElement("DeliveryTime"); + + _writer.Write("Id", ((int)product.DeliveryTime.Id).ToString()); + _writer.Write("Name", (string)product.DeliveryTime.Name); + _writer.Write("DisplayLocale", (string)product.DeliveryTime.DisplayLocale); + _writer.Write("ColorHexValue", (string)product.DeliveryTime.ColorHexValue); + _writer.Write("DisplayOrder", ((int)product.DeliveryTime.DisplayOrder).ToString()); + + _writer.WriteEndElement(); // DeliveryTime + } + + if (product.QuantityUnit != null) + { + _writer.WriteStartElement("QuantityUnit"); + + _writer.Write("Id", ((int)product.QuantityUnit.Id).ToString()); + _writer.Write("Name", (string)product.QuantityUnit.Name); + _writer.Write("Description", (string)product.QuantityUnit.Description); + _writer.Write("DisplayLocale", (string)product.QuantityUnit.DisplayLocale); + _writer.Write("DisplayOrder", ((int)product.QuantityUnit.DisplayOrder).ToString()); + _writer.Write("IsDefault", ((bool)product.QuantityUnit.IsDefault).ToString()); + + _writer.WriteEndElement(); // QuantityUnit + } + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 71fba3dc47..157fa3bf87 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -170,10 +170,12 @@ - - + + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs index add4ae97aa..e023a6f598 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs @@ -894,7 +894,7 @@ public ActionResult ExportXmlAll() if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - return Export(OrderExportXmlProvider.SystemName, null); + return Export(ExportOrderXmlProvider.SystemName, null); } [HttpPost, Compress] @@ -903,7 +903,7 @@ public ActionResult ExportXmlSelected(string selectedIds) if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - return Export(OrderExportXmlProvider.SystemName, selectedIds); + return Export(ExportOrderXmlProvider.SystemName, selectedIds); } [Compress] @@ -912,7 +912,7 @@ public ActionResult ExportExcelAll() if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - return Export(OrderExportXlsxProvider.SystemName, null); + return Export(ExportOrderXlsxProvider.SystemName, null); } [HttpPost, Compress] @@ -921,7 +921,7 @@ public ActionResult ExportExcelSelected(string selectedIds) if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - return Export(OrderExportXlsxProvider.SystemName, selectedIds); + return Export(ExportOrderXlsxProvider.SystemName, selectedIds); } public ActionResult ExportPdf(bool all, string selectedIds = null) From 04643ae5d449d9a6dda836df9cac5dcc923c8bb8 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 29 Sep 2015 11:43:08 +0200 Subject: [PATCH 092/874] Smarter product filtering: IsPublished should be initially set to true and ShowHidden is internally set to true if IsPublished is not specified (otherwise published and unpublished products could not be exported together) --- .../SmartStore.Core/Domain/DataExchange/ExportFilter.cs | 5 +++++ .../SmartStore.Services/DataExchange/ExportService.cs | 8 +++++--- .../DataExchange/ExportTask/ExportProfileTask.cs | 3 +++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs index 7f22f55302..5820606bf7 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs @@ -10,6 +10,11 @@ namespace SmartStore.Core.Domain.DataExchange [Serializable] public class ExportFilter { + public ExportFilter() + { + IsPublished = true; + } + /// /// Store identifier; 0 to load all records /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index 64d9f40193..324600e925 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -69,10 +69,12 @@ public virtual ExportProfile CreateVolatileProfile(Provider pro Deployments = new List() }; - if (storeId != 0) + var filter = new ExportFilter { - profile.Filtering = XmlHelper.Serialize(new ExportFilter { StoreId = storeId }); - } + StoreId = storeId + }; + + profile.Filtering = XmlHelper.Serialize(filter); return profile; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index a25afe714e..37d2b5185d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -140,6 +140,9 @@ private ProductSearchContext GetProductSearchContext(ExportProfileTaskContext ct AvailabilityMaximum = ctx.Filter.AvailabilityMaximum }; + if (!ctx.Filter.IsPublished.HasValue) + searchContext.ShowHidden = true; + if (ctx.Filter.CategoryIds != null && ctx.Filter.CategoryIds.Length > 0) searchContext.CategoryIds = ctx.Filter.CategoryIds.ToList(); From 17ad0aae93fa0510dd74b24178f2ba877c83278d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 29 Sep 2015 11:50:42 +0200 Subject: [PATCH 093/874] Forget a statement --- .../SmartStore.Services/DataExchange/ExportService.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index 324600e925..38cde6c262 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -69,10 +69,9 @@ public virtual ExportProfile CreateVolatileProfile(Provider pro Deployments = new List() }; - var filter = new ExportFilter - { - StoreId = storeId - }; + var filter = new ExportFilter(); + filter.IsPublished = null; + filter.StoreId = storeId; profile.Filtering = XmlHelper.Serialize(filter); From 877ff1d189b8c6e9fd7bb3b012751dc448ec711c Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 29 Sep 2015 13:52:59 +0200 Subject: [PATCH 094/874] Added missing checkout widget zones --- .../Shared/EditorTemplates/WidgetZone.cshtml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/WidgetZone.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/WidgetZone.cshtml index 5368dd5aa0..086cbd34a2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/WidgetZone.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/WidgetZone.cshtml @@ -191,6 +191,27 @@ 'orderdetails_page_bottom' ] }, + { + text: "@T("Admin.Configuration.Settings.ShoppingCart.Checkout")", children: [ + 'checkout_billing_address_top', + 'checkout_billing_address_middle', + 'checkout_billing_address_bottom', + 'checkout_progress_before', + 'checkout_progress_after', + 'checkout_completed_top', + 'checkout_completed_bottom', + 'checkout_confirm_top', + 'checkout_confirm_before_summary', + 'checkout_confirm_bottom', + 'checkout_payment_method_top', + 'checkout_payment_method_bottom', + 'checkout_shipping_address_top', + 'checkout_shipping_address_middle', + 'checkout_shipping_address_bottom', + 'checkout_shipping_method_top', + 'checkout_shipping_method_bottom' + ] + }, { text: "@T("Shoppingcart")", children: [ 'mini_shopping_cart_bottom', From 66a107456f65237b129b730265b405eee16d205e Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 30 Sep 2015 10:57:32 +0200 Subject: [PATCH 095/874] Convert product XML export to support new export framework (part 2) --- .../Domain/DataExchange/ExportCore.cs | 9 +- .../Catalog/CategoryService.cs | 2 +- .../Catalog/IProductService.cs | 15 + .../Catalog/PriceCalculationContext.cs | 25 +- .../Catalog/PriceCalculationService.cs | 23 +- .../Catalog/ProductService.cs | 47 +++ .../DataExchange/ExpandoExtensions.cs | 59 +++ .../ExportProductXmlProvider.cs | 1 + .../ExportTask/ExportProductDataContext.cs | 32 +- .../ExportTask/ExportProfileTask.cs | 76 +++- .../DataExchange/ExportXmlHelper.cs | 350 +++++++++++++++++- 11 files changed, 596 insertions(+), 43 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs index 67cd684c5d..927a0c5ba9 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs @@ -63,16 +63,17 @@ public enum ExportAttributeValueMerging ///
    public enum ExportProjectionSupport { - Description = 0, + HighDataDepth = 0, + AttributeCombinationAsProduct, + CreateInitialPublicDeployment, + Description, Brand, MainPictureUrl, UseOwnProductNo, ShippingTime, ShippingCosts, - AttributeCombinationAsProduct, OldPrice, - SpecialPrice, - CreateInitialPublicDeployment + SpecialPrice } /// diff --git a/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs b/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs index 824dae49e0..4e735e594d 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs @@ -655,7 +655,7 @@ public virtual Multimap GetProductCategoriesByProductIds(i Guard.ArgumentNotNull(() => productIds); var query = - from pc in _productCategoryRepository.TableUntracked.Expand(x => x.Category) + from pc in _productCategoryRepository.TableUntracked.Expand(x => x.Category).Expand(x => x.Category.Picture) join c in _categoryRepository.Table on pc.CategoryId equals c.Id where productIds.Contains(pc.ProductId) && !c.Deleted && (showHidden || c.Published) orderby pc.DisplayOrder diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs index b42e8c9954..395e3387e3 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs @@ -7,6 +7,7 @@ using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Orders; using SmartStore.Utilities; @@ -162,6 +163,20 @@ public partial interface IProductService /// SmartSyndicationFeed object SmartSyndicationFeed CreateRecentlyAddedProductsRssFeed(UrlHelper urlHelper); + /// + /// Get product tags by product identifiers + /// + /// Product identifiers + /// Map of product tags + Multimap GetProductTagsByProductIds(int[] productIds); + + /// + /// Get applied discounts by product identifiers + /// + /// Product identifiers + /// Map of applied discounts + Multimap GetAppliedDiscountsByProductIds(int[] productIds); + #endregion #region Related products diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs index 261e82e881..4645f4eac0 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationContext.cs @@ -3,6 +3,7 @@ using System.Linq; using SmartStore.Collections; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Discounts; namespace SmartStore.Services.Catalog { @@ -13,38 +14,45 @@ public class PriceCalculationContext { protected List _productIds; private List _productIdsTierPrices; + private List _productIdsAppliedDiscounts; private Func> _funcAttributes; private Func> _funcAttributeCombinations; private Func> _funcTierPrices; private Func> _funcProductCategories; + private Func> _funcAppliedDiscounts; private LazyMultimap _attributes; private LazyMultimap _attributeCombinations; private LazyMultimap _tierPrices; private LazyMultimap _productCategories; + private LazyMultimap _appliedDiscounts; public PriceCalculationContext(IEnumerable products, Func> attributes, Func> attributeCombinations, Func> tierPrices, - Func> productCategories) + Func> productCategories, + Func> appliedDiscounts) { if (products == null) { _productIds = new List(); _productIdsTierPrices = new List(); + _productIdsAppliedDiscounts = new List(); } else { _productIds = new List(products.Select(x => x.Id)); _productIdsTierPrices = new List(products.Where(x => x.HasTierPrices).Select(x => x.Id)); + _productIdsAppliedDiscounts = new List(products.Where(x => x.HasDiscountsApplied).Select(x => x.Id)); } _funcAttributes = attributes; _funcAttributeCombinations = attributeCombinations; _funcTierPrices = tierPrices; _funcProductCategories = productCategories; + _funcAppliedDiscounts = appliedDiscounts; } public void Clear() @@ -57,6 +65,8 @@ public void Clear() _tierPrices.Clear(); if (_productCategories != null) _productCategories.Clear(); + if (_appliedDiscounts != null) + _appliedDiscounts.Clear(); } public LazyMultimap Attributes @@ -107,12 +117,25 @@ public LazyMultimap ProductCategories } } + public LazyMultimap AppliedDiscounts + { + get + { + if (_appliedDiscounts == null) + { + _appliedDiscounts = new LazyMultimap(keys => _funcAppliedDiscounts(keys), _productIdsAppliedDiscounts); + } + return _appliedDiscounts; + } + } + public void Collect(IEnumerable productIds) { Attributes.Collect(productIds); AttributeCombinations.Collect(productIds); TierPrices.Collect(productIds); ProductCategories.Collect(productIds); + AppliedDiscounts.Collect(productIds); } } } diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs index 498f356868..dea35afd41 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs @@ -74,13 +74,23 @@ protected virtual IList GetAllowedDiscounts(Product product, Customer if (product.HasDiscountsApplied) { //we use this property ("HasDiscountsApplied") for performance optimziation to avoid unnecessary database calls - foreach (var discount in product.AppliedDiscounts) - { - if (discount.DiscountType == DiscountType.AssignedToSkus && !result.Any(x => x.Id == discount.Id) && _discountService.IsDiscountValid(discount, customer)) + IEnumerable appliedDiscounts = null; + + if (context == null) + appliedDiscounts = product.AppliedDiscounts; + else + appliedDiscounts = context.AppliedDiscounts.Load(product.Id); + + if (appliedDiscounts != null) + { + foreach (var discount in appliedDiscounts) { - result.Add(discount); + if (discount.DiscountType == DiscountType.AssignedToSkus && !result.Any(x => x.Id == discount.Id) && _discountService.IsDiscountValid(discount, customer)) + { + result.Add(discount); + } } - } + } } //performance optimization. load all category discounts just to ensure that we have at least one @@ -270,7 +280,8 @@ public virtual PriceCalculationContext CreatePriceCalculationContext(IEnumerable x => _productAttributeService.GetProductVariantAttributesByProductIds(x, null), x => _productAttributeService.GetProductVariantAttributeCombinations(x), x => _productService.GetTierPrices(x, _services.WorkContext.CurrentCustomer, _services.StoreContext.CurrentStore.Id), - x => _categoryService.GetProductCategoriesByProductIds(x, true) + x => _categoryService.GetProductCategoriesByProductIds(x, true), + x => _productService.GetAppliedDiscountsByProductIds(x) ); return context; diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs index 8082cace64..ca06caebb8 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs @@ -13,6 +13,7 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Orders; @@ -1426,6 +1427,52 @@ public virtual SmartSyndicationFeed CreateRecentlyAddedProductsRssFeed(UrlHelper return feed; } + public virtual Multimap GetProductTagsByProductIds(int[] productIds) + { + var query = _productRepository.TableUntracked + .Expand(x => x.ProductTags) + .Where(x => productIds.Contains(x.Id)) + .Select(x => new + { + ProductId = x.Id, + Tags = x.ProductTags + }); + + var map = new Multimap(); + + foreach (var item in query.ToList()) + { + foreach (var tag in item.Tags) + map.Add(item.ProductId, tag); + } + + return map; + } + + public virtual Multimap GetAppliedDiscountsByProductIds(int[] productIds) + { + var query = _productRepository.TableUntracked + .Expand(x => x.AppliedDiscounts.Select(y => y.DiscountRequirements)) + .Where(x => productIds.Contains(x.Id)) + .Select(x => new + { + ProductId = x.Id, + Discounts = x.AppliedDiscounts + }); + + query.ToString().Dump(); + + var map = new Multimap(); + + foreach (var item in query.ToList()) + { + foreach (var discount in item.Discounts) + map.Add(item.ProductId, discount); + } + + return map; + } + #endregion #region Related products diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs index 510d451260..77b8148c5d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs @@ -4,6 +4,7 @@ using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Orders; @@ -336,6 +337,7 @@ public static ExpandoObject ToExpando(this ProductVariantAttributeCombination pv expando.Height = pvac.Height; expando.BasePriceAmount = pvac.BasePriceAmount; expando.BasePriceBaseAmount = pvac.BasePriceBaseAmount; + expando.AssignedPictureIds = pvac.AssignedPictureIds; expando.DeliveryTimeId = pvac.DeliveryTimeId; expando.IsActive = pvac.IsActive; @@ -391,6 +393,7 @@ public static ExpandoObject ToExpando(this Category category, int languageId) expando.MetaTitle = category.GetLocalized(x => x.MetaTitle, languageId, true, false); expando.SeName = category.GetSeName(languageId, true, false); expando.ParentCategoryId = category.ParentCategoryId; + expando.PictureId = category.PictureId; expando.PageSize = category.PageSize; expando.AllowCustomersToSelectPageSize = category.AllowCustomersToSelectPageSize; expando.PageSizeOptions = category.PageSizeOptions; @@ -519,6 +522,20 @@ public static ExpandoObject ToExpando(this Product product, int languageId) return expando as ExpandoObject; } + public static ExpandoObject ToExpando(this ProductTag productTag, int languageId) + { + if (productTag == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = productTag; + + expando.Id = productTag.Id; + expando.Name = productTag.GetLocalized(x => x.Name, languageId, true, false); + + return expando as ExpandoObject; + } + public static ExpandoObject ToExpando(this Order order, int languageId, ILocalizationService localization) { if (order == null) @@ -679,5 +696,47 @@ public static ExpandoObject ToExpando(this ShipmentItem shipmentItem) return expando as ExpandoObject; } + + public static ExpandoObject ToExpando(this Discount discount) + { + if (discount == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = discount; + + expando.Id = discount.Id; + expando.Name = discount.Name; + expando.DiscountTypeId = discount.DiscountTypeId; + expando.UsePercentage = discount.UsePercentage; + expando.DiscountPercentage = discount.DiscountPercentage; + expando.DiscountAmount = discount.DiscountAmount; + expando.StartDateUtc = discount.StartDateUtc; + expando.EndDateUtc = discount.EndDateUtc; + expando.RequiresCouponCode = discount.RequiresCouponCode; + expando.CouponCode = discount.CouponCode; + expando.DiscountLimitationId = discount.DiscountLimitationId; + expando.LimitationTimes = discount.LimitationTimes; + + return expando as ExpandoObject; + } + + public static ExpandoObject ToExpando(this TierPrice tierPrice) + { + if (tierPrice == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = tierPrice; + + expando.Id = tierPrice.Id; + expando.ProductId = tierPrice.ProductId; + expando.StoreId = tierPrice.StoreId; + expando.CustomerRoleId = tierPrice.CustomerRoleId; + expando.Quantity = tierPrice.Quantity; + expando.Price = tierPrice.Price; + + return expando as ExpandoObject; + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs index f81b0fe66b..adda26f97b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs @@ -13,6 +13,7 @@ namespace SmartStore.Services.DataExchange.ExportProvider [SystemName("Exports.SmartStoreNetProductXml")] [FriendlyName("SmartStore.NET XML product export")] [IsHidden(true)] + [ExportProjectionSupport(ExportProjectionSupport.HighDataDepth)] public class ExportProductXmlProvider : IExportProvider { public static string SystemName diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProductDataContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProductDataContext.cs index 9a8ffb27d0..613dd166bf 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProductDataContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProductDataContext.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using SmartStore.Collections; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Discounts; using SmartStore.Services.Catalog; namespace SmartStore.Services.DataExchange.ExportTask @@ -10,9 +11,11 @@ internal class ExportProductDataContext : PriceCalculationContext { private Func> _funcProductManufacturers; private Func> _funcProductPictures; + private Func> _funcProductTags; private LazyMultimap _productManufacturers; private LazyMultimap _productPictures; + private LazyMultimap _productTags; public ExportProductDataContext(IEnumerable products, Func> attributes, @@ -20,15 +23,20 @@ public ExportProductDataContext(IEnumerable products, Func> tierPrices, Func> productCategories, Func> productManufacturers, - Func> productPictures) + Func> productPictures, + Func> productTags, + Func> productAppliedDiscounts) : base(products, - attributes, - attributeCombinations, - tierPrices, - productCategories) + attributes, + attributeCombinations, + tierPrices, + productCategories, + productAppliedDiscounts + ) { _funcProductManufacturers = productManufacturers; _funcProductPictures = productPictures; + _funcProductTags = productTags; } public new void Clear() @@ -37,6 +45,8 @@ public ExportProductDataContext(IEnumerable products, _productManufacturers.Clear(); if (_productPictures != null) _productPictures.Clear(); + if (_productTags != null) + _productTags.Clear(); base.Clear(); } @@ -72,5 +82,17 @@ public LazyMultimap ProductPictures return _productPictures; } } + + public LazyMultimap ProductTags + { + get + { + if (_productTags == null) + { + _productTags = new LazyMultimap(keys => _funcProductTags(keys), _productIds); + } + return _productTags; + } + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 37d2b5185d..0b1fcdb039 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -587,7 +587,9 @@ private List GetProducts(ExportProfileTaskContext ctx, int pageIndex) x => _productService.GetTierPrices(x, ctx.ProjectionCustomer, ctx.Store.Id), x => _categoryService.GetProductCategoriesByProductIds(x), x => _manufacturerService.GetProductManufacturersByProductIds(x), - x => _productService.GetProductPicturesByProductIds(x) + x => _productService.GetProductPicturesByProductIds(x), + x => _productService.GetProductTagsByProductIds(x), + x => _productService.GetAppliedDiscountsByProductIds(x) ); SetProgress(ctx, products.Count); @@ -658,15 +660,24 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod { var result = new List(); var languageId = (ctx.Projection.LanguageId ?? 0); + var productTemplate = ctx.ProductTemplates.FirstOrDefault(x => x.Key == product.ProductTemplateId); + var pictureSize = _mediaSettings.ProductDetailsPictureSize; + + if (ctx.Provider.Supports(ExportProjectionSupport.MainPictureUrl) && ctx.Projection.PictureSize > 0) + pictureSize = ctx.Projection.PictureSize; + var productPictures = ctx.ProductDataContext.ProductPictures.Load(product.Id); var productManufacturers = ctx.ProductDataContext.ProductManufacturers.Load(product.Id); var productCategories = ctx.ProductDataContext.ProductCategories.Load(product.Id); var productAttributes = ctx.ProductDataContext.Attributes.Load(product.Id); var productAttributeCombinations = ctx.ProductDataContext.AttributeCombinations.Load(product.Id); - var productTemplate = ctx.ProductTemplates.FirstOrDefault(x => x.Key == product.ProductTemplateId); + var appliedDiscounts = ctx.ProductDataContext.AppliedDiscounts.Load(product.Id); + var tierPrices = ctx.ProductDataContext.TierPrices.Load(product.Id); dynamic expando = product.ToExpando(languageId); + #region gerneral data + expando._ProductTemplateViewPath = (productTemplate.Value == null ? "" : productTemplate.Value.ViewPath); expando._DetailUrl = ctx.Store.Url + expando.SeName; @@ -686,11 +697,6 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod .OrderBy(x => x.DisplayOrder) .Select(x => { - int pictureSize = _mediaSettings.ProductDetailsPictureSize; - - if (ctx.Provider.Supports(ExportProjectionSupport.MainPictureUrl) && ctx.Projection.PictureSize > 0) - pictureSize = ctx.Projection.PictureSize; - dynamic exp = new ExpandoObject(); exp._Entity = x; exp.Id = x.Id; @@ -712,6 +718,11 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod exp.IsFeaturedProduct = x.IsFeaturedProduct; exp.Manufacturer = x.Manufacturer.ToExpando(languageId); + if (x.Manufacturer != null && x.Manufacturer.PictureId.HasValue) + exp.Manufacturer.Picture = x.Manufacturer.Picture.ToExpando(_pictureService, ctx.Store, _mediaSettings.ManufacturerThumbPictureSize, _mediaSettings.ManufacturerThumbPictureSize); + else + exp.Manufacturer.Picture = null; + return exp as ExpandoObject; }) .ToList(); @@ -727,6 +738,11 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod exp.IsFeaturedProduct = x.IsFeaturedProduct; exp.Category = x.Category.ToExpando(languageId); + if (x.Category != null && x.Category.PictureId.HasValue) + exp.Category.Picture = x.Category.Picture.ToExpando(_pictureService, ctx.Store, _mediaSettings.CategoryThumbPictureSize, _mediaSettings.CategoryThumbPictureSize); + else + exp.Category.Picture = null; + if (expando._CategoryName == null) expando._CategoryName = (string)exp.Category.Name; @@ -743,15 +759,54 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod .Select(x => { dynamic exp = x.ToExpando(); + var assignedPictures = new List(); GetDeliveryTimeAndQuantityUnit(ctx, expando, x.DeliveryTimeId, x.QuantityUnitId); + foreach (int pictureId in x.GetAssignedPictureIds()) + { + var assignedPicture = productPictures.FirstOrDefault(y => y.PictureId == pictureId); + if (assignedPicture != null && assignedPicture.Picture != null) + { + assignedPictures.Add(assignedPicture.Picture.ToExpando(_pictureService, ctx.Store, _mediaSettings.ProductThumbPictureSize, pictureSize)); + } + } + + exp.Pictures = assignedPictures; + return exp as ExpandoObject; }) .ToList(); + expando.AppliedDiscounts = appliedDiscounts + .Select(x => x.ToExpando()) + .ToList(); + + expando.TierPrices = tierPrices + .Select(x => x.ToExpando()) + .ToList(); + + #endregion + + #region high data depth + + if (ctx.Provider.Supports(ExportProjectionSupport.HighDataDepth)) + { + var productTags = ctx.ProductDataContext.ProductTags.Load(product.Id); + + expando.ProductTags = productTags + .Select(x => x.ToExpando(languageId)) + .ToList(); + } + else + { + expando.ProductTags = new List(); + } + + #endregion + + #region more attribute controlled data - // data controlled through ExportProjectionSupport attribute if (ctx.Provider.Supports(ExportProjectionSupport.Description)) { PrepareProductDescription(ctx, expando, product); @@ -779,6 +834,9 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod expando._MainPictureUrl = _pictureService.GetDefaultPictureUrl(ctx.Projection.PictureSize, storeLocation: ctx.Store.Url); } + #endregion + + #region matter of data merging Action matterOfDataMerging = (exp, combination) => { @@ -872,8 +930,8 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod } }; + #endregion - // yield return expando if (ctx.Projection.AttributeCombinationAsProduct && productAttributeCombinations.Where(x => x.IsActive).Count() > 0) { // EF does not support entities to be cconstructed in a LINQ to entities query. diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs index f3db20bfe0..9e7cb73217 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs @@ -142,6 +142,161 @@ public void WriteRewardPointsHistory(dynamic rewardPoints, string node) } } + public void WriteDeliveryTime(dynamic deliveryTime, string node) + { + if (deliveryTime == null) + return; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", ((int)deliveryTime.Id).ToString()); + _writer.Write("Name", (string)deliveryTime.Name); + _writer.Write("DisplayLocale", (string)deliveryTime.DisplayLocale); + _writer.Write("ColorHexValue", (string)deliveryTime.ColorHexValue); + _writer.Write("DisplayOrder", ((int)deliveryTime.DisplayOrder).ToString()); + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WriteQuantityUnit(dynamic quantityUnit, string node) + { + if (quantityUnit == null) + return; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", ((int)quantityUnit.Id).ToString()); + _writer.Write("Name", (string)quantityUnit.Name); + _writer.Write("Description", (string)quantityUnit.Description); + _writer.Write("DisplayLocale", (string)quantityUnit.DisplayLocale); + _writer.Write("DisplayOrder", ((int)quantityUnit.DisplayOrder).ToString()); + _writer.Write("IsDefault", ((bool)quantityUnit.IsDefault).ToString()); + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WritePicture(dynamic picture, string node) + { + if (picture == null) + return; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", ((int)picture.Id).ToString()); + _writer.Write("SeoFilename", (string)picture.SeoFilename); + _writer.Write("MimeType", (string)picture.MimeType); + _writer.Write("ThumbImageUrl", (string)picture._ThumbImageUrl); + _writer.Write("ImageUrl", (string)picture._ImageUrl); + _writer.Write("FullSizeImageUrl", (string)picture._FullSizeImageUrl); + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WriteCategory(dynamic category, string node) + { + if (category == null) + return; + + int? pictureId = category.PictureId; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", ((int)category.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", ((int)category.CategoryTemplateId).ToString()); + _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", ((int)category.ParentCategoryId).ToString()); + _writer.Write("PictureId", pictureId.HasValue ? pictureId.Value.ToString() : ""); + _writer.Write("PageSize", ((int)category.PageSize).ToString()); + _writer.Write("AllowCustomersToSelectPageSize", ((bool)category.AllowCustomersToSelectPageSize).ToString()); + _writer.Write("PageSizeOptions", (string)category.PageSizeOptions); + _writer.Write("PriceRanges", (string)category.PriceRanges); + _writer.Write("ShowOnHomePage", ((bool)category.ShowOnHomePage).ToString()); + _writer.Write("HasDiscountsApplied", ((bool)category.HasDiscountsApplied).ToString()); + _writer.Write("Published", ((bool)category.Published).ToString()); + _writer.Write("Deleted", ((bool)category.Deleted).ToString()); + _writer.Write("DisplayOrder", ((int)category.DisplayOrder).ToString()); + _writer.Write("CreatedOnUtc", ((DateTime)category.CreatedOnUtc).ToString(_culture)); + _writer.Write("UpdatedOnUtc", ((DateTime)category.UpdatedOnUtc).ToString(_culture)); + _writer.Write("SubjectToAcl", ((bool)category.SubjectToAcl).ToString()); + _writer.Write("LimitedToStores", ((bool)category.LimitedToStores).ToString()); + _writer.Write("Alias", (string)category.Alias); + _writer.Write("DefaultViewMode", (string)category.DefaultViewMode); + + WritePicture(category.Picture, "Picture"); + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + + public void WriteManufacturer(dynamic manufacturer, string node) + { + if (manufacturer == null) + return; + + int? pictureId = manufacturer.PictureId; + + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } + + _writer.Write("Id", ((int)manufacturer.Id).ToString()); + _writer.Write("Name", (string)manufacturer.Name); + _writer.Write("SeName", (string)manufacturer.SeName); + _writer.Write("Description", (string)manufacturer.Description); + _writer.Write("ManufacturerTemplateId", ((int)manufacturer.ManufacturerTemplateId).ToString()); + _writer.Write("MetaKeywords", (string)manufacturer.MetaKeywords); + _writer.Write("MetaDescription", (string)manufacturer.MetaDescription); + _writer.Write("MetaTitle", (string)manufacturer.MetaTitle); + _writer.Write("PictureId", pictureId.HasValue ? pictureId.Value.ToString() : ""); + _writer.Write("PageSize", ((int)manufacturer.PageSize).ToString()); + _writer.Write("AllowCustomersToSelectPageSize", ((bool)manufacturer.AllowCustomersToSelectPageSize).ToString()); + _writer.Write("PageSizeOptions", (string)manufacturer.PageSizeOptions); + _writer.Write("PriceRanges", (string)manufacturer.PriceRanges); + _writer.Write("Published", ((bool)manufacturer.Published).ToString()); + _writer.Write("Deleted", ((bool)manufacturer.Deleted).ToString()); + _writer.Write("DisplayOrder", ((int)manufacturer.DisplayOrder).ToString()); + _writer.Write("CreatedOnUtc", ((DateTime)manufacturer.CreatedOnUtc).ToString(_culture)); + _writer.Write("UpdatedOnUtc", ((DateTime)manufacturer.UpdatedOnUtc).ToString(_culture)); + + WritePicture(manufacturer.Picture, "Picture"); + + if (node.HasValue()) + { + _writer.WriteEndElement(); + } + } + public void WriteProduct(dynamic product, string node) { if (product == null) @@ -263,32 +418,193 @@ public void WriteProduct(dynamic product, string node) _writer.Write("LowestAttributeCombinationPrice", lowestAttributeCombinationPrice.HasValue ? lowestAttributeCombinationPrice.Value.ToString(_culture) : ""); _writer.Write("IsEsd", ((bool)product.IsEsd).ToString()); - if (product.DeliveryTime != null) + WriteDeliveryTime(product.DeliveryTime, "DeliveryTime"); + + WriteQuantityUnit(product.QuantityUnit, "QuantityUnit"); + + + _writer.WriteStartElement("AppliedDiscounts"); + foreach (var discount in product.AppliedDiscounts) { - _writer.WriteStartElement("DeliveryTime"); + DateTime? startDateUtc = discount.StartDateUtc; + DateTime? endDateUtc = discount.EndDateUtc; + + _writer.WriteStartElement("AppliedDiscount"); + _writer.Write("Id", ((int)discount.Id).ToString()); + _writer.Write("Name", (string)discount.Name); + _writer.Write("DiscountTypeId", ((int)discount.DiscountTypeId).ToString()); + _writer.Write("UsePercentage", ((bool)discount.UsePercentage).ToString()); + _writer.Write("DiscountPercentage", ((decimal)discount.DiscountPercentage).ToString(_culture)); + _writer.Write("DiscountAmount", ((decimal)discount.DiscountAmount).ToString(_culture)); + _writer.Write("StartDateUtc", startDateUtc.HasValue ? startDateUtc.Value.ToString(_culture) : ""); + _writer.Write("EndDateUtc", endDateUtc.HasValue ? endDateUtc.Value.ToString(_culture) : ""); + _writer.Write("RequiresCouponCode", ((bool)discount.RequiresCouponCode).ToString()); + _writer.Write("CouponCode", (string)discount.CouponCode); + _writer.Write("DiscountLimitationId", ((int)discount.DiscountLimitationId).ToString()); + _writer.Write("LimitationTimes", ((int)discount.LimitationTimes).ToString()); + _writer.WriteEndElement(); // AppliedDiscount + } + _writer.WriteEndElement(); // AppliedDiscounts - _writer.Write("Id", ((int)product.DeliveryTime.Id).ToString()); - _writer.Write("Name", (string)product.DeliveryTime.Name); - _writer.Write("DisplayLocale", (string)product.DeliveryTime.DisplayLocale); - _writer.Write("ColorHexValue", (string)product.DeliveryTime.ColorHexValue); - _writer.Write("DisplayOrder", ((int)product.DeliveryTime.DisplayOrder).ToString()); - _writer.WriteEndElement(); // DeliveryTime + _writer.WriteStartElement("TierPrices"); + foreach (dynamic tierPrice in product.ProductTags) + { + int? customerRoleId = tierPrice.CustomerRoleId; + + _writer.WriteStartElement("TierPrice"); + _writer.Write("ProductId", ((int)tierPrice.ProductId).ToString()); + _writer.Write("StoreId", ((int)tierPrice.StoreId).ToString()); + _writer.Write("CustomerRoleId", customerRoleId.HasValue ? customerRoleId.Value.ToString() : ""); + _writer.Write("Quantity", ((int)tierPrice.Quantity).ToString()); + _writer.Write("Price", ((decimal)tierPrice.Price).ToString(_culture)); + _writer.WriteEndElement(); // TierPrice } + _writer.WriteEndElement(); // TierPrices - if (product.QuantityUnit != null) + + _writer.WriteStartElement("ProductTags"); + foreach (dynamic tag in product.ProductTags) { - _writer.WriteStartElement("QuantityUnit"); + _writer.WriteStartElement("ProductTag"); + _writer.Write("Id", ((int)tag.Id).ToString()); + _writer.Write("Name", (string)tag.Name); + _writer.WriteEndElement(); // ProductTag + } + _writer.WriteEndElement(); // ProductTags - _writer.Write("Id", ((int)product.QuantityUnit.Id).ToString()); - _writer.Write("Name", (string)product.QuantityUnit.Name); - _writer.Write("Description", (string)product.QuantityUnit.Description); - _writer.Write("DisplayLocale", (string)product.QuantityUnit.DisplayLocale); - _writer.Write("DisplayOrder", ((int)product.QuantityUnit.DisplayOrder).ToString()); - _writer.Write("IsDefault", ((bool)product.QuantityUnit.IsDefault).ToString()); - _writer.WriteEndElement(); // QuantityUnit + _writer.WriteStartElement("ProductAttributes"); + foreach (var pa in product.ProductAttributes) + { + _writer.WriteStartElement("ProductAttribute"); + _writer.Write("Id", ((int)pa.Id).ToString()); + _writer.Write("TextPrompt", (string)pa.TextPrompt); + _writer.Write("IsRequired", ((bool)pa.IsRequired).ToString()); + _writer.Write("AttributeControlTypeId", ((int)pa.AttributeControlTypeId).ToString()); + _writer.Write("DisplayOrder", ((int)pa.DisplayOrder).ToString()); + + _writer.WriteStartElement("Attribute"); + _writer.Write("Id", ((int)pa.Attribute.Id).ToString()); + _writer.Write("Alias", (string)pa.Attribute.Alias); + _writer.Write("Name", (string)pa.Attribute.Name); + _writer.Write("Description", (string)pa.Attribute.Description); + _writer.WriteEndElement(); // Attribute + + _writer.WriteStartElement("AttributeValues"); + foreach (var value in pa.Attribute.Values) + { + _writer.WriteStartElement("AttributeValue"); + _writer.Write("Id", ((int)value.Id).ToString()); + _writer.Write("Alias", (string)value.Alias); + _writer.Write("Name", (string)value.Name); + _writer.Write("ColorSquaresRgb", (string)value.ColorSquaresRgb); + _writer.Write("PriceAdjustment", ((decimal)value.PriceAdjustment).ToString(_culture)); + _writer.Write("WeightAdjustment", ((decimal)value.WeightAdjustment).ToString(_culture)); + _writer.Write("IsPreSelected", ((bool)value.IsPreSelected).ToString()); + _writer.Write("DisplayOrder", ((int)value.DisplayOrder).ToString()); + _writer.Write("ValueTypeId", ((int)value.ValueTypeId).ToString()); + _writer.Write("LinkedProductId", ((int)value.LinkedProductId).ToString()); + _writer.Write("Quantity", ((int)value.Quantity).ToString()); + _writer.WriteEndElement(); // AttributeValue + } + _writer.WriteEndElement(); // AttributeValues + + _writer.WriteEndElement(); // ProductAttribute } + _writer.WriteEndElement(); // ProductAttributes + + + _writer.WriteStartElement("ProductAttributeCombinations"); + foreach (dynamic combination in product.ProductAttributeCombinations) + { + decimal? price = combination.Price; + decimal? length = combination.Length; + decimal? width = combination.Width; + decimal? height = combination.Height; + decimal? bpAmount = combination.BasePriceAmount; + int? bpbAmount = combination.BasePriceBaseAmount; + int? dlvTimeId = combination.DeliveryTimeId; + + _writer.WriteStartElement("ProductAttributeCombination"); + _writer.Write("Id", ((int)combination.Id).ToString()); + _writer.Write("StockQuantity", ((int)combination.StockQuantity).ToString()); + _writer.Write("AllowOutOfStockOrders", ((bool)combination.AllowOutOfStockOrders).ToString()); + _writer.Write("AttributesXml", (string)combination.AttributesXml); + _writer.Write("Sku", (string)combination.Sku); + _writer.Write("Gtin", (string)combination.Gtin); + _writer.Write("ManufacturerPartNumber", (string)combination.ManufacturerPartNumber); + _writer.Write("Price", price.HasValue ? price.Value.ToString(_culture) : ""); + _writer.Write("Length", length.HasValue ? length.Value.ToString(_culture) : ""); + _writer.Write("Width", width.HasValue ? width.Value.ToString(_culture) : ""); + _writer.Write("Height", height.HasValue ? height.Value.ToString(_culture) : ""); + _writer.Write("BasePriceAmount", bpAmount.HasValue ? bpAmount.Value.ToString(_culture) : ""); + _writer.Write("BasePriceBaseAmount", bpbAmount.HasValue ? bpbAmount.Value.ToString() : ""); + _writer.Write("AssignedPictureIds", (string)combination.AssignedPictureIds); + _writer.Write("DeliveryTimeId", dlvTimeId.HasValue ? dlvTimeId.Value.ToString() : ""); + _writer.Write("IsActive", ((bool)combination.IsActive).ToString()); + + WriteDeliveryTime(combination.DeliveryTime, "DeliveryTime"); + + WriteQuantityUnit(combination.QuantityUnit, "QuantityUnit"); + + _writer.WriteStartElement("Pictures"); + foreach (dynamic assignedPicture in combination.Pictures) + { + WritePicture(assignedPicture, "Picture"); + } + _writer.WriteEndElement(); // Pictures + + _writer.WriteEndElement(); // ProductAttributeCombination + } + _writer.WriteEndElement(); // ProductAttributeCombinations + + + _writer.WriteStartElement("ProductPictures"); + foreach (dynamic productPicture in product.ProductPictures) + { + _writer.WriteStartElement("ProductPicture"); + _writer.Write("Id", ((int)productPicture.Id).ToString()); + _writer.Write("DisplayOrder", ((int)productPicture.DisplayOrder).ToString()); + + WritePicture(productPicture.Picture, "Picture"); + + _writer.WriteEndElement(); // ProductPicture + } + _writer.WriteEndElement(); // ProductPictures + + + _writer.WriteStartElement("ProductCategories"); + foreach (dynamic productCategory in product.ProductCategories) + { + _writer.WriteStartElement("ProductCategory"); + _writer.Write("DisplayOrder", ((int)productCategory.DisplayOrder).ToString()); + _writer.Write("IsFeaturedProduct", ((bool)productCategory.IsFeaturedProduct).ToString()); + + WriteCategory(productCategory.Category, "Category"); + + _writer.WriteEndElement(); // ProductCategory + } + _writer.WriteEndElement(); // ProductCategories + + + _writer.WriteStartElement("ProductManufacturers"); + foreach (dynamic productManu in product.ProductManufacturers) + { + _writer.WriteStartElement("ProductManufacturer"); + + _writer.Write("Id", ((int)productManu.Id).ToString()); + _writer.Write("DisplayOrder", ((int)productManu.DisplayOrder).ToString()); + _writer.Write("IsFeaturedProduct", ((bool)productManu.IsFeaturedProduct).ToString()); + + WriteManufacturer(productManu.Manufacturer, "Manufacturer"); + + _writer.WriteEndElement(); // ProductManufacturer + } + _writer.WriteEndElement(); // ProductManufacturers + + + if (node.HasValue()) { From 08a566a1e178e92d42059d4a7001afc5b1ad8224 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 30 Sep 2015 14:07:42 +0200 Subject: [PATCH 096/874] Convert product XML export to support new export framework (part 3) --- .../Catalog/IProductService.cs | 15 +++++ .../Catalog/ProductService.cs | 41 +++++++++++- .../DataExchange/ExpandoExtensions.cs | 62 +++++++++++++++++++ .../ExportTask/ExportProductDataContext.cs | 53 +++++++++++++++- .../ExportTask/ExportProfileTask.cs | 56 ++++++++++++++--- .../DataExchange/ExportXmlHelper.cs | 56 +++++++++++++++++ 6 files changed, 269 insertions(+), 14 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs index 395e3387e3..acaafcf70e 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs @@ -177,6 +177,13 @@ public partial interface IProductService /// Map of applied discounts Multimap GetAppliedDiscountsByProductIds(int[] productIds); + /// + /// Get product specification attributes by product identifiers + /// + /// Product identifiers + /// Map of product specification attributes + Multimap GetProductSpecificationAttributesByProductIds(int[] productIds); + #endregion #region Related products @@ -391,6 +398,14 @@ public partial interface IProductService /// List of bundle items IList GetBundleItems(int bundleProductId, bool showHidden = false); + /// + /// Get bundle items by product identifiers + /// + /// Product identifiers + /// A value indicating whether to show hidden records + /// Map of bundle items + Multimap GetBundleItemsByProductIds(int[] productIds, bool showHidden = false); + #endregion } diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs index ca06caebb8..a341dcc3c7 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs @@ -1429,6 +1429,8 @@ public virtual SmartSyndicationFeed CreateRecentlyAddedProductsRssFeed(UrlHelper public virtual Multimap GetProductTagsByProductIds(int[] productIds) { + Guard.ArgumentNotNull(() => productIds); + var query = _productRepository.TableUntracked .Expand(x => x.ProductTags) .Where(x => productIds.Contains(x.Id)) @@ -1451,6 +1453,8 @@ public virtual Multimap GetProductTagsByProductIds(int[] produc public virtual Multimap GetAppliedDiscountsByProductIds(int[] productIds) { + Guard.ArgumentNotNull(() => productIds); + var query = _productRepository.TableUntracked .Expand(x => x.AppliedDiscounts.Select(y => y.DiscountRequirements)) .Where(x => productIds.Contains(x.Id)) @@ -1460,8 +1464,6 @@ public virtual Multimap GetAppliedDiscountsByProductIds(int[] pro Discounts = x.AppliedDiscounts }); - query.ToString().Dump(); - var map = new Multimap(); foreach (var item in query.ToList()) @@ -1473,6 +1475,23 @@ public virtual Multimap GetAppliedDiscountsByProductIds(int[] pro return map; } + public virtual Multimap GetProductSpecificationAttributesByProductIds(int[] productIds) + { + Guard.ArgumentNotNull(() => productIds); + + var query = _productSpecificationAttributeRepository.TableUntracked + .Expand(x => x.SpecificationAttributeOption) + .Expand(x => x.SpecificationAttributeOption.SpecificationAttribute) + .Where(x => productIds.Contains(x.ProductId)); + + var map = query + .OrderBy(x => x.DisplayOrder) + .ToList() + .ToMultimap(x => x.ProductId, x => x); + + return map; + } + #endregion #region Related products @@ -2035,6 +2054,24 @@ orderby pbi.DisplayOrder return bundleItemData; } + public virtual Multimap GetBundleItemsByProductIds(int[] productIds, bool showHidden = false) + { + Guard.ArgumentNotNull(() => productIds); + + var query = + from pbi in _productBundleItemRepository.TableUntracked + join p in _productRepository.TableUntracked on pbi.ProductId equals p.Id + where productIds.Contains(pbi.BundleProductId) && !p.Deleted && (showHidden || (pbi.Published && p.Published)) + orderby pbi.DisplayOrder + select pbi; + + var map = query + .ToList() + .ToMultimap(x => x.BundleProductId, x => x); + + return map; + } + #endregion #endregion diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs index 77b8148c5d..518d909e47 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs @@ -738,5 +738,67 @@ public static ExpandoObject ToExpando(this TierPrice tierPrice) return expando as ExpandoObject; } + + public static ExpandoObject ToExpando(this ProductSpecificationAttribute psa, int languageId) + { + if (psa == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = psa; + + expando.Id = psa.Id; + expando.ProductId = psa.ProductId; + expando.SpecificationAttributeOptionId = psa.SpecificationAttributeOptionId; + expando.AllowFiltering = psa.AllowFiltering; + expando.ShowOnProductPage = psa.ShowOnProductPage; + expando.DisplayOrder = psa.DisplayOrder; + + var option = psa.SpecificationAttributeOption; + + expando.SpecificationAttributeOption._Entity = option; + expando.SpecificationAttributeOption.Id = option.Id; + expando.SpecificationAttributeOption.SpecificationAttributeId = option.SpecificationAttributeId; + expando.SpecificationAttributeOption.Name = option.GetLocalized(x => x.Name, languageId, true, false); + expando.SpecificationAttributeOption.DisplayOrder = option.DisplayOrder; + + expando.SpecificationAttributeOption.SpecificationAttribute._Entity = option.SpecificationAttribute; + expando.SpecificationAttributeOption.SpecificationAttribute.Id = option.SpecificationAttribute.Id; + expando.SpecificationAttributeOption.SpecificationAttribute.Name = option.SpecificationAttribute.GetLocalized(x => x.Name, languageId, true, false); + expando.SpecificationAttributeOption.SpecificationAttribute.DisplayOrder = option.SpecificationAttribute.DisplayOrder; + + return expando as ExpandoObject; + } + + public static ExpandoObject ToExpando(this ProductBundleItem bundleItem, string productName, int languageId) + { + if (bundleItem == null) + return null; + + var name = bundleItem.GetLocalized(x => x.Name, languageId, true, false); + if (name.IsEmpty()) + name = productName; + + dynamic expando = new ExpandoObject(); + expando._Entity = bundleItem; + + expando.Id = bundleItem.Id; + expando.ProductId = bundleItem.ProductId; + expando.BundleProductId = bundleItem.BundleProductId; + expando.Quantity = bundleItem.Quantity; + expando.Discount = bundleItem.Discount; + expando.DiscountPercentage = bundleItem.DiscountPercentage; + expando.Name = name; + expando.ShortDescription = bundleItem.GetLocalized(x => x.ShortDescription, languageId, true, false); + expando.FilterAttributes = bundleItem.FilterAttributes; + expando.HideThumbnail = bundleItem.HideThumbnail; + expando.Visible = bundleItem.Visible; + expando.Published = bundleItem.Published; + expando.DisplayOrder = bundleItem.DisplayOrder; + expando.CreatedOnUtc = bundleItem.CreatedOnUtc; + expando.UpdatedOnUtc = bundleItem.UpdatedOnUtc; + + return expando as ExpandoObject; + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProductDataContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProductDataContext.cs index 613dd166bf..512513e7e8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProductDataContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProductDataContext.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Collections.Generic; using SmartStore.Collections; using SmartStore.Core.Domain.Catalog; @@ -9,13 +10,19 @@ namespace SmartStore.Services.DataExchange.ExportTask { internal class ExportProductDataContext : PriceCalculationContext { + private List _productIdsBundleItems; + private Func> _funcProductManufacturers; private Func> _funcProductPictures; private Func> _funcProductTags; + private Func> _funcProductSpecificationAttributes; + private Func> _funcProductBundleItems; private LazyMultimap _productManufacturers; private LazyMultimap _productPictures; private LazyMultimap _productTags; + private LazyMultimap _productSpecificationAttributes; + private LazyMultimap _productBundleItems; public ExportProductDataContext(IEnumerable products, Func> attributes, @@ -25,18 +32,30 @@ public ExportProductDataContext(IEnumerable products, Func> productManufacturers, Func> productPictures, Func> productTags, - Func> productAppliedDiscounts) + Func> productAppliedDiscounts, + Func> productSpecificationAttributes, + Func> productBundleItems) : base(products, attributes, attributeCombinations, tierPrices, productCategories, - productAppliedDiscounts - ) + productAppliedDiscounts) { + if (products == null) + { + _productIdsBundleItems = new List(); + } + else + { + _productIdsBundleItems = new List(products.Where(x => x.ProductType == ProductType.BundledProduct).Select(x => x.Id)); + } + _funcProductManufacturers = productManufacturers; _funcProductPictures = productPictures; _funcProductTags = productTags; + _funcProductSpecificationAttributes = productSpecificationAttributes; + _funcProductBundleItems = productBundleItems; } public new void Clear() @@ -47,6 +66,10 @@ public ExportProductDataContext(IEnumerable products, _productPictures.Clear(); if (_productTags != null) _productTags.Clear(); + if (_productSpecificationAttributes != null) + _productSpecificationAttributes.Clear(); + if (_productBundleItems != null) + _productBundleItems.Clear(); base.Clear(); } @@ -94,5 +117,29 @@ public LazyMultimap ProductTags return _productTags; } } + + public LazyMultimap ProductSpecificationAttributes + { + get + { + if (_productSpecificationAttributes == null) + { + _productSpecificationAttributes = new LazyMultimap(keys => _funcProductSpecificationAttributes(keys), _productIds); + } + return _productSpecificationAttributes; + } + } + + public LazyMultimap ProductBundleItems + { + get + { + if (_productBundleItems == null) + { + _productBundleItems = new LazyMultimap(keys => _funcProductBundleItems(keys), _productIdsBundleItems); + } + return _productBundleItems; + } + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 0b1fcdb039..7ffc9d8a2b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -589,7 +589,9 @@ private List GetProducts(ExportProfileTaskContext ctx, int pageIndex) x => _manufacturerService.GetProductManufacturersByProductIds(x), x => _productService.GetProductPicturesByProductIds(x), x => _productService.GetProductTagsByProductIds(x), - x => _productService.GetAppliedDiscountsByProductIds(x) + x => _productService.GetAppliedDiscountsByProductIds(x), + x => _productService.GetProductSpecificationAttributesByProductIds(x), + x => _productService.GetBundleItemsByProductIds(x) ); SetProgress(ctx, products.Count); @@ -671,8 +673,6 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod var productCategories = ctx.ProductDataContext.ProductCategories.Load(product.Id); var productAttributes = ctx.ProductDataContext.Attributes.Load(product.Id); var productAttributeCombinations = ctx.ProductDataContext.AttributeCombinations.Load(product.Id); - var appliedDiscounts = ctx.ProductDataContext.AppliedDiscounts.Load(product.Id); - var tierPrices = ctx.ProductDataContext.TierPrices.Load(product.Id); dynamic expando = product.ToExpando(languageId); @@ -778,13 +778,31 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod }) .ToList(); - expando.AppliedDiscounts = appliedDiscounts - .Select(x => x.ToExpando()) - .ToList(); + if (product.HasDiscountsApplied) + { + var appliedDiscounts = ctx.ProductDataContext.AppliedDiscounts.Load(product.Id); - expando.TierPrices = tierPrices - .Select(x => x.ToExpando()) - .ToList(); + expando.AppliedDiscounts = appliedDiscounts + .Select(x => x.ToExpando()) + .ToList(); + } + else + { + expando.AppliedDiscounts = new List(); + } + + if (product.HasTierPrices) + { + var tierPrices = ctx.ProductDataContext.TierPrices.Load(product.Id); + + expando.TierPrices = tierPrices + .Select(x => x.ToExpando()) + .ToList(); + } + else + { + expando.TierPrices = new List(); + } #endregion @@ -793,14 +811,34 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod if (ctx.Provider.Supports(ExportProjectionSupport.HighDataDepth)) { var productTags = ctx.ProductDataContext.ProductTags.Load(product.Id); + var specificationAttributes = ctx.ProductDataContext.ProductSpecificationAttributes.Load(product.Id); expando.ProductTags = productTags .Select(x => x.ToExpando(languageId)) .ToList(); + + expando.ProductSpecificationAttributes = specificationAttributes + .Select(x => x.ToExpando(languageId)) + .ToList(); + + if (product.ProductType == ProductType.BundledProduct) + { + var bundleItems = ctx.ProductDataContext.ProductBundleItems.Load(product.Id); + + expando.ProductBundleItems = bundleItems + .Select(x => x.ToExpando((string)expando.Name, languageId)) + .ToList(); + } + else + { + expando.ProductBundleItems = new List(); + } } else { expando.ProductTags = new List(); + expando.ProductSpecificationAttributes = new List(); + expando.ProductBundleItems = new List(); } #endregion diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs index 9e7cb73217..67690802ea 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs @@ -604,6 +604,62 @@ public void WriteProduct(dynamic product, string node) _writer.WriteEndElement(); // ProductManufacturers + _writer.WriteStartElement("ProductSpecificationAttributes"); + foreach (dynamic psa in product.ProductSpecificationAttributes) + { + _writer.WriteStartElement("ProductSpecificationAttribute"); + + _writer.Write("Id", ((int)psa.Id).ToString()); + _writer.Write("ProductId", ((int)psa.ProductId).ToString()); + _writer.Write("SpecificationAttributeOptionId", ((int)psa.SpecificationAttributeOptionId).ToString()); + _writer.Write("AllowFiltering", ((bool)psa.AllowFiltering).ToString()); + _writer.Write("ShowOnProductPage", ((bool)psa.ShowOnProductPage).ToString()); + _writer.Write("DisplayOrder", ((int)psa.DisplayOrder).ToString()); + + dynamic option = psa.SpecificationAttributeOption; + + _writer.WriteStartElement("SpecificationAttributeOption"); + _writer.Write("Id", ((int)option.Id).ToString()); + _writer.Write("SpecificationAttributeId", ((int)option.SpecificationAttributeId).ToString()); + _writer.Write("DisplayOrder", ((int)option.DisplayOrder).ToString()); + _writer.Write("Name", (string)option.Name); + + _writer.WriteStartElement("SpecificationAttribute"); + _writer.Write("Id", ((int)option.SpecificationAttribute.Id).ToString()); + _writer.Write("Name", (string)option.SpecificationAttribute.Name); + _writer.Write("DisplayOrder", ((int)option.SpecificationAttribute.DisplayOrder).ToString()); + _writer.WriteEndElement(); // SpecificationAttribute + _writer.WriteEndElement(); // SpecificationAttributeOption + + _writer.WriteEndElement(); // ProductSpecificationAttribute + } + _writer.WriteEndElement(); // ProductSpecificationAttributes + + + _writer.WriteStartElement("ProductBundleItems"); + foreach (var bundleItem in product.ProductBundleItems) + { + decimal? bundleItemDiscount = bundleItem.Discount; + + _writer.WriteStartElement("ProductBundleItem"); + _writer.Write("Id", ((int)bundleItem.Id).ToString()); + _writer.Write("ProductId", ((int)bundleItem.ProductId).ToString()); + _writer.Write("BundleProductId", ((int)bundleItem.BundleProductId).ToString()); + _writer.Write("Quantity", ((int)bundleItem.Quantity).ToString()); + _writer.Write("Discount", bundleItemDiscount.HasValue ? bundleItemDiscount.Value.ToString(_culture) : ""); + _writer.Write("DiscountPercentage", ((bool)bundleItem.DiscountPercentage).ToString()); + _writer.Write("Name", (string)bundleItem.Name); + _writer.Write("ShortDescription", (string)bundleItem.ShortDescription); + _writer.Write("FilterAttributes", ((bool)bundleItem.FilterAttributes).ToString()); + _writer.Write("HideThumbnail", ((bool)bundleItem.HideThumbnail).ToString()); + _writer.Write("Visible", ((bool)bundleItem.Visible).ToString()); + _writer.Write("Published", ((bool)bundleItem.Published).ToString()); + _writer.Write("DisplayOrder", ((int)bundleItem.DisplayOrder).ToString()); + _writer.Write("CreatedOnUtc", ((DateTime)bundleItem.CreatedOnUtc).ToString(_culture)); + _writer.Write("UpdatedOnUtc", ((DateTime)bundleItem.UpdatedOnUtc).ToString(_culture)); + _writer.WriteEndElement(); // ProductBundleItem + } + _writer.WriteEndElement(); // ProductBundleItems if (node.HasValue()) From 7966301acbc7dc763a789b4daeb6f6da0208d422 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 30 Sep 2015 18:37:01 +0200 Subject: [PATCH 097/874] Added filename to expando object --- .../SmartStore.Services/DataExchange/ExpandoExtensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs index 518d909e47..9fe4c7dd95 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs @@ -265,6 +265,9 @@ public static ExpandoObject ToExpando(this Picture picture, IPictureService pict expando._ImageUrl = pictureService.GetPictureUrl(picture, detailsPictureSize, false, store.Url); expando._FullSizeImageUrl = pictureService.GetPictureUrl(picture, 0, false, store.Url); + var relativeUrl = pictureService.GetPictureUrl(picture); + expando._FileName = relativeUrl.Substring(relativeUrl.LastIndexOf("/") + 1); + return expando as ExpandoObject; } From fba4d7392dce422d6fbc5b0908fb8efafe2752d3 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 1 Oct 2015 11:21:51 +0200 Subject: [PATCH 098/874] Fixed: Localized description of quantity unit was not loaded on edit page Reduce database round-trips while loading localized properties during export --- .../DataExchange/ExpandoExtensions.cs | 804 --------------- .../ExportTask/ExportProfileTask.cs | 921 ++++++++++++++++-- .../ExportTask/ExportProfileTaskContext.cs | 21 + .../Localization/ILocalizedEntityService.cs | 8 + .../Localization/LocalizedEntityService.cs | 16 + .../SmartStore.Services.csproj | 1 - .../Controllers/QuantityUnitController.cs | 61 +- 7 files changed, 917 insertions(+), 915 deletions(-) delete mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs deleted file mode 100644 index 518d909e47..0000000000 --- a/src/Libraries/SmartStore.Services/DataExchange/ExpandoExtensions.cs +++ /dev/null @@ -1,804 +0,0 @@ -using System.Dynamic; -using System.Linq; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Discounts; -using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Domain.Media; -using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Domain.Shipping; -using SmartStore.Core.Domain.Stores; -using SmartStore.Services.Localization; -using SmartStore.Services.Media; -using SmartStore.Services.Seo; - -namespace SmartStore.Services.DataExchange -{ - public static class ExpandoExtensions - { - public static ExpandoObject ToExpando(this Currency currency, int languageId) - { - if (currency == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = currency; - - expando.Id = currency.Id; - expando.Name = currency.GetLocalized(x => x.Name, languageId, true, false); - expando.CurrencyCode = currency.CurrencyCode; - expando.Rate = currency.Rate; - expando.DisplayLocale = currency.DisplayLocale; - expando.CustomFormatting = currency.CustomFormatting; - expando.LimitedToStores = currency.LimitedToStores; - expando.Published = currency.Published; - expando.DisplayOrder = currency.DisplayOrder; - expando.CreatedOnUtc = currency.CreatedOnUtc; - expando.UpdatedOnUtc = currency.UpdatedOnUtc; - expando.DomainEndings = currency.DomainEndings; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this Language language) - { - if (language == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = language; - - expando.Id = language.Id; - expando.Name = language.Name; - expando.LanguageCulture = language.LanguageCulture; - expando.UniqueSeoCode = language.UniqueSeoCode; - expando.FlagImageFileName = language.FlagImageFileName; - expando.Rtl = language.Rtl; - expando.LimitedToStores = language.LimitedToStores; - expando.Published = language.Published; - expando.DisplayOrder = language.DisplayOrder; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this Country country, int languageId) - { - if (country == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = country; - - expando.Id = country.Id; - expando.Name = country.GetLocalized(x => x.Name, languageId, true, false); - expando.AllowsBilling = country.AllowsBilling; - expando.AllowsShipping = country.AllowsShipping; - expando.TwoLetterIsoCode = country.TwoLetterIsoCode; - expando.ThreeLetterIsoCode = country.ThreeLetterIsoCode; - expando.NumericIsoCode = country.NumericIsoCode; - expando.SubjectToVat = country.SubjectToVat; - expando.Published = country.Published; - expando.DisplayOrder = country.DisplayOrder; - expando.LimitedToStores = country.LimitedToStores; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this StateProvince stateProvince, int languageId) - { - if (stateProvince == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = stateProvince; - - expando.Id = stateProvince.Id; - expando.CountryId = stateProvince.CountryId; - expando.Name = stateProvince.GetLocalized(x => x.Name, languageId, true, false); - expando.Abbreviation = stateProvince.Abbreviation; - expando.Published = stateProvince.Published; - expando.DisplayOrder = stateProvince.DisplayOrder; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this Address address, int languageId) - { - if (address == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = address; - - expando.Id = address.Id; - expando.FirstName = address.FirstName; - expando.LastName = address.LastName; - expando.Email = address.Email; - expando.Company = address.Company; - expando.CountryId = address.CountryId; - expando.StateProvinceId = address.StateProvinceId; - expando.City = address.City; - expando.Address1 = address.Address1; - expando.Address2 = address.Address2; - expando.ZipPostalCode = address.ZipPostalCode; - expando.PhoneNumber = address.PhoneNumber; - expando.FaxNumber = address.FaxNumber; - expando.CreatedOnUtc = address.CreatedOnUtc; - - expando.Country = address.Country.ToExpando(languageId); - expando.StateProvince = address.StateProvince.ToExpando(languageId); - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this RewardPointsHistory points) - { - if (points == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = points; - - expando.Id = points.Id; - expando.CustomerId = points.CustomerId; - expando.Points = points.Points; - expando.PointsBalance = points.PointsBalance; - expando.UsedAmount = points.UsedAmount; - expando.Message = points.Message; - expando.CreatedOnUtc = points.CreatedOnUtc; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this Customer customer, int languageId) - { - if (customer == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = customer; - - expando.Id = customer.Id; - expando.CustomerGuid = customer.CustomerGuid; - expando.Username = customer.Username; - expando.Email = customer.Email; - //Password... we do not provide that data - expando.PasswordFormatId = customer.PasswordFormatId; - expando.AdminComment = customer.AdminComment; - expando.IsTaxExempt = customer.IsTaxExempt; - expando.AffiliateId = customer.AffiliateId; - expando.Active = customer.Active; - expando.Deleted = customer.Deleted; - expando.IsSystemAccount = customer.IsSystemAccount; - expando.SystemName = customer.SystemName; - expando.LastIpAddress = customer.LastIpAddress; - expando.CreatedOnUtc = customer.CreatedOnUtc; - expando.LastLoginDateUtc = customer.LastLoginDateUtc; - expando.LastActivityDateUtc = customer.LastActivityDateUtc; - - expando.BillingAddress = customer.BillingAddress.ToExpando(languageId); - expando.ShippingAddress = customer.ShippingAddress.ToExpando(languageId); - - expando.RewardPointsHistory = null; - expando._RewardPointsBalance = 0; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this Store store, int languageId) - { - if (store == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = store; - - expando.Id = store.Id; - expando.Name = store.Name; - expando.Url = store.Url; - expando.SslEnabled = store.SslEnabled; - expando.SecureUrl = store.SecureUrl; - expando.Hosts = store.Hosts; - expando.LogoPictureId = store.LogoPictureId; - expando.DisplayOrder = store.DisplayOrder; - expando.HtmlBodyId = store.HtmlBodyId; - expando.ContentDeliveryNetwork = store.ContentDeliveryNetwork; - expando.PrimaryStoreCurrencyId = store.PrimaryStoreCurrencyId; - expando.PrimaryExchangeRateCurrencyId = store.PrimaryExchangeRateCurrencyId; - - expando.PrimaryStoreCurrency = store.PrimaryStoreCurrency.ToExpando(languageId); - expando.PrimaryExchangeRateCurrency = store.PrimaryExchangeRateCurrency.ToExpando(languageId); - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this DeliveryTime deliveryTime, int languageId) - { - if (deliveryTime == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = deliveryTime; - - expando.Id = deliveryTime.Id; - expando.Name = deliveryTime.GetLocalized(x => x.Name, languageId, true, false); - expando.DisplayLocale = deliveryTime.DisplayLocale; - expando.ColorHexValue = deliveryTime.ColorHexValue; - expando.DisplayOrder = deliveryTime.DisplayOrder; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this QuantityUnit quantityUnit, int languageId) - { - if (quantityUnit == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = quantityUnit; - - expando.Id = quantityUnit.Id; - expando.Name = quantityUnit.GetLocalized(x => x.Name, languageId, true, false); - expando.Description = quantityUnit.GetLocalized(x => x.Description, languageId, true, false); - expando.DisplayLocale = quantityUnit.DisplayLocale; - expando.DisplayOrder = quantityUnit.DisplayOrder; - expando.IsDefault = quantityUnit.IsDefault; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this Picture picture, IPictureService pictureService, Store store, int thumbPictureSize, int detailsPictureSize) - { - if (picture == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = picture; - - expando.Id = picture.Id; - expando.SeoFilename = picture.SeoFilename; - expando.MimeType = picture.MimeType; - - expando._ThumbImageUrl = pictureService.GetPictureUrl(picture, thumbPictureSize, false, store.Url); - expando._ImageUrl = pictureService.GetPictureUrl(picture, detailsPictureSize, false, store.Url); - expando._FullSizeImageUrl = pictureService.GetPictureUrl(picture, 0, false, store.Url); - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this ProductVariantAttribute pva, int languageId) - { - if (pva == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = pva; - - expando.Id = pva.Id; - expando.TextPrompt = pva.TextPrompt; - expando.IsRequired = pva.IsRequired; - expando.AttributeControlTypeId = pva.AttributeControlTypeId; - expando.DisplayOrder = pva.DisplayOrder; - - dynamic attribute = new ExpandoObject(); - attribute._Entity = pva.ProductAttribute; - attribute.Id = pva.ProductAttribute.Id; - attribute.Alias = pva.ProductAttribute.Alias; - attribute.Name = pva.ProductAttribute.GetLocalized(y => y.Name, languageId, true, false); - attribute.Description = pva.ProductAttribute.GetLocalized(y => y.Description, languageId, true, false); - - attribute.Values = pva.ProductVariantAttributeValues - .OrderBy(x => x.DisplayOrder) - .Select(x => - { - dynamic value = new ExpandoObject(); - value._Entity = x; - value.Id = x.Id; - value.Alias = x.Alias; - value.Name = x.GetLocalized(z => z.Name, languageId, true, false); - value.ColorSquaresRgb = x.ColorSquaresRgb; - value.PriceAdjustment = x.PriceAdjustment; - value.WeightAdjustment = x.WeightAdjustment; - value.IsPreSelected = x.IsPreSelected; - value.DisplayOrder = x.DisplayOrder; - value.ValueTypeId = x.ValueTypeId; - value.LinkedProductId = x.LinkedProductId; - value.Quantity = x.Quantity; - - return value as ExpandoObject; - }) - .ToList(); - - expando.Attribute = attribute as ExpandoObject; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this ProductVariantAttributeCombination pvac) - { - if (pvac == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = pvac; - - expando.Id = pvac.Id; - expando.StockQuantity = pvac.StockQuantity; - expando.AllowOutOfStockOrders = pvac.AllowOutOfStockOrders; - expando.AttributesXml = pvac.AttributesXml; - expando.Sku = pvac.Sku; - expando.Gtin = pvac.Gtin; - expando.ManufacturerPartNumber = pvac.ManufacturerPartNumber; - expando.Price = pvac.Price; - expando.Length = pvac.Length; - expando.Width = pvac.Width; - expando.Height = pvac.Height; - expando.BasePriceAmount = pvac.BasePriceAmount; - expando.BasePriceBaseAmount = pvac.BasePriceBaseAmount; - expando.AssignedPictureIds = pvac.AssignedPictureIds; - expando.DeliveryTimeId = pvac.DeliveryTimeId; - expando.IsActive = pvac.IsActive; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this Manufacturer manufacturer, int languageId) - { - if (manufacturer == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = manufacturer; - - expando.Id = manufacturer.Id; - expando.Name = manufacturer.GetLocalized(x => x.Name, languageId, true, false); - expando.SeName = manufacturer.GetSeName(languageId, true, false); - expando.Description = manufacturer.GetLocalized(x => x.Description, languageId, true, false); - expando.ManufacturerTemplateId = manufacturer.ManufacturerTemplateId; - expando.MetaKeywords = manufacturer.GetLocalized(x => x.MetaKeywords, languageId, true, false); - expando.MetaDescription = manufacturer.GetLocalized(x => x.MetaDescription, languageId, true, false); - expando.MetaTitle = manufacturer.GetLocalized(x => x.MetaTitle, languageId, true, false); - expando.PictureId = manufacturer.PictureId; - expando.PageSize = manufacturer.PageSize; - expando.AllowCustomersToSelectPageSize = manufacturer.AllowCustomersToSelectPageSize; - expando.PageSizeOptions = manufacturer.PageSizeOptions; - expando.PriceRanges = manufacturer.PriceRanges; - expando.Published = manufacturer.Published; - expando.Deleted = manufacturer.Deleted; - expando.DisplayOrder = manufacturer.DisplayOrder; - expando.CreatedOnUtc = manufacturer.CreatedOnUtc; - expando.UpdatedOnUtc = manufacturer.UpdatedOnUtc; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this Category category, int languageId) - { - if (category == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = category; - - expando.Id = category.Id; - expando.Name = category.GetLocalized(x => x.Name, languageId, true, false); - expando.FullName = category.GetLocalized(x => x.FullName, languageId, true, false); - expando.Description = category.GetLocalized(x => x.Description, languageId, true, false); - expando.BottomDescription = category.GetLocalized(x => x.BottomDescription, languageId, true, false); - expando.CategoryTemplateId = category.CategoryTemplateId; - expando.MetaKeywords = category.GetLocalized(x => x.MetaKeywords, languageId, true, false); - expando.MetaDescription = category.GetLocalized(x => x.MetaDescription, languageId, true, false); - expando.MetaTitle = category.GetLocalized(x => x.MetaTitle, languageId, true, false); - expando.SeName = category.GetSeName(languageId, true, false); - expando.ParentCategoryId = category.ParentCategoryId; - expando.PictureId = category.PictureId; - expando.PageSize = category.PageSize; - expando.AllowCustomersToSelectPageSize = category.AllowCustomersToSelectPageSize; - expando.PageSizeOptions = category.PageSizeOptions; - expando.PriceRanges = category.PriceRanges; - expando.ShowOnHomePage = category.ShowOnHomePage; - expando.HasDiscountsApplied = category.HasDiscountsApplied; - expando.Published = category.Published; - expando.Deleted = category.Deleted; - expando.DisplayOrder = category.DisplayOrder; - expando.CreatedOnUtc = category.CreatedOnUtc; - expando.UpdatedOnUtc = category.UpdatedOnUtc; - expando.SubjectToAcl = category.SubjectToAcl; - expando.LimitedToStores = category.LimitedToStores; - expando.Alias = category.Alias; - expando.DefaultViewMode = category.DefaultViewMode; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this Product product, int languageId) - { - if (product == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = product; - - expando.Id = product.Id; - expando.Name = product.GetLocalized(x => x.Name, languageId, true, false); - expando.SeName = product.GetSeName(languageId, true, false); - expando.ShortDescription = product.GetLocalized(x => x.ShortDescription, languageId, true, false); - expando.FullDescription = product.GetLocalized(x => x.FullDescription, languageId, true, false); - expando.AdminComment = product.AdminComment; - expando.ProductTemplateId = product.ProductTemplateId; - expando.ShowOnHomePage = product.ShowOnHomePage; - expando.HomePageDisplayOrder = product.HomePageDisplayOrder; - expando.MetaKeywords = product.GetLocalized(x => x.MetaKeywords, languageId, true, false); - expando.MetaDescription = product.GetLocalized(x => x.MetaDescription, languageId, true, false); - expando.MetaTitle = product.GetLocalized(x => x.MetaTitle, languageId, true, false); - expando.AllowCustomerReviews = product.AllowCustomerReviews; - expando.ApprovedRatingSum = product.ApprovedRatingSum; - expando.NotApprovedRatingSum = product.NotApprovedRatingSum; - expando.ApprovedTotalReviews = product.ApprovedTotalReviews; - expando.NotApprovedTotalReviews = product.NotApprovedTotalReviews; - expando.Published = product.Published; - expando.CreatedOnUtc = product.CreatedOnUtc; - expando.UpdatedOnUtc = product.UpdatedOnUtc; - expando.SubjectToAcl = product.SubjectToAcl; - expando.LimitedToStores = product.LimitedToStores; - expando.ProductTypeId = product.ProductTypeId; - expando.ParentGroupedProductId = product.ParentGroupedProductId; - expando.Sku = product.Sku; - expando.ManufacturerPartNumber = product.ManufacturerPartNumber; - expando.Gtin = product.Gtin; - expando.IsGiftCard = product.IsGiftCard; - expando.GiftCardTypeId = product.GiftCardTypeId; - expando.RequireOtherProducts = product.RequireOtherProducts; - expando.RequiredProductIds = product.RequiredProductIds; - expando.AutomaticallyAddRequiredProducts = product.AutomaticallyAddRequiredProducts; - expando.IsDownload = product.IsDownload; - expando.DownloadId = product.DownloadId; - expando.UnlimitedDownloads = product.UnlimitedDownloads; - expando.MaxNumberOfDownloads = product.MaxNumberOfDownloads; - expando.DownloadExpirationDays = product.DownloadExpirationDays; - expando.DownloadActivationTypeId = product.DownloadActivationTypeId; - expando.HasSampleDownload = product.HasSampleDownload; - expando.SampleDownloadId = product.SampleDownloadId; - expando.HasUserAgreement = product.HasUserAgreement; - expando.UserAgreementText = product.UserAgreementText; - expando.IsRecurring = product.IsRecurring; - expando.RecurringCycleLength = product.RecurringCycleLength; - expando.RecurringCyclePeriodId = product.RecurringCyclePeriodId; - expando.RecurringTotalCycles = product.RecurringTotalCycles; - expando.IsShipEnabled = product.IsShipEnabled; - expando.IsFreeShipping = product.IsFreeShipping; - expando.AdditionalShippingCharge = product.AdditionalShippingCharge; - expando.IsTaxExempt = product.IsTaxExempt; - expando.TaxCategoryId = product.TaxCategoryId; - expando.ManageInventoryMethodId = product.ManageInventoryMethodId; - expando.StockQuantity = product.StockQuantity; - expando.DisplayStockAvailability = product.DisplayStockAvailability; - expando.DisplayStockQuantity = product.DisplayStockQuantity; - expando.MinStockQuantity = product.MinStockQuantity; - expando.LowStockActivityId = product.LowStockActivityId; - expando.NotifyAdminForQuantityBelow = product.NotifyAdminForQuantityBelow; - expando.BackorderModeId = product.BackorderModeId; - expando.AllowBackInStockSubscriptions = product.AllowBackInStockSubscriptions; - expando.OrderMinimumQuantity = product.OrderMinimumQuantity; - expando.OrderMaximumQuantity = product.OrderMaximumQuantity; - expando.AllowedQuantities = product.AllowedQuantities; - expando.DisableBuyButton = product.DisableBuyButton; - expando.DisableWishlistButton = product.DisableWishlistButton; - expando.AvailableForPreOrder = product.AvailableForPreOrder; - expando.CallForPrice = product.CallForPrice; - expando.Price = product.Price; - expando.OldPrice = product.OldPrice; - expando.ProductCost = product.ProductCost; - expando.SpecialPrice = product.SpecialPrice; - expando.SpecialPriceStartDateTimeUtc = product.SpecialPriceStartDateTimeUtc; - expando.SpecialPriceEndDateTimeUtc = product.SpecialPriceEndDateTimeUtc; - expando.CustomerEntersPrice = product.CustomerEntersPrice; - expando.MinimumCustomerEnteredPrice = product.MinimumCustomerEnteredPrice; - expando.MaximumCustomerEnteredPrice = product.MaximumCustomerEnteredPrice; - expando.HasTierPrices = product.HasTierPrices; - expando.HasDiscountsApplied = product.HasDiscountsApplied; - expando.Weight = product.Weight; - expando.Length = product.Length; - expando.Width = product.Width; - expando.Height = product.Height; - expando.AvailableStartDateTimeUtc = product.AvailableStartDateTimeUtc; - expando.AvailableEndDateTimeUtc = product.AvailableEndDateTimeUtc; - expando.BasePriceEnabled = product.BasePriceEnabled; - expando.BasePriceMeasureUnit = product.BasePriceMeasureUnit; - expando.BasePriceAmount = product.BasePriceAmount; - expando.BasePriceBaseAmount = product.BasePriceBaseAmount; - expando.BasePriceHasValue = product.BasePriceHasValue; - expando.VisibleIndividually = product.VisibleIndividually; - expando.DisplayOrder = product.DisplayOrder; - expando.BundleTitleText = product.GetLocalized(x => x.BundleTitleText, languageId, true, false); - expando.BundlePerItemPricing = product.BundlePerItemPricing; - expando.BundlePerItemShipping = product.BundlePerItemShipping; - expando.BundlePerItemShoppingCart = product.BundlePerItemShoppingCart; - expando.LowestAttributeCombinationPrice = product.LowestAttributeCombinationPrice; - expando.IsEsd = product.IsEsd; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this ProductTag productTag, int languageId) - { - if (productTag == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = productTag; - - expando.Id = productTag.Id; - expando.Name = productTag.GetLocalized(x => x.Name, languageId, true, false); - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this Order order, int languageId, ILocalizationService localization) - { - if (order == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = order; - - expando.Id = order.Id; - expando.OrderNumber = order.GetOrderNumber(); - expando.OrderGuid = order.OrderGuid; - expando.StoreId = order.StoreId; - expando.CustomerId = order.CustomerId; - expando.BillingAddressId = order.BillingAddressId; - expando.ShippingAddressId = order.ShippingAddressId; - expando.OrderStatusId = order.OrderStatusId; - expando.ShippingStatusId = order.ShippingStatusId; - expando.PaymentStatusId = order.PaymentStatusId; - expando.PaymentMethodSystemName = order.PaymentMethodSystemName; - expando.CustomerCurrencyCode = order.CustomerCurrencyCode; - expando.CurrencyRate = order.CurrencyRate; - expando.CustomerTaxDisplayTypeId = order.CustomerTaxDisplayTypeId; - expando.VatNumber = order.VatNumber; - expando.OrderSubtotalInclTax = order.OrderSubtotalInclTax; - expando.OrderSubtotalExclTax = order.OrderSubtotalExclTax; - expando.OrderSubTotalDiscountInclTax = order.OrderSubTotalDiscountInclTax; - expando.OrderSubTotalDiscountExclTax = order.OrderSubTotalDiscountExclTax; - expando.OrderShippingInclTax = order.OrderShippingInclTax; - expando.OrderShippingExclTax = order.OrderShippingExclTax; - expando.OrderShippingTaxRate = order.OrderShippingTaxRate; - expando.PaymentMethodAdditionalFeeInclTax = order.PaymentMethodAdditionalFeeInclTax; - expando.PaymentMethodAdditionalFeeExclTax = order.PaymentMethodAdditionalFeeExclTax; - expando.PaymentMethodAdditionalFeeTaxRate = order.PaymentMethodAdditionalFeeTaxRate; - expando.TaxRates = order.TaxRates; - expando.OrderTax = order.OrderTax; - expando.OrderDiscount = order.OrderDiscount; - expando.OrderTotal = order.OrderTotal; - expando.RefundedAmount = order.RefundedAmount; - expando.RewardPointsWereAdded = order.RewardPointsWereAdded; - expando.CheckoutAttributeDescription = order.CheckoutAttributeDescription; - expando.CheckoutAttributesXml = order.CheckoutAttributesXml; - expando.CustomerLanguageId = order.CustomerLanguageId; - expando.AffiliateId = order.AffiliateId; - expando.CustomerIp = order.CustomerIp; - expando.AllowStoringCreditCardNumber = order.AllowStoringCreditCardNumber; - expando.CardType = order.CardType; - expando.CardName = order.CardName; - expando.CardNumber = order.CardNumber; - expando.MaskedCreditCardNumber = order.MaskedCreditCardNumber; - expando.CardCvv2 = order.CardCvv2; - expando.CardExpirationMonth = order.CardExpirationMonth; - expando.CardExpirationYear = order.CardExpirationYear; - expando.AllowStoringDirectDebit = order.AllowStoringDirectDebit; - expando.DirectDebitAccountHolder = order.DirectDebitAccountHolder; - expando.DirectDebitAccountNumber = order.DirectDebitAccountNumber; - expando.DirectDebitBankCode = order.DirectDebitBankCode; - expando.DirectDebitBankName = order.DirectDebitBankName; - expando.DirectDebitBIC = order.DirectDebitBIC; - expando.DirectDebitCountry = order.DirectDebitCountry; - expando.DirectDebitIban = order.DirectDebitIban; - expando.CustomerOrderComment = order.CustomerOrderComment; - expando.AuthorizationTransactionId = order.AuthorizationTransactionId; - expando.AuthorizationTransactionCode = order.AuthorizationTransactionCode; - expando.AuthorizationTransactionResult = order.AuthorizationTransactionResult; - expando.CaptureTransactionId = order.CaptureTransactionId; - expando.CaptureTransactionResult = order.CaptureTransactionResult; - expando.SubscriptionTransactionId = order.SubscriptionTransactionId; - expando.PurchaseOrderNumber = order.PurchaseOrderNumber; - expando.PaidDateUtc = order.PaidDateUtc; - expando.ShippingMethod = order.ShippingMethod; - expando.ShippingRateComputationMethodSystemName = order.ShippingRateComputationMethodSystemName; - expando.Deleted = order.Deleted; - expando.CreatedOnUtc = order.CreatedOnUtc; - expando.UpdatedOnUtc = order.UpdatedOnUtc; - expando.RewardPointsRemaining = order.RewardPointsRemaining; - expando.HasNewPaymentNotification = order.HasNewPaymentNotification; - expando.OrderStatus = order.OrderStatus.GetLocalizedEnum(localization, languageId); - expando.PaymentStatus = order.PaymentStatus.GetLocalizedEnum(localization, languageId); - expando.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(localization, languageId); - - expando.Customer = null; - expando.BillingAddress = null; - expando.ShippingAddress = null; - expando.Store = null; - expando.Shipments = null; - expando.RedeemedRewardPointsEntry = order.RedeemedRewardPointsEntry.ToExpando(); - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this OrderItem orderItem, int languageId) - { - if (orderItem == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = orderItem; - - expando.Id = orderItem.Id; - expando.OrderItemGuid = orderItem.OrderItemGuid; - expando.OrderId = orderItem.OrderId; - expando.ProductId = orderItem.ProductId; - expando.Quantity = orderItem.Quantity; - expando.UnitPriceInclTax = orderItem.UnitPriceInclTax; - expando.UnitPriceExclTax = orderItem.UnitPriceExclTax; - expando.PriceInclTax = orderItem.PriceInclTax; - expando.PriceExclTax = orderItem.PriceExclTax; - expando.TaxRate = orderItem.TaxRate; - expando.DiscountAmountInclTax = orderItem.DiscountAmountInclTax; - expando.DiscountAmountExclTax = orderItem.DiscountAmountExclTax; - expando.AttributeDescription = orderItem.AttributeDescription; - expando.AttributesXml = orderItem.AttributesXml; - expando.DownloadCount = orderItem.DownloadCount; - expando.IsDownloadActivated = orderItem.IsDownloadActivated; - expando.LicenseDownloadId = orderItem.LicenseDownloadId; - expando.ItemWeight = orderItem.ItemWeight; - expando.BundleData = orderItem.BundleData; - expando.ProductCost = orderItem.ProductCost; - - expando.Product = orderItem.Product.ToExpando(languageId); - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this Shipment shipment) - { - if (shipment == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = shipment; - - expando.Id = shipment.Id; - expando.OrderId = shipment.OrderId; - expando.TrackingNumber = shipment.TrackingNumber; - expando.TotalWeight = shipment.TotalWeight; - expando.ShippedDateUtc = shipment.ShippedDateUtc; - expando.DeliveryDateUtc = shipment.DeliveryDateUtc; - expando.CreatedOnUtc = shipment.CreatedOnUtc; - - expando.ShipmentItems = null; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this ShipmentItem shipmentItem) - { - if (shipmentItem == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = shipmentItem; - - expando.Id = shipmentItem.Id; - expando.ShipmentId = shipmentItem.ShipmentId; - expando.OrderItemId = shipmentItem.OrderItemId; - expando.Quantity = shipmentItem.Quantity; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this Discount discount) - { - if (discount == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = discount; - - expando.Id = discount.Id; - expando.Name = discount.Name; - expando.DiscountTypeId = discount.DiscountTypeId; - expando.UsePercentage = discount.UsePercentage; - expando.DiscountPercentage = discount.DiscountPercentage; - expando.DiscountAmount = discount.DiscountAmount; - expando.StartDateUtc = discount.StartDateUtc; - expando.EndDateUtc = discount.EndDateUtc; - expando.RequiresCouponCode = discount.RequiresCouponCode; - expando.CouponCode = discount.CouponCode; - expando.DiscountLimitationId = discount.DiscountLimitationId; - expando.LimitationTimes = discount.LimitationTimes; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this TierPrice tierPrice) - { - if (tierPrice == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = tierPrice; - - expando.Id = tierPrice.Id; - expando.ProductId = tierPrice.ProductId; - expando.StoreId = tierPrice.StoreId; - expando.CustomerRoleId = tierPrice.CustomerRoleId; - expando.Quantity = tierPrice.Quantity; - expando.Price = tierPrice.Price; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this ProductSpecificationAttribute psa, int languageId) - { - if (psa == null) - return null; - - dynamic expando = new ExpandoObject(); - expando._Entity = psa; - - expando.Id = psa.Id; - expando.ProductId = psa.ProductId; - expando.SpecificationAttributeOptionId = psa.SpecificationAttributeOptionId; - expando.AllowFiltering = psa.AllowFiltering; - expando.ShowOnProductPage = psa.ShowOnProductPage; - expando.DisplayOrder = psa.DisplayOrder; - - var option = psa.SpecificationAttributeOption; - - expando.SpecificationAttributeOption._Entity = option; - expando.SpecificationAttributeOption.Id = option.Id; - expando.SpecificationAttributeOption.SpecificationAttributeId = option.SpecificationAttributeId; - expando.SpecificationAttributeOption.Name = option.GetLocalized(x => x.Name, languageId, true, false); - expando.SpecificationAttributeOption.DisplayOrder = option.DisplayOrder; - - expando.SpecificationAttributeOption.SpecificationAttribute._Entity = option.SpecificationAttribute; - expando.SpecificationAttributeOption.SpecificationAttribute.Id = option.SpecificationAttribute.Id; - expando.SpecificationAttributeOption.SpecificationAttribute.Name = option.SpecificationAttribute.GetLocalized(x => x.Name, languageId, true, false); - expando.SpecificationAttributeOption.SpecificationAttribute.DisplayOrder = option.SpecificationAttribute.DisplayOrder; - - return expando as ExpandoObject; - } - - public static ExpandoObject ToExpando(this ProductBundleItem bundleItem, string productName, int languageId) - { - if (bundleItem == null) - return null; - - var name = bundleItem.GetLocalized(x => x.Name, languageId, true, false); - if (name.IsEmpty()) - name = productName; - - dynamic expando = new ExpandoObject(); - expando._Entity = bundleItem; - - expando.Id = bundleItem.Id; - expando.ProductId = bundleItem.ProductId; - expando.BundleProductId = bundleItem.BundleProductId; - expando.Quantity = bundleItem.Quantity; - expando.Discount = bundleItem.Discount; - expando.DiscountPercentage = bundleItem.DiscountPercentage; - expando.Name = name; - expando.ShortDescription = bundleItem.GetLocalized(x => x.ShortDescription, languageId, true, false); - expando.FilterAttributes = bundleItem.FilterAttributes; - expando.HideThumbnail = bundleItem.HideThumbnail; - expando.Visible = bundleItem.Visible; - expando.Published = bundleItem.Published; - expando.DisplayOrder = bundleItem.DisplayOrder; - expando.CreatedOnUtc = bundleItem.CreatedOnUtc; - expando.UpdatedOnUtc = bundleItem.UpdatedOnUtc; - - return expando as ExpandoObject; - } - } -} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 7ffc9d8a2b..ea85dd3375 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -1,32 +1,40 @@ using System; using System.Collections.Generic; -using System.Data.Entity; +using System.Diagnostics; using System.Dynamic; using System.IO; using System.IO.Compression; using System.Linq; +using System.Linq.Expressions; using System.Net; using System.Net.Http; +using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; using System.Web.Mvc; using Autofac; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Async; using SmartStore.Core.Data; using SmartStore.Core.Domain; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Domain.Discounts; +using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Messages; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Email; using SmartStore.Core.Html; using SmartStore.Core.IO; -using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Catalog; @@ -38,6 +46,7 @@ using SmartStore.Services.Media; using SmartStore.Services.Messages; using SmartStore.Services.Orders; +using SmartStore.Services.Seo; using SmartStore.Services.Shipping; using SmartStore.Services.Tasks; using SmartStore.Services.Tax; @@ -78,6 +87,7 @@ public class ExportProfileTask : ITask private ILanguageService _languageService; private IShipmentService _shipmentService; private IProductTemplateService _productTemplateService; + private ILocalizedEntityService _localizedEntityService; private void InitDependencies(TaskExecutionContext context) { @@ -109,12 +119,34 @@ private void InitDependencies(TaskExecutionContext context) _languageService = context.Resolve(); _shipmentService = context.Resolve(); _productTemplateService = context.Resolve(); + _localizedEntityService = context.Resolve(); } #endregion #region Utilities + private TPropType GetLocalized(Multimap map, T entity, Expression> keySelector, int languageId) + where T : BaseEntity, ILocalizedEntity + { + if (languageId > 0 && map.ContainsKey(entity.Id)) + { + var member = keySelector.Body as MemberExpression; + var propInfo = member.Member as PropertyInfo; + + var property = map[entity.Id].FirstOrDefault(x => x.LocaleKey == propInfo.Name && x.LanguageId == languageId); + if (property != null) + { + Debug.Assert(property.LocaleKeyGroup.IsCaseInsensitiveEqual(entity.GetType().Name), "Wrong localized map for entity " + entity.GetType().Name); + + return property.LocaleValue.Convert(); + } + } + + var localizer = keySelector.Compile(); + return localizer(entity); + } + private ProductSearchContext GetProductSearchContext(ExportProfileTaskContext ctx, int pageIndex, int pageSize) { var searchContext = new ProductSearchContext @@ -293,12 +325,12 @@ private decimal CalculatePrice(ExportProfileTaskContext ctx, Product product, bo private void GetDeliveryTimeAndQuantityUnit(ExportProfileTaskContext ctx, dynamic expando, int? deliveryTimeId, int? quantityUnitId) { if (deliveryTimeId.HasValue && ctx.DeliveryTimes.ContainsKey(deliveryTimeId.Value)) - expando.DeliveryTime = ctx.DeliveryTimes[deliveryTimeId.Value].ToExpando(ctx.Projection.LanguageId ?? 0); + expando.DeliveryTime = ToExpando(ctx, ctx.DeliveryTimes[deliveryTimeId.Value]); else expando.DeliveryTime = null; if (quantityUnitId.HasValue && ctx.QuantityUnits.ContainsKey(quantityUnitId.Value)) - expando.QuantityUnit = ctx.QuantityUnits[quantityUnitId.Value].ToExpando(ctx.Projection.LanguageId ?? 0); + expando.QuantityUnit = ToExpando(ctx, ctx.QuantityUnits[quantityUnitId.Value]); else expando.QuantityUnit = null; } @@ -539,6 +571,717 @@ private async void DeployFtp(ExportProfileTaskContext ctx, ExportDeployment depl #endregion + #region Entity to expando + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Currency currency) + { + if (currency == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = currency; + + expando.Id = currency.Id; + expando.Name = currency.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + expando.CurrencyCode = currency.CurrencyCode; + expando.Rate = currency.Rate; + expando.DisplayLocale = currency.DisplayLocale; + expando.CustomFormatting = currency.CustomFormatting; + expando.LimitedToStores = currency.LimitedToStores; + expando.Published = currency.Published; + expando.DisplayOrder = currency.DisplayOrder; + expando.CreatedOnUtc = currency.CreatedOnUtc; + expando.UpdatedOnUtc = currency.UpdatedOnUtc; + expando.DomainEndings = currency.DomainEndings; + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Language language) + { + if (language == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = language; + + expando.Id = language.Id; + expando.Name = language.Name; + expando.LanguageCulture = language.LanguageCulture; + expando.UniqueSeoCode = language.UniqueSeoCode; + expando.FlagImageFileName = language.FlagImageFileName; + expando.Rtl = language.Rtl; + expando.LimitedToStores = language.LimitedToStores; + expando.Published = language.Published; + expando.DisplayOrder = language.DisplayOrder; + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Country country) + { + if (country == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = country; + + expando.Id = country.Id; + expando.Name = country.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + expando.AllowsBilling = country.AllowsBilling; + expando.AllowsShipping = country.AllowsShipping; + expando.TwoLetterIsoCode = country.TwoLetterIsoCode; + expando.ThreeLetterIsoCode = country.ThreeLetterIsoCode; + expando.NumericIsoCode = country.NumericIsoCode; + expando.SubjectToVat = country.SubjectToVat; + expando.Published = country.Published; + expando.DisplayOrder = country.DisplayOrder; + expando.LimitedToStores = country.LimitedToStores; + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Address address) + { + if (address == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = address; + + expando.Id = address.Id; + expando.FirstName = address.FirstName; + expando.LastName = address.LastName; + expando.Email = address.Email; + expando.Company = address.Company; + expando.CountryId = address.CountryId; + expando.StateProvinceId = address.StateProvinceId; + expando.City = address.City; + expando.Address1 = address.Address1; + expando.Address2 = address.Address2; + expando.ZipPostalCode = address.ZipPostalCode; + expando.PhoneNumber = address.PhoneNumber; + expando.FaxNumber = address.FaxNumber; + expando.CreatedOnUtc = address.CreatedOnUtc; + + expando.Country = ToExpando(ctx, address.Country); + + dynamic sp = new ExpandoObject(); + sp._Entity = address.StateProvince; + sp.Id = address.StateProvince.Id; + sp.CountryId = address.StateProvince.CountryId; + sp.Name = address.StateProvince.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + sp.Abbreviation = address.StateProvince.Abbreviation; + sp.Published = address.StateProvince.Published; + sp.DisplayOrder = address.StateProvince.DisplayOrder; + + expando.StateProvince = sp as ExpandoObject; + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, RewardPointsHistory points) + { + if (points == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = points; + + expando.Id = points.Id; + expando.CustomerId = points.CustomerId; + expando.Points = points.Points; + expando.PointsBalance = points.PointsBalance; + expando.UsedAmount = points.UsedAmount; + expando.Message = points.Message; + expando.CreatedOnUtc = points.CreatedOnUtc; + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Customer customer) + { + if (customer == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = customer; + + expando.Id = customer.Id; + expando.CustomerGuid = customer.CustomerGuid; + expando.Username = customer.Username; + expando.Email = customer.Email; + //Password... we do not provide that data + expando.PasswordFormatId = customer.PasswordFormatId; + expando.AdminComment = customer.AdminComment; + expando.IsTaxExempt = customer.IsTaxExempt; + expando.AffiliateId = customer.AffiliateId; + expando.Active = customer.Active; + expando.Deleted = customer.Deleted; + expando.IsSystemAccount = customer.IsSystemAccount; + expando.SystemName = customer.SystemName; + expando.LastIpAddress = customer.LastIpAddress; + expando.CreatedOnUtc = customer.CreatedOnUtc; + expando.LastLoginDateUtc = customer.LastLoginDateUtc; + expando.LastActivityDateUtc = customer.LastActivityDateUtc; + + expando.BillingAddress = ToExpando(ctx, customer.BillingAddress); + expando.ShippingAddress = ToExpando(ctx, customer.ShippingAddress); + + expando.RewardPointsHistory = null; + expando._RewardPointsBalance = 0; + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Store store) + { + if (store == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = store; + + expando.Id = store.Id; + expando.Name = store.Name; + expando.Url = store.Url; + expando.SslEnabled = store.SslEnabled; + expando.SecureUrl = store.SecureUrl; + expando.Hosts = store.Hosts; + expando.LogoPictureId = store.LogoPictureId; + expando.DisplayOrder = store.DisplayOrder; + expando.HtmlBodyId = store.HtmlBodyId; + expando.ContentDeliveryNetwork = store.ContentDeliveryNetwork; + expando.PrimaryStoreCurrencyId = store.PrimaryStoreCurrencyId; + expando.PrimaryExchangeRateCurrencyId = store.PrimaryExchangeRateCurrencyId; + + expando.PrimaryStoreCurrency = ToExpando(ctx, store.PrimaryStoreCurrency); + expando.PrimaryExchangeRateCurrency = ToExpando(ctx, store.PrimaryExchangeRateCurrency); + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, DeliveryTime deliveryTime) + { + if (deliveryTime == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = deliveryTime; + + expando.Id = deliveryTime.Id; + expando.Name = GetLocalized(ctx.Localized.DeliveryTimes, deliveryTime, x => x.Name, ctx.Projection.LanguageId ?? 0); + expando.DisplayLocale = deliveryTime.DisplayLocale; + expando.ColorHexValue = deliveryTime.ColorHexValue; + expando.DisplayOrder = deliveryTime.DisplayOrder; + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, QuantityUnit quantityUnit) + { + if (quantityUnit == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = quantityUnit; + + expando.Id = quantityUnit.Id; + expando.Name = GetLocalized(ctx.Localized.QuantityUnits, quantityUnit, x => x.Name, ctx.Projection.LanguageId ?? 0); + expando.Description = GetLocalized(ctx.Localized.QuantityUnits, quantityUnit, x => x.Description, ctx.Projection.LanguageId ?? 0); + expando.DisplayLocale = quantityUnit.DisplayLocale; + expando.DisplayOrder = quantityUnit.DisplayOrder; + expando.IsDefault = quantityUnit.IsDefault; + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Picture picture, int thumbPictureSize, int detailsPictureSize) + { + if (picture == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = picture; + + expando.Id = picture.Id; + expando.SeoFilename = picture.SeoFilename; + expando.MimeType = picture.MimeType; + + expando._ThumbImageUrl = _pictureService.GetPictureUrl(picture, thumbPictureSize, false, ctx.Store.Url); + expando._ImageUrl = _pictureService.GetPictureUrl(picture, detailsPictureSize, false, ctx.Store.Url); + expando._FullSizeImageUrl = _pictureService.GetPictureUrl(picture, 0, false, ctx.Store.Url); + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, ProductVariantAttribute pva) + { + if (pva == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = pva; + + expando.Id = pva.Id; + expando.TextPrompt = pva.TextPrompt; + expando.IsRequired = pva.IsRequired; + expando.AttributeControlTypeId = pva.AttributeControlTypeId; + expando.DisplayOrder = pva.DisplayOrder; + + dynamic attribute = new ExpandoObject(); + attribute._Entity = pva.ProductAttribute; + attribute.Id = pva.ProductAttribute.Id; + attribute.Alias = pva.ProductAttribute.Alias; + attribute.Name = GetLocalized(ctx.Localized.ProductAttributes, pva.ProductAttribute, x => x.Name, ctx.Projection.LanguageId ?? 0); + attribute.Description = GetLocalized(ctx.Localized.ProductAttributes, pva.ProductAttribute, x => x.Description, ctx.Projection.LanguageId ?? 0); + + attribute.Values = pva.ProductVariantAttributeValues + .OrderBy(x => x.DisplayOrder) + .Select(x => + { + dynamic value = new ExpandoObject(); + value._Entity = x; + value.Id = x.Id; + value.Alias = x.Alias; + value.Name = x.GetLocalized(y => y.Name, ctx.Projection.LanguageId ?? 0, true, false); + value.ColorSquaresRgb = x.ColorSquaresRgb; + value.PriceAdjustment = x.PriceAdjustment; + value.WeightAdjustment = x.WeightAdjustment; + value.IsPreSelected = x.IsPreSelected; + value.DisplayOrder = x.DisplayOrder; + value.ValueTypeId = x.ValueTypeId; + value.LinkedProductId = x.LinkedProductId; + value.Quantity = x.Quantity; + + return value as ExpandoObject; + }) + .ToList(); + + expando.Attribute = attribute as ExpandoObject; + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, ProductVariantAttributeCombination pvac) + { + if (pvac == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = pvac; + + expando.Id = pvac.Id; + expando.StockQuantity = pvac.StockQuantity; + expando.AllowOutOfStockOrders = pvac.AllowOutOfStockOrders; + expando.AttributesXml = pvac.AttributesXml; + expando.Sku = pvac.Sku; + expando.Gtin = pvac.Gtin; + expando.ManufacturerPartNumber = pvac.ManufacturerPartNumber; + expando.Price = pvac.Price; + expando.Length = pvac.Length; + expando.Width = pvac.Width; + expando.Height = pvac.Height; + expando.BasePriceAmount = pvac.BasePriceAmount; + expando.BasePriceBaseAmount = pvac.BasePriceBaseAmount; + expando.AssignedPictureIds = pvac.AssignedPictureIds; + expando.DeliveryTimeId = pvac.DeliveryTimeId; + expando.IsActive = pvac.IsActive; + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Manufacturer manufacturer) + { + if (manufacturer == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = manufacturer; + + expando.Id = manufacturer.Id; + expando.Name = manufacturer.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + expando.SeName = manufacturer.GetSeName(ctx.Projection.LanguageId ?? 0, true, false); + expando.Description = manufacturer.GetLocalized(x => x.Description, ctx.Projection.LanguageId ?? 0, true, false); + expando.ManufacturerTemplateId = manufacturer.ManufacturerTemplateId; + expando.MetaKeywords = manufacturer.GetLocalized(x => x.MetaKeywords, ctx.Projection.LanguageId ?? 0, true, false); + expando.MetaDescription = manufacturer.GetLocalized(x => x.MetaDescription, ctx.Projection.LanguageId ?? 0, true, false); + expando.MetaTitle = manufacturer.GetLocalized(x => x.MetaTitle, ctx.Projection.LanguageId ?? 0, true, false); + expando.PictureId = manufacturer.PictureId; + expando.PageSize = manufacturer.PageSize; + expando.AllowCustomersToSelectPageSize = manufacturer.AllowCustomersToSelectPageSize; + expando.PageSizeOptions = manufacturer.PageSizeOptions; + expando.PriceRanges = manufacturer.PriceRanges; + expando.Published = manufacturer.Published; + expando.Deleted = manufacturer.Deleted; + expando.DisplayOrder = manufacturer.DisplayOrder; + expando.CreatedOnUtc = manufacturer.CreatedOnUtc; + expando.UpdatedOnUtc = manufacturer.UpdatedOnUtc; + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Category category) + { + if (category == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = category; + + expando.Id = category.Id; + expando.Name = category.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + expando.FullName = category.GetLocalized(x => x.FullName, ctx.Projection.LanguageId ?? 0, true, false); + expando.Description = category.GetLocalized(x => x.Description, ctx.Projection.LanguageId ?? 0, true, false); + expando.BottomDescription = category.GetLocalized(x => x.BottomDescription, ctx.Projection.LanguageId ?? 0, true, false); + expando.CategoryTemplateId = category.CategoryTemplateId; + expando.MetaKeywords = category.GetLocalized(x => x.MetaKeywords, ctx.Projection.LanguageId ?? 0, true, false); + expando.MetaDescription = category.GetLocalized(x => x.MetaDescription, ctx.Projection.LanguageId ?? 0, true, false); + expando.MetaTitle = category.GetLocalized(x => x.MetaTitle, ctx.Projection.LanguageId ?? 0, true, false); + expando.SeName = category.GetSeName(ctx.Projection.LanguageId ?? 0, true, false); + expando.ParentCategoryId = category.ParentCategoryId; + expando.PictureId = category.PictureId; + expando.PageSize = category.PageSize; + expando.AllowCustomersToSelectPageSize = category.AllowCustomersToSelectPageSize; + expando.PageSizeOptions = category.PageSizeOptions; + expando.PriceRanges = category.PriceRanges; + expando.ShowOnHomePage = category.ShowOnHomePage; + expando.HasDiscountsApplied = category.HasDiscountsApplied; + expando.Published = category.Published; + expando.Deleted = category.Deleted; + expando.DisplayOrder = category.DisplayOrder; + expando.CreatedOnUtc = category.CreatedOnUtc; + expando.UpdatedOnUtc = category.UpdatedOnUtc; + expando.SubjectToAcl = category.SubjectToAcl; + expando.LimitedToStores = category.LimitedToStores; + expando.Alias = category.Alias; + expando.DefaultViewMode = category.DefaultViewMode; + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) + { + if (product == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = product; + + expando.Id = product.Id; + expando.Name = product.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + expando.SeName = product.GetSeName(ctx.Projection.LanguageId ?? 0, true, false); + expando.ShortDescription = product.GetLocalized(x => x.ShortDescription, ctx.Projection.LanguageId ?? 0, true, false); + expando.FullDescription = product.GetLocalized(x => x.FullDescription, ctx.Projection.LanguageId ?? 0, true, false); + expando.AdminComment = product.AdminComment; + expando.ProductTemplateId = product.ProductTemplateId; + expando.ShowOnHomePage = product.ShowOnHomePage; + expando.HomePageDisplayOrder = product.HomePageDisplayOrder; + expando.MetaKeywords = product.GetLocalized(x => x.MetaKeywords, ctx.Projection.LanguageId ?? 0, true, false); + expando.MetaDescription = product.GetLocalized(x => x.MetaDescription, ctx.Projection.LanguageId ?? 0, true, false); + expando.MetaTitle = product.GetLocalized(x => x.MetaTitle, ctx.Projection.LanguageId ?? 0, true, false); + expando.AllowCustomerReviews = product.AllowCustomerReviews; + expando.ApprovedRatingSum = product.ApprovedRatingSum; + expando.NotApprovedRatingSum = product.NotApprovedRatingSum; + expando.ApprovedTotalReviews = product.ApprovedTotalReviews; + expando.NotApprovedTotalReviews = product.NotApprovedTotalReviews; + expando.Published = product.Published; + expando.CreatedOnUtc = product.CreatedOnUtc; + expando.UpdatedOnUtc = product.UpdatedOnUtc; + expando.SubjectToAcl = product.SubjectToAcl; + expando.LimitedToStores = product.LimitedToStores; + expando.ProductTypeId = product.ProductTypeId; + expando.ParentGroupedProductId = product.ParentGroupedProductId; + expando.Sku = product.Sku; + expando.ManufacturerPartNumber = product.ManufacturerPartNumber; + expando.Gtin = product.Gtin; + expando.IsGiftCard = product.IsGiftCard; + expando.GiftCardTypeId = product.GiftCardTypeId; + expando.RequireOtherProducts = product.RequireOtherProducts; + expando.RequiredProductIds = product.RequiredProductIds; + expando.AutomaticallyAddRequiredProducts = product.AutomaticallyAddRequiredProducts; + expando.IsDownload = product.IsDownload; + expando.DownloadId = product.DownloadId; + expando.UnlimitedDownloads = product.UnlimitedDownloads; + expando.MaxNumberOfDownloads = product.MaxNumberOfDownloads; + expando.DownloadExpirationDays = product.DownloadExpirationDays; + expando.DownloadActivationTypeId = product.DownloadActivationTypeId; + expando.HasSampleDownload = product.HasSampleDownload; + expando.SampleDownloadId = product.SampleDownloadId; + expando.HasUserAgreement = product.HasUserAgreement; + expando.UserAgreementText = product.UserAgreementText; + expando.IsRecurring = product.IsRecurring; + expando.RecurringCycleLength = product.RecurringCycleLength; + expando.RecurringCyclePeriodId = product.RecurringCyclePeriodId; + expando.RecurringTotalCycles = product.RecurringTotalCycles; + expando.IsShipEnabled = product.IsShipEnabled; + expando.IsFreeShipping = product.IsFreeShipping; + expando.AdditionalShippingCharge = product.AdditionalShippingCharge; + expando.IsTaxExempt = product.IsTaxExempt; + expando.TaxCategoryId = product.TaxCategoryId; + expando.ManageInventoryMethodId = product.ManageInventoryMethodId; + expando.StockQuantity = product.StockQuantity; + expando.DisplayStockAvailability = product.DisplayStockAvailability; + expando.DisplayStockQuantity = product.DisplayStockQuantity; + expando.MinStockQuantity = product.MinStockQuantity; + expando.LowStockActivityId = product.LowStockActivityId; + expando.NotifyAdminForQuantityBelow = product.NotifyAdminForQuantityBelow; + expando.BackorderModeId = product.BackorderModeId; + expando.AllowBackInStockSubscriptions = product.AllowBackInStockSubscriptions; + expando.OrderMinimumQuantity = product.OrderMinimumQuantity; + expando.OrderMaximumQuantity = product.OrderMaximumQuantity; + expando.AllowedQuantities = product.AllowedQuantities; + expando.DisableBuyButton = product.DisableBuyButton; + expando.DisableWishlistButton = product.DisableWishlistButton; + expando.AvailableForPreOrder = product.AvailableForPreOrder; + expando.CallForPrice = product.CallForPrice; + expando.Price = product.Price; + expando.OldPrice = product.OldPrice; + expando.ProductCost = product.ProductCost; + expando.SpecialPrice = product.SpecialPrice; + expando.SpecialPriceStartDateTimeUtc = product.SpecialPriceStartDateTimeUtc; + expando.SpecialPriceEndDateTimeUtc = product.SpecialPriceEndDateTimeUtc; + expando.CustomerEntersPrice = product.CustomerEntersPrice; + expando.MinimumCustomerEnteredPrice = product.MinimumCustomerEnteredPrice; + expando.MaximumCustomerEnteredPrice = product.MaximumCustomerEnteredPrice; + expando.HasTierPrices = product.HasTierPrices; + expando.HasDiscountsApplied = product.HasDiscountsApplied; + expando.Weight = product.Weight; + expando.Length = product.Length; + expando.Width = product.Width; + expando.Height = product.Height; + expando.AvailableStartDateTimeUtc = product.AvailableStartDateTimeUtc; + expando.AvailableEndDateTimeUtc = product.AvailableEndDateTimeUtc; + expando.BasePriceEnabled = product.BasePriceEnabled; + expando.BasePriceMeasureUnit = product.BasePriceMeasureUnit; + expando.BasePriceAmount = product.BasePriceAmount; + expando.BasePriceBaseAmount = product.BasePriceBaseAmount; + expando.BasePriceHasValue = product.BasePriceHasValue; + expando.VisibleIndividually = product.VisibleIndividually; + expando.DisplayOrder = product.DisplayOrder; + expando.BundleTitleText = product.GetLocalized(x => x.BundleTitleText, ctx.Projection.LanguageId ?? 0, true, false); + expando.BundlePerItemPricing = product.BundlePerItemPricing; + expando.BundlePerItemShipping = product.BundlePerItemShipping; + expando.BundlePerItemShoppingCart = product.BundlePerItemShoppingCart; + expando.LowestAttributeCombinationPrice = product.LowestAttributeCombinationPrice; + expando.IsEsd = product.IsEsd; + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Order order) + { + if (order == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = order; + + expando.Id = order.Id; + expando.OrderNumber = order.GetOrderNumber(); + expando.OrderGuid = order.OrderGuid; + expando.StoreId = order.StoreId; + expando.CustomerId = order.CustomerId; + expando.BillingAddressId = order.BillingAddressId; + expando.ShippingAddressId = order.ShippingAddressId; + expando.OrderStatusId = order.OrderStatusId; + expando.ShippingStatusId = order.ShippingStatusId; + expando.PaymentStatusId = order.PaymentStatusId; + expando.PaymentMethodSystemName = order.PaymentMethodSystemName; + expando.CustomerCurrencyCode = order.CustomerCurrencyCode; + expando.CurrencyRate = order.CurrencyRate; + expando.CustomerTaxDisplayTypeId = order.CustomerTaxDisplayTypeId; + expando.VatNumber = order.VatNumber; + expando.OrderSubtotalInclTax = order.OrderSubtotalInclTax; + expando.OrderSubtotalExclTax = order.OrderSubtotalExclTax; + expando.OrderSubTotalDiscountInclTax = order.OrderSubTotalDiscountInclTax; + expando.OrderSubTotalDiscountExclTax = order.OrderSubTotalDiscountExclTax; + expando.OrderShippingInclTax = order.OrderShippingInclTax; + expando.OrderShippingExclTax = order.OrderShippingExclTax; + expando.OrderShippingTaxRate = order.OrderShippingTaxRate; + expando.PaymentMethodAdditionalFeeInclTax = order.PaymentMethodAdditionalFeeInclTax; + expando.PaymentMethodAdditionalFeeExclTax = order.PaymentMethodAdditionalFeeExclTax; + expando.PaymentMethodAdditionalFeeTaxRate = order.PaymentMethodAdditionalFeeTaxRate; + expando.TaxRates = order.TaxRates; + expando.OrderTax = order.OrderTax; + expando.OrderDiscount = order.OrderDiscount; + expando.OrderTotal = order.OrderTotal; + expando.RefundedAmount = order.RefundedAmount; + expando.RewardPointsWereAdded = order.RewardPointsWereAdded; + expando.CheckoutAttributeDescription = order.CheckoutAttributeDescription; + expando.CheckoutAttributesXml = order.CheckoutAttributesXml; + expando.CustomerLanguageId = order.CustomerLanguageId; + expando.AffiliateId = order.AffiliateId; + expando.CustomerIp = order.CustomerIp; + expando.AllowStoringCreditCardNumber = order.AllowStoringCreditCardNumber; + expando.CardType = order.CardType; + expando.CardName = order.CardName; + expando.CardNumber = order.CardNumber; + expando.MaskedCreditCardNumber = order.MaskedCreditCardNumber; + expando.CardCvv2 = order.CardCvv2; + expando.CardExpirationMonth = order.CardExpirationMonth; + expando.CardExpirationYear = order.CardExpirationYear; + expando.AllowStoringDirectDebit = order.AllowStoringDirectDebit; + expando.DirectDebitAccountHolder = order.DirectDebitAccountHolder; + expando.DirectDebitAccountNumber = order.DirectDebitAccountNumber; + expando.DirectDebitBankCode = order.DirectDebitBankCode; + expando.DirectDebitBankName = order.DirectDebitBankName; + expando.DirectDebitBIC = order.DirectDebitBIC; + expando.DirectDebitCountry = order.DirectDebitCountry; + expando.DirectDebitIban = order.DirectDebitIban; + expando.CustomerOrderComment = order.CustomerOrderComment; + expando.AuthorizationTransactionId = order.AuthorizationTransactionId; + expando.AuthorizationTransactionCode = order.AuthorizationTransactionCode; + expando.AuthorizationTransactionResult = order.AuthorizationTransactionResult; + expando.CaptureTransactionId = order.CaptureTransactionId; + expando.CaptureTransactionResult = order.CaptureTransactionResult; + expando.SubscriptionTransactionId = order.SubscriptionTransactionId; + expando.PurchaseOrderNumber = order.PurchaseOrderNumber; + expando.PaidDateUtc = order.PaidDateUtc; + expando.ShippingMethod = order.ShippingMethod; + expando.ShippingRateComputationMethodSystemName = order.ShippingRateComputationMethodSystemName; + expando.Deleted = order.Deleted; + expando.CreatedOnUtc = order.CreatedOnUtc; + expando.UpdatedOnUtc = order.UpdatedOnUtc; + expando.RewardPointsRemaining = order.RewardPointsRemaining; + expando.HasNewPaymentNotification = order.HasNewPaymentNotification; + expando.OrderStatus = order.OrderStatus.GetLocalizedEnum(_services.Localization, ctx.Projection.LanguageId ?? 0); + expando.PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_services.Localization, ctx.Projection.LanguageId ?? 0); + expando.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_services.Localization, ctx.Projection.LanguageId ?? 0); + + expando.Customer = null; + expando.BillingAddress = null; + expando.ShippingAddress = null; + expando.Store = null; + expando.Shipments = null; + expando.RedeemedRewardPointsEntry = ToExpando(ctx, order.RedeemedRewardPointsEntry); + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, OrderItem orderItem) + { + if (orderItem == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = orderItem; + + expando.Id = orderItem.Id; + expando.OrderItemGuid = orderItem.OrderItemGuid; + expando.OrderId = orderItem.OrderId; + expando.ProductId = orderItem.ProductId; + expando.Quantity = orderItem.Quantity; + expando.UnitPriceInclTax = orderItem.UnitPriceInclTax; + expando.UnitPriceExclTax = orderItem.UnitPriceExclTax; + expando.PriceInclTax = orderItem.PriceInclTax; + expando.PriceExclTax = orderItem.PriceExclTax; + expando.TaxRate = orderItem.TaxRate; + expando.DiscountAmountInclTax = orderItem.DiscountAmountInclTax; + expando.DiscountAmountExclTax = orderItem.DiscountAmountExclTax; + expando.AttributeDescription = orderItem.AttributeDescription; + expando.AttributesXml = orderItem.AttributesXml; + expando.DownloadCount = orderItem.DownloadCount; + expando.IsDownloadActivated = orderItem.IsDownloadActivated; + expando.LicenseDownloadId = orderItem.LicenseDownloadId; + expando.ItemWeight = orderItem.ItemWeight; + expando.BundleData = orderItem.BundleData; + expando.ProductCost = orderItem.ProductCost; + + expando.Product = ToExpando(ctx, orderItem.Product); + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Shipment shipment) + { + if (shipment == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = shipment; + + expando.Id = shipment.Id; + expando.OrderId = shipment.OrderId; + expando.TrackingNumber = shipment.TrackingNumber; + expando.TotalWeight = shipment.TotalWeight; + expando.ShippedDateUtc = shipment.ShippedDateUtc; + expando.DeliveryDateUtc = shipment.DeliveryDateUtc; + expando.CreatedOnUtc = shipment.CreatedOnUtc; + + expando.ShipmentItems = shipment.ShipmentItems + .Select(x => + { + dynamic exp = new ExpandoObject(); + exp._Entity = x; + exp.Id = x.Id; + exp.ShipmentId = x.ShipmentId; + exp.OrderItemId = x.OrderItemId; + exp.Quantity = x.Quantity; + return exp as ExpandoObject; + }) + .ToList(); + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Discount discount) + { + if (discount == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = discount; + + expando.Id = discount.Id; + expando.Name = discount.Name; + expando.DiscountTypeId = discount.DiscountTypeId; + expando.UsePercentage = discount.UsePercentage; + expando.DiscountPercentage = discount.DiscountPercentage; + expando.DiscountAmount = discount.DiscountAmount; + expando.StartDateUtc = discount.StartDateUtc; + expando.EndDateUtc = discount.EndDateUtc; + expando.RequiresCouponCode = discount.RequiresCouponCode; + expando.CouponCode = discount.CouponCode; + expando.DiscountLimitationId = discount.DiscountLimitationId; + expando.LimitationTimes = discount.LimitationTimes; + + return expando as ExpandoObject; + } + + private ExpandoObject ToExpando(ExportProfileTaskContext ctx, ProductSpecificationAttribute psa) + { + if (psa == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = psa; + + expando.Id = psa.Id; + expando.ProductId = psa.ProductId; + expando.SpecificationAttributeOptionId = psa.SpecificationAttributeOptionId; + expando.AllowFiltering = psa.AllowFiltering; + expando.ShowOnProductPage = psa.ShowOnProductPage; + expando.DisplayOrder = psa.DisplayOrder; + + var option = psa.SpecificationAttributeOption; + + expando.SpecificationAttributeOption._Entity = option; + expando.SpecificationAttributeOption.Id = option.Id; + expando.SpecificationAttributeOption.SpecificationAttributeId = option.SpecificationAttributeId; + expando.SpecificationAttributeOption.Name = option.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + expando.SpecificationAttributeOption.DisplayOrder = option.DisplayOrder; + + expando.SpecificationAttributeOption.SpecificationAttribute._Entity = option.SpecificationAttribute; + expando.SpecificationAttributeOption.SpecificationAttribute.Id = option.SpecificationAttribute.Id; + expando.SpecificationAttributeOption.SpecificationAttribute.Name = option.SpecificationAttribute.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + expando.SpecificationAttributeOption.SpecificationAttribute.DisplayOrder = option.SpecificationAttribute.DisplayOrder; + + return expando as ExpandoObject; + } + + #endregion + #region Segmenter callbacks private List GetProducts(ExportProfileTaskContext ctx, int pageIndex) @@ -658,14 +1401,14 @@ private List GetOrders(ExportProfileTaskContext ctx, int pageIndex, int p return result; } - private List ToExpando(ExportProfileTaskContext ctx, Product product) + private List ConvertToExpando(ExportProfileTaskContext ctx, Product product) { var result = new List(); - var languageId = (ctx.Projection.LanguageId ?? 0); + var productTemplate = ctx.ProductTemplates.FirstOrDefault(x => x.Key == product.ProductTemplateId); var pictureSize = _mediaSettings.ProductDetailsPictureSize; - if (ctx.Provider.Supports(ExportProjectionSupport.MainPictureUrl) && ctx.Projection.PictureSize > 0) + if (ctx.Supporting[ExportProjectionSupport.MainPictureUrl] && ctx.Projection.PictureSize > 0) pictureSize = ctx.Projection.PictureSize; var productPictures = ctx.ProductDataContext.ProductPictures.Load(product.Id); @@ -674,13 +1417,13 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod var productAttributes = ctx.ProductDataContext.Attributes.Load(product.Id); var productAttributeCombinations = ctx.ProductDataContext.AttributeCombinations.Load(product.Id); - dynamic expando = product.ToExpando(languageId); + dynamic expando = ToExpando(ctx, product); #region gerneral data expando._ProductTemplateViewPath = (productTemplate.Value == null ? "" : productTemplate.Value.ViewPath); - expando._DetailUrl = ctx.Store.Url + expando.SeName; + expando._DetailUrl = ctx.Store.Url + (string)expando.SeName; expando._CategoryName = null; @@ -701,7 +1444,7 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod exp._Entity = x; exp.Id = x.Id; exp.DisplayOrder = x.DisplayOrder; - exp.Picture = x.Picture.ToExpando(_pictureService, ctx.Store, _mediaSettings.ProductThumbPictureSize, pictureSize); + exp.Picture = ToExpando(ctx, x.Picture, _mediaSettings.ProductThumbPictureSize, pictureSize); return exp as ExpandoObject; }) @@ -716,10 +1459,10 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod exp.Id = x.Id; exp.DisplayOrder = x.DisplayOrder; exp.IsFeaturedProduct = x.IsFeaturedProduct; - exp.Manufacturer = x.Manufacturer.ToExpando(languageId); + exp.Manufacturer = ToExpando(ctx, x.Manufacturer); if (x.Manufacturer != null && x.Manufacturer.PictureId.HasValue) - exp.Manufacturer.Picture = x.Manufacturer.Picture.ToExpando(_pictureService, ctx.Store, _mediaSettings.ManufacturerThumbPictureSize, _mediaSettings.ManufacturerThumbPictureSize); + exp.Manufacturer.Picture = ToExpando(ctx, x.Manufacturer.Picture, _mediaSettings.ManufacturerThumbPictureSize, _mediaSettings.ManufacturerThumbPictureSize); else exp.Manufacturer.Picture = null; @@ -736,10 +1479,10 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod exp.Id = x.Id; exp.DisplayOrder = x.DisplayOrder; exp.IsFeaturedProduct = x.IsFeaturedProduct; - exp.Category = x.Category.ToExpando(languageId); + exp.Category = ToExpando(ctx, x.Category); if (x.Category != null && x.Category.PictureId.HasValue) - exp.Category.Picture = x.Category.Picture.ToExpando(_pictureService, ctx.Store, _mediaSettings.CategoryThumbPictureSize, _mediaSettings.CategoryThumbPictureSize); + exp.Category.Picture = ToExpando(ctx, x.Category.Picture, _mediaSettings.CategoryThumbPictureSize, _mediaSettings.CategoryThumbPictureSize); else exp.Category.Picture = null; @@ -752,13 +1495,13 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod expando.ProductAttributes = productAttributes .OrderBy(x => x.DisplayOrder) - .Select(x => x.ToExpando(languageId)) + .Select(x => ToExpando(ctx, x)) .ToList(); expando.ProductAttributeCombinations = productAttributeCombinations .Select(x => { - dynamic exp = x.ToExpando(); + dynamic exp = ToExpando(ctx, x); var assignedPictures = new List(); GetDeliveryTimeAndQuantityUnit(ctx, expando, x.DeliveryTimeId, x.QuantityUnitId); @@ -768,7 +1511,7 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod var assignedPicture = productPictures.FirstOrDefault(y => y.PictureId == pictureId); if (assignedPicture != null && assignedPicture.Picture != null) { - assignedPictures.Add(assignedPicture.Picture.ToExpando(_pictureService, ctx.Store, _mediaSettings.ProductThumbPictureSize, pictureSize)); + assignedPictures.Add(ToExpando(ctx, assignedPicture.Picture, _mediaSettings.ProductThumbPictureSize, pictureSize)); } } @@ -778,47 +1521,65 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod }) .ToList(); - if (product.HasDiscountsApplied) + if (product.HasTierPrices) { - var appliedDiscounts = ctx.ProductDataContext.AppliedDiscounts.Load(product.Id); + var tierPrices = ctx.ProductDataContext.TierPrices.Load(product.Id); - expando.AppliedDiscounts = appliedDiscounts - .Select(x => x.ToExpando()) + expando.TierPrices = tierPrices + .Select(x => + { + dynamic exp = new ExpandoObject(); + exp._Entity = x; + exp.Id = x.Id; + exp.ProductId = x.ProductId; + exp.StoreId = x.StoreId; + exp.CustomerRoleId = x.CustomerRoleId; + exp.Quantity = x.Quantity; + exp.Price = x.Price; + return exp as ExpandoObject; + }) .ToList(); } else { - expando.AppliedDiscounts = new List(); + expando.TierPrices = new List(); } - if (product.HasTierPrices) + if (product.HasDiscountsApplied) { - var tierPrices = ctx.ProductDataContext.TierPrices.Load(product.Id); + var appliedDiscounts = ctx.ProductDataContext.AppliedDiscounts.Load(product.Id); - expando.TierPrices = tierPrices - .Select(x => x.ToExpando()) + expando.AppliedDiscounts = appliedDiscounts + .Select(x => ToExpando(ctx, x)) .ToList(); } else { - expando.TierPrices = new List(); + expando.AppliedDiscounts = new List(); } #endregion #region high data depth - if (ctx.Provider.Supports(ExportProjectionSupport.HighDataDepth)) + if (ctx.Supporting[ExportProjectionSupport.HighDataDepth]) { var productTags = ctx.ProductDataContext.ProductTags.Load(product.Id); var specificationAttributes = ctx.ProductDataContext.ProductSpecificationAttributes.Load(product.Id); expando.ProductTags = productTags - .Select(x => x.ToExpando(languageId)) + .Select(x => + { + dynamic exp = new ExpandoObject(); + exp._Entity = x; + exp.Id = x.Id; + exp.Name = x.GetLocalized(y => y.Name, ctx.Projection.LanguageId ?? 0, true, false); + return exp as ExpandoObject; + }) .ToList(); expando.ProductSpecificationAttributes = specificationAttributes - .Select(x => x.ToExpando(languageId)) + .Select(x => ToExpando(ctx, x)) .ToList(); if (product.ProductType == ProductType.BundledProduct) @@ -826,7 +1587,31 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod var bundleItems = ctx.ProductDataContext.ProductBundleItems.Load(product.Id); expando.ProductBundleItems = bundleItems - .Select(x => x.ToExpando((string)expando.Name, languageId)) + .Select(x => + { + var name = x.GetLocalized(y => y.Name, ctx.Projection.LanguageId ?? 0, true, false); + if (name.IsEmpty()) + name = (string)expando.Name; + + dynamic exp = new ExpandoObject(); + exp._Entity = x; + exp.Id = x.Id; + exp.ProductId = x.ProductId; + exp.BundleProductId = x.BundleProductId; + exp.Quantity = x.Quantity; + exp.Discount = x.Discount; + exp.DiscountPercentage = x.DiscountPercentage; + exp.Name = name; + exp.ShortDescription = x.GetLocalized(y => y.ShortDescription, ctx.Projection.LanguageId ?? 0, true, false); + exp.FilterAttributes = x.FilterAttributes; + exp.HideThumbnail = x.HideThumbnail; + exp.Visible = x.Visible; + exp.Published = x.Published; + exp.DisplayOrder = x.DisplayOrder; + exp.CreatedOnUtc = x.CreatedOnUtc; + exp.UpdatedOnUtc = x.UpdatedOnUtc; + return exp as ExpandoObject; + }) .ToList(); } else @@ -845,18 +1630,18 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod #region more attribute controlled data - if (ctx.Provider.Supports(ExportProjectionSupport.Description)) + if (ctx.Supporting[ExportProjectionSupport.Description]) { PrepareProductDescription(ctx, expando, product); } - if (ctx.Provider.Supports(ExportProjectionSupport.Brand)) + if (ctx.Supporting[ExportProjectionSupport.Brand]) { string brand = null; var productManus = ctx.ProductDataContext.ProductManufacturers.Load(product.Id); if (productManus != null && productManus.Any()) - brand = productManus.First().Manufacturer.GetLocalized(x => x.Name, languageId, true, false); + brand = productManus.First().Manufacturer.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); if (brand.IsEmpty()) brand = ctx.Projection.Brand; @@ -864,7 +1649,7 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod expando._Brand = brand; } - if (ctx.Provider.Supports(ExportProjectionSupport.MainPictureUrl)) + if (ctx.Supporting[ExportProjectionSupport.MainPictureUrl]) { if (productPictures != null && productPictures.Any()) expando._MainPictureUrl = _pictureService.GetPictureUrl(productPictures.First().Picture, ctx.Projection.PictureSize, storeLocation: ctx.Store.Url); @@ -896,7 +1681,7 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod if (combination != null && ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) { - var values = _productAttributeParser.ParseProductVariantAttributeValues(combination.AttributesXml, productAttributes, languageId); + var values = _productAttributeParser.ParseProductVariantAttributeValues(combination.AttributesXml, productAttributes, ctx.Projection.LanguageId ?? 0); exp.Name = ((string)exp.Name).Grow(string.Join(", ", values), " "); } @@ -905,18 +1690,18 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod // navigation properties GetDeliveryTimeAndQuantityUnit(ctx, exp, product.DeliveryTimeId, product.QuantityUnitId); - if (ctx.Provider.Supports(ExportProjectionSupport.UseOwnProductNo) && product.ManufacturerPartNumber.IsEmpty()) + if (ctx.Supporting[ExportProjectionSupport.UseOwnProductNo] && product.ManufacturerPartNumber.IsEmpty()) { exp.ManufacturerPartNumber = product.Sku; } - if (ctx.Provider.Supports(ExportProjectionSupport.ShippingTime)) + if (ctx.Supporting[ExportProjectionSupport.ShippingTime]) { dynamic deliveryTime = exp.DeliveryTime; exp._ShippingTime = (deliveryTime == null ? ctx.Projection.ShippingTime : deliveryTime.Name); } - if (ctx.Provider.Supports(ExportProjectionSupport.ShippingCosts)) + if (ctx.Supporting[ExportProjectionSupport.ShippingCosts]) { exp._FreeShippingThreshold = ctx.Projection.FreeShippingThreshold; @@ -926,7 +1711,7 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod exp._ShippingCosts = ctx.Projection.ShippingCosts; } - if (ctx.Provider.Supports(ExportProjectionSupport.OldPrice)) + if (ctx.Supporting[ExportProjectionSupport.OldPrice]) { if (product.OldPrice != decimal.Zero && product.OldPrice != (decimal)exp.Price && !(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)) { @@ -946,7 +1731,7 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod } } - if (ctx.Provider.Supports(ExportProjectionSupport.SpecialPrice)) + if (ctx.Supporting[ExportProjectionSupport.SpecialPrice]) { exp._SpecialPrice = null; exp._RegularPrice = null; // price if a special price would not exist @@ -993,10 +1778,9 @@ private List ToExpando(ExportProfileTaskContext ctx, Product prod return result; } - private List ToExpando(ExportProfileTaskContext ctx, Order order) + private List ConvertToExpando(ExportProfileTaskContext ctx, Order order) { var result = new List(); - var languageId = (ctx.Projection.LanguageId ?? 0); ctx.OrderDataContext.Addresses.Collect(order.ShippingAddressId ?? 0); @@ -1006,20 +1790,17 @@ private List ToExpando(ExportProfileTaskContext ctx, Order order) var orderItems = ctx.OrderDataContext.OrderItems.Load(order.Id); var shipments = ctx.OrderDataContext.Shipments.Load(order.Id); - dynamic expando = order.ToExpando(languageId, _services.Localization); + dynamic expando = ToExpando(ctx, order); if (ctx.Stores.ContainsKey(order.StoreId)) { - expando.Store = ctx.Stores[order.StoreId] - .ToExpando(languageId); + expando.Store = ToExpando(ctx, ctx.Stores[order.StoreId]); } - expando.Customer = customers - .FirstOrDefault(x => x.Id == order.CustomerId) - .ToExpando(languageId); + expando.Customer = ToExpando(ctx, customers.FirstOrDefault(x => x.Id == order.CustomerId)); expando.Customer.RewardPointsHistory = rewardPointsHistories - .Select(x => x.ToExpando()) + .Select(x => ToExpando(ctx, x)) .ToList(); if (rewardPointsHistories.Count > 0) @@ -1031,21 +1812,17 @@ private List ToExpando(ExportProfileTaskContext ctx, Order order) .PointsBalance; } - expando.BillingAddress = addresses - .FirstOrDefault(x => x.Id == order.BillingAddressId) - .ToExpando(languageId); + expando.BillingAddress = ToExpando(ctx, addresses.FirstOrDefault(x => x.Id == order.BillingAddressId)); if (order.ShippingAddressId.HasValue) { - expando.ShippingAddress = addresses - .FirstOrDefault(x => x.Id == order.ShippingAddressId.Value) - .ToExpando(languageId); + expando.ShippingAddress = ToExpando(ctx, addresses.FirstOrDefault(x => x.Id == order.ShippingAddressId.Value)); } expando.OrderItems = orderItems .Select(e => { - dynamic exp = e.ToExpando(languageId); + dynamic exp = ToExpando(ctx, e); var productTemplate = ctx.ProductTemplates.FirstOrDefault(x => x.Key == e.Product.ProductTemplateId); exp.Product._ProductTemplateViewPath = (productTemplate.Value == null ? "" : productTemplate.Value.ViewPath); @@ -1059,16 +1836,7 @@ private List ToExpando(ExportProfileTaskContext ctx, Order order) .ToList(); expando.Shipments = shipments - .Select(e => - { - dynamic exp = e.ToExpando(); - - exp.ShipmentItems = e.ShipmentItems - .Select(x => x.ToExpando()) - .ToList(); - - return exp as ExpandoObject; - }) + .Select(x => ToExpando(ctx, x)) .ToList(); result.Add(expando); @@ -1163,7 +1931,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Log.Information(logHead.ToString()); } - ctx.Export.Store = ctx.Store.ToExpando(ctx.Projection.LanguageId ?? 0); + ctx.Export.Store = ToExpando(ctx, ctx.Store); ctx.Export.MaxFileNameLength = _dataExchangeSettings.MaxFileNameLength; @@ -1179,7 +1947,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) { ctx.Export.Data = new ExportSegmenter( pageIndex => GetProducts(ctx, pageIndex), - entity => ToExpando(ctx, entity), + entity => ConvertToExpando(ctx, entity), new PagedList(ctx.Profile.Offset, ctx.Profile.Limit, ctx.PageIndex, ctx.PageSize, totalCount), ctx.IsPreview ? 0 : ctx.Profile.BatchSize ); @@ -1190,7 +1958,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.Data = new ExportSegmenter( pageIndex => GetOrders(ctx, pageIndex, ctx.PageSize, out unused), - entity => ToExpando(ctx, entity), + entity => ConvertToExpando(ctx, entity), new PagedList(ctx.Profile.Offset, ctx.Profile.Limit, ctx.PageIndex, ctx.PageSize, totalCount), ctx.IsPreview ? 0 : ctx.Profile.BatchSize ); @@ -1314,6 +2082,10 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) ctx.QuantityUnits = _quantityUnitService.GetAllQuantityUnits().ToDictionary(x => x.Id, x => x); ctx.ProductTemplates = _productTemplateService.GetAllProductTemplates().ToDictionary(x => x.Id, x => x); + ctx.Localized.DeliveryTimes = _localizedEntityService.GetLocalizedProperties("DeliveryTime", null).ToMultimap(x => x.EntityId, x => x); + ctx.Localized.QuantityUnits = _localizedEntityService.GetLocalizedProperties("QuantityUnit", null).ToMultimap(x => x.EntityId, x => x); + ctx.Localized.ProductAttributes = _localizedEntityService.GetLocalizedProperties("ProductAttribute", null).ToMultimap(x => x.EntityId, x => x); + if (ctx.Provider.Value.EntityType == ExportEntityType.Product) { var allCategories = _categoryService.GetAllCategories(showHidden: true, applyNavigationFilters: false); @@ -1327,9 +2099,9 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) var stores = Init(ctx); - ctx.Export.Language = ctx.ProjectionLanguage.ToExpando(); - ctx.Export.Customer = ctx.ProjectionCustomer.ToExpando(ctx.ProjectionLanguage.Id); - ctx.Export.Currency = ctx.ProjectionCurrency.ToExpando(ctx.ProjectionLanguage.Id); + ctx.Export.Language = ToExpando(ctx, ctx.ProjectionLanguage); + ctx.Export.Customer = ToExpando(ctx, ctx.ProjectionCustomer); + ctx.Export.Currency = ToExpando(ctx, ctx.ProjectionCurrency); stores.ForEach(x => ExportCoreInner(ctx, x)); } @@ -1405,6 +2177,9 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) try { + ctx.Localized.DeliveryTimes.Clear(); + ctx.Localized.QuantityUnits.Clear(); + ctx.Localized.ProductAttributes.Clear(); ctx.ProductTemplates.Clear(); ctx.Countries.Clear(); ctx.Stores.Clear(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs index 2fefe8e4d6..d206690381 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain; using SmartStore.Core.Domain.Catalog; @@ -43,6 +44,10 @@ public ExportProfileTaskContext( TotalRecords = totalRecords; PreviewData = previewData; + Supporting = Enum.GetValues(typeof(ExportProjectionSupport)) + .Cast() + .ToDictionary(x => x, x => Provider.Supports(x)); + FolderContent = FileSystemHelper.TempDir(@"Profile\Export\{0}\Content".FormatInvariant(profile.FolderName)); FolderRoot = System.IO.Directory.GetParent(FolderContent).FullName; @@ -51,6 +56,13 @@ public ExportProfileTaskContext( Countries = new Dictionary(); ProductTemplates = new Dictionary(); + Localized = new LocalizedProperties + { + DeliveryTimes = new Multimap(), + QuantityUnits = new Multimap(), + ProductAttributes = new Multimap() + }; + RecordsPerStore = new Dictionary(); EntityIdsLoaded = new List(); @@ -82,6 +94,7 @@ public bool IsPreview public TaskExecutionContext TaskContext { get; private set; } public ExportProfile Profile { get; private set; } public Provider Provider { get; private set; } + public Dictionary Supporting { get; private set; } public ExportFilter Filter { get; private set; } public ExportProjection Projection { get; private set; } @@ -130,6 +143,7 @@ public string[] GetDeploymentFiles(ExportDeployment deployment) public Dictionary Stores { get; set; } public Dictionary Countries { get; set; } public Dictionary ProductTemplates { get; set; } + public LocalizedProperties Localized { get; set; } // data loaded once per page public ExportProductDataContext ProductDataContext @@ -163,5 +177,12 @@ public ExportOrderDataContext OrderDataContext public ExportExecuteContext Export { get; set; } public ExportExecuteResult Result { get; set; } + + public class LocalizedProperties + { + public Multimap DeliveryTimes { get; set; } + public Multimap QuantityUnits { get; set; } + public Multimap ProductAttributes { get; set; } + } } } diff --git a/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs b/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs index 871afe9887..8abe7af790 100644 --- a/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs +++ b/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs @@ -42,6 +42,14 @@ public partial interface ILocalizedEntityService /// Localized properties IList GetLocalizedProperties(int entityId, string localeKeyGroup); + /// + /// Gets localized properties + /// + /// Locale key group + /// Entity identifiers. Can be null. + /// Localized properties + IList GetLocalizedProperties(string localeKeyGroup, int[] entityIds); + /// /// Inserts a localized property /// diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs b/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs index fd48d2a7c6..0a5f07242b 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; @@ -147,6 +148,21 @@ orderby lp.Id return props; } + public virtual IList GetLocalizedProperties(string localeKeyGroup, int[] entityIds) + { + Guard.ArgumentNotEmpty(() => localeKeyGroup); + + var query = _localizedPropertyRepository.Table + .Where(x => x.LocaleKeyGroup == localeKeyGroup); + + if (entityIds != null && entityIds.Length > 0) + { + query = query.Where(x => entityIds.Contains(x.EntityId)); + } + + return query.ToList(); + } + /// /// Inserts a localized property /// diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 157fa3bf87..366942ced1 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -178,7 +178,6 @@ - diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/QuantityUnitController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/QuantityUnitController.cs index 85bfe5b8db..365cd40acd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/QuantityUnitController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/QuantityUnitController.cs @@ -1,13 +1,10 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Directory; -using SmartStore.Core; using SmartStore.Core.Domain.Directory; -using SmartStore.Services.Configuration; +using SmartStore.Services; using SmartStore.Services.Directory; -using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Security; using SmartStore.Web.Framework.Controllers; @@ -21,29 +18,24 @@ public class QuantityUnitController : AdminControllerBase #region Fields private readonly IQuantityUnitService _quantityUnitService; - private readonly ISettingService _settingService; - private readonly ILocalizationService _localizationService; - private readonly IPermissionService _permissionService; private readonly ILocalizedEntityService _localizedEntityService; private readonly ILanguageService _languageService; + private readonly ICommonServices _services; #endregion #region Constructors - public QuantityUnitController(IQuantityUnitService quantityUnitService, - ISettingService settingService, - ILocalizationService localizationService, - IPermissionService permissionService, + public QuantityUnitController( + IQuantityUnitService quantityUnitService, ILocalizedEntityService localizedEntityService, - ILanguageService languageService) + ILanguageService languageService, + ICommonServices services) { - this._quantityUnitService = quantityUnitService; - this._settingService = settingService; - this._localizationService = localizationService; - this._permissionService = permissionService; - this._localizedEntityService = localizedEntityService; - this._languageService = languageService; + _quantityUnitService = quantityUnitService; + _localizedEntityService = localizedEntityService; + _languageService = languageService; + _services = services; } #endregion @@ -60,7 +52,6 @@ public void UpdateLocales(QuantityUnit quantityUnit, QuantityUnitModel model) } } - #endregion #region Methods @@ -72,7 +63,7 @@ public ActionResult Index() public ActionResult List() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMeasures)) return AccessDeniedView(); var quantityUnitModel = _quantityUnitService.GetAllQuantityUnits().Select(x => x.ToModel()).ToList(); @@ -111,7 +102,7 @@ public ActionResult AllQuantityUnits(string label, int selectedId) public ActionResult Create() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMeasures)) return AccessDeniedView(); var model = new QuantityUnitModel(); @@ -124,7 +115,7 @@ public ActionResult Create() [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] public ActionResult Create(QuantityUnitModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMeasures)) return AccessDeniedView(); if (ModelState.IsValid) @@ -135,7 +126,7 @@ public ActionResult Create(QuantityUnitModel model, bool continueEditing) //locales UpdateLocales(quantityUnit, model); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.QuantityUnit.Added")); + NotifySuccess(T("Admin.Configuration.QuantityUnit.Added")); return continueEditing ? RedirectToAction("Edit", new { id = quantityUnit.Id }) : RedirectToAction("List"); } @@ -145,32 +136,32 @@ public ActionResult Create(QuantityUnitModel model, bool continueEditing) public ActionResult Edit(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMeasures)) return AccessDeniedView(); var quantityUnit = _quantityUnitService.GetQuantityUnitById(id); if (quantityUnit == null) - //No currency found with the specified id return RedirectToAction("List"); var model = quantityUnit.ToModel(); - //locales + AddLocales(_languageService, model.Locales, (locale, languageId) => { locale.Name = quantityUnit.GetLocalized(x => x.Name, languageId, false, false); + locale.Description = quantityUnit.GetLocalized(x => x.Description, languageId, false, false); }); + return View(model); } [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] public ActionResult Edit(QuantityUnitModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMeasures)) return AccessDeniedView(); var quantityUnit = _quantityUnitService.GetQuantityUnitById(model.Id); if (quantityUnit == null) - //No currency found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) @@ -178,14 +169,10 @@ public ActionResult Edit(QuantityUnitModel model, bool continueEditing) quantityUnit = model.ToEntity(quantityUnit); UpdateLocales(quantityUnit, model); - _quantityUnitService.UpdateQuantityUnit(quantityUnit); - if (model.IsDefault) - { - - } + _quantityUnitService.UpdateQuantityUnit(quantityUnit); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.QuantityUnits.Updated")); + NotifySuccess(T("Admin.Configuration.QuantityUnits.Updated")); return continueEditing ? RedirectToAction("Edit", new { id = quantityUnit.Id }) : RedirectToAction("List"); } @@ -195,7 +182,7 @@ public ActionResult Edit(QuantityUnitModel model, bool continueEditing) [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMeasures)) return AccessDeniedView(); var quantityUnit = _quantityUnitService.GetQuantityUnitById(id); @@ -208,7 +195,7 @@ public ActionResult DeleteConfirmed(int id) _quantityUnitService.DeleteQuantityUnit(quantityUnit); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.QuantityUnits.Deleted")); + NotifySuccess(T("Admin.Configuration.QuantityUnits.Deleted")); return RedirectToAction("List"); } catch (Exception exc) @@ -221,7 +208,7 @@ public ActionResult DeleteConfirmed(int id) [HttpPost] public ActionResult Save(FormCollection formValues) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMeasures)) return AccessDeniedView(); return RedirectToAction("List", "QuantityUnit"); From cfbeab4a4e716e2b91f42fdf3fa613e07e677c83 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 1 Oct 2015 11:45:23 +0200 Subject: [PATCH 099/874] Cannot merge "Added filename to expando object" because the target file does not exist anymore --- .../DataExchange/ExportTask/ExportProfileTask.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index ea85dd3375..fef39ff5fe 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -812,6 +812,9 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Picture picture, i expando._ImageUrl = _pictureService.GetPictureUrl(picture, detailsPictureSize, false, ctx.Store.Url); expando._FullSizeImageUrl = _pictureService.GetPictureUrl(picture, 0, false, ctx.Store.Url); + var relativeUrl = _pictureService.GetPictureUrl(picture); + expando._FileName = relativeUrl.Substring(relativeUrl.LastIndexOf("/") + 1); + return expando as ExpandoObject; } From c5666e81bfa502b36830e36c4c810031cc8a8a88 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 1 Oct 2015 13:28:08 +0200 Subject: [PATCH 100/874] Reverted parts of my last commit because they are not necessary --- .../ExportTask/ExportProfileTask.cs | 42 +++---------------- .../ExportTask/ExportProfileTaskContext.cs | 16 ------- .../Localization/ILocalizedEntityService.cs | 8 ---- .../Localization/LocalizedEntityService.cs | 17 -------- 4 files changed, 5 insertions(+), 78 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index fef39ff5fe..94f1f64e30 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -1,21 +1,17 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Dynamic; using System.IO; using System.IO.Compression; using System.Linq; -using System.Linq.Expressions; using System.Net; using System.Net.Http; -using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web; using System.Web.Mvc; using Autofac; -using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Async; using SmartStore.Core.Data; @@ -126,27 +122,6 @@ private void InitDependencies(TaskExecutionContext context) #region Utilities - private TPropType GetLocalized(Multimap map, T entity, Expression> keySelector, int languageId) - where T : BaseEntity, ILocalizedEntity - { - if (languageId > 0 && map.ContainsKey(entity.Id)) - { - var member = keySelector.Body as MemberExpression; - var propInfo = member.Member as PropertyInfo; - - var property = map[entity.Id].FirstOrDefault(x => x.LocaleKey == propInfo.Name && x.LanguageId == languageId); - if (property != null) - { - Debug.Assert(property.LocaleKeyGroup.IsCaseInsensitiveEqual(entity.GetType().Name), "Wrong localized map for entity " + entity.GetType().Name); - - return property.LocaleValue.Convert(); - } - } - - var localizer = keySelector.Compile(); - return localizer(entity); - } - private ProductSearchContext GetProductSearchContext(ExportProfileTaskContext ctx, int pageIndex, int pageSize) { var searchContext = new ProductSearchContext @@ -770,7 +745,7 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, DeliveryTime deliv expando._Entity = deliveryTime; expando.Id = deliveryTime.Id; - expando.Name = GetLocalized(ctx.Localized.DeliveryTimes, deliveryTime, x => x.Name, ctx.Projection.LanguageId ?? 0); + expando.Name = deliveryTime.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); expando.DisplayLocale = deliveryTime.DisplayLocale; expando.ColorHexValue = deliveryTime.ColorHexValue; expando.DisplayOrder = deliveryTime.DisplayOrder; @@ -787,8 +762,8 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, QuantityUnit quant expando._Entity = quantityUnit; expando.Id = quantityUnit.Id; - expando.Name = GetLocalized(ctx.Localized.QuantityUnits, quantityUnit, x => x.Name, ctx.Projection.LanguageId ?? 0); - expando.Description = GetLocalized(ctx.Localized.QuantityUnits, quantityUnit, x => x.Description, ctx.Projection.LanguageId ?? 0); + expando.Name = quantityUnit.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + expando.Description = quantityUnit.GetLocalized(x => x.Description, ctx.Projection.LanguageId ?? 0, true, false); expando.DisplayLocale = quantityUnit.DisplayLocale; expando.DisplayOrder = quantityUnit.DisplayOrder; expando.IsDefault = quantityUnit.IsDefault; @@ -836,8 +811,8 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, ProductVariantAttr attribute._Entity = pva.ProductAttribute; attribute.Id = pva.ProductAttribute.Id; attribute.Alias = pva.ProductAttribute.Alias; - attribute.Name = GetLocalized(ctx.Localized.ProductAttributes, pva.ProductAttribute, x => x.Name, ctx.Projection.LanguageId ?? 0); - attribute.Description = GetLocalized(ctx.Localized.ProductAttributes, pva.ProductAttribute, x => x.Description, ctx.Projection.LanguageId ?? 0); + attribute.Name = pva.ProductAttribute.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + attribute.Description = pva.ProductAttribute.GetLocalized(x => x.Description, ctx.Projection.LanguageId ?? 0, true, false); attribute.Values = pva.ProductVariantAttributeValues .OrderBy(x => x.DisplayOrder) @@ -2085,10 +2060,6 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) ctx.QuantityUnits = _quantityUnitService.GetAllQuantityUnits().ToDictionary(x => x.Id, x => x); ctx.ProductTemplates = _productTemplateService.GetAllProductTemplates().ToDictionary(x => x.Id, x => x); - ctx.Localized.DeliveryTimes = _localizedEntityService.GetLocalizedProperties("DeliveryTime", null).ToMultimap(x => x.EntityId, x => x); - ctx.Localized.QuantityUnits = _localizedEntityService.GetLocalizedProperties("QuantityUnit", null).ToMultimap(x => x.EntityId, x => x); - ctx.Localized.ProductAttributes = _localizedEntityService.GetLocalizedProperties("ProductAttribute", null).ToMultimap(x => x.EntityId, x => x); - if (ctx.Provider.Value.EntityType == ExportEntityType.Product) { var allCategories = _categoryService.GetAllCategories(showHidden: true, applyNavigationFilters: false); @@ -2180,9 +2151,6 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) try { - ctx.Localized.DeliveryTimes.Clear(); - ctx.Localized.QuantityUnits.Clear(); - ctx.Localized.ProductAttributes.Clear(); ctx.ProductTemplates.Clear(); ctx.Countries.Clear(); ctx.Stores.Clear(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs index d206690381..6c96b63c22 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain; using SmartStore.Core.Domain.Catalog; @@ -56,13 +55,6 @@ public ExportProfileTaskContext( Countries = new Dictionary(); ProductTemplates = new Dictionary(); - Localized = new LocalizedProperties - { - DeliveryTimes = new Multimap(), - QuantityUnits = new Multimap(), - ProductAttributes = new Multimap() - }; - RecordsPerStore = new Dictionary(); EntityIdsLoaded = new List(); @@ -143,7 +135,6 @@ public string[] GetDeploymentFiles(ExportDeployment deployment) public Dictionary Stores { get; set; } public Dictionary Countries { get; set; } public Dictionary ProductTemplates { get; set; } - public LocalizedProperties Localized { get; set; } // data loaded once per page public ExportProductDataContext ProductDataContext @@ -177,12 +168,5 @@ public ExportOrderDataContext OrderDataContext public ExportExecuteContext Export { get; set; } public ExportExecuteResult Result { get; set; } - - public class LocalizedProperties - { - public Multimap DeliveryTimes { get; set; } - public Multimap QuantityUnits { get; set; } - public Multimap ProductAttributes { get; set; } - } } } diff --git a/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs b/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs index 8abe7af790..871afe9887 100644 --- a/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs +++ b/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs @@ -42,14 +42,6 @@ public partial interface ILocalizedEntityService /// Localized properties IList GetLocalizedProperties(int entityId, string localeKeyGroup); - /// - /// Gets localized properties - /// - /// Locale key group - /// Entity identifiers. Can be null. - /// Localized properties - IList GetLocalizedProperties(string localeKeyGroup, int[] entityIds); - /// /// Inserts a localized property /// diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs b/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs index 0a5f07242b..a0b2c2989f 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizedEntityService.cs @@ -3,12 +3,10 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; -using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Infrastructure; namespace SmartStore.Services.Localization { @@ -148,21 +146,6 @@ orderby lp.Id return props; } - public virtual IList GetLocalizedProperties(string localeKeyGroup, int[] entityIds) - { - Guard.ArgumentNotEmpty(() => localeKeyGroup); - - var query = _localizedPropertyRepository.Table - .Where(x => x.LocaleKeyGroup == localeKeyGroup); - - if (entityIds != null && entityIds.Length > 0) - { - query = query.Where(x => entityIds.Contains(x.EntityId)); - } - - return query.ToList(); - } - /// /// Inserts a localized property /// From 1d031b8403da7176f46fbbc7bd01100961368fed Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 1 Oct 2015 16:49:04 +0200 Subject: [PATCH 101/874] Closes #452 Extend Google Analytics widget to support new universal analytics --- .../WidgetsGoogleAnalyticsController.cs | 50 +----------------- .../Description.txt | 4 +- .../GoogleAnalyticPlugin.cs | 51 +++++++++++++------ 3 files changed, 39 insertions(+), 66 deletions(-) diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs b/src/Plugins/SmartStore.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs index 3ed283c958..52a8a60a0d 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs +++ b/src/Plugins/SmartStore.GoogleAnalytics/Controllers/WidgetsGoogleAnalyticsController.cs @@ -126,19 +126,6 @@ private Order GetLastOrder() return order; } - // private string GetTrackingScript() { var googleAnalyticsSettings = _settingService.LoadSetting(_storeContext.CurrentStore.Id); @@ -149,42 +136,6 @@ private string GetTrackingScript() return analyticsTrackingScript; } - // private string GetEcommerceScript(Order order) { var googleAnalyticsSettings = _settingService.LoadSetting(_storeContext.CurrentStore.Id); @@ -206,6 +157,7 @@ private string GetEcommerceScript(Order order) analyticsEcommerceScript = analyticsEcommerceScript.Replace("{CITY}", order.BillingAddress == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.City)); analyticsEcommerceScript = analyticsEcommerceScript.Replace("{STATEPROVINCE}", order.BillingAddress == null || order.BillingAddress.StateProvince == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.StateProvince.Name)); analyticsEcommerceScript = analyticsEcommerceScript.Replace("{COUNTRY}", order.BillingAddress == null || order.BillingAddress.Country == null ? "" : FixIllegalJavaScriptChars(order.BillingAddress.Country.Name)); + analyticsEcommerceScript = analyticsEcommerceScript.Replace("{CURRENCY}", order.CustomerCurrencyCode); var sb = new StringBuilder(); foreach (var item in order.OrderItems) diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Description.txt b/src/Plugins/SmartStore.GoogleAnalytics/Description.txt index c19a7f4113..2e32945c39 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Description.txt +++ b/src/Plugins/SmartStore.GoogleAnalytics/Description.txt @@ -1,9 +1,9 @@ FriendlyName: Google Analytics SystemName: SmartStore.GoogleAnalytics Group: Analytics -Version: 2.2.2.1 +Version: 2.2.2.2 MinAppVersion: 2.2.0 DisplayOrder: 1 FileName: SmartStore.GoogleAnalytics.dll ResourceRootKey: Plugins.Widgets.GoogleAnalytics -Url: http://community.smartstore.com/index.php?/files/file/25-google-analytics/ \ No newline at end of file +Url: http://community.smartstore.com/marketplace/file/25-google-analytics/ \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleAnalytics/GoogleAnalyticPlugin.cs b/src/Plugins/SmartStore.GoogleAnalytics/GoogleAnalyticPlugin.cs index 61d434af55..185c203527 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/GoogleAnalyticPlugin.cs +++ b/src/Plugins/SmartStore.GoogleAnalytics/GoogleAnalyticPlugin.cs @@ -82,23 +82,44 @@ public override void Install() var settings = new GoogleAnalyticsSettings() { GoogleId = "UA-0000000-0", - //TrackingScript = " ", TrackingScript = @" - ", - EcommerceScript = @"_gaq.push(['_addTrans', '{ORDERID}', '{SITE}', '{TOTAL}', '{TAX}', '{SHIP}', '{CITY}', '{STATEPROVINCE}', '{COUNTRY}']); - {DETAILS} - _gaq.push(['_trackTrans']); ", - EcommerceDetailScript = @"_gaq.push(['_addItem', '{ORDERID}', '{PRODUCTSKU}', '{PRODUCTNAME}', '{CATEGORYNAME}', '{UNITPRICE}', '{QUANTITY}' ]); ", + EcommerceScript = @" + ga('require', 'ecommerce'); + + ga('ecommerce:addTransaction', { + 'id': '{ORDERID}', + 'affiliation': '{SITE}', + 'revenue': '{TOTAL}', + 'shipping': '{SHIP}', + 'tax': '{TAX}', + 'currency': '{CURRENCY}' + }); + + {DETAILS} + + ga('ecommerce:send'); + ", + EcommerceDetailScript = @" + ga('ecommerce:addItem', { + 'id': '{ORDERID}', + 'name': '{PRODUCTNAME}', + 'sku': '{PRODUCTSKU}', + 'category': '{CATEGORYNAME}', + 'price': '{UNITPRICE}', + 'quantity': '{QUANTITY}' + }); + ", }; _settingService.SaveSetting(settings); From 1acbd2b0010057b5f771d62739c6bdf1721ea949 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 1 Oct 2015 16:58:20 +0200 Subject: [PATCH 102/874] Changed links of marketplace URLs --- src/Plugins/SmartStore.AmazonPay/Description.txt | 2 +- src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt | 2 +- src/Plugins/SmartStore.PayPal/Description.txt | 2 +- src/Plugins/SmartStore.WebApi/Description.txt | 2 +- .../Administration/Views/Home/MarketplaceFeed.cshtml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Plugins/SmartStore.AmazonPay/Description.txt b/src/Plugins/SmartStore.AmazonPay/Description.txt index ab15464d35..92acfd1fab 100644 --- a/src/Plugins/SmartStore.AmazonPay/Description.txt +++ b/src/Plugins/SmartStore.AmazonPay/Description.txt @@ -7,4 +7,4 @@ Author: SmartStore AG DisplayOrder: 1 FileName: SmartStore.AmazonPay.dll ResourceRootKey: Plugins.Payments.AmazonPay -Url: http://community.smartstore.com/index.php?/files/file/11-amazon-payments-plugin/ \ No newline at end of file +Url: http://community.smartstore.com/marketplace/file/11-amazon-payments-plugin/ \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt index 5d20784e8c..2f613ddd29 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt @@ -7,4 +7,4 @@ Author: SmartStore AG DisplayOrder: 1 FileName: SmartStore.GoogleMerchantCenter.dll ResourceRootKey: Plugins.Feed.Froogle -Url: http://community.smartstore.com/index.php?/files/file/37-google-merchant-center-gmc-feed/ \ No newline at end of file +Url: http://community.smartstore.com/marketplace/file/37-google-merchant-center-gmc-feed/ \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Description.txt b/src/Plugins/SmartStore.PayPal/Description.txt index c921d9b989..ecf91d9d38 100644 --- a/src/Plugins/SmartStore.PayPal/Description.txt +++ b/src/Plugins/SmartStore.PayPal/Description.txt @@ -7,4 +7,4 @@ MinAppVersion: 2.2.0 DisplayOrder: 1 FileName: SmartStore.PayPal.dll ResourceRootKey: Plugins.SmartStore.PayPal -Url: http://community.smartstore.com/index.php?/files/file/29-paypal-payment-plugins/ \ No newline at end of file +Url: http://community.smartstore.com/marketplace/file/29-paypal-payment-plugins/ \ No newline at end of file diff --git a/src/Plugins/SmartStore.WebApi/Description.txt b/src/Plugins/SmartStore.WebApi/Description.txt index 75af20211c..cbe8fe9af5 100644 --- a/src/Plugins/SmartStore.WebApi/Description.txt +++ b/src/Plugins/SmartStore.WebApi/Description.txt @@ -7,4 +7,4 @@ Author: SmartStore AG DisplayOrder: 1 FileName: SmartStore.WebApi.dll ResourceRootKey: Plugins.Api.WebApi -Url: http://community.smartstore.com/index.php?/files/file/27-smartstorenet-web-api/ \ No newline at end of file +Url: http://community.smartstore.com/marketplace/file/27-smartstorenet-web-api/ \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Home/MarketplaceFeed.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Home/MarketplaceFeed.cshtml index a2bde39bcb..8b0c7061da 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Home/MarketplaceFeed.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Home/MarketplaceFeed.cshtml @@ -45,7 +45,7 @@ }
    - + @T("Admin.Marketplace.Visit")
    From 927ad1fc3029d15bb7324dade80820a99e9ae1ce Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 1 Oct 2015 18:09:53 +0200 Subject: [PATCH 103/874] Convert product XML export to support new export framework (part 4) --- .../Domain/DataExchange/ExportCore.cs | 20 +-- .../Plugins/Providers/ProviderMetadata.cs | 4 +- .../DataExchange/ExportAttributes.cs | 10 +- .../DataExchange/ExportExtensions.cs | 4 +- .../ExportProductXmlProvider.cs | 2 +- .../DataExchange/ExportService.cs | 2 +- .../ExportTask/ExportProfileTask.cs | 132 ++++++++++++++++-- .../ExportTask/ExportProfileTaskContext.cs | 7 +- .../DataExchange/ExportXmlHelper.cs | 53 +++++++ .../Providers/ProductExportXmlProvider.cs | 16 +-- .../DependencyRegistrar.cs | 10 +- .../Controllers/ExportController.cs | 2 +- .../Models/DataExchange/ExportProfileModel.cs | 2 +- .../Views/Export/_Tab.Projection.cshtml | 12 +- 14 files changed, 220 insertions(+), 56 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs index 927a0c5ba9..2f40f8cedd 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs @@ -59,21 +59,21 @@ public enum ExportAttributeValueMerging } /// - /// Projection types supported by export provider + /// Controls data processing supported by export provider /// - public enum ExportProjectionSupport + public enum ExportSupport { HighDataDepth = 0, AttributeCombinationAsProduct, CreateInitialPublicDeployment, - Description, - Brand, - MainPictureUrl, - UseOwnProductNo, - ShippingTime, - ShippingCosts, - OldPrice, - SpecialPrice + ProjectionDescription, + ProjectionBrand, + ProjectionMainPictureUrl, + ProjectionUseOwnProductNo, + ProjectionShippingTime, + ProjectionShippingCosts, + ProjectionOldPrice, + ProjectionSpecialPrice } /// diff --git a/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs b/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs index a579f72977..b1da4a1304 100644 --- a/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs +++ b/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs @@ -74,9 +74,9 @@ public class ProviderMetadata public bool IsHidden { get; set; } /// - /// Gets or sets an array of values that reflects what export projection is supported by a provider + /// Gets or sets an array of values that reflects what export data processing is supported by a provider /// - public ExportProjectionSupport[] ExportProjectionSupport { get; set; } + public ExportSupport[] ExportSupport { get; set; } /// /// Gets or sets an array of widget system names, which depend on the current provider diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportAttributes.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportAttributes.cs index e54904d903..ece4d81d38 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportAttributes.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportAttributes.cs @@ -4,17 +4,17 @@ namespace SmartStore.Services.DataExchange { /// - /// Declares projection types supported by an export provider. - /// Controls whether to display corresponding projection fields while editing an export profile. + /// Declares data processing types supported by an export provider. + /// Projection type controls whether to display corresponding projection fields while editing an export profile. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public sealed class ExportProjectionSupportAttribute : Attribute + public sealed class ExportSupportingAttribute : Attribute { - public ExportProjectionSupportAttribute(params ExportProjectionSupport[] types) + public ExportSupportingAttribute(params ExportSupport[] types) { Types = types; } - public ExportProjectionSupport[] Types { get; set; } + public ExportSupport[] Types { get; set; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs index a79efba103..c7f6827bb0 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs @@ -30,10 +30,10 @@ public static bool IsValid(this Provider provider) /// Export provider /// The type to check /// true provider supports type, false provider does not support type. - public static bool Supports(this Provider provider, ExportProjectionSupport type) + public static bool Supports(this Provider provider, ExportSupport type) { if (provider != null) - return provider.Metadata.ExportProjectionSupport.Contains(type); + return provider.Metadata.ExportSupport.Contains(type); return false; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs index adda26f97b..571ae4a24c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs @@ -13,7 +13,7 @@ namespace SmartStore.Services.DataExchange.ExportProvider [SystemName("Exports.SmartStoreNetProductXml")] [FriendlyName("SmartStore.NET XML product export")] [IsHidden(true)] - [ExportProjectionSupport(ExportProjectionSupport.HighDataDepth)] + [ExportSupporting(ExportSupport.HighDataDepth)] public class ExportProductXmlProvider : IExportProvider { public static string SystemName diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index 38cde6c262..c5125f4df8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -139,7 +139,7 @@ public virtual ExportProfile InsertExportProfile(Provider provi { if (cloneProfile == null) { - if (provider.Supports(ExportProjectionSupport.CreateInitialPublicDeployment)) + if (provider.Supports(ExportSupport.CreateInitialPublicDeployment)) { profile.Deployments.Add(new ExportDeployment { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 94f1f64e30..e431e6e434 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -4,8 +4,10 @@ using System.IO; using System.IO.Compression; using System.Linq; +using System.Linq.Expressions; using System.Net; using System.Net.Http; +using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -26,6 +28,7 @@ using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Messages; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Seo; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Email; @@ -84,6 +87,7 @@ public class ExportProfileTask : ITask private IShipmentService _shipmentService; private IProductTemplateService _productTemplateService; private ILocalizedEntityService _localizedEntityService; + private IUrlRecordService _urlRecordService; private void InitDependencies(TaskExecutionContext context) { @@ -116,12 +120,66 @@ private void InitDependencies(TaskExecutionContext context) _shipmentService = context.Resolve(); _productTemplateService = context.Resolve(); _localizedEntityService = context.Resolve(); + _urlRecordService = context.Resolve(); } #endregion #region Utilities + private List GetLocalized(ExportProfileTaskContext ctx, T entity, params Expression>[] keySelectors) + where T : BaseEntity, ILocalizedEntity + { + if (ctx.Languages.Count <= 1 || !ctx.Supporting[ExportSupport.HighDataDepth]) + return null; + + var localized = new List(); + + var localeKeyGroup = typeof(T).Name; + var isSlugSupported = typeof(ISlugSupported).IsAssignableFrom(typeof(T)); + + foreach (var language in ctx.Languages) + { + var languageCulture = language.Value.LanguageCulture.EmptyNull().ToLower(); + + // add SeName + if (isSlugSupported) + { + var value = _urlRecordService.GetActiveSlug(entity.Id, localeKeyGroup, language.Value.Id); + if (value.HasValue()) + { + dynamic exp = new ExpandoObject(); + exp.Culture = languageCulture; + exp.LocaleKey = "SeName"; + exp.LocaleValue = value; + + localized.Add(exp as ExpandoObject); + } + } + + foreach (var keySelector in keySelectors) + { + var member = keySelector.Body as MemberExpression; + var propInfo = member.Member as PropertyInfo; + string localeKey = propInfo.Name; + var value = _localizedEntityService.GetLocalizedValue(language.Value.Id, entity.Id, localeKeyGroup, localeKey); + + // we better not export empty values. the risk is to high that they are imported and unnecessary fill databases. + if (value.HasValue()) + { + dynamic exp = new ExpandoObject(); + exp.Culture = languageCulture; + exp.LocaleKey = localeKey; + exp.LocaleValue = value; + + localized.Add(exp as ExpandoObject); + } + } + } + + return localized; + } + private ProductSearchContext GetProductSearchContext(ExportProfileTaskContext ctx, int pageIndex, int pageSize) { var searchContext = new ProductSearchContext @@ -569,6 +627,8 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Currency currency) expando.UpdatedOnUtc = currency.UpdatedOnUtc; expando.DomainEndings = currency.DomainEndings; + expando._Localized = GetLocalized(ctx, currency, x => x.Name); + return expando as ExpandoObject; } @@ -613,6 +673,8 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Country country) expando.DisplayOrder = country.DisplayOrder; expando.LimitedToStores = country.LimitedToStores; + expando._Localized = GetLocalized(ctx, country, x => x.Name); + return expando as ExpandoObject; } @@ -650,6 +712,8 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Address address) sp.Published = address.StateProvince.Published; sp.DisplayOrder = address.StateProvince.DisplayOrder; + sp._Localized = GetLocalized(ctx, address.StateProvince, x => x.Name); + expando.StateProvince = sp as ExpandoObject; return expando as ExpandoObject; @@ -750,6 +814,8 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, DeliveryTime deliv expando.ColorHexValue = deliveryTime.ColorHexValue; expando.DisplayOrder = deliveryTime.DisplayOrder; + expando._Localized = GetLocalized(ctx, deliveryTime, x => x.Name); + return expando as ExpandoObject; } @@ -768,6 +834,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, QuantityUnit quant expando.DisplayOrder = quantityUnit.DisplayOrder; expando.IsDefault = quantityUnit.IsDefault; + expando._Localized = GetLocalized(ctx, quantityUnit, + x => x.Name, + x => x.Description); + return expando as ExpandoObject; } @@ -832,10 +902,16 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, ProductVariantAttr value.LinkedProductId = x.LinkedProductId; value.Quantity = x.Quantity; + value._Localized = GetLocalized(ctx, x, y => y.Name); + return value as ExpandoObject; }) .ToList(); + attribute._Localized = GetLocalized(ctx, pva.ProductAttribute, + x => x.Name, + x => x.Description); + expando.Attribute = attribute as ExpandoObject; return expando as ExpandoObject; @@ -896,6 +972,13 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Manufacturer manuf expando.CreatedOnUtc = manufacturer.CreatedOnUtc; expando.UpdatedOnUtc = manufacturer.UpdatedOnUtc; + expando._Localized = GetLocalized(ctx, manufacturer, + x => x.Name, + x => x.Description, + x => x.MetaKeywords, + x => x.MetaDescription, + x => x.MetaTitle); + return expando as ExpandoObject; } @@ -909,6 +992,7 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Category category) expando.Id = category.Id; expando.Name = category.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + expando.SeName = category.GetSeName(ctx.Projection.LanguageId ?? 0, true, false); expando.FullName = category.GetLocalized(x => x.FullName, ctx.Projection.LanguageId ?? 0, true, false); expando.Description = category.GetLocalized(x => x.Description, ctx.Projection.LanguageId ?? 0, true, false); expando.BottomDescription = category.GetLocalized(x => x.BottomDescription, ctx.Projection.LanguageId ?? 0, true, false); @@ -916,7 +1000,6 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Category category) expando.MetaKeywords = category.GetLocalized(x => x.MetaKeywords, ctx.Projection.LanguageId ?? 0, true, false); expando.MetaDescription = category.GetLocalized(x => x.MetaDescription, ctx.Projection.LanguageId ?? 0, true, false); expando.MetaTitle = category.GetLocalized(x => x.MetaTitle, ctx.Projection.LanguageId ?? 0, true, false); - expando.SeName = category.GetSeName(ctx.Projection.LanguageId ?? 0, true, false); expando.ParentCategoryId = category.ParentCategoryId; expando.PictureId = category.PictureId; expando.PageSize = category.PageSize; @@ -935,6 +1018,15 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Category category) expando.Alias = category.Alias; expando.DefaultViewMode = category.DefaultViewMode; + expando._Localized = GetLocalized(ctx, category, + x => x.Name, + x => x.FullName, + x => x.Description, + x => x.BottomDescription, + x => x.MetaKeywords, + x => x.MetaDescription, + x => x.MetaTitle); + return expando as ExpandoObject; } @@ -1044,6 +1136,15 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) expando.LowestAttributeCombinationPrice = product.LowestAttributeCombinationPrice; expando.IsEsd = product.IsEsd; + expando._Localized = GetLocalized(ctx, product, + x => x.Name, + x => x.ShortDescription, + x => x.FullDescription, + x => x.MetaKeywords, + x => x.MetaDescription, + x => x.MetaTitle, + x => x.BundleTitleText); + return expando as ExpandoObject; } @@ -1250,11 +1351,15 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, ProductSpecificati expando.SpecificationAttributeOption.Name = option.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); expando.SpecificationAttributeOption.DisplayOrder = option.DisplayOrder; + expando.SpecificationAttributeOption._Localized = GetLocalized(ctx, option, x => x.Name); + expando.SpecificationAttributeOption.SpecificationAttribute._Entity = option.SpecificationAttribute; expando.SpecificationAttributeOption.SpecificationAttribute.Id = option.SpecificationAttribute.Id; expando.SpecificationAttributeOption.SpecificationAttribute.Name = option.SpecificationAttribute.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); expando.SpecificationAttributeOption.SpecificationAttribute.DisplayOrder = option.SpecificationAttribute.DisplayOrder; + expando.SpecificationAttributeOption.SpecificationAttribute._Localized = GetLocalized(ctx, option.SpecificationAttribute, x => x.Name); + return expando as ExpandoObject; } @@ -1386,7 +1491,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ var productTemplate = ctx.ProductTemplates.FirstOrDefault(x => x.Key == product.ProductTemplateId); var pictureSize = _mediaSettings.ProductDetailsPictureSize; - if (ctx.Supporting[ExportProjectionSupport.MainPictureUrl] && ctx.Projection.PictureSize > 0) + if (ctx.Supporting[ExportSupport.ProjectionMainPictureUrl] && ctx.Projection.PictureSize > 0) pictureSize = ctx.Projection.PictureSize; var productPictures = ctx.ProductDataContext.ProductPictures.Load(product.Id); @@ -1540,7 +1645,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ #region high data depth - if (ctx.Supporting[ExportProjectionSupport.HighDataDepth]) + if (ctx.Supporting[ExportSupport.HighDataDepth]) { var productTags = ctx.ProductDataContext.ProductTags.Load(product.Id); var specificationAttributes = ctx.ProductDataContext.ProductSpecificationAttributes.Load(product.Id); @@ -1552,6 +1657,8 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ exp._Entity = x; exp.Id = x.Id; exp.Name = x.GetLocalized(y => y.Name, ctx.Projection.LanguageId ?? 0, true, false); + exp.SeName = x.GetSeName(ctx.Projection.LanguageId ?? 0); + exp._Localized = GetLocalized(ctx, x, y => y.Name); return exp as ExpandoObject; }) .ToList(); @@ -1588,6 +1695,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ exp.DisplayOrder = x.DisplayOrder; exp.CreatedOnUtc = x.CreatedOnUtc; exp.UpdatedOnUtc = x.UpdatedOnUtc; + exp._Localized = GetLocalized(ctx, x, y => y.Name, y => y.ShortDescription); return exp as ExpandoObject; }) .ToList(); @@ -1608,12 +1716,12 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ #region more attribute controlled data - if (ctx.Supporting[ExportProjectionSupport.Description]) + if (ctx.Supporting[ExportSupport.ProjectionDescription]) { PrepareProductDescription(ctx, expando, product); } - if (ctx.Supporting[ExportProjectionSupport.Brand]) + if (ctx.Supporting[ExportSupport.ProjectionBrand]) { string brand = null; var productManus = ctx.ProductDataContext.ProductManufacturers.Load(product.Id); @@ -1627,7 +1735,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ expando._Brand = brand; } - if (ctx.Supporting[ExportProjectionSupport.MainPictureUrl]) + if (ctx.Supporting[ExportSupport.ProjectionMainPictureUrl]) { if (productPictures != null && productPictures.Any()) expando._MainPictureUrl = _pictureService.GetPictureUrl(productPictures.First().Picture, ctx.Projection.PictureSize, storeLocation: ctx.Store.Url); @@ -1668,18 +1776,18 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ // navigation properties GetDeliveryTimeAndQuantityUnit(ctx, exp, product.DeliveryTimeId, product.QuantityUnitId); - if (ctx.Supporting[ExportProjectionSupport.UseOwnProductNo] && product.ManufacturerPartNumber.IsEmpty()) + if (ctx.Supporting[ExportSupport.ProjectionUseOwnProductNo] && product.ManufacturerPartNumber.IsEmpty()) { exp.ManufacturerPartNumber = product.Sku; } - if (ctx.Supporting[ExportProjectionSupport.ShippingTime]) + if (ctx.Supporting[ExportSupport.ProjectionShippingTime]) { dynamic deliveryTime = exp.DeliveryTime; exp._ShippingTime = (deliveryTime == null ? ctx.Projection.ShippingTime : deliveryTime.Name); } - if (ctx.Supporting[ExportProjectionSupport.ShippingCosts]) + if (ctx.Supporting[ExportSupport.ProjectionShippingCosts]) { exp._FreeShippingThreshold = ctx.Projection.FreeShippingThreshold; @@ -1689,7 +1797,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ exp._ShippingCosts = ctx.Projection.ShippingCosts; } - if (ctx.Supporting[ExportProjectionSupport.OldPrice]) + if (ctx.Supporting[ExportSupport.ProjectionOldPrice]) { if (product.OldPrice != decimal.Zero && product.OldPrice != (decimal)exp.Price && !(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)) { @@ -1709,7 +1817,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ } } - if (ctx.Supporting[ExportProjectionSupport.SpecialPrice]) + if (ctx.Supporting[ExportSupport.ProjectionSpecialPrice]) { exp._SpecialPrice = null; exp._RegularPrice = null; // price if a special price would not exist @@ -1825,6 +1933,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Order private List Init(ExportProfileTaskContext ctx) { + // Init base things that are even required for preview. Init all other things in ExportCoreOuter. List result = null; if (ctx.Projection.CurrencyId.HasValue) @@ -1843,6 +1952,7 @@ private List Init(ExportProfileTaskContext ctx) ctx.ProjectionLanguage = _services.WorkContext.WorkingLanguage; ctx.Stores = _services.StoreService.GetAllStores().ToDictionary(x => x.Id, x => x); + ctx.Languages = _languageService.GetAllLanguages(true).ToDictionary(x => x.Id, x => x); if (!ctx.IsPreview && ctx.Profile.PerStore) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs index 6c96b63c22..aa719d5be5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs @@ -43,8 +43,8 @@ public ExportProfileTaskContext( TotalRecords = totalRecords; PreviewData = previewData; - Supporting = Enum.GetValues(typeof(ExportProjectionSupport)) - .Cast() + Supporting = Enum.GetValues(typeof(ExportSupport)) + .Cast() .ToDictionary(x => x, x => Provider.Supports(x)); FolderContent = FileSystemHelper.TempDir(@"Profile\Export\{0}\Content".FormatInvariant(profile.FolderName)); @@ -86,7 +86,7 @@ public bool IsPreview public TaskExecutionContext TaskContext { get; private set; } public ExportProfile Profile { get; private set; } public Provider Provider { get; private set; } - public Dictionary Supporting { get; private set; } + public Dictionary Supporting { get; private set; } public ExportFilter Filter { get; private set; } public ExportProjection Projection { get; private set; } @@ -133,6 +133,7 @@ public string[] GetDeploymentFiles(ExportDeployment deployment) public Dictionary DeliveryTimes { get; set; } public Dictionary QuantityUnits { get; set; } public Dictionary Stores { get; set; } + public Dictionary Languages { get; set; } public Dictionary Countries { get; set; } public Dictionary ProductTemplates { get; set; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs index 67690802ea..0d0b50c27c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs @@ -17,6 +17,22 @@ public ExportXmlHelper(XmlWriter writer, CultureInfo culture) _culture = culture; } + public void WriteLocalized(dynamic parentNode) + { + if (parentNode == null || parentNode._Localized == null) + return; + + _writer.WriteStartElement("Localized"); + foreach (dynamic item in parentNode._Localized) + { + _writer.WriteStartElement((string)item.LocaleKey); + _writer.WriteAttributeString("culture", (string)item.Culture); + _writer.WriteString(((string)item.LocaleValue).RemoveInvalidXmlChars()); + _writer.WriteEndElement(); // item.LocaleKey + } + _writer.WriteEndElement(); // Localized + } + public void WriteAddress(dynamic address, string node) { if (address == null) @@ -61,6 +77,9 @@ public void WriteAddress(dynamic address, string node) _writer.Write("Published", ((bool)country.Published).ToString()); _writer.Write("DisplayOrder", ((int)country.DisplayOrder).ToString()); _writer.Write("LimitedToStores", ((bool)country.LimitedToStores).ToString()); + + WriteLocalized(country); + _writer.WriteEndElement(); // Country } @@ -75,6 +94,9 @@ public void WriteAddress(dynamic address, string node) _writer.Write("Abbreviation", (string)stateProvince.Abbreviation); _writer.Write("Published", ((bool)stateProvince.Published).ToString()); _writer.Write("DisplayOrder", ((int)stateProvince.DisplayOrder).ToString()); + + WriteLocalized(stateProvince); + _writer.WriteEndElement(); // StateProvince } @@ -107,6 +129,8 @@ public void WriteCurrency(dynamic currency, string node) _writer.Write("UpdatedOnUtc", ((DateTime)currency.UpdatedOnUtc).ToString(_culture)); _writer.Write("DomainEndings", (string)currency.DomainEndings); + WriteLocalized(currency); + if (node.HasValue()) { _writer.WriteEndElement(); @@ -158,6 +182,8 @@ public void WriteDeliveryTime(dynamic deliveryTime, string node) _writer.Write("ColorHexValue", (string)deliveryTime.ColorHexValue); _writer.Write("DisplayOrder", ((int)deliveryTime.DisplayOrder).ToString()); + WriteLocalized(deliveryTime); + if (node.HasValue()) { _writer.WriteEndElement(); @@ -181,6 +207,8 @@ public void WriteQuantityUnit(dynamic quantityUnit, string node) _writer.Write("DisplayOrder", ((int)quantityUnit.DisplayOrder).ToString()); _writer.Write("IsDefault", ((bool)quantityUnit.IsDefault).ToString()); + WriteLocalized(quantityUnit); + if (node.HasValue()) { _writer.WriteEndElement(); @@ -203,6 +231,7 @@ public void WritePicture(dynamic picture, string node) _writer.Write("ThumbImageUrl", (string)picture._ThumbImageUrl); _writer.Write("ImageUrl", (string)picture._ImageUrl); _writer.Write("FullSizeImageUrl", (string)picture._FullSizeImageUrl); + _writer.Write("FileName", (string)picture._FileName); if (node.HasValue()) { @@ -252,6 +281,8 @@ public void WriteCategory(dynamic category, string node) WritePicture(category.Picture, "Picture"); + WriteLocalized(category); + if (node.HasValue()) { _writer.WriteEndElement(); @@ -289,6 +320,8 @@ public void WriteManufacturer(dynamic manufacturer, string node) _writer.Write("CreatedOnUtc", ((DateTime)manufacturer.CreatedOnUtc).ToString(_culture)); _writer.Write("UpdatedOnUtc", ((DateTime)manufacturer.UpdatedOnUtc).ToString(_culture)); + WriteLocalized(manufacturer); + WritePicture(manufacturer.Picture, "Picture"); if (node.HasValue()) @@ -418,6 +451,8 @@ public void WriteProduct(dynamic product, string node) _writer.Write("LowestAttributeCombinationPrice", lowestAttributeCombinationPrice.HasValue ? lowestAttributeCombinationPrice.Value.ToString(_culture) : ""); _writer.Write("IsEsd", ((bool)product.IsEsd).ToString()); + WriteLocalized(product); + WriteDeliveryTime(product.DeliveryTime, "DeliveryTime"); WriteQuantityUnit(product.QuantityUnit, "QuantityUnit"); @@ -469,6 +504,10 @@ public void WriteProduct(dynamic product, string node) _writer.WriteStartElement("ProductTag"); _writer.Write("Id", ((int)tag.Id).ToString()); _writer.Write("Name", (string)tag.Name); + _writer.Write("SeName", (string)tag.SeName); + + WriteLocalized(tag); + _writer.WriteEndElement(); // ProductTag } _writer.WriteEndElement(); // ProductTags @@ -489,6 +528,9 @@ public void WriteProduct(dynamic product, string node) _writer.Write("Alias", (string)pa.Attribute.Alias); _writer.Write("Name", (string)pa.Attribute.Name); _writer.Write("Description", (string)pa.Attribute.Description); + + WriteLocalized(pa.Attribute); + _writer.WriteEndElement(); // Attribute _writer.WriteStartElement("AttributeValues"); @@ -506,6 +548,9 @@ public void WriteProduct(dynamic product, string node) _writer.Write("ValueTypeId", ((int)value.ValueTypeId).ToString()); _writer.Write("LinkedProductId", ((int)value.LinkedProductId).ToString()); _writer.Write("Quantity", ((int)value.Quantity).ToString()); + + WriteLocalized(value); + _writer.WriteEndElement(); // AttributeValue } _writer.WriteEndElement(); // AttributeValues @@ -624,10 +669,15 @@ public void WriteProduct(dynamic product, string node) _writer.Write("DisplayOrder", ((int)option.DisplayOrder).ToString()); _writer.Write("Name", (string)option.Name); + WriteLocalized(option); + _writer.WriteStartElement("SpecificationAttribute"); _writer.Write("Id", ((int)option.SpecificationAttribute.Id).ToString()); _writer.Write("Name", (string)option.SpecificationAttribute.Name); _writer.Write("DisplayOrder", ((int)option.SpecificationAttribute.DisplayOrder).ToString()); + + WriteLocalized(option.SpecificationAttribute); + _writer.WriteEndElement(); // SpecificationAttribute _writer.WriteEndElement(); // SpecificationAttributeOption @@ -657,6 +707,9 @@ public void WriteProduct(dynamic product, string node) _writer.Write("DisplayOrder", ((int)bundleItem.DisplayOrder).ToString()); _writer.Write("CreatedOnUtc", ((DateTime)bundleItem.CreatedOnUtc).ToString(_culture)); _writer.Write("UpdatedOnUtc", ((DateTime)bundleItem.UpdatedOnUtc).ToString(_culture)); + + WriteLocalized(bundleItem); + _writer.WriteEndElement(); // ProductBundleItem } _writer.WriteEndElement(); // ProductBundleItems diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs index 82918f9e14..88ce13fb7c 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs @@ -19,14 +19,14 @@ namespace SmartStore.GoogleMerchantCenter.Providers [SystemName("Feeds.GoogleMerchantCenterProductXml")] [FriendlyName("Google Merchant Center XML product feed")] [DisplayOrder(1)] - [ExportProjectionSupport( - ExportProjectionSupport.CreateInitialPublicDeployment, - ExportProjectionSupport.AttributeCombinationAsProduct, - ExportProjectionSupport.Description, - ExportProjectionSupport.UseOwnProductNo, - ExportProjectionSupport.Brand, - ExportProjectionSupport.MainPictureUrl, - ExportProjectionSupport.SpecialPrice)] + [ExportSupporting( + ExportSupport.CreateInitialPublicDeployment, + ExportSupport.AttributeCombinationAsProduct, + ExportSupport.ProjectionDescription, + ExportSupport.ProjectionUseOwnProductNo, + ExportSupport.ProjectionBrand, + ExportSupport.ProjectionMainPictureUrl, + ExportSupport.ProjectionSpecialPrice)] public class ProductExportXmlProvider : IExportProvider { private const string _googleNamespace = "http://base.google.com/ns/1.0"; diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index 749e07c047..c250e6858f 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -774,7 +774,7 @@ protected override void Load(ContainerBuilder builder) var isConfigurable = typeof(IConfigurable).IsAssignableFrom(type); var isEditable = typeof(IUserEditable).IsAssignableFrom(type); var isHidden = GetIsHidden(type); - var exportProjectionSupport = GetExportProjectionSupport(type); + var exportSupport = GetExportSupport(type); var registration = builder.RegisterType(type).Named(systemName).InstancePerRequest().PropertiesAutowired(PropertyWiringOptions.None); registration.WithMetadata(m => @@ -791,7 +791,7 @@ protected override void Load(ContainerBuilder builder) m.For(em => em.IsConfigurable, isConfigurable); m.For(em => em.IsEditable, isEditable); m.For(em => em.IsHidden, isHidden); - m.For(em => em.ExportProjectionSupport, exportProjectionSupport); + m.For(em => em.ExportSupport, exportSupport); }); // register specific provider type @@ -869,16 +869,16 @@ private bool GetIsHidden(Type type) return false; } - private ExportProjectionSupport[] GetExportProjectionSupport(Type type) + private ExportSupport[] GetExportSupport(Type type) { - var attr = type.GetAttribute(false); + var attr = type.GetAttribute(false); if (attr != null && attr.Types != null) { return attr.Types; } - return new ExportProjectionSupport[0]; + return new ExportSupport[0]; } private Tuple GetFriendlyName(Type type, PluginDescriptor descriptor) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 5501bf0acd..005e384ee9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -171,7 +171,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile // get and initialize provider specific configuration data if (provider != null) { - model.Provider.ProjectionSupport = provider.Metadata.ExportProjectionSupport; + model.Provider.Supporting = provider.Metadata.ExportSupport; try { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 5bcd1f7eb1..a61989d0d5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -88,7 +88,7 @@ public class ProviderModel public Type ConfigDataType { get; set; } public object ConfigData { get; set; } - public ExportProjectionSupport[] ProjectionSupport { get; set; } + public ExportSupport[] Supporting { get; set; } [SmartResourceDisplayName("Common.Image")] public string ThumbnailUrl { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml index 40fad51c7e..5e304f8953 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml @@ -47,7 +47,7 @@ @if (Model.Provider.EntityType == ExportEntityType.Product) { - if (Model.Provider.ProjectionSupport.Contains(ExportProjectionSupport.AttributeCombinationAsProduct)) + if (Model.Provider.Supporting.Contains(ExportSupport.AttributeCombinationAsProduct)) { @@ -88,7 +88,7 @@ - if (Model.Provider.ProjectionSupport.Contains(ExportProjectionSupport.ShippingTime)) + if (Model.Provider.Supporting.Contains(ExportSupport.ProjectionShippingTime)) { @@ -101,7 +101,7 @@ } - if (Model.Provider.ProjectionSupport.Contains(ExportProjectionSupport.ShippingCosts)) + if (Model.Provider.Supporting.Contains(ExportSupport.ProjectionShippingCosts)) { @@ -123,7 +123,7 @@ } - if (Model.Provider.ProjectionSupport.Contains(ExportProjectionSupport.MainPictureUrl)) + if (Model.Provider.Supporting.Contains(ExportSupport.ProjectionMainPictureUrl)) { @@ -136,7 +136,7 @@ } - if (Model.Provider.ProjectionSupport.Contains(ExportProjectionSupport.Brand)) + if (Model.Provider.Supporting.Contains(ExportSupport.ProjectionBrand)) { @@ -149,7 +149,7 @@ } - if (Model.Provider.ProjectionSupport.Contains(ExportProjectionSupport.Description)) + if (Model.Provider.Supporting.Contains(ExportSupport.ProjectionDescription)) { From 936db7119daeee35839daaa9c4bc0238eafeb688 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 2 Oct 2015 15:51:33 +0200 Subject: [PATCH 104/874] Convert product XML export to support new export framework (final part) --- .../201509150931528_ExportFramework2.cs | 1 + .../Catalog/IProductService.cs | 2 +- .../Catalog/PriceCalculationService.cs | 8 +- .../Catalog/ProductService.cs | 3 +- .../DataExchange/ExportExecuteResult.cs | 6 ++ .../ExportProvider/ExportOrderXlsxProvider.cs | 6 +- .../ExportProvider/ExportOrderXmlProvider.cs | 6 +- .../ExportProductXmlProvider.cs | 6 +- .../ExportTask/ExportProfileTask.cs | 90 ++++++++++++------- .../DataExchange/ExportXmlHelper.cs | 4 +- .../Controllers/OrderController.cs | 2 +- .../Controllers/ProductController.cs | 77 ++++------------ 12 files changed, 104 insertions(+), 107 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index f180e7b0c5..d320e4a6ff 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -38,6 +38,7 @@ public void Seed(SmartObjectContext context) public void MigrateLocaleResources(LocaleResourcesBuilder builder) { builder.AddOrUpdate("Common.Example", "Example", "Beispiel"); + builder.AddOrUpdate("Admin.Common.Selected", "Selected", "Ausgewhlte"); builder.AddOrUpdate("Admin.Common.FilesDeleted", "{0} files were deleted", diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs index acaafcf70e..69a8c2254b 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs @@ -304,7 +304,7 @@ public partial interface IProductService /// Filter tier prices by customer /// Filter tier prices by store /// Map of tier prices - Multimap GetTierPrices(int[] productIds, Customer customer = null, int storeId = 0); + Multimap GetTierPricesByProductIds(int[] productIds, Customer customer = null, int storeId = 0); /// /// Inserts a tier price diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs index dea35afd41..0f916e0e70 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs @@ -154,7 +154,8 @@ protected virtual IList GetAllowedDiscounts(Product product, Customer } else { - tierPrices = context.TierPrices.Load(product.Id); + tierPrices = context.TierPrices.Load(product.Id) + .RemoveDuplicatedQuantities(); } if (tierPrices == null) @@ -279,7 +280,7 @@ public virtual PriceCalculationContext CreatePriceCalculationContext(IEnumerable var context = new PriceCalculationContext(products, x => _productAttributeService.GetProductVariantAttributesByProductIds(x, null), x => _productAttributeService.GetProductVariantAttributeCombinations(x), - x => _productService.GetTierPrices(x, _services.WorkContext.CurrentCustomer, _services.StoreContext.CurrentStore.Id), + x => _productService.GetTierPricesByProductIds(x, _services.WorkContext.CurrentCustomer, _services.StoreContext.CurrentStore.Id), x => _categoryService.GetProductCategoriesByProductIds(x, true), x => _productService.GetAppliedDiscountsByProductIds(x) ); @@ -480,7 +481,8 @@ public virtual decimal GetLowestPrice(Product product, PriceCalculationContext c if (!displayFromMessage && product.HasTierPrices && !isBundlePerItemPricing) { - var tierPrices = context.TierPrices.Load(product.Id); + var tierPrices = context.TierPrices.Load(product.Id) + .RemoveDuplicatedQuantities(); displayFromMessage = (tierPrices.Count > 0 && !(tierPrices.Count == 1 && tierPrices.First().Quantity <= 1)); } diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs index a341dcc3c7..ada4880ecc 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs @@ -1773,7 +1773,7 @@ public virtual TierPrice GetTierPriceById(int tierPriceId) return tierPrice; } - public virtual Multimap GetTierPrices(int[] productIds, Customer customer = null, int storeId = 0) + public virtual Multimap GetTierPricesByProductIds(int[] productIds, Customer customer = null, int storeId = 0) { Guard.ArgumentNotNull(() => productIds); @@ -1793,7 +1793,6 @@ where productIds.Contains(x.ProductId) list = list.FilterForCustomer(customer).ToList(); var map = list - .RemoveDuplicatedQuantities() .ToMultimap(x => x.ProductId, x => x); return map; diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteResult.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteResult.cs index ead41dbccf..6bb8a87429 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteResult.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteResult.cs @@ -37,6 +37,12 @@ public bool Succeeded [XmlIgnore] public string FileFolder { get; set; } + /// + /// Suggested download file name + /// + [XmlIgnore] + public string DownloadFileName { get; set; } + [Serializable] public class ExportFileInfo { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs index 12fb46ac15..47641ec41f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs @@ -9,8 +9,8 @@ namespace SmartStore.Services.DataExchange.ExportProvider { - [SystemName("Exports.SmartStoreNetOrderXlsx")] - [FriendlyName("SmartStore.NET Excel order export")] + [SystemName("Exports.SmartStoreOrderXlsx")] + [FriendlyName("SmartStore Excel order export")] [IsHidden(true)] public class ExportOrderXlsxProvider : IExportProvider { @@ -90,7 +90,7 @@ private void WriteCell(ExcelWorksheet worksheet, int row, ref int column, object public static string SystemName { - get { return "Exports.SmartStoreNetOrderXlsx"; } + get { return "Exports.SmartStoreOrderXlsx"; } } public ExportConfigurationInfo ConfigurationInfo diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs index 7a608dd976..4dbfd8ee28 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs @@ -10,14 +10,14 @@ namespace SmartStore.Services.DataExchange.ExportProvider { - [SystemName("Exports.SmartStoreNetOrderXml")] - [FriendlyName("SmartStore.NET XML order export")] + [SystemName("Exports.SmartStoreOrderXml")] + [FriendlyName("SmartStore XML order export")] [IsHidden(true)] public class ExportOrderXmlProvider : IExportProvider { public static string SystemName { - get { return "Exports.SmartStoreNetOrderXml"; } + get { return "Exports.SmartStoreOrderXml"; } } public ExportConfigurationInfo ConfigurationInfo diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs index 571ae4a24c..a2cdefcaeb 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs @@ -10,15 +10,15 @@ namespace SmartStore.Services.DataExchange.ExportProvider { - [SystemName("Exports.SmartStoreNetProductXml")] - [FriendlyName("SmartStore.NET XML product export")] + [SystemName("Exports.SmartStoreProductXml")] + [FriendlyName("SmartStore XML product export")] [IsHidden(true)] [ExportSupporting(ExportSupport.HighDataDepth)] public class ExportProductXmlProvider : IExportProvider { public static string SystemName { - get { return "Exports.SmartStoreNetProductXml"; } + get { return "Exports.SmartStoreProductXml"; } } public ExportConfigurationInfo ConfigurationInfo diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index e431e6e434..5c1f46ee63 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -177,7 +177,7 @@ private List GetLocalized(ExportProfileTaskContext ctx, T enti } } - return localized; + return (localized.Count == 0 ? null : localized); } private ProductSearchContext GetProductSearchContext(ExportProfileTaskContext ctx, int pageIndex, int pageSize) @@ -942,6 +942,8 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, ProductVariantAttr expando.DeliveryTimeId = pvac.DeliveryTimeId; expando.IsActive = pvac.IsActive; + GetDeliveryTimeAndQuantityUnit(ctx, expando, pvac.DeliveryTimeId, pvac.QuantityUnitId); + return expando as ExpandoObject; } @@ -1333,6 +1335,8 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, ProductSpecificati if (psa == null) return null; + var option = psa.SpecificationAttributeOption; + dynamic expando = new ExpandoObject(); expando._Entity = psa; @@ -1343,22 +1347,23 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, ProductSpecificati expando.ShowOnProductPage = psa.ShowOnProductPage; expando.DisplayOrder = psa.DisplayOrder; - var option = psa.SpecificationAttributeOption; - - expando.SpecificationAttributeOption._Entity = option; - expando.SpecificationAttributeOption.Id = option.Id; - expando.SpecificationAttributeOption.SpecificationAttributeId = option.SpecificationAttributeId; - expando.SpecificationAttributeOption.Name = option.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - expando.SpecificationAttributeOption.DisplayOrder = option.DisplayOrder; + dynamic expAttribute = new ExpandoObject(); + expAttribute._Entity = option.SpecificationAttribute; + expAttribute.Id = option.SpecificationAttribute.Id; + expAttribute.Name = option.SpecificationAttribute.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + expAttribute.DisplayOrder = option.SpecificationAttribute.DisplayOrder; + expAttribute._Localized = GetLocalized(ctx, option.SpecificationAttribute, x => x.Name); - expando.SpecificationAttributeOption._Localized = GetLocalized(ctx, option, x => x.Name); + dynamic expOption = new ExpandoObject(); + expOption._Entity = option; + expOption.Id = option.Id; + expOption.SpecificationAttributeId = option.SpecificationAttributeId; + expOption.Name = option.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + expOption.DisplayOrder = option.DisplayOrder; + expOption._Localized = GetLocalized(ctx, option, x => x.Name); + expOption.SpecificationAttribute = expAttribute; - expando.SpecificationAttributeOption.SpecificationAttribute._Entity = option.SpecificationAttribute; - expando.SpecificationAttributeOption.SpecificationAttribute.Id = option.SpecificationAttribute.Id; - expando.SpecificationAttributeOption.SpecificationAttribute.Name = option.SpecificationAttribute.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - expando.SpecificationAttributeOption.SpecificationAttribute.DisplayOrder = option.SpecificationAttribute.DisplayOrder; - - expando.SpecificationAttributeOption.SpecificationAttribute._Localized = GetLocalized(ctx, option.SpecificationAttribute, x => x.Name); + expando.SpecificationAttributeOption = expOption; return expando as ExpandoObject; } @@ -1410,14 +1415,14 @@ private List GetProducts(ExportProfileTaskContext ctx, int pageIndex) ctx.ProductDataContext = new ExportProductDataContext(products, x => _productAttributeService.GetProductVariantAttributesByProductIds(x, null), x => _productAttributeService.GetProductVariantAttributeCombinations(x), - x => _productService.GetTierPrices(x, ctx.ProjectionCustomer, ctx.Store.Id), + x => _productService.GetTierPricesByProductIds(x, (ctx.Projection.CurrencyId ?? 0) != 0 ? ctx.ProjectionCustomer : null, ctx.Store.Id), x => _categoryService.GetProductCategoriesByProductIds(x), x => _manufacturerService.GetProductManufacturersByProductIds(x), x => _productService.GetProductPicturesByProductIds(x), x => _productService.GetProductTagsByProductIds(x), x => _productService.GetAppliedDiscountsByProductIds(x), x => _productService.GetProductSpecificationAttributesByProductIds(x), - x => _productService.GetBundleItemsByProductIds(x) + x => _productService.GetBundleItemsByProductIds(x, true) ); SetProgress(ctx, products.Count); @@ -1587,8 +1592,6 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ dynamic exp = ToExpando(ctx, x); var assignedPictures = new List(); - GetDeliveryTimeAndQuantityUnit(ctx, expando, x.DeliveryTimeId, x.QuantityUnitId); - foreach (int pictureId in x.GetAssignedPictureIds()) { var assignedPicture = productPictures.FirstOrDefault(y => y.PictureId == pictureId); @@ -1606,7 +1609,8 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ if (product.HasTierPrices) { - var tierPrices = ctx.ProductDataContext.TierPrices.Load(product.Id); + var tierPrices = ctx.ProductDataContext.TierPrices.Load(product.Id) + .RemoveDuplicatedQuantities(); expando.TierPrices = tierPrices .Select(x => @@ -1674,10 +1678,6 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ expando.ProductBundleItems = bundleItems .Select(x => { - var name = x.GetLocalized(y => y.Name, ctx.Projection.LanguageId ?? 0, true, false); - if (name.IsEmpty()) - name = (string)expando.Name; - dynamic exp = new ExpandoObject(); exp._Entity = x; exp.Id = x.Id; @@ -1686,7 +1686,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ exp.Quantity = x.Quantity; exp.Discount = x.Discount; exp.DiscountPercentage = x.DiscountPercentage; - exp.Name = name; + exp.Name = x.GetLocalized(y => y.Name, ctx.Projection.LanguageId ?? 0, true, false); exp.ShortDescription = x.GetLocalized(y => y.ShortDescription, ctx.Projection.LanguageId ?? 0, true, false); exp.FilterAttributes = x.FilterAttributes; exp.HideThumbnail = x.HideThumbnail; @@ -2362,13 +2362,10 @@ public static FileStreamResult Export(string providerSystemName, string selected var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read); var result = new FileStreamResult(stream, MimeTypes.MapNameToMimeType(fileName)); - if (downloadFileName.IsEmpty()) - downloadFileName = providerSystemName; - - if (selectedEntityIds.HasValue() && !selectedEntityIds.Contains(",")) - downloadFileName = string.Concat(downloadFileName, "-", selectedEntityIds); - - result.FileDownloadName = downloadFileName.ToValidFileName() + Path.GetExtension(filePath); + if (downloadFileName.HasValue()) + result.FileDownloadName = downloadFileName.ToValidFileName() + Path.GetExtension(filePath); + else + result.FileDownloadName = task.Result.DownloadFileName; return result; } @@ -2431,6 +2428,35 @@ public ExportExecuteResult Execute(string providerSystemName, IComponentContext ExportCoreOuter(ctx); + if (ctx.Result != null && ctx.Result.Succeeded && ctx.Result.Files.Count > 0) + { + string prefix = null; + string suffix = null; + var extension = Path.GetExtension(ctx.Result.Files.First().FileName); + + if (provider.Value.EntityType == ExportEntityType.Product) + prefix = _services.Localization.GetResource("Admin.Catalog.Products"); + else if (provider.Value.EntityType == ExportEntityType.Order) + prefix = _services.Localization.GetResource("Admin.Orders"); + else if (provider.Value.EntityType == ExportEntityType.Category) + prefix = _services.Localization.GetResource("Admin.Catalog.Categories"); + else if (provider.Value.EntityType == ExportEntityType.Manufacturer) + prefix = _services.Localization.GetResource("Admin.Catalog.Manufacturers"); + else if (provider.Value.EntityType == ExportEntityType.Customer) + prefix = _services.Localization.GetResource("Admin.Customers"); + else if (provider.Value.EntityType == ExportEntityType.NewsletterSubscriber) + prefix = _services.Localization.GetResource("Admin.Promotions.NewsLetterSubscriptions"); + else + prefix = provider.Value.EntityType.ToString(); + + if (selectedEntityIds.HasValue()) + suffix = (selectedEntityIds.Contains(",") ? _services.Localization.GetResource("Admin.Common.Selected") : selectedEntityIds); + else + suffix = _services.Localization.GetResource("Common.All"); + + ctx.Result.DownloadFileName = string.Concat(prefix, "-", suffix).ToLower().ToValidFileName() + extension; + } + return ctx.Result; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs index 0d0b50c27c..f65bfec11e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs @@ -483,11 +483,12 @@ public void WriteProduct(dynamic product, string node) _writer.WriteStartElement("TierPrices"); - foreach (dynamic tierPrice in product.ProductTags) + foreach (dynamic tierPrice in product.TierPrices) { int? customerRoleId = tierPrice.CustomerRoleId; _writer.WriteStartElement("TierPrice"); + _writer.Write("Id", ((int)tierPrice.Id).ToString()); _writer.Write("ProductId", ((int)tierPrice.ProductId).ToString()); _writer.Write("StoreId", ((int)tierPrice.StoreId).ToString()); _writer.Write("CustomerRoleId", customerRoleId.HasValue ? customerRoleId.Value.ToString() : ""); @@ -623,6 +624,7 @@ public void WriteProduct(dynamic product, string node) foreach (dynamic productCategory in product.ProductCategories) { _writer.WriteStartElement("ProductCategory"); + _writer.Write("Id", ((int)productCategory.Id).ToString()); _writer.Write("DisplayOrder", ((int)productCategory.DisplayOrder).ToString()); _writer.Write("IsFeaturedProduct", ((bool)productCategory.IsFeaturedProduct).ToString()); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs index e023a6f598..fec8cf03bd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs @@ -776,7 +776,7 @@ private void PrepareOrderAddressModel(OrderAddressModel model, Address address) private ActionResult Export(string providerSystemName, string selectedIds) { string error = null; - var fileStreamResult = ExportProfileTask.Export(providerSystemName, selectedIds, "smnet-orders", out error); + var fileStreamResult = ExportProfileTask.Export(providerSystemName, selectedIds, null, out error); if (fileStreamResult != null) return fileStreamResult; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 0aa775ff8f..bf2ffe38ee 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -29,6 +29,8 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; +using SmartStore.Services.DataExchange.ExportProvider; +using SmartStore.Services.DataExchange.ExportTask; using SmartStore.Services.Directory; using SmartStore.Services.Discounts; using SmartStore.Services.ExportImport; @@ -828,6 +830,19 @@ private void PrepareProductPictureThumbnailModel(ProductModel model, Product pro } } + private ActionResult Export(string providerSystemName, string selectedIds) + { + string error = null; + var fileStreamResult = ExportProfileTask.Export(providerSystemName, selectedIds, null, out error); + + if (fileStreamResult != null) + return fileStreamResult; + + NotifyError(string.Concat("

    ", T("Admin.Common.UnknownError"), "

    ", error.NaIfEmpty())); + + return RedirectToAction("List"); + } + #endregion Utitilies #region Methods @@ -2771,76 +2786,22 @@ public ActionResult EditProductTag(string btnId, string formId, ProductTagModel #region Export / Import + [Compress] public ActionResult ExportXmlAll() { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - try - { - using (var scope = new DbContextScope(_dbContext, autoDetectChanges: false, forceNoTracking: true)) - { - var searchContext = new ProductSearchContext - { - LanguageId = _workContext.WorkingLanguage.Id, - OrderBy = ProductSortingEnum.Position, - PageSize = 100, - ShowHidden = true - }; - - var stream = new MemoryStream(); - _exportManager.ExportProductsToXml(stream, searchContext); - - var result = new FileStreamResult(stream, MediaTypeNames.Text.Xml); - result.FileDownloadName = string.Format("products_{0}.xml", DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")); - - return result; - } - } - catch (Exception exc) - { - NotifyError(exc); - } - return RedirectToAction("List"); + return Export(ExportProductXmlProvider.SystemName, null); } - [HttpPost] + [HttpPost, Compress] public ActionResult ExportXmlSelected(string selectedIds) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - try - { - using (var scope = new DbContextScope(_dbContext, autoDetectChanges: false, forceNoTracking: true)) - { - var searchContext = new ProductSearchContext - { - LanguageId = _workContext.WorkingLanguage.Id, - OrderBy = ProductSortingEnum.Position, - PageSize = 100, - ShowHidden = true - }; - - searchContext.ProductIds = selectedIds - .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToList(); - - var stream = new MemoryStream(); - _exportManager.ExportProductsToXml(stream, searchContext); - - var result = new FileStreamResult(stream, MediaTypeNames.Text.Xml); - result.FileDownloadName = string.Format("products_{0}.xml", DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")); - - return result; - } - } - catch (Exception exc) - { - NotifyError(exc); - } - return RedirectToAction("List"); + return Export(ExportProductXmlProvider.SystemName, selectedIds); } public ActionResult ExportExcelAll() From 6d80ff90d09cfc9849fcc0e4af3d93653ba17562 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 5 Oct 2015 09:55:55 +0200 Subject: [PATCH 105/874] Convert Shop-Connector export to support new export framework --- .../Domain/DataExchange/ExportCore.cs | 63 +- .../Domain/DataExchange/ExportProjection.cs | 16 +- .../Extensions/EnumerableExtensions.cs | 41 +- .../201509150931528_ExportFramework2.cs | 10 + .../DataExchange/ExportService.cs | 31 +- .../ExportTask/ExportProfileTask.cs | 154 +++-- .../ExportTask/ExportProfileTaskContext.cs | 7 +- .../DataExchange/IExportService.cs | 4 +- .../ExportImport/ExportManager.cs | 563 ------------------ .../ExportImport/IExportManager.cs | 18 - .../Providers/ProductExportXmlProvider.cs | 3 +- .../Controllers/ExportController.cs | 2 + .../DataExchange/ExportProjectionModel.cs | 3 + .../Administration/Views/Export/Edit.cshtml | 5 + .../Views/Export/_Tab.Projection.cshtml | 22 +- 15 files changed, 249 insertions(+), 693 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs index 2f40f8cedd..578698e062 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs @@ -59,20 +59,68 @@ public enum ExportAttributeValueMerging } /// - /// Controls data processing supported by export provider + /// Controls data processing and projection items supported by export provider /// public enum ExportSupport { + /// + /// Whether to get more data like localized properties, product tags, bundle items etc. + /// HighDataDepth = 0, - AttributeCombinationAsProduct, + + /// + /// Whether to automatically create a file based public deployment when an export profile is created + /// CreateInitialPublicDeployment, + + /// + /// Whether to offer option to include\exclude grouped products + /// + ProjectionNoGroupedProducts, + + /// + /// Whether to offer option to export attribute combinations as products + /// + ProjectionAttributeCombinationAsProduct, + + /// + /// Whether to offer further options to manipulate the product description + /// ProjectionDescription, + + /// + /// Whether to offer option to enter a brand fallback + /// ProjectionBrand, + + /// + /// Whether to offer option to set a picture size and to get the URL of the main image + /// ProjectionMainPictureUrl, + + /// + /// Whether to use SKU as manufacturer part number if MPN is empty + /// ProjectionUseOwnProductNo, + + /// + /// Whether to offer option to enter a shipping time fallback + /// ProjectionShippingTime, + + /// + /// Whether to offer option to enter a shipping costs fallback and a free shipping threshold + /// ProjectionShippingCosts, + + /// + /// Whether to get the calculated old product price + /// ProjectionOldPrice, + + /// + /// Whether to get the calculated special and regular (ignoring special offers) price + /// ProjectionSpecialPrice } @@ -91,8 +139,19 @@ public enum ExportOrderStatusChange ///
    public enum ExportAbortion { + /// + /// No abortion. Go on with processing. + /// None = 0, + + /// + /// Break item processing but not the rest of the execution. Typically used for demo limitations. + /// Soft, + + /// + /// Break processing immediately + /// Hard } } diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs index c4bce8bc6c..abe979107d 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs @@ -4,16 +4,13 @@ namespace SmartStore.Core.Domain.DataExchange { + /// + /// Settings projected onto an export + /// + /// Note possible projection controlling: a) developer controls, b) merchant controls, c) developer controls what the merchant can control [Serializable] public class ExportProjection { - public ExportProjection() - { - RemoveCriticalCharacters = true; - CriticalCharacters = "¼,½,¾"; - PriceType = PriceDisplayType.PreSelectedPrice; - } - #region All entity types /// @@ -142,6 +139,11 @@ public ExportAttributeValueMerging AttributeCombinationValueMerging } } + /// + /// Whether to export grouped products + /// + public bool NoGroupedProducts { get; set; } + #endregion #region Order diff --git a/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs index e588f20f6a..ad2d91cd13 100644 --- a/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs @@ -198,26 +198,29 @@ public static string BuildQueryString(this NameValueCollection nvc, Encoding enc { var sb = new StringBuilder(); - foreach (string str in nvc) + if (nvc != null) { - if (sb.Length > 0) - sb.Append('&'); - - if (!encode) - sb.Append(str); - else if (encoding == null) - sb.Append(HttpUtility.UrlEncode(str)); - else - sb.Append(HttpUtility.UrlEncode(str, encoding)); - - sb.Append('='); - - if (!encode) - sb.Append(nvc[str]); - else if (encoding == null) - sb.Append(HttpUtility.UrlEncode(nvc[str])); - else - sb.Append(HttpUtility.UrlEncode(nvc[str], encoding)); + foreach (string str in nvc) + { + if (sb.Length > 0) + sb.Append('&'); + + if (!encode) + sb.Append(str); + else if (encoding == null) + sb.Append(HttpUtility.UrlEncode(str)); + else + sb.Append(HttpUtility.UrlEncode(str, encoding)); + + sb.Append('='); + + if (!encode) + sb.Append(nvc[str]); + else if (encoding == null) + sb.Append(HttpUtility.UrlEncode(nvc[str])); + else + sb.Append(HttpUtility.UrlEncode(nvc[str], encoding)); + } } return sb.ToString(); diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index d320e4a6ff..05eacb8d86 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -58,6 +58,16 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "The export provider does not explicit support any file type. Therefore, the export provider is responsible for futher deployment of export data.", "Der Export-Provider untersttzt keinen expliziten Dateityp. Fr eine weitere Bereitstellung der Exportdaten ist daher der Export-Provider verantwortlich."); + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.NoGroupedProducts", + "Do not export grouped products", + "Keine Gruppenprodukte exportieren", + "Specifies whether to export grouped products. If this option is deactivated, then the associated products will be deactivated.", + "Legt fest, ob Gruppenprodukte exportiert werden sollen. Ist diese Option deaktiviert, so werden die zur Gruppe gehrenden Produkte exportiert."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Projection.NoGroupedProducts.Note", + "In the preview group products are always displayed regardless of this option. Therefore, the displayed product number may vary from the number of exported products.", + "In der Vorschau werden Gruppenprodukte ungeachtet dieser Option immer angezeigt. Daher kann die dort angezeigte Produktanzahl von der Anzahl der exportierten Produkte abweichen."); + builder.Delete( "Plugins.Feed.Froogle.TaskEnabled", diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index c5125f4df8..250e0eecff 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -4,6 +4,7 @@ using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain; +using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Tasks; using SmartStore.Core.Events; @@ -47,7 +48,7 @@ public ExportService( #region Export profiles - public virtual ExportProfile CreateVolatileProfile(Provider provider, string providerConfigData, int storeId) + public virtual ExportProfile CreateVolatileProfile(Provider provider) { var name = provider.GetName(_localizationService); var seoName = SeoHelper.GetSeName(name, true, false).Replace("/", "").Replace("-", ""); @@ -61,19 +62,14 @@ public virtual ExportProfile CreateVolatileProfile(Provider pro ProviderSystemName = provider.Metadata.SystemName, Enabled = true, SchedulingTaskId = 0, - ProviderConfigData = providerConfigData, PerStore = false, CreateZipArchive = false, Cleanup = false, - ScheduleTask = null, + ScheduleTask = null, // volatile schedule task impossible cause of database accesses by core Deployments = new List() }; - var filter = new ExportFilter(); - filter.IsPublished = null; - filter.StoreId = storeId; - - profile.Filtering = XmlHelper.Serialize(filter); + // profile.Projection and profile.Filtering should be null here return profile; } @@ -113,11 +109,26 @@ public virtual ExportProfile InsertExportProfile(Provider provi if (cloneProfile == null) { + // what we do here is to preset typical settings for feed creation + // but on the other hand they may be untypical for generic data export\exchange + var projection = new ExportProjection + { + RemoveCriticalCharacters = true, + CriticalCharacters = "¼,½,¾", + PriceType = PriceDisplayType.PreSelectedPrice, + NoGroupedProducts = (provider.Supports(ExportSupport.ProjectionNoGroupedProducts) ? true : false) + }; + + var filter = new ExportFilter + { + IsPublished = true + }; + profile = new ExportProfile { FileNamePattern = _defaultFileNamePattern, - Filtering = XmlHelper.Serialize(new ExportFilter()), - Projection = XmlHelper.Serialize(new ExportProjection()) + Filtering = XmlHelper.Serialize(filter), + Projection = XmlHelper.Serialize(projection) }; } else diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 5c1f46ee63..362ee31bdb 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -180,44 +180,55 @@ private List GetLocalized(ExportProfileTaskContext ctx, T enti return (localized.Count == 0 ? null : localized); } - private ProductSearchContext GetProductSearchContext(ExportProfileTaskContext ctx, int pageIndex, int pageSize) + private IPagedList GetProductPage(ExportProfileTaskContext ctx, int pageIndex, int pageSize) { - var searchContext = new ProductSearchContext + if (ctx.QueryProducts == null) { - OrderBy = ProductSortingEnum.CreatedOn, - PageIndex = pageIndex, - PageSize = pageSize, - ProductIds = ctx.EntityIdsSelected, - StoreId = (ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId), - VisibleIndividuallyOnly = true, - PriceMin = ctx.Filter.PriceMinimum, - PriceMax = ctx.Filter.PriceMaximum, - IsPublished = ctx.Filter.IsPublished, - WithoutCategories = ctx.Filter.WithoutCategories, - WithoutManufacturers = ctx.Filter.WithoutManufacturers, - ManufacturerId = ctx.Filter.ManufacturerId ?? 0, - FeaturedProducts = ctx.Filter.FeaturedProducts, - ProductType = ctx.Filter.ProductType, - ProductTagId = ctx.Filter.ProductTagId ?? 0, - IdMin = ctx.Filter.IdMinimum ?? 0, - IdMax = ctx.Filter.IdMaximum ?? 0, - AvailabilityMinimum = ctx.Filter.AvailabilityMinimum, - AvailabilityMaximum = ctx.Filter.AvailabilityMaximum - }; + var searchContext = new ProductSearchContext + { + OrderBy = ProductSortingEnum.CreatedOn, + PageIndex = pageIndex, + PageSize = pageSize, + ProductIds = ctx.EntityIdsSelected, + StoreId = (ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId), + VisibleIndividuallyOnly = true, + PriceMin = ctx.Filter.PriceMinimum, + PriceMax = ctx.Filter.PriceMaximum, + IsPublished = ctx.Filter.IsPublished, + WithoutCategories = ctx.Filter.WithoutCategories, + WithoutManufacturers = ctx.Filter.WithoutManufacturers, + ManufacturerId = ctx.Filter.ManufacturerId ?? 0, + FeaturedProducts = ctx.Filter.FeaturedProducts, + ProductType = ctx.Filter.ProductType, + ProductTagId = ctx.Filter.ProductTagId ?? 0, + IdMin = ctx.Filter.IdMinimum ?? 0, + IdMax = ctx.Filter.IdMaximum ?? 0, + AvailabilityMinimum = ctx.Filter.AvailabilityMinimum, + AvailabilityMaximum = ctx.Filter.AvailabilityMaximum + }; - if (!ctx.Filter.IsPublished.HasValue) - searchContext.ShowHidden = true; + if (!ctx.Filter.IsPublished.HasValue) + searchContext.ShowHidden = true; - if (ctx.Filter.CategoryIds != null && ctx.Filter.CategoryIds.Length > 0) - searchContext.CategoryIds = ctx.Filter.CategoryIds.ToList(); + if (ctx.Filter.CategoryIds != null && ctx.Filter.CategoryIds.Length > 0) + searchContext.CategoryIds = ctx.Filter.CategoryIds.ToList(); - if (ctx.Filter.CreatedFrom.HasValue) - searchContext.CreatedFromUtc = _dateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _dateTimeHelper.CurrentTimeZone); + if (ctx.Filter.CreatedFrom.HasValue) + searchContext.CreatedFromUtc = _dateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _dateTimeHelper.CurrentTimeZone); - if (ctx.Filter.CreatedTo.HasValue) - searchContext.CreatedToUtc = _dateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedTo.Value, _dateTimeHelper.CurrentTimeZone); + if (ctx.Filter.CreatedTo.HasValue) + searchContext.CreatedToUtc = _dateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedTo.Value, _dateTimeHelper.CurrentTimeZone); - return searchContext; + var products = _productService.SearchProducts(searchContext); + + return products; + } + else + { + var products = new PagedList(ctx.QueryProducts, pageIndex, pageSize); + + return products; + } } private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic expando, Product product) @@ -317,12 +328,12 @@ private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic exp if (ctx.Projection.ConvertNetToGrossPrices) { decimal taxRate; - price = _taxService.GetProductPrice(product, price.Value, true, ctx.ProjectionCustomer, out taxRate); + price = _taxService.GetProductPrice(product, price.Value, true, ctx.ContextCustomer, out taxRate); } if (price != decimal.Zero) { - price = _currencyService.ConvertFromPrimaryStoreCurrency(price.Value, ctx.ProjectionCurrency, ctx.Store); + price = _currencyService.ConvertFromPrimaryStoreCurrency(price.Value, ctx.ContextCurrency, ctx.Store); } } return price; @@ -348,7 +359,7 @@ private decimal CalculatePrice(ExportProfileTaskContext ctx, Product product, bo } else if (ctx.Projection.PriceType.Value == PriceDisplayType.PriceWithoutDiscountsAndAttributes) { - price = _priceCalculationService.GetFinalPrice(product, null, ctx.ProjectionCustomer, decimal.Zero, false, 1, null, priceCalculationContext); + price = _priceCalculationService.GetFinalPrice(product, null, ctx.ContextCustomer, decimal.Zero, false, 1, null, priceCalculationContext); } } @@ -1376,9 +1387,7 @@ private List GetProducts(ExportProfileTaskContext ctx, int pageIndex) { var result = new List(); - var searchContext = GetProductSearchContext(ctx, pageIndex, ctx.PageSize); - - var products = _productService.SearchProducts(searchContext); + var products = GetProductPage(ctx, pageIndex, ctx.PageSize); foreach (var product in products) { @@ -1388,11 +1397,7 @@ private List GetProducts(ExportProfileTaskContext ctx, int pageIndex) } else if (product.ProductType == ProductType.GroupedProduct) { - if (ctx.IsPreview) - { - result.Add(product); - } - else + if (ctx.Projection.NoGroupedProducts && !ctx.IsPreview) { var associatedSearchContext = new ProductSearchContext { @@ -1408,6 +1413,10 @@ private List GetProducts(ExportProfileTaskContext ctx, int pageIndex) result.Add(associatedProduct); } } + else + { + result.Add(product); + } } } @@ -1415,7 +1424,7 @@ private List GetProducts(ExportProfileTaskContext ctx, int pageIndex) ctx.ProductDataContext = new ExportProductDataContext(products, x => _productAttributeService.GetProductVariantAttributesByProductIds(x, null), x => _productAttributeService.GetProductVariantAttributeCombinations(x), - x => _productService.GetTierPricesByProductIds(x, (ctx.Projection.CurrencyId ?? 0) != 0 ? ctx.ProjectionCustomer : null, ctx.Store.Id), + x => _productService.GetTierPricesByProductIds(x, (ctx.Projection.CurrencyId ?? 0) != 0 ? ctx.ContextCustomer : null, ctx.Store.Id), x => _categoryService.GetProductCategoriesByProductIds(x), x => _manufacturerService.GetProductManufacturersByProductIds(x), x => _productService.GetProductPicturesByProductIds(x), @@ -1804,7 +1813,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ if (ctx.Projection.ConvertNetToGrossPrices) { decimal taxRate; - exp._OldPrice = _taxService.GetProductPrice(product, product.OldPrice, true, ctx.ProjectionCustomer, out taxRate); + exp._OldPrice = _taxService.GetProductPrice(product, product.OldPrice, true, ctx.ContextCustomer, out taxRate); } else { @@ -1937,19 +1946,19 @@ private List Init(ExportProfileTaskContext ctx) List result = null; if (ctx.Projection.CurrencyId.HasValue) - ctx.ProjectionCurrency = _currencyService.GetCurrencyById(ctx.Projection.CurrencyId.Value); + ctx.ContextCurrency = _currencyService.GetCurrencyById(ctx.Projection.CurrencyId.Value); else - ctx.ProjectionCurrency = _services.WorkContext.WorkingCurrency; + ctx.ContextCurrency = _services.WorkContext.WorkingCurrency; if (ctx.Projection.CustomerId.HasValue) - ctx.ProjectionCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); + ctx.ContextCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); else - ctx.ProjectionCustomer = _services.WorkContext.CurrentCustomer; + ctx.ContextCustomer = _services.WorkContext.CurrentCustomer; if (ctx.Projection.LanguageId.HasValue) - ctx.ProjectionLanguage = _languageService.GetLanguageById(ctx.Projection.LanguageId.Value); + ctx.ContextLanguage = _languageService.GetLanguageById(ctx.Projection.LanguageId.Value); else - ctx.ProjectionLanguage = _services.WorkContext.WorkingLanguage; + ctx.ContextLanguage = _services.WorkContext.WorkingLanguage; ctx.Stores = _services.StoreService.GetAllStores().ToDictionary(x => x.Id, x => x); ctx.Languages = _languageService.GetAllLanguages(true).ToDictionary(x => x.Id, x => x); @@ -1977,8 +1986,7 @@ private List Init(ExportProfileTaskContext ctx) else if (ctx.Provider.Value.EntityType == ExportEntityType.Product) { ctx.Store = store; - var searchContext = GetProductSearchContext(ctx, ctx.Profile.Offset, 1); - var anySingleProduct = _productService.SearchProducts(searchContext); + var anySingleProduct = GetProductPage(ctx, ctx.Profile.Offset, 1); ctx.RecordsPerStore.Add(store.Id, anySingleProduct.TotalCount); } else if (ctx.Provider.Value.EntityType == ExportEntityType.Order) @@ -2183,9 +2191,9 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) var stores = Init(ctx); - ctx.Export.Language = ToExpando(ctx, ctx.ProjectionLanguage); - ctx.Export.Customer = ToExpando(ctx, ctx.ProjectionCustomer); - ctx.Export.Currency = ToExpando(ctx, ctx.ProjectionCurrency); + ctx.Export.Language = ToExpando(ctx, ctx.ContextLanguage); + ctx.Export.Customer = ToExpando(ctx, ctx.ContextCustomer); + ctx.Export.Currency = ToExpando(ctx, ctx.ContextCurrency); stores.ForEach(x => ExportCoreInner(ctx, x)); } @@ -2332,7 +2340,7 @@ public static string PublicFolder } /// - /// Exports with a volatile profile + /// Export using async runner and a volatile profile. Helper for internal exports to reduce duplicate code. /// /// Provider system name /// Entity identifiers of entities to be exported. Can be null to export all entities. @@ -2377,9 +2385,9 @@ public static FileStreamResult Export(string providerSystemName, string selected } /// - /// Executes the export profile task + /// Export by executing schedule task of export profile /// - /// + /// Schedule task execution context public void Execute(TaskExecutionContext taskContext) { InitDependencies(taskContext); @@ -2400,17 +2408,23 @@ public void Execute(TaskExecutionContext taskContext) } /// - /// Executes the export profile task with a volatile profile + /// Direct export using an export profile /// /// Provider system name /// Component context + /// Export profile. Can be null to use a volatile profile. /// Cancellation token - /// Provider specific configuration data. Cane be null. /// Entity identifiers of entities to be exported. Can be null to export all entities. - /// Store identifier + /// Any data passed on IExportExecuteContext.CustomProperties + /// Product query that supersede profile filtering /// Export execute result - public ExportExecuteResult Execute(string providerSystemName, IComponentContext context, CancellationToken cancellationToken, - string providerConfigData = null, string selectedEntityIds = null, int storeId = 0) + public ExportExecuteResult Execute(string providerSystemName, + IComponentContext context, + CancellationToken cancellationToken, + ExportProfile profile = null, + string selectedEntityIds = null, + Dictionary customProperties = null, + IQueryable queryProducts = null) { Guard.ArgumentNotEmpty(() => providerSystemName); Guard.ArgumentNotNull(() => context); @@ -2422,9 +2436,17 @@ public ExportExecuteResult Execute(string providerSystemName, IComponentContext var provider = _exportService.LoadProvider(providerSystemName); - var profile = _exportService.CreateVolatileProfile(provider, providerConfigData, storeId); + if (profile == null) + profile = _exportService.CreateVolatileProfile(provider); var ctx = new ExportProfileTaskContext(taskContext, profile, provider, selectedEntityIds); + ctx.QueryProducts = queryProducts; + + if (customProperties != null) + { + foreach (var item in customProperties) + ctx.Export.CustomProperties.Add(item.Key, item.Value); + } ExportCoreOuter(ctx); @@ -2461,7 +2483,7 @@ public ExportExecuteResult Execute(string providerSystemName, IComponentContext } /// - /// Executes the export profile task to get preview data + /// Get preview data of an export profile /// /// Export profile /// Component context @@ -2488,7 +2510,7 @@ public void Preview(ExportProfile profile, IComponentContext context, int pageIn } /// - /// Get the number of total records + /// Get the number of total export records /// /// Export profile /// Export provider diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs index aa719d5be5..83d0b060ac 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs @@ -76,6 +76,7 @@ public ExportProfileTaskContext( public int RecordCount { get; set; } public Dictionary RecordsPerStore { get; set; } public string ProgressInfo { get; set; } + public IQueryable QueryProducts { get; set; } public Action PreviewData { get; private set; } public bool IsPreview @@ -90,9 +91,9 @@ public bool IsPreview public ExportFilter Filter { get; private set; } public ExportProjection Projection { get; private set; } - public Currency ProjectionCurrency { get; set; } - public Customer ProjectionCustomer { get; set; } - public Language ProjectionLanguage { get; set; } + public Currency ContextCurrency { get; set; } + public Customer ContextCustomer { get; set; } + public Language ContextLanguage { get; set; } public TraceLogger Log { get; set; } public Store Store { get; set; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs index 7ce264f229..734777325f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs @@ -11,10 +11,8 @@ public interface IExportService /// Creates a volatile export project /// /// Export provider - /// Provider configuration data - /// Store identifier /// New export profile - ExportProfile CreateVolatileProfile(Provider provider, string providerConfigData, int storeId); + ExportProfile CreateVolatileProfile(Provider provider); /// /// Inserts an export profile diff --git a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs index 6d731cb6d3..605fdbe689 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Drawing; -using System.Globalization; using System.IO; using System.Linq; using System.Text; @@ -11,11 +10,6 @@ using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Domain.Media; -using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Domain.Stores; using SmartStore.Core.Logging; using SmartStore.Services.Catalog; using SmartStore.Services.Common; @@ -38,11 +32,9 @@ public partial class ExportManager : IExportManager private readonly ICategoryService _categoryService; private readonly IManufacturerService _manufacturerService; private readonly IProductService _productService; - private readonly IProductTemplateService _productTemplateService; private readonly IPictureService _pictureService; private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; private readonly ILanguageService _languageService; - private readonly MediaSettings _mediaSettings; private readonly ICommonServices _services; private readonly IStoreMappingService _storeMappingService; @@ -53,22 +45,18 @@ public partial class ExportManager : IExportManager public ExportManager(ICategoryService categoryService, IManufacturerService manufacturerService, IProductService productService, - IProductTemplateService productTemplateService, IPictureService pictureService, INewsLetterSubscriptionService newsLetterSubscriptionService, ILanguageService languageService, - MediaSettings mediaSettings, ICommonServices services, IStoreMappingService storeMappingService) { this._categoryService = categoryService; this._manufacturerService = manufacturerService; this._productService = productService; - this._productTemplateService = productTemplateService; this._pictureService = pictureService; this._newsLetterSubscriptionService = newsLetterSubscriptionService; this._languageService = languageService; - this._mediaSettings = mediaSettings; this._services = services; this._storeMappingService = storeMappingService; @@ -81,19 +69,6 @@ public ExportManager(ICategoryService categoryService, #region Utilities - protected Action> WriteLocalized = (writer, context, content) => - { - if (context.Languages.Count > 1) - { - writer.WriteStartElement("Localized"); - foreach (var language in context.Languages) - { - content(language); - } - writer.WriteEndElement(); - } - }; - protected virtual void WriteCategories(XmlWriter writer, int parentCategoryId) { var categories = _categoryService.GetAllCategoriesByParentCategoryId(parentCategoryId, true); @@ -154,41 +129,6 @@ protected virtual void WriteCategories(XmlWriter writer, int parentCategoryId) } } - protected virtual void WritePicture(XmlWriter writer, XmlExportContext context, Picture picture, int thumbSize, int defaultSize) - { - if (picture != null) - { - writer.WriteStartElement("Picture"); - writer.Write("Id", picture.Id.ToString()); - writer.Write("SeoFileName", picture.SeoFilename); - writer.Write("MimeType", picture.MimeType); - writer.Write("ThumbImageUrl", _pictureService.GetPictureUrl(picture, thumbSize, false, context.Store.Url)); - writer.Write("ImageUrl", _pictureService.GetPictureUrl(picture, defaultSize, false, context.Store.Url)); - writer.Write("FullSizeImageUrl", _pictureService.GetPictureUrl(picture, 0, false, context.Store.Url)); - writer.WriteEndElement(); - } - } - - protected virtual void WriteQuantityUnit(XmlWriter writer, XmlExportContext context, QuantityUnit quantityUnit) - { - if (quantityUnit != null) - { - writer.WriteStartElement("QuantityUnit"); - writer.Write("Id", quantityUnit.Id.ToString()); - writer.Write("Name", quantityUnit.Name); - writer.Write("Description", quantityUnit.Description); - writer.Write("DisplayLocale", quantityUnit.DisplayLocale); - writer.Write("DisplayOrder", quantityUnit.DisplayOrder.ToString()); - writer.Write("IsDefault", quantityUnit.IsDefault.ToString()); - WriteLocalized(writer, context, lang => - { - writer.Write("Name", quantityUnit.GetLocalized(x => x.Name, lang.Id, false, false), lang); - writer.Write("Description", quantityUnit.GetLocalized(x => x.Description, lang.Id, false, false), lang); - }); - writer.WriteEndElement(); - } - } - #endregion #region Methods @@ -278,501 +218,6 @@ public virtual string ExportCategoriesToXml() return stringWriter.ToString(); } - /// - /// Writes a single product - /// - /// The XML writer - /// The product - /// Context objects - public virtual void WriteProductToXml(XmlWriter writer, Product product, XmlExportContext context) - { - var culture = CultureInfo.InvariantCulture; - var productTemplate = context.ProductTemplates.FirstOrDefault(x => x.Id == product.ProductTemplateId); - - writer.Write("Id", product.Id.ToString()); - writer.Write("Name", product.Name); - writer.Write("SeName", product.GetSeName(0, true, false)); - - writer.Write("ShortDescription", product.ShortDescription, null, true); - writer.Write("FullDescription", product.FullDescription, null, true); - - writer.Write("AdminComment", product.AdminComment); - writer.Write("ProductTemplateId", product.ProductTemplateId.ToString()); - writer.Write("ProductTemplateViewPath", productTemplate == null ? "" : productTemplate.ViewPath); - writer.Write("ShowOnHomePage", product.ShowOnHomePage.ToString()); - writer.Write("HomePageDisplayOrder", product.HomePageDisplayOrder.ToString()); - writer.Write("MetaKeywords", product.MetaKeywords); - writer.Write("MetaDescription", product.MetaDescription); - writer.Write("MetaTitle", product.MetaTitle); - writer.Write("AllowCustomerReviews", product.AllowCustomerReviews.ToString()); - writer.Write("ApprovedRatingSum", product.ApprovedRatingSum.ToString()); - writer.Write("NotApprovedRatingSum", product.NotApprovedRatingSum.ToString()); - writer.Write("ApprovedTotalReviews", product.ApprovedTotalReviews.ToString()); - writer.Write("NotApprovedTotalReviews", product.NotApprovedTotalReviews.ToString()); - writer.Write("Published", product.Published.ToString()); - writer.Write("CreatedOnUtc", product.CreatedOnUtc.ToString(culture)); - writer.Write("UpdatedOnUtc", product.UpdatedOnUtc.ToString(culture)); - writer.Write("SubjectToAcl", product.SubjectToAcl.ToString()); - writer.Write("LimitedToStores", product.LimitedToStores.ToString()); - writer.Write("ProductTypeId", product.ProductTypeId.ToString()); - writer.Write("ParentGroupedProductId", product.ParentGroupedProductId.ToString()); - writer.Write("Sku", product.Sku); - writer.Write("ManufacturerPartNumber", product.ManufacturerPartNumber); - writer.Write("Gtin", product.Gtin); - writer.Write("IsGiftCard", product.IsGiftCard.ToString()); - writer.Write("GiftCardTypeId", product.GiftCardTypeId.ToString()); - writer.Write("RequireOtherProducts", product.RequireOtherProducts.ToString()); - writer.Write("RequiredProductIds", product.RequiredProductIds); - writer.Write("AutomaticallyAddRequiredProducts", product.AutomaticallyAddRequiredProducts.ToString()); - writer.Write("IsDownload", product.IsDownload.ToString()); - writer.Write("DownloadId", product.DownloadId.ToString()); - writer.Write("UnlimitedDownloads", product.UnlimitedDownloads.ToString()); - writer.Write("MaxNumberOfDownloads", product.MaxNumberOfDownloads.ToString()); - writer.Write("DownloadExpirationDays", product.DownloadExpirationDays.HasValue ? product.DownloadExpirationDays.ToString() : ""); - writer.Write("DownloadActivationType", product.DownloadActivationType.ToString()); - writer.Write("HasSampleDownload", product.HasSampleDownload.ToString()); - writer.Write("SampleDownloadId", product.SampleDownloadId.ToString()); - writer.Write("HasUserAgreement", product.HasUserAgreement.ToString()); - writer.Write("UserAgreementText", product.UserAgreementText); - writer.Write("IsRecurring", product.IsRecurring.ToString()); - writer.Write("RecurringCycleLength", product.RecurringCycleLength.ToString()); - writer.Write("RecurringCyclePeriodId", product.RecurringCyclePeriodId.ToString()); - writer.Write("RecurringTotalCycles", product.RecurringTotalCycles.ToString()); - writer.Write("IsShipEnabled", product.IsShipEnabled.ToString()); - writer.Write("IsFreeShipping", product.IsFreeShipping.ToString()); - writer.Write("AdditionalShippingCharge", product.AdditionalShippingCharge.ToString(culture)); - writer.Write("IsTaxExempt", product.IsTaxExempt.ToString()); - writer.Write("TaxCategoryId", product.TaxCategoryId.ToString()); - writer.Write("ManageInventoryMethodId", product.ManageInventoryMethodId.ToString()); - writer.Write("StockQuantity", product.StockQuantity.ToString()); - writer.Write("DisplayStockAvailability", product.DisplayStockAvailability.ToString()); - writer.Write("DisplayStockQuantity", product.DisplayStockQuantity.ToString()); - writer.Write("MinStockQuantity", product.MinStockQuantity.ToString()); - writer.Write("LowStockActivityId", product.LowStockActivityId.ToString()); - writer.Write("NotifyAdminForQuantityBelow", product.NotifyAdminForQuantityBelow.ToString()); - writer.Write("BackorderModeId", product.BackorderModeId.ToString()); - writer.Write("AllowBackInStockSubscriptions", product.AllowBackInStockSubscriptions.ToString()); - writer.Write("OrderMinimumQuantity", product.OrderMinimumQuantity.ToString()); - writer.Write("OrderMaximumQuantity", product.OrderMaximumQuantity.ToString()); - writer.Write("AllowedQuantities", product.AllowedQuantities); - writer.Write("DisableBuyButton", product.DisableBuyButton.ToString()); - writer.Write("DisableWishlistButton", product.DisableWishlistButton.ToString()); - writer.Write("AvailableForPreOrder", product.AvailableForPreOrder.ToString()); - writer.Write("CallForPrice", product.CallForPrice.ToString()); - writer.Write("Price", product.Price.ToString(culture)); - writer.Write("OldPrice", product.OldPrice.ToString(culture)); - writer.Write("ProductCost", product.ProductCost.ToString(culture)); - writer.Write("SpecialPrice", product.SpecialPrice.HasValue ? product.SpecialPrice.Value.ToString(culture) : ""); - writer.Write("SpecialPriceStartDateTimeUtc", product.SpecialPriceStartDateTimeUtc.HasValue ? product.SpecialPriceStartDateTimeUtc.Value.ToString(culture) : ""); - writer.Write("SpecialPriceEndDateTimeUtc", product.SpecialPriceEndDateTimeUtc.HasValue ? product.SpecialPriceEndDateTimeUtc.Value.ToString(culture) : ""); - writer.Write("CustomerEntersPrice", product.CustomerEntersPrice.ToString()); - writer.Write("MinimumCustomerEnteredPrice", product.MinimumCustomerEnteredPrice.ToString(culture)); - writer.Write("MaximumCustomerEnteredPrice", product.MaximumCustomerEnteredPrice.ToString(culture)); - writer.Write("HasTierPrices", product.HasTierPrices.ToString()); - writer.Write("HasDiscountsApplied", product.HasDiscountsApplied.ToString()); - writer.Write("Weight", product.Weight.ToString(culture)); - writer.Write("Length", product.Length.ToString(culture)); - writer.Write("Width", product.Width.ToString(culture)); - writer.Write("Height", product.Height.ToString(culture)); - writer.Write("AvailableStartDateTimeUtc", product.AvailableStartDateTimeUtc.HasValue ? product.AvailableStartDateTimeUtc.Value.ToString(culture) : ""); - writer.Write("AvailableEndDateTimeUtc", product.AvailableEndDateTimeUtc.HasValue ? product.AvailableEndDateTimeUtc.Value.ToString(culture) : ""); - writer.Write("BasePriceEnabled", product.BasePriceEnabled.ToString()); - writer.Write("BasePriceMeasureUnit", product.BasePriceMeasureUnit); - writer.Write("BasePriceAmount", product.BasePriceAmount.HasValue ? product.BasePriceAmount.Value.ToString(culture) : ""); - writer.Write("BasePriceBaseAmount", product.BasePriceBaseAmount.HasValue ? product.BasePriceBaseAmount.Value.ToString() : ""); - writer.Write("VisibleIndividually", product.VisibleIndividually.ToString()); - writer.Write("DisplayOrder", product.DisplayOrder.ToString()); - writer.Write("BundleTitleText", product.BundleTitleText); - writer.Write("BundlePerItemPricing", product.BundlePerItemPricing.ToString()); - writer.Write("BundlePerItemShipping", product.BundlePerItemShipping.ToString()); - writer.Write("BundlePerItemShoppingCart", product.BundlePerItemShoppingCart.ToString()); - writer.Write("LowestAttributeCombinationPrice", product.LowestAttributeCombinationPrice.HasValue ? product.LowestAttributeCombinationPrice.Value.ToString(culture) : ""); - writer.Write("IsEsd", product.IsEsd.ToString()); - - WriteLocalized(writer, context, lang => - { - writer.Write("Name", product.GetLocalized(x => x.Name, lang.Id, false, false), lang); - writer.Write("SeName", product.GetSeName(lang.Id, false, false), lang); - writer.Write("ShortDescription", product.GetLocalized(x => x.ShortDescription, lang.Id, false, false), lang, true); - writer.Write("FullDescription", product.GetLocalized(x => x.FullDescription, lang.Id, false, false), lang, true); - writer.Write("MetaKeywords", product.GetLocalized(x => x.MetaKeywords, lang.Id, false, false), lang); - writer.Write("MetaDescription", product.GetLocalized(x => x.MetaDescription, lang.Id, false, false), lang); - writer.Write("MetaTitle", product.GetLocalized(x => x.MetaTitle, lang.Id, false, false), lang); - writer.Write("BundleTitleText", product.GetLocalized(x => x.BundleTitleText, lang.Id, false, false), lang); - }); - - if (product.DeliveryTime != null) - { - writer.WriteStartElement("DeliveryTime"); - writer.Write("Id", product.DeliveryTime.Id.ToString()); - writer.Write("Name", product.DeliveryTime.Name); - writer.Write("DisplayLocale", product.DeliveryTime.DisplayLocale); - writer.Write("ColorHexValue", product.DeliveryTime.ColorHexValue); - writer.Write("DisplayOrder", product.DeliveryTime.DisplayOrder.ToString()); - WriteLocalized(writer, context, lang => - { - writer.Write("Name", product.DeliveryTime.GetLocalized(x => x.Name, lang.Id, false, false), lang); - }); - writer.WriteEndElement(); - } - - WriteQuantityUnit(writer, context, product.QuantityUnit); - - writer.WriteStartElement("ProductTags"); - foreach (var tag in product.ProductTags) - { - writer.WriteStartElement("ProductTag"); - writer.Write("Id", tag.Id.ToString()); - writer.Write("Name", tag.Name); - - WriteLocalized(writer, context, lang => - { - writer.Write("Name", tag.GetLocalized(x => x.Name, lang.Id, false, false), lang); - }); - - writer.WriteEndElement(); - } - writer.WriteEndElement(); - - writer.WriteStartElement("ProductDiscounts"); - foreach (var discount in product.AppliedDiscounts) - { - writer.WriteStartElement("ProductDiscount"); - writer.Write("DiscountId", discount.Id.ToString()); - writer.WriteEndElement(); - } - writer.WriteEndElement(); - - writer.WriteStartElement("TierPrices"); - foreach (var tierPrice in product.TierPrices) - { - writer.WriteStartElement("TierPrice"); - writer.Write("Id", tierPrice.Id.ToString()); - writer.Write("StoreId", tierPrice.StoreId.ToString()); - writer.Write("CustomerRoleId", tierPrice.CustomerRoleId.HasValue ? tierPrice.CustomerRoleId.ToString() : "0"); - writer.Write("Quantity", tierPrice.Quantity.ToString()); - writer.Write("Price", tierPrice.Price.ToString(culture)); - writer.WriteEndElement(); - } - writer.WriteEndElement(); - - writer.WriteStartElement("ProductAttributes"); - foreach (var pva in product.ProductVariantAttributes.OrderBy(x => x.DisplayOrder)) - { - writer.WriteStartElement("ProductAttribute"); - - writer.Write("Id", pva.Id.ToString()); - writer.Write("TextPrompt", pva.TextPrompt); - writer.Write("IsRequired", pva.IsRequired.ToString()); - writer.Write("AttributeControlTypeId", pva.AttributeControlTypeId.ToString()); - writer.Write("DisplayOrder", pva.DisplayOrder.ToString()); - - writer.WriteStartElement("Attribute"); - writer.Write("Id", pva.ProductAttribute.Id.ToString()); - writer.Write("Alias", pva.ProductAttribute.Alias); - writer.Write("Name", pva.ProductAttribute.Name); - writer.Write("Description", pva.ProductAttribute.Description); - WriteLocalized(writer, context, lang => - { - writer.Write("Name", pva.ProductAttribute.GetLocalized(x => x.Name, lang.Id, false, false), lang); - writer.Write("Description", pva.ProductAttribute.GetLocalized(x => x.Description, lang.Id, false, false), lang); - }); - writer.WriteEndElement(); // Attribute - - writer.WriteStartElement("AttributeValues"); - foreach (var value in pva.ProductVariantAttributeValues.OrderBy(x => x.DisplayOrder)) - { - writer.WriteStartElement("AttributeValue"); - writer.Write("Id", value.Id.ToString()); - writer.Write("Alias", value.Alias); - writer.Write("Name", value.Name); - writer.Write("ColorSquaresRgb", value.ColorSquaresRgb); - writer.Write("PriceAdjustment", value.PriceAdjustment.ToString(culture)); - writer.Write("WeightAdjustment", value.WeightAdjustment.ToString(culture)); - writer.Write("IsPreSelected", value.IsPreSelected.ToString()); - writer.Write("DisplayOrder", value.DisplayOrder.ToString()); - writer.Write("ValueTypeId", value.ValueTypeId.ToString()); - writer.Write("LinkedProductId", value.LinkedProductId.ToString()); - writer.Write("Quantity", value.Quantity.ToString()); - WriteLocalized(writer, context, lang => - { - writer.Write("Name", value.GetLocalized(x => x.Name, lang.Id, false, false), lang); - }); - writer.WriteEndElement(); // AttributeValue - } - writer.WriteEndElement(); // AttributeValues - - writer.WriteEndElement(); // ProductAttribute - } - writer.WriteEndElement(); // ProductAttributes - - writer.WriteStartElement("ProductAttributeCombinations"); - foreach (var combination in product.ProductVariantAttributeCombinations) - { - writer.WriteStartElement("ProductAttributeCombination"); - - writer.Write("Id", combination.Id.ToString()); - writer.Write("StockQuantity", combination.StockQuantity.ToString()); - writer.Write("AllowOutOfStockOrders", combination.AllowOutOfStockOrders.ToString()); - writer.Write("AttributesXml", combination.AttributesXml, null, true); - writer.Write("Sku", combination.Sku); - writer.Write("Gtin", combination.Gtin); - writer.Write("ManufacturerPartNumber", combination.ManufacturerPartNumber); - writer.Write("Price", combination.Price.HasValue ? combination.Price.Value.ToString(culture) : ""); - writer.Write("Length", combination.Length.HasValue ? combination.Length.Value.ToString(culture) : ""); - writer.Write("Width", combination.Width.HasValue ? combination.Width.Value.ToString(culture) : ""); - writer.Write("Height", combination.Height.HasValue ? combination.Height.Value.ToString(culture) : ""); - writer.Write("BasePriceAmount", combination.BasePriceAmount.HasValue ? combination.BasePriceAmount.Value.ToString(culture) : ""); - writer.Write("BasePriceBaseAmount", combination.BasePriceBaseAmount.HasValue ? combination.BasePriceBaseAmount.Value.ToString() : ""); - writer.Write("DeliveryTimeId", combination.DeliveryTimeId.HasValue ? combination.DeliveryTimeId.Value.ToString() : ""); - writer.Write("IsActive", combination.IsActive.ToString()); - - WriteQuantityUnit(writer, context, combination.QuantityUnit); - - writer.WriteStartElement("Pictures"); - foreach (int pictureId in combination.GetAssignedPictureIds()) - { - WritePicture(writer, context, _pictureService.GetPictureById(pictureId), _mediaSettings.ProductThumbPictureSize, _mediaSettings.ProductDetailsPictureSize); - } - writer.WriteEndElement(); // Pictures - - writer.WriteEndElement(); // ProductAttributeCombination - } - writer.WriteEndElement(); // ProductAttributeCombinations - - writer.WriteStartElement("ProductPictures"); - foreach (var productPicture in product.ProductPictures.OrderBy(x => x.DisplayOrder)) - { - writer.WriteStartElement("ProductPicture"); - writer.Write("Id", productPicture.Id.ToString()); - writer.Write("DisplayOrder", productPicture.DisplayOrder.ToString()); - - WritePicture(writer, context, productPicture.Picture, _mediaSettings.ProductThumbPictureSize, _mediaSettings.ProductDetailsPictureSize); - - writer.WriteEndElement(); - } - writer.WriteEndElement(); - - writer.WriteStartElement("ProductCategories"); - var productCategories = _categoryService.GetProductCategoriesByProductId(product.Id); - if (productCategories != null) - { - foreach (var productCategory in productCategories.OrderBy(x => x.DisplayOrder)) - { - var category = productCategory.Category; - writer.WriteStartElement("ProductCategory"); - writer.Write("IsFeaturedProduct", productCategory.IsFeaturedProduct.ToString()); - writer.Write("DisplayOrder", productCategory.DisplayOrder.ToString()); - - writer.WriteStartElement("Category"); - writer.Write("Id", category.Id.ToString()); - writer.Write("Name", category.Name); - writer.Write("FullName", category.FullName); - writer.Write("Description", category.Description); - writer.Write("BottomDescription", category.BottomDescription); - writer.Write("CategoryTemplateId", category.CategoryTemplateId.ToString()); - writer.Write("MetaKeywords", category.MetaKeywords); - writer.Write("MetaDescription", category.MetaDescription); - writer.Write("MetaTitle", category.MetaTitle); - writer.Write("SeName", category.GetSeName(0)); - writer.Write("ParentCategoryId", category.ParentCategoryId.ToString()); - writer.Write("PageSize", category.PageSize.ToString()); - writer.Write("AllowCustomersToSelectPageSize", category.AllowCustomersToSelectPageSize.ToString()); - writer.Write("PageSizeOptions", category.PageSizeOptions); - writer.Write("PriceRanges", category.PriceRanges); - writer.Write("ShowOnHomePage", category.ShowOnHomePage.ToString()); - writer.Write("HasDiscountsApplied", category.HasDiscountsApplied.ToString()); - writer.Write("Published", category.Published.ToString()); - writer.Write("Deleted", category.Deleted.ToString()); - writer.Write("DisplayOrder", category.DisplayOrder.ToString()); - writer.Write("CreatedOnUtc", category.CreatedOnUtc.ToString(culture)); - writer.Write("UpdatedOnUtc", category.UpdatedOnUtc.ToString(culture)); - writer.Write("SubjectToAcl", category.SubjectToAcl.ToString()); - writer.Write("LimitedToStores", category.LimitedToStores.ToString()); - writer.Write("Alias", category.Alias); - writer.Write("DefaultViewMode", category.DefaultViewMode); - - WritePicture(writer, context, category.Picture, _mediaSettings.CategoryThumbPictureSize, _mediaSettings.CategoryThumbPictureSize); - - WriteLocalized(writer, context, lang => - { - writer.Write("Name", category.GetLocalized(x => x.Name, lang.Id, false, false), lang); - writer.Write("FullName", category.GetLocalized(x => x.FullName, lang.Id, false, false), lang); - writer.Write("Description", category.GetLocalized(x => x.Description, lang.Id, false, false), lang); - writer.Write("BottomDescription", category.GetLocalized(x => x.BottomDescription, lang.Id, false, false), lang); - writer.Write("MetaKeywords", category.GetLocalized(x => x.MetaKeywords, lang.Id, false, false), lang); - writer.Write("MetaDescription", category.GetLocalized(x => x.MetaDescription, lang.Id, false, false), lang); - writer.Write("MetaTitle", category.GetLocalized(x => x.MetaTitle, lang.Id, false, false), lang); - writer.Write("SeName", category.GetSeName(lang.Id, false, false)); - }); - - writer.WriteEndElement(); - - writer.WriteEndElement(); - } - } - writer.WriteEndElement(); - - writer.WriteStartElement("ProductManufacturers"); - var productManufacturers = _manufacturerService.GetProductManufacturersByProductId(product.Id); - if (productManufacturers != null) - { - foreach (var productManufacturer in productManufacturers.OrderBy(x => x.DisplayOrder)) - { - var manu = productManufacturer.Manufacturer; - writer.WriteStartElement("ProductManufacturer"); - - writer.Write("Id", productManufacturer.Id.ToString()); - writer.Write("IsFeaturedProduct", productManufacturer.IsFeaturedProduct.ToString()); - writer.Write("DisplayOrder", productManufacturer.DisplayOrder.ToString()); - - writer.WriteStartElement("Manufacturer"); - writer.Write("Id", manu.Id.ToString()); - writer.Write("Name", manu.Name); - writer.Write("SeName", manu.GetSeName(0, true, false)); - writer.Write("Description", manu.Description); - writer.Write("MetaKeywords", manu.MetaKeywords); - writer.Write("MetaDescription", manu.MetaDescription); - writer.Write("MetaTitle", manu.MetaTitle); - - WritePicture(writer, context, manu.Picture, _mediaSettings.ManufacturerThumbPictureSize, _mediaSettings.ManufacturerThumbPictureSize); - - WriteLocalized(writer, context, lang => - { - writer.Write("Name", manu.GetLocalized(x => x.Name, lang.Id, false, false), lang); - writer.Write("SeName", manu.GetSeName(lang.Id, false, false), lang); - writer.Write("Description", manu.GetLocalized(x => x.Description, lang.Id, false, false), lang); - writer.Write("MetaKeywords", manu.GetLocalized(x => x.MetaKeywords, lang.Id, false, false), lang); - writer.Write("MetaDescription", manu.GetLocalized(x => x.MetaDescription, lang.Id, false, false), lang); - writer.Write("MetaTitle", manu.GetLocalized(x => x.MetaTitle, lang.Id, false, false), lang); - }); - - writer.WriteEndElement(); - - writer.WriteEndElement(); - } - } - writer.WriteEndElement(); - - writer.WriteStartElement("ProductSpecificationAttributes"); - foreach (var pca in product.ProductSpecificationAttributes.OrderBy(x => x.DisplayOrder)) - { - writer.WriteStartElement("ProductSpecificationAttribute"); - writer.Write("Id", pca.Id.ToString()); - writer.Write("AllowFiltering", pca.AllowFiltering.ToString()); - writer.Write("ShowOnProductPage", pca.ShowOnProductPage.ToString()); - writer.Write("DisplayOrder", pca.DisplayOrder.ToString()); - - writer.WriteStartElement("SpecificationAttributeOption"); - writer.Write("Id", pca.SpecificationAttributeOption.Id.ToString()); - writer.Write("DisplayOrder", pca.SpecificationAttributeOption.DisplayOrder.ToString()); - writer.Write("Name", pca.SpecificationAttributeOption.Name); - WriteLocalized(writer, context, lang => - { - writer.Write("Name", pca.SpecificationAttributeOption.GetLocalized(x => x.Name, lang.Id, false, false), lang); - }); - - writer.WriteStartElement("SpecificationAttribute"); - writer.Write("Id", pca.SpecificationAttributeOption.SpecificationAttribute.Id.ToString()); - writer.Write("DisplayOrder", pca.SpecificationAttributeOption.SpecificationAttribute.DisplayOrder.ToString()); - writer.Write("Name", pca.SpecificationAttributeOption.SpecificationAttribute.Name); - WriteLocalized(writer, context, lang => - { - writer.Write("Name", pca.SpecificationAttributeOption.SpecificationAttribute.GetLocalized(x => x.Name, lang.Id, false, false), lang); - }); - writer.WriteEndElement(); // SpecificationAttribute - - writer.WriteEndElement(); // SpecificationAttributeOption - - writer.WriteEndElement(); // ProductSpecificationAttribute - } - writer.WriteEndElement(); - - writer.WriteStartElement("ProductBundleItems"); - var bundleItems = _productService.GetBundleItems(product.Id, true); - foreach (var bundleItem in bundleItems.Select(x => x.Item).OrderBy(x => x.DisplayOrder)) - { - writer.WriteStartElement("ProductBundleItem"); - writer.Write("ProductId", bundleItem.ProductId.ToString()); - writer.Write("BundleProductId", bundleItem.BundleProductId.ToString()); - writer.Write("Quantity", bundleItem.Quantity.ToString()); - writer.Write("Discount", bundleItem.Discount.HasValue ? bundleItem.Discount.Value.ToString(culture) : ""); - writer.Write("DiscountPercentage", bundleItem.DiscountPercentage.ToString()); - writer.Write("Name", bundleItem.GetLocalizedName()); - writer.Write("ShortDescription", bundleItem.ShortDescription); - writer.Write("FilterAttributes", bundleItem.FilterAttributes.ToString()); - writer.Write("HideThumbnail", bundleItem.HideThumbnail.ToString()); - writer.Write("Visible", bundleItem.Visible.ToString()); - writer.Write("Published", bundleItem.Published.ToString()); - writer.Write("DisplayOrder", bundleItem.DisplayOrder.ToString()); - writer.Write("CreatedOnUtc", bundleItem.CreatedOnUtc.ToString(culture)); - writer.Write("UpdatedOnUtc", bundleItem.UpdatedOnUtc.ToString(culture)); - writer.WriteEndElement(); - } - writer.WriteEndElement(); - } - - /// - /// Export product list to XML - /// - /// Stream to write - /// Search context - public virtual void ExportProductsToXml(Stream stream, ProductSearchContext searchContext) - { - var settings = new XmlWriterSettings - { - Encoding = new UTF8Encoding(false), - CheckCharacters = false - }; - - var context = new XmlExportContext - { - ProductTemplates = _productTemplateService.GetAllProductTemplates(), - Languages = _languageService.GetAllLanguages(true), - Store = _services.StoreContext.CurrentStore - }; - - using (var writer = XmlWriter.Create(stream, settings)) - { - writer.WriteStartDocument(); - writer.WriteStartElement("Products"); - writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - - for (int i = 0; i < 9999999; ++i) - { - searchContext.PageIndex = i; - - var products = _productService.SearchProducts(searchContext); - - foreach (var product in products) - { - writer.WriteStartElement("Product"); - - try - { - WriteProductToXml(writer, product, context); - } - catch (Exception exc) - { - Logger.Error("{0} (Product.Id {1})".FormatWith(exc.Message, product.Id), exc); - } - - writer.WriteEndElement(); // Product - } - - if (!products.HasNextPage) - break; - } - - writer.WriteEndElement(); - writer.WriteEndDocument(); - writer.Flush(); - writer.Close(); - - stream.Seek(0, SeekOrigin.Begin); - } - } - /// /// Export products to XLSX /// @@ -1659,12 +1104,4 @@ public virtual string ExportCustomersToXml(IList customers) #endregion } - - - public class XmlExportContext - { - public IList ProductTemplates { get; set; } - public IList Languages { get; set; } - public Store Store { get; set; } - } } diff --git a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs index 27937c87e8..290f60dfe1 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs @@ -1,10 +1,7 @@ using System.Collections.Generic; using System.IO; -using System.Xml; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Orders; -using SmartStore.Services.Catalog; namespace SmartStore.Services.ExportImport { @@ -26,21 +23,6 @@ public interface IExportManager /// Result in XML format string ExportCategoriesToXml(); - /// - /// Writes a single product - /// - /// The XML writer - /// The product - /// Context objects - void WriteProductToXml(XmlWriter writer, Product product, XmlExportContext context); - - /// - /// Export product list to XML - /// - /// Stream to write - /// Search context - void ExportProductsToXml(Stream stream, ProductSearchContext searchContext); - /// /// Export products to XLSX /// diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs index 88ce13fb7c..380d866ec0 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs @@ -21,7 +21,8 @@ namespace SmartStore.GoogleMerchantCenter.Providers [DisplayOrder(1)] [ExportSupporting( ExportSupport.CreateInitialPublicDeployment, - ExportSupport.AttributeCombinationAsProduct, + ExportSupport.ProjectionNoGroupedProducts, + ExportSupport.ProjectionAttributeCombinationAsProduct, ExportSupport.ProjectionDescription, ExportSupport.ProjectionUseOwnProductNo, ExportSupport.ProjectionBrand, diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 005e384ee9..4fb97abe32 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -222,6 +222,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile FreeShippingThreshold = projection.FreeShippingThreshold, AttributeCombinationAsProduct = projection.AttributeCombinationAsProduct, AttributeCombinationValueMergingId = projection.AttributeCombinationValueMergingId, + NoGroupedProducts = projection.NoGroupedProducts, OrderStatusChangeId = projection.OrderStatusChangeId }; @@ -621,6 +622,7 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) FreeShippingThreshold = model.Projection.FreeShippingThreshold, AttributeCombinationAsProduct = model.Projection.AttributeCombinationAsProduct, AttributeCombinationValueMergingId = model.Projection.AttributeCombinationValueMergingId, + NoGroupedProducts = model.Projection.NoGroupedProducts, OrderStatusChangeId = model.Projection.OrderStatusChangeId }; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs index 942ad0ab1f..a2df29a5b4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProjectionModel.cs @@ -76,6 +76,9 @@ public class ExportProjectionModel public int AttributeCombinationValueMergingId { get; set; } public SelectList AvailableAttributeCombinationValueMerging { get; set; } + [SmartResourceDisplayName("Admin.DataExchange.Export.Projection.NoGroupedProducts")] + public bool NoGroupedProducts { get; set; } + #endregion #region Order diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index a290c681d2..dda87168cd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -252,6 +252,11 @@ $('#ProjectionStoreContainer').toggle(!$(this).is(':checked')); }).trigger('change'); + // show/hide no grouped products note + $('#@(Html.FieldIdFor(x => x.Projection.NoGroupedProducts))').change(function () { + $('#NoGroupedProductsNote').toggle($(this).is(':checked')); + }).trigger('change'); + $('#@(Html.FieldIdFor(x => x.Projection.AppendDescriptionText))').select2({ tags: [@(Html.Raw(Model.Projection.SerializedAppendDescriptionText))] }); $('#@(Html.FieldIdFor(x => x.Projection.CriticalCharacters))').select2({ tags: [@(Html.Raw(Model.Projection.SerializedCriticalCharacters))] }); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml index 5e304f8953..5390cb529e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml @@ -47,7 +47,7 @@ @if (Model.Provider.EntityType == ExportEntityType.Product) { - if (Model.Provider.Supporting.Contains(ExportSupport.AttributeCombinationAsProduct)) + if (Model.Provider.Supporting.Contains(ExportSupport.ProjectionAttributeCombinationAsProduct)) { @@ -69,6 +69,26 @@ } + if (Model.Provider.Supporting.Contains(ExportSupport.ProjectionNoGroupedProducts)) + { + + + @Html.SmartLabelFor(x => x.Projection.NoGroupedProducts) + + + @Html.EditorFor(x => x.Projection.NoGroupedProducts) + @Html.ValidationMessageFor(x => x.Projection.NoGroupedProducts) + + + + +
    + @T("Admin.DataExchange.Export.Projection.NoGroupedProducts.Note") +
    + + + } + @Html.SmartLabelFor(x => x.Projection.PriceType) From 0a21ca91127a1b02c08c5795e2e03c3b5aa1d036 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 5 Oct 2015 19:15:24 +0200 Subject: [PATCH 106/874] Convert product XLSX export to support new export framework --- .../ExportProductXlsxProvider.cs | 424 ++++++++++++++ .../DataExchange/ExportSegmenter.cs | 12 +- .../ExportTask/ExportProfileTask.cs | 164 +++--- .../ExportImport/ExportManager.cs | 518 +----------------- .../ExportImport/IExportManager.cs | 7 - .../SmartStore.Services.csproj | 1 + .../Controllers/ProductController.cs | 53 +- .../ExportImport/ExportManagerTests.cs | 237 -------- .../SmartStore.Services.Tests.csproj | 1 - 9 files changed, 522 insertions(+), 895 deletions(-) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs delete mode 100644 src/Tests/SmartStore.Services.Tests/ExportImport/ExportManagerTests.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs new file mode 100644 index 0000000000..baff832560 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs @@ -0,0 +1,424 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using OfficeOpenXml; +using OfficeOpenXml.Style; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; +using SmartStore.Services.Catalog; +using SmartStore.Services.Localization; +using SmartStore.Services.Stores; + +namespace SmartStore.Services.DataExchange.ExportProvider +{ + [SystemName("Exports.SmartStoreProductXlsx")] + [FriendlyName("SmartStore Excel product export")] + [IsHidden(true)] + [ExportSupporting(ExportSupport.HighDataDepth)] + public class ExportProductXlsxProvider : IExportProvider + { + private readonly ILanguageService _languageService; + private readonly IProductService _productService; + private readonly IStoreMappingService _storeMappingService; + + private string[] Properties + { + get + { + return new string[] + { + "ProductTypeId", + "ParentGroupedProductId", + "VisibleIndividually", + "Name", + "ShortDescription", + "FullDescription", + "ProductTemplateId", + "ShowOnHomePage", + "HomePageDisplayOrder", + "MetaKeywords", + "MetaDescription", + "MetaTitle", + "SeName", + "AllowCustomerReviews", + "Published", + "SKU", + "ManufacturerPartNumber", + "Gtin", + "IsGiftCard", + "GiftCardTypeId", + "RequireOtherProducts", + "RequiredProductIds", + "AutomaticallyAddRequiredProducts", + "IsDownload", + "DownloadId", + "UnlimitedDownloads", + "MaxNumberOfDownloads", + "DownloadActivationTypeId", + "HasSampleDownload", + "SampleDownloadId", + "HasUserAgreement", + "UserAgreementText", + "IsRecurring", + "RecurringCycleLength", + "RecurringCyclePeriodId", + "RecurringTotalCycles", + "IsShipEnabled", + "IsFreeShipping", + "AdditionalShippingCharge", + "IsEsd", + "IsTaxExempt", + "TaxCategoryId", + "ManageInventoryMethodId", + "StockQuantity", + "DisplayStockAvailability", + "DisplayStockQuantity", + "MinStockQuantity", + "LowStockActivityId", + "NotifyAdminForQuantityBelow", + "BackorderModeId", + "AllowBackInStockSubscriptions", + "OrderMinimumQuantity", + "OrderMaximumQuantity", + "AllowedQuantities", + "DisableBuyButton", + "DisableWishlistButton", + "AvailableForPreOrder", + "CallForPrice", + "Price", + "OldPrice", + "ProductCost", + "SpecialPrice", + "SpecialPriceStartDateTimeUtc", + "SpecialPriceEndDateTimeUtc", + "CustomerEntersPrice", + "MinimumCustomerEnteredPrice", + "MaximumCustomerEnteredPrice", + "Weight", + "Length", + "Width", + "Height", + "CreatedOnUtc", + "CategoryIds", + "ManufacturerIds", + "Picture1", + "Picture2", + "Picture3", + "DeliveryTimeId", + "QuantityUnitId", + "BasePriceEnabled", + "BasePriceMeasureUnit", + "BasePriceAmount", + "BasePriceBaseAmount", + "BundleTitleText", + "BundlePerItemShipping", + "BundlePerItemPricing", + "BundlePerItemShoppingCart", + "BundleItemSkus", + "AvailableStartDateTimeUtc", + "AvailableEndDateTimeUtc", + "StoreIds", + "LimitedToStores" + }; + } + } + + public ExportProductXlsxProvider( + ILanguageService languageService, + IProductService productService, + IStoreMappingService storeMappingService) + { + _languageService = languageService; + _productService = productService; + _storeMappingService = storeMappingService; + } + + private void WriteCell(ExcelWorksheet worksheet, int row, ref int column, object value) + { + worksheet.Cells[row, column].Value = value; + ++column; + } + + private string GetLocalized(List allLocalized, Language language, string property) + { + if (allLocalized != null) + { + string localizeValue = allLocalized + .Where(x => property.IsCaseInsensitiveEqual((string)x.LocaleKey) && language.LanguageCulture.IsCaseInsensitiveEqual((string)x.Culture)) + .Select(x => (string)x.LocaleValue) + .FirstOrDefault(); + + return localizeValue; + } + return ""; + } + + public static string SystemName + { + get { return "Exports.SmartStoreProductXlsx"; } + } + + public ExportConfigurationInfo ConfigurationInfo + { + get { return null; } + } + + public ExportEntityType EntityType + { + get { return ExportEntityType.Product; } + } + + public string FileExtension + { + get { return "XLSX"; } + } + + public void Execute(IExportExecuteContext context) + { + var path = context.FilePath; + + context.Log.Information("Creating file " + path); + + using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + using (var xlPackage = new ExcelPackage(stream)) + { + // uncomment this line if you want the XML written out to the outputDir + //xlPackage.DebugMode = true; + + // get handle to the existing worksheet + var worksheet = xlPackage.Workbook.Worksheets.Add("Products"); + + // get handle to the cells range of the worksheet + var cells = worksheet.Cells; + string[] properties = Properties; + var languages = _languageService.GetAllLanguages(true); + + var headlines = new string[properties.Length + languages.Count * 3]; + var languageFields = new string[languages.Count * 3]; + var row = 2; + var j = 0; + + foreach (var lang in languages) + { + languageFields.SetValue("Name[" + lang.UniqueSeoCode + "]", j++); + languageFields.SetValue("ShortDescription[" + lang.UniqueSeoCode + "]", j++); + languageFields.SetValue("FullDescription[" + lang.UniqueSeoCode + "]", j++); + } + + properties.CopyTo(headlines, 0); + languageFields.CopyTo(headlines, properties.Length); + + for (int i = 0; i < headlines.Length; i++) + { + cells[1, i + 1].Value = headlines[i]; + cells[1, i + 1].Style.Fill.PatternType = ExcelFillStyle.Solid; + cells[1, i + 1].Style.Fill.BackgroundColor.SetColor(Color.FromArgb(184, 204, 228)); + cells[1, i + 1].Style.Font.Bold = true; + } + + while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + { + var segment = context.Data.CurrentSegment; + + foreach (dynamic product in segment) + { + if (context.Abort != ExportAbortion.None) + break; + + int column = 1; + int productId = product.Id; + + try + { + DateTime? specialPriceStartDateTimeUtc = product.SpecialPriceStartDateTimeUtc; + DateTime? specialPriceEndDateTimeUtc = product.SpecialPriceEndDateTimeUtc; + + List productCategories = product.ProductCategories; + List productManus = product.ProductManufacturers; + List productPictures = product.ProductPictures; + List localized = product._Localized; + + WriteCell(worksheet, row, ref column, (int)product.ProductTypeId); + WriteCell(worksheet, row, ref column, (int)product.ParentGroupedProductId); + WriteCell(worksheet, row, ref column, (bool)product.VisibleIndividually); + WriteCell(worksheet, row, ref column, (string)product.Name); + WriteCell(worksheet, row, ref column, (string)product.ShortDescription); + WriteCell(worksheet, row, ref column, (string)product.FullDescription); + WriteCell(worksheet, row, ref column, (int)product.ProductTemplateId); + WriteCell(worksheet, row, ref column, (bool)product.ShowOnHomePage); + WriteCell(worksheet, row, ref column, (int)product.HomePageDisplayOrder); + WriteCell(worksheet, row, ref column, (string)product.MetaKeywords); + WriteCell(worksheet, row, ref column, (string)product.MetaDescription); + WriteCell(worksheet, row, ref column, (string)product.MetaTitle); + WriteCell(worksheet, row, ref column, (string)product.SeName); + WriteCell(worksheet, row, ref column, (bool)product.AllowCustomerReviews); + WriteCell(worksheet, row, ref column, (bool)product.Published); + WriteCell(worksheet, row, ref column, (string)product.Sku); + WriteCell(worksheet, row, ref column, (string)product.ManufacturerPartNumber); + WriteCell(worksheet, row, ref column, (string)product.Gtin); + WriteCell(worksheet, row, ref column, (bool)product.IsGiftCard); + WriteCell(worksheet, row, ref column, (int)product.GiftCardTypeId); + WriteCell(worksheet, row, ref column, (bool)product.RequireOtherProducts); + WriteCell(worksheet, row, ref column, (string)product.RequiredProductIds); + WriteCell(worksheet, row, ref column, (bool)product.AutomaticallyAddRequiredProducts); + WriteCell(worksheet, row, ref column, (bool)product.IsDownload); + WriteCell(worksheet, row, ref column, (int)product.DownloadId); + WriteCell(worksheet, row, ref column, (bool)product.UnlimitedDownloads); + WriteCell(worksheet, row, ref column, (int)product.MaxNumberOfDownloads); + WriteCell(worksheet, row, ref column, (int)product.DownloadActivationTypeId); + WriteCell(worksheet, row, ref column, (bool)product.HasSampleDownload); + WriteCell(worksheet, row, ref column, (int?)product.SampleDownloadId); + WriteCell(worksheet, row, ref column, (bool)product.HasUserAgreement); + WriteCell(worksheet, row, ref column, (string)product.UserAgreementText); + WriteCell(worksheet, row, ref column, (bool)product.IsRecurring); + WriteCell(worksheet, row, ref column, (int)product.RecurringCycleLength); + WriteCell(worksheet, row, ref column, (int)product.RecurringCyclePeriodId); + WriteCell(worksheet, row, ref column, (int)product.RecurringTotalCycles); + WriteCell(worksheet, row, ref column, (bool)product.IsShipEnabled); + WriteCell(worksheet, row, ref column, (bool)product.IsFreeShipping); + WriteCell(worksheet, row, ref column, (decimal)product.AdditionalShippingCharge); + WriteCell(worksheet, row, ref column, (bool)product.IsEsd); + WriteCell(worksheet, row, ref column, (bool)product.IsTaxExempt); + WriteCell(worksheet, row, ref column, (int)product.TaxCategoryId); + WriteCell(worksheet, row, ref column, (int)product.ManageInventoryMethodId); + WriteCell(worksheet, row, ref column, (int)product.StockQuantity); + WriteCell(worksheet, row, ref column, (bool)product.DisplayStockAvailability); + WriteCell(worksheet, row, ref column, (bool)product.DisplayStockQuantity); + WriteCell(worksheet, row, ref column, (int)product.MinStockQuantity); + WriteCell(worksheet, row, ref column, (int)product.LowStockActivityId); + WriteCell(worksheet, row, ref column, (int)product.NotifyAdminForQuantityBelow); + WriteCell(worksheet, row, ref column, (int)product.BackorderModeId); + WriteCell(worksheet, row, ref column, (bool)product.AllowBackInStockSubscriptions); + WriteCell(worksheet, row, ref column, (int)product.OrderMinimumQuantity); + WriteCell(worksheet, row, ref column, (int)product.OrderMaximumQuantity); + WriteCell(worksheet, row, ref column, (string)product.AllowedQuantities); + WriteCell(worksheet, row, ref column, (bool)product.DisableBuyButton); + WriteCell(worksheet, row, ref column, (bool)product.DisableWishlistButton); + WriteCell(worksheet, row, ref column, (bool)product.AvailableForPreOrder); + WriteCell(worksheet, row, ref column, (bool)product.CallForPrice); + WriteCell(worksheet, row, ref column, (decimal)product.Price); + WriteCell(worksheet, row, ref column, (decimal)product.OldPrice); + WriteCell(worksheet, row, ref column, (decimal)product.ProductCost); + WriteCell(worksheet, row, ref column, (decimal?)product.SpecialPrice); + WriteCell(worksheet, row, ref column, specialPriceStartDateTimeUtc.HasValue ? specialPriceStartDateTimeUtc.Value.ToOADate() : (double)0.0); + WriteCell(worksheet, row, ref column, specialPriceEndDateTimeUtc.HasValue ? specialPriceEndDateTimeUtc.Value.ToOADate() : (double)0.0); + WriteCell(worksheet, row, ref column, (bool)product.CustomerEntersPrice); + WriteCell(worksheet, row, ref column, (decimal)product.MinimumCustomerEnteredPrice); + WriteCell(worksheet, row, ref column, (decimal)product.MaximumCustomerEnteredPrice); + WriteCell(worksheet, row, ref column, (decimal)product.Weight); + WriteCell(worksheet, row, ref column, (decimal)product.Length); + WriteCell(worksheet, row, ref column, (decimal)product.Width); + WriteCell(worksheet, row, ref column, (decimal)product.Height); + WriteCell(worksheet, row, ref column, ((DateTime)product.CreatedOnUtc).ToOADate()); + + WriteCell(worksheet, row, ref column, string.Join(";", productCategories.Select(x => (int)x.CategoryId))); + WriteCell(worksheet, row, ref column, string.Join(";", productManus.Select(x => (int)x.ManufacturerId))); + + WriteCell(worksheet, row, ref column, productPictures.Count > 0 ? (string)productPictures[0].Picture._ThumbLocalPath : null); + WriteCell(worksheet, row, ref column, productPictures.Count > 1 ? (string)productPictures[1].Picture._ThumbLocalPath : null); + WriteCell(worksheet, row, ref column, productPictures.Count > 2 ? (string)productPictures[2].Picture._ThumbLocalPath : null); + + WriteCell(worksheet, row, ref column, (int?)product.DeliveryTimeId); + WriteCell(worksheet, row, ref column, (int?)product.QuantityUnitId); + WriteCell(worksheet, row, ref column, (bool)product.BasePriceEnabled); + WriteCell(worksheet, row, ref column, (string)product.BasePriceMeasureUnit); + WriteCell(worksheet, row, ref column, (decimal?)product.BasePriceAmount); + WriteCell(worksheet, row, ref column, (int?)product.BasePriceBaseAmount); + WriteCell(worksheet, row, ref column, (string)product.BundleTitleText); + WriteCell(worksheet, row, ref column, (bool)product.BundlePerItemShipping); + WriteCell(worksheet, row, ref column, (bool)product.BundlePerItemShipping); + WriteCell(worksheet, row, ref column, (bool)product.BundlePerItemShoppingCart); + + if ((int)product.ProductTypeId == (int)ProductType.BundledProduct) + { + List bundleItems = product.ProductBundleItems; + + var searchContext = new ProductSearchContext + { + ProductIds = bundleItems.Select(x => (int)x.ProductId).ToList(), + PageSize = int.MaxValue, + ShowHidden = true + }; + + var query = _productService.PrepareProductSearchQuery(searchContext, x => x.Sku); + var skus = query.ToList(); + + WriteCell(worksheet, row, ref column, string.Join(",", skus)); + } + else + { + WriteCell(worksheet, row, ref column, ""); + } + + WriteCell(worksheet, row, ref column, (DateTime?)product.AvailableStartDateTimeUtc); + WriteCell(worksheet, row, ref column, (DateTime?)product.AvailableEndDateTimeUtc); + + if ((bool)product.LimitedToStores) + { + var storeIds = _storeMappingService.GetStoreMappingsFor("Product", productId) + .Select(x => x.StoreId) + .ToList(); + + WriteCell(worksheet, row, ref column, string.Join(";", storeIds)); + } + else + { + WriteCell(worksheet, row, ref column, ""); + } + + WriteCell(worksheet, row, ref column, (bool)product.LimitedToStores); + + foreach (var lang in languages) + { + WriteCell(worksheet, row, ref column, GetLocalized(localized, lang, "Name")); + WriteCell(worksheet, row, ref column, GetLocalized(localized, lang, "ShortDescription")); + WriteCell(worksheet, row, ref column, GetLocalized(localized, lang, "FullDescription")); + } + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.Log.Error("Error while processing product with id {0}: {1}".FormatInvariant(productId, exc.ToAllMessages()), exc); + ++context.RecordsFailed; + } + + ++row; + } + } + + // we had better add some document properties to the spreadsheet + + // set some core property values + //var storeName = _storeInformationSettings.StoreName; + //var storeUrl = _storeInformationSettings.StoreUrl; + //xlPackage.Workbook.Properties.Title = string.Format("{0} products", storeName); + //xlPackage.Workbook.Properties.Author = storeName; + //xlPackage.Workbook.Properties.Subject = string.Format("{0} products", storeName); + //xlPackage.Workbook.Properties.Keywords = string.Format("{0} products", storeName); + //xlPackage.Workbook.Properties.Category = "Products"; + //xlPackage.Workbook.Properties.Comments = string.Format("{0} products", storeName); + + // set some extended property values + //xlPackage.Workbook.Properties.Company = storeName; + //xlPackage.Workbook.Properties.HyperlinkBase = new Uri(storeUrl); + + // save the new spreadsheet + xlPackage.Save(); + } + + // EPPLus had serious memory leak problems in V3. We enforce the garbage collector to release unused memory, it's not perfect, but better than nothing. + GC.Collect(); + } + + public void ExecuteEnded(IExportExecuteContext context) + { + // nothing to do + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs index 4b76ff1e2c..eaf24b7505 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs @@ -42,7 +42,7 @@ public interface IExportSegmenter /// /// Gets the data of the current segment /// - ICollection CurrentSegment { get; } + ICollection CurrentSegment { get; } } internal interface IExportExecuter @@ -55,9 +55,9 @@ internal interface IExportExecuter public class ExportSegmenter : IExportSegmenter, IExportExecuter, IDisposable where T : BaseEntity { private Func> _loadData; - private Func> _convertData; + private Func> _convertData; private List _currentData; - private List _currentSegment; + private List _currentSegment; private IPageable _pageable; private int _pageIndexReset; private bool _isBeginOfSegmentation; @@ -72,7 +72,7 @@ public class ExportSegmenter : IExportSegmenter, IExportExecuter, IDisposable public ExportSegmenter( Func> loadData, - Func> convertData, + Func> convertData, PagedList pageable, int itemsPerFile) { @@ -195,7 +195,7 @@ public bool ReadNextSegment() return false; } - public ICollection CurrentSegment + public ICollection CurrentSegment { get { @@ -203,7 +203,7 @@ public ICollection CurrentSegment if (_currentSegment == null) { - _currentSegment = new List(); + _currentSegment = new List(); } if (_currentData == null) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 362ee31bdb..901f867194 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -127,13 +127,13 @@ private void InitDependencies(TaskExecutionContext context) #region Utilities - private List GetLocalized(ExportProfileTaskContext ctx, T entity, params Expression>[] keySelectors) + private List GetLocalized(ExportProfileTaskContext ctx, T entity, params Expression>[] keySelectors) where T : BaseEntity, ILocalizedEntity { if (ctx.Languages.Count <= 1 || !ctx.Supporting[ExportSupport.HighDataDepth]) return null; - var localized = new List(); + var localized = new List(); var localeKeyGroup = typeof(T).Name; var isSlugSupported = typeof(ISlugSupported).IsAssignableFrom(typeof(T)); @@ -153,7 +153,7 @@ private List GetLocalized(ExportProfileTaskContext ctx, T enti exp.LocaleKey = "SeName"; exp.LocaleValue = value; - localized.Add(exp as ExpandoObject); + localized.Add(exp); } } @@ -172,7 +172,7 @@ private List GetLocalized(ExportProfileTaskContext ctx, T enti exp.LocaleKey = localeKey; exp.LocaleValue = value; - localized.Add(exp as ExpandoObject); + localized.Add(exp); } } } @@ -617,7 +617,7 @@ private async void DeployFtp(ExportProfileTaskContext ctx, ExportDeployment depl #region Entity to expando - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Currency currency) + private dynamic ToExpando(ExportProfileTaskContext ctx, Currency currency) { if (currency == null) return null; @@ -640,10 +640,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Currency currency) expando._Localized = GetLocalized(ctx, currency, x => x.Name); - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Language language) + private dynamic ToExpando(ExportProfileTaskContext ctx, Language language) { if (language == null) return null; @@ -661,10 +661,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Language language) expando.Published = language.Published; expando.DisplayOrder = language.DisplayOrder; - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Country country) + private dynamic ToExpando(ExportProfileTaskContext ctx, Country country) { if (country == null) return null; @@ -686,10 +686,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Country country) expando._Localized = GetLocalized(ctx, country, x => x.Name); - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Address address) + private dynamic ToExpando(ExportProfileTaskContext ctx, Address address) { if (address == null) return null; @@ -725,12 +725,12 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Address address) sp._Localized = GetLocalized(ctx, address.StateProvince, x => x.Name); - expando.StateProvince = sp as ExpandoObject; + expando.StateProvince = sp; - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, RewardPointsHistory points) + private dynamic ToExpando(ExportProfileTaskContext ctx, RewardPointsHistory points) { if (points == null) return null; @@ -746,10 +746,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, RewardPointsHistor expando.Message = points.Message; expando.CreatedOnUtc = points.CreatedOnUtc; - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Customer customer) + private dynamic ToExpando(ExportProfileTaskContext ctx, Customer customer) { if (customer == null) return null; @@ -781,10 +781,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Customer customer) expando.RewardPointsHistory = null; expando._RewardPointsBalance = 0; - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Store store) + private dynamic ToExpando(ExportProfileTaskContext ctx, Store store) { if (store == null) return null; @@ -808,10 +808,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Store store) expando.PrimaryStoreCurrency = ToExpando(ctx, store.PrimaryStoreCurrency); expando.PrimaryExchangeRateCurrency = ToExpando(ctx, store.PrimaryExchangeRateCurrency); - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, DeliveryTime deliveryTime) + private dynamic ToExpando(ExportProfileTaskContext ctx, DeliveryTime deliveryTime) { if (deliveryTime == null) return null; @@ -827,10 +827,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, DeliveryTime deliv expando._Localized = GetLocalized(ctx, deliveryTime, x => x.Name); - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, QuantityUnit quantityUnit) + private dynamic ToExpando(ExportProfileTaskContext ctx, QuantityUnit quantityUnit) { if (quantityUnit == null) return null; @@ -849,10 +849,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, QuantityUnit quant x => x.Name, x => x.Description); - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Picture picture, int thumbPictureSize, int detailsPictureSize) + private dynamic ToExpando(ExportProfileTaskContext ctx, Picture picture, int thumbPictureSize, int detailsPictureSize) { if (picture == null) return null; @@ -871,10 +871,12 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Picture picture, i var relativeUrl = _pictureService.GetPictureUrl(picture); expando._FileName = relativeUrl.Substring(relativeUrl.LastIndexOf("/") + 1); - return expando as ExpandoObject; + expando._ThumbLocalPath = _pictureService.GetThumbLocalPath(picture); + + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, ProductVariantAttribute pva) + private dynamic ToExpando(ExportProfileTaskContext ctx, ProductVariantAttribute pva) { if (pva == null) return null; @@ -915,7 +917,7 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, ProductVariantAttr value._Localized = GetLocalized(ctx, x, y => y.Name); - return value as ExpandoObject; + return value; }) .ToList(); @@ -923,12 +925,12 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, ProductVariantAttr x => x.Name, x => x.Description); - expando.Attribute = attribute as ExpandoObject; + expando.Attribute = attribute; - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, ProductVariantAttributeCombination pvac) + private dynamic ToExpando(ExportProfileTaskContext ctx, ProductVariantAttributeCombination pvac) { if (pvac == null) return null; @@ -955,10 +957,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, ProductVariantAttr GetDeliveryTimeAndQuantityUnit(ctx, expando, pvac.DeliveryTimeId, pvac.QuantityUnitId); - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Manufacturer manufacturer) + private dynamic ToExpando(ExportProfileTaskContext ctx, Manufacturer manufacturer) { if (manufacturer == null) return null; @@ -992,10 +994,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Manufacturer manuf x => x.MetaDescription, x => x.MetaTitle); - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Category category) + private dynamic ToExpando(ExportProfileTaskContext ctx, Category category) { if (category == null) return null; @@ -1040,10 +1042,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Category category) x => x.MetaDescription, x => x.MetaTitle); - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) + private dynamic ToExpando(ExportProfileTaskContext ctx, Product product) { if (product == null) return null; @@ -1052,6 +1054,9 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) expando._Entity = product; expando.Id = product.Id; + expando.ProductTypeId = product.ProductTypeId; + expando.ParentGroupedProductId = product.ParentGroupedProductId; + expando.VisibleIndividually = product.VisibleIndividually; expando.Name = product.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); expando.SeName = product.GetSeName(ctx.Projection.LanguageId ?? 0, true, false); expando.ShortDescription = product.GetLocalized(x => x.ShortDescription, ctx.Projection.LanguageId ?? 0, true, false); @@ -1068,13 +1073,8 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) expando.NotApprovedRatingSum = product.NotApprovedRatingSum; expando.ApprovedTotalReviews = product.ApprovedTotalReviews; expando.NotApprovedTotalReviews = product.NotApprovedTotalReviews; - expando.Published = product.Published; - expando.CreatedOnUtc = product.CreatedOnUtc; - expando.UpdatedOnUtc = product.UpdatedOnUtc; expando.SubjectToAcl = product.SubjectToAcl; expando.LimitedToStores = product.LimitedToStores; - expando.ProductTypeId = product.ProductTypeId; - expando.ParentGroupedProductId = product.ParentGroupedProductId; expando.Sku = product.Sku; expando.ManufacturerPartNumber = product.ManufacturerPartNumber; expando.Gtin = product.Gtin; @@ -1101,6 +1101,7 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) expando.IsFreeShipping = product.IsFreeShipping; expando.AdditionalShippingCharge = product.AdditionalShippingCharge; expando.IsTaxExempt = product.IsTaxExempt; + expando.IsEsd = product.IsEsd; expando.TaxCategoryId = product.TaxCategoryId; expando.ManageInventoryMethodId = product.ManageInventoryMethodId; expando.StockQuantity = product.StockQuantity; @@ -1128,6 +1129,7 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) expando.MinimumCustomerEnteredPrice = product.MinimumCustomerEnteredPrice; expando.MaximumCustomerEnteredPrice = product.MaximumCustomerEnteredPrice; expando.HasTierPrices = product.HasTierPrices; + expando.LowestAttributeCombinationPrice = product.LowestAttributeCombinationPrice; expando.HasDiscountsApplied = product.HasDiscountsApplied; expando.Weight = product.Weight; expando.Length = product.Length; @@ -1135,19 +1137,22 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) expando.Height = product.Height; expando.AvailableStartDateTimeUtc = product.AvailableStartDateTimeUtc; expando.AvailableEndDateTimeUtc = product.AvailableEndDateTimeUtc; + expando.DisplayOrder = product.DisplayOrder; + expando.Published = product.Published; + expando.Deleted = product.Deleted; + expando.CreatedOnUtc = product.CreatedOnUtc; + expando.UpdatedOnUtc = product.UpdatedOnUtc; + expando.DeliveryTimeId = product.DeliveryTimeId; + expando.QuantityUnitId = product.QuantityUnitId; expando.BasePriceEnabled = product.BasePriceEnabled; expando.BasePriceMeasureUnit = product.BasePriceMeasureUnit; expando.BasePriceAmount = product.BasePriceAmount; expando.BasePriceBaseAmount = product.BasePriceBaseAmount; expando.BasePriceHasValue = product.BasePriceHasValue; - expando.VisibleIndividually = product.VisibleIndividually; - expando.DisplayOrder = product.DisplayOrder; expando.BundleTitleText = product.GetLocalized(x => x.BundleTitleText, ctx.Projection.LanguageId ?? 0, true, false); - expando.BundlePerItemPricing = product.BundlePerItemPricing; expando.BundlePerItemShipping = product.BundlePerItemShipping; + expando.BundlePerItemPricing = product.BundlePerItemPricing; expando.BundlePerItemShoppingCart = product.BundlePerItemShoppingCart; - expando.LowestAttributeCombinationPrice = product.LowestAttributeCombinationPrice; - expando.IsEsd = product.IsEsd; expando._Localized = GetLocalized(ctx, product, x => x.Name, @@ -1158,10 +1163,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Product product) x => x.MetaTitle, x => x.BundleTitleText); - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Order order) + private dynamic ToExpando(ExportProfileTaskContext ctx, Order order) { if (order == null) return null; @@ -1248,10 +1253,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Order order) expando.Shipments = null; expando.RedeemedRewardPointsEntry = ToExpando(ctx, order.RedeemedRewardPointsEntry); - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, OrderItem orderItem) + private dynamic ToExpando(ExportProfileTaskContext ctx, OrderItem orderItem) { if (orderItem == null) return null; @@ -1282,10 +1287,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, OrderItem orderIte expando.Product = ToExpando(ctx, orderItem.Product); - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Shipment shipment) + private dynamic ToExpando(ExportProfileTaskContext ctx, Shipment shipment) { if (shipment == null) return null; @@ -1310,14 +1315,14 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Shipment shipment) exp.ShipmentId = x.ShipmentId; exp.OrderItemId = x.OrderItemId; exp.Quantity = x.Quantity; - return exp as ExpandoObject; + return exp; }) .ToList(); - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Discount discount) + private dynamic ToExpando(ExportProfileTaskContext ctx, Discount discount) { if (discount == null) return null; @@ -1338,10 +1343,10 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, Discount discount) expando.DiscountLimitationId = discount.DiscountLimitationId; expando.LimitationTimes = discount.LimitationTimes; - return expando as ExpandoObject; + return expando; } - private ExpandoObject ToExpando(ExportProfileTaskContext ctx, ProductSpecificationAttribute psa) + private dynamic ToExpando(ExportProfileTaskContext ctx, ProductSpecificationAttribute psa) { if (psa == null) return null; @@ -1376,7 +1381,7 @@ private ExpandoObject ToExpando(ExportProfileTaskContext ctx, ProductSpecificati expando.SpecificationAttributeOption = expOption; - return expando as ExpandoObject; + return expando; } #endregion @@ -1498,9 +1503,9 @@ private List GetOrders(ExportProfileTaskContext ctx, int pageIndex, int p return result; } - private List ConvertToExpando(ExportProfileTaskContext ctx, Product product) + private List ConvertToExpando(ExportProfileTaskContext ctx, Product product) { - var result = new List(); + var result = new List(); var productTemplate = ctx.ProductTemplates.FirstOrDefault(x => x.Key == product.ProductTemplateId); var pictureSize = _mediaSettings.ProductDetailsPictureSize; @@ -1541,9 +1546,10 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ exp._Entity = x; exp.Id = x.Id; exp.DisplayOrder = x.DisplayOrder; + exp.PictureId = x.PictureId; exp.Picture = ToExpando(ctx, x.Picture, _mediaSettings.ProductThumbPictureSize, pictureSize); - return exp as ExpandoObject; + return exp; }) .ToList(); @@ -1556,6 +1562,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ exp.Id = x.Id; exp.DisplayOrder = x.DisplayOrder; exp.IsFeaturedProduct = x.IsFeaturedProduct; + exp.ManufacturerId = x.ManufacturerId; exp.Manufacturer = ToExpando(ctx, x.Manufacturer); if (x.Manufacturer != null && x.Manufacturer.PictureId.HasValue) @@ -1563,7 +1570,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ else exp.Manufacturer.Picture = null; - return exp as ExpandoObject; + return exp; }) .ToList(); @@ -1576,6 +1583,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ exp.Id = x.Id; exp.DisplayOrder = x.DisplayOrder; exp.IsFeaturedProduct = x.IsFeaturedProduct; + exp.CategoryId = x.CategoryId; exp.Category = ToExpando(ctx, x.Category); if (x.Category != null && x.Category.PictureId.HasValue) @@ -1586,7 +1594,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ if (expando._CategoryName == null) expando._CategoryName = (string)exp.Category.Name; - return exp as ExpandoObject; + return exp; }) .ToList(); @@ -1599,7 +1607,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ .Select(x => { dynamic exp = ToExpando(ctx, x); - var assignedPictures = new List(); + var assignedPictures = new List(); foreach (int pictureId in x.GetAssignedPictureIds()) { @@ -1612,7 +1620,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ exp.Pictures = assignedPictures; - return exp as ExpandoObject; + return exp; }) .ToList(); @@ -1632,13 +1640,13 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ exp.CustomerRoleId = x.CustomerRoleId; exp.Quantity = x.Quantity; exp.Price = x.Price; - return exp as ExpandoObject; + return exp; }) .ToList(); } else { - expando.TierPrices = new List(); + expando.TierPrices = new List(); } if (product.HasDiscountsApplied) @@ -1651,7 +1659,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ } else { - expando.AppliedDiscounts = new List(); + expando.AppliedDiscounts = new List(); } #endregion @@ -1672,7 +1680,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ exp.Name = x.GetLocalized(y => y.Name, ctx.Projection.LanguageId ?? 0, true, false); exp.SeName = x.GetSeName(ctx.Projection.LanguageId ?? 0); exp._Localized = GetLocalized(ctx, x, y => y.Name); - return exp as ExpandoObject; + return exp; }) .ToList(); @@ -1705,20 +1713,20 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ exp.CreatedOnUtc = x.CreatedOnUtc; exp.UpdatedOnUtc = x.UpdatedOnUtc; exp._Localized = GetLocalized(ctx, x, y => y.Name, y => y.ShortDescription); - return exp as ExpandoObject; + return exp; }) .ToList(); } else { - expando.ProductBundleItems = new List(); + expando.ProductBundleItems = new List(); } } else { - expando.ProductTags = new List(); - expando.ProductSpecificationAttributes = new List(); - expando.ProductBundleItems = new List(); + expando.ProductTags = new List(); + expando.ProductSpecificationAttributes = new List(); + expando.ProductBundleItems = new List(); } #endregion @@ -1873,9 +1881,9 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Produ return result; } - private List ConvertToExpando(ExportProfileTaskContext ctx, Order order) + private List ConvertToExpando(ExportProfileTaskContext ctx, Order order) { - var result = new List(); + var result = new List(); ctx.OrderDataContext.Addresses.Collect(order.ShippingAddressId ?? 0); @@ -1926,7 +1934,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Order GetDeliveryTimeAndQuantityUnit(ctx, exp.Product, e.Product.DeliveryTimeId, e.Product.QuantityUnitId); - return exp as ExpandoObject; + return exp; }) .ToList(); @@ -2013,7 +2021,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) logHead.AppendLine(); logHead.AppendLine(new string('-', 40)); logHead.AppendLine("SmartStore.NET:\t\tv." + SmartStoreVersion.CurrentFullVersion); - logHead.AppendLine("Export profile:\t\t{0} (Id {1})".FormatInvariant(ctx.Profile.Name, ctx.Profile.Id)); + logHead.AppendLine("Export profile:\t\t{0} (Id {1})".FormatInvariant(ctx.Profile.Name, ctx.Profile.Id == 0 ? "volatile" : ctx.Profile.Id.ToString())); var plugin = ctx.Provider.Metadata.PluginDescriptor; logHead.Append("Plugin:\t\t\t\t"); diff --git a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs index 605fdbe689..5b43c9c921 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Drawing; using System.IO; -using System.Linq; using System.Text; using System.Xml; using OfficeOpenXml; @@ -14,11 +13,8 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; -using SmartStore.Services.Localization; -using SmartStore.Services.Media; using SmartStore.Services.Messages; using SmartStore.Services.Seo; -using SmartStore.Services.Stores; namespace SmartStore.Services.ExportImport { @@ -31,12 +27,7 @@ public partial class ExportManager : IExportManager private readonly ICategoryService _categoryService; private readonly IManufacturerService _manufacturerService; - private readonly IProductService _productService; - private readonly IPictureService _pictureService; private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; - private readonly ILanguageService _languageService; - private readonly ICommonServices _services; - private readonly IStoreMappingService _storeMappingService; #endregion @@ -44,21 +35,11 @@ public partial class ExportManager : IExportManager public ExportManager(ICategoryService categoryService, IManufacturerService manufacturerService, - IProductService productService, - IPictureService pictureService, - INewsLetterSubscriptionService newsLetterSubscriptionService, - ILanguageService languageService, - ICommonServices services, - IStoreMappingService storeMappingService) + INewsLetterSubscriptionService newsLetterSubscriptionService) { this._categoryService = categoryService; this._manufacturerService = manufacturerService; - this._productService = productService; - this._pictureService = pictureService; this._newsLetterSubscriptionService = newsLetterSubscriptionService; - this._languageService = languageService; - this._services = services; - this._storeMappingService = storeMappingService; Logger = NullLogger.Instance; } @@ -218,503 +199,6 @@ public virtual string ExportCategoriesToXml() return stringWriter.ToString(); } - /// - /// Export products to XLSX - /// - /// Stream - /// Products - public virtual void ExportProductsToXlsx(Stream stream, IList products) - { - if (stream == null) - throw new ArgumentNullException("stream"); - - // ok, we can run the real code of the sample now - using (var xlPackage = new ExcelPackage(stream)) - { - // uncomment this line if you want the XML written out to the outputDir - //xlPackage.DebugMode = true; - - // get handle to the existing worksheet - var worksheet = xlPackage.Workbook.Worksheets.Add("Products"); - - // get handle to the cells range of the worksheet - var cells = worksheet.Cells; - - //Create Headers and format them - var properties = new string[] - { - "ProductTypeId", - "ParentGroupedProductId", - "VisibleIndividually", - "Name", - "ShortDescription", - "FullDescription", - "ProductTemplateId", - "ShowOnHomePage", - "HomePageDisplayOrder", - "MetaKeywords", - "MetaDescription", - "MetaTitle", - "SeName", - "AllowCustomerReviews", - "Published", - "SKU", - "ManufacturerPartNumber", - "Gtin", - "IsGiftCard", - "GiftCardTypeId", - "RequireOtherProducts", - "RequiredProductIds", - "AutomaticallyAddRequiredProducts", - "IsDownload", - "DownloadId", - "UnlimitedDownloads", - "MaxNumberOfDownloads", - "DownloadActivationTypeId", - "HasSampleDownload", - "SampleDownloadId", - "HasUserAgreement", - "UserAgreementText", - "IsRecurring", - "RecurringCycleLength", - "RecurringCyclePeriodId", - "RecurringTotalCycles", - "IsShipEnabled", - "IsFreeShipping", - "AdditionalShippingCharge", - "IsEsd", - "IsTaxExempt", - "TaxCategoryId", - "ManageInventoryMethodId", - "StockQuantity", - "DisplayStockAvailability", - "DisplayStockQuantity", - "MinStockQuantity", - "LowStockActivityId", - "NotifyAdminForQuantityBelow", - "BackorderModeId", - "AllowBackInStockSubscriptions", - "OrderMinimumQuantity", - "OrderMaximumQuantity", - "AllowedQuantities", - "DisableBuyButton", - "DisableWishlistButton", - "AvailableForPreOrder", - "CallForPrice", - "Price", - "OldPrice", - "ProductCost", - "SpecialPrice", - "SpecialPriceStartDateTimeUtc", - "SpecialPriceEndDateTimeUtc", - "CustomerEntersPrice", - "MinimumCustomerEnteredPrice", - "MaximumCustomerEnteredPrice", - "Weight", - "Length", - "Width", - "Height", - "CreatedOnUtc", - "CategoryIds", - "ManufacturerIds", - "Picture1", - "Picture2", - "Picture3", - "DeliveryTimeId", - "QuantityUnitId", - "BasePriceEnabled", - "BasePriceMeasureUnit", - "BasePriceAmount", - "BasePriceBaseAmount", - "BundleTitleText", - "BundlePerItemShipping", - "BundlePerItemPricing", - "BundlePerItemShoppingCart", - "BundleItemSkus", - "AvailableStartDateTimeUtc", - "AvailableEndDateTimeUtc", - "StoreIds", - "LimitedToStores" - }; - - //BEGIN: add headers for languages - var languages = _languageService.GetAllLanguages(true); - var headlines = new string[properties.Length + languages.Count * 3]; - var languageFields = new string[languages.Count * 3]; - var j = 0; - - foreach (var lang in languages) - { - languageFields.SetValue("Name[" + lang.UniqueSeoCode + "]", j++); - languageFields.SetValue("ShortDescription[" + lang.UniqueSeoCode + "]", j++); - languageFields.SetValue("FullDescription[" + lang.UniqueSeoCode + "]", j++); - } - - properties.CopyTo(headlines, 0); - languageFields.CopyTo(headlines, properties.Length); - //END: add headers for languages - - for (int i = 0; i < headlines.Length; i++) - { - cells[1, i + 1].Value = headlines[i]; - cells[1, i + 1].Style.Fill.PatternType = ExcelFillStyle.Solid; - cells[1, i + 1].Style.Fill.BackgroundColor.SetColor(Color.FromArgb(184, 204, 228)); - cells[1, i + 1].Style.Font.Bold = true; - } - - - int row = 2; - foreach (var p in products) - { - int col = 1; - - cells[row, col].Value = p.ProductTypeId; - col++; - - cells[row, col].Value = p.ParentGroupedProductId; - col++; - - cells[row, col].Value = p.VisibleIndividually; - col++; - - cells[row, col].Value = p.Name; - col++; - - cells[row, col].Value = p.ShortDescription; - col++; - - cells[row, col].Value = p.FullDescription; - col++; - - cells[row, col].Value = p.ProductTemplateId; - col++; - - cells[row, col].Value = p.ShowOnHomePage; - col++; - - cells[row, col].Value = p.HomePageDisplayOrder; - col++; - - cells[row, col].Value = p.MetaKeywords; - col++; - - cells[row, col].Value = p.MetaDescription; - col++; - - cells[row, col].Value = p.MetaTitle; - col++; - - cells[row, col].Value = p.GetSeName(0); - col++; - - cells[row, col].Value = p.AllowCustomerReviews; - col++; - - cells[row, col].Value = p.Published; - col++; - - cells[row, col].Value = p.Sku; - col++; - - cells[row, col].Value = p.ManufacturerPartNumber; - col++; - - cells[row, col].Value = p.Gtin; - col++; - - cells[row, col].Value = p.IsGiftCard; - col++; - - cells[row, col].Value = p.GiftCardTypeId; - col++; - - cells[row, col].Value = p.RequireOtherProducts; - col++; - - cells[row, col].Value = p.RequiredProductIds; - col++; - - cells[row, col].Value = p.AutomaticallyAddRequiredProducts; - col++; - - cells[row, col].Value = p.IsDownload; - col++; - - cells[row, col].Value = p.DownloadId; - col++; - - cells[row, col].Value = p.UnlimitedDownloads; - col++; - - cells[row, col].Value = p.MaxNumberOfDownloads; - col++; - - cells[row, col].Value = p.DownloadActivationTypeId; - col++; - - cells[row, col].Value = p.HasSampleDownload; - col++; - - cells[row, col].Value = p.SampleDownloadId; - col++; - - cells[row, col].Value = p.HasUserAgreement; - col++; - - cells[row, col].Value = p.UserAgreementText; - col++; - - cells[row, col].Value = p.IsRecurring; - col++; - - cells[row, col].Value = p.RecurringCycleLength; - col++; - - cells[row, col].Value = p.RecurringCyclePeriodId; - col++; - - cells[row, col].Value = p.RecurringTotalCycles; - col++; - - cells[row, col].Value = p.IsShipEnabled; - col++; - - cells[row, col].Value = p.IsFreeShipping; - col++; - - cells[row, col].Value = p.AdditionalShippingCharge; - col++; - - cells[row, col].Value = p.IsEsd; - col++; - - cells[row, col].Value = p.IsTaxExempt; - col++; - - cells[row, col].Value = p.TaxCategoryId; - col++; - - cells[row, col].Value = p.ManageInventoryMethodId; - col++; - - cells[row, col].Value = p.StockQuantity; - col++; - - cells[row, col].Value = p.DisplayStockAvailability; - col++; - - cells[row, col].Value = p.DisplayStockQuantity; - col++; - - cells[row, col].Value = p.MinStockQuantity; - col++; - - cells[row, col].Value = p.LowStockActivityId; - col++; - - cells[row, col].Value = p.NotifyAdminForQuantityBelow; - col++; - - cells[row, col].Value = p.BackorderModeId; - col++; - - cells[row, col].Value = p.AllowBackInStockSubscriptions; - col++; - - cells[row, col].Value = p.OrderMinimumQuantity; - col++; - - cells[row, col].Value = p.OrderMaximumQuantity; - col++; - - cells[row, col].Value = p.AllowedQuantities; - col++; - - cells[row, col].Value = p.DisableBuyButton; - col++; - - cells[row, col].Value = p.DisableWishlistButton; - col++; - - cells[row, col].Value = p.AvailableForPreOrder; - col++; - - cells[row, col].Value = p.CallForPrice; - col++; - - cells[row, col].Value = p.Price; - col++; - - cells[row, col].Value = p.OldPrice; - col++; - - cells[row, col].Value = p.ProductCost; - col++; - - cells[row, col].Value = p.SpecialPrice; - col++; - - cells[row, col].Value = p.SpecialPriceStartDateTimeUtc; - col++; - - cells[row, col].Value = p.SpecialPriceEndDateTimeUtc; - col++; - - cells[row, col].Value = p.CustomerEntersPrice; - col++; - - cells[row, col].Value = p.MinimumCustomerEnteredPrice; - col++; - - cells[row, col].Value = p.MaximumCustomerEnteredPrice; - col++; - - cells[row, col].Value = p.Weight; - col++; - - cells[row, col].Value = p.Length; - col++; - - cells[row, col].Value = p.Width; - col++; - - cells[row, col].Value = p.Height; - col++; - - cells[row, col].Value = p.CreatedOnUtc.ToOADate(); - col++; - - //category identifiers - string categoryIds = null; - foreach (var pc in _categoryService.GetProductCategoriesByProductId(p.Id)) - { - categoryIds += pc.CategoryId; - categoryIds += ";"; - } - cells[row, col].Value = categoryIds; - col++; - - //manufacturer identifiers - string manufacturerIds = null; - foreach (var pm in _manufacturerService.GetProductManufacturersByProductId(p.Id)) - { - manufacturerIds += pm.ManufacturerId; - manufacturerIds += ";"; - } - cells[row, col].Value = manufacturerIds; - col++; - - //pictures (up to 3 pictures) - var pics = new string[] { null, null, null }; - var pictures = _pictureService.GetPicturesByProductId(p.Id, 3); - for (int i = 0; i < pictures.Count; i++) - { - pics[i] = _pictureService.GetThumbLocalPath(pictures[i]); - pictures[i].PictureBinary = null; - } - cells[row, col].Value = pics[0]; - col++; - cells[row, col].Value = pics[1]; - col++; - cells[row, col].Value = pics[2]; - col++; - - cells[row, col].Value = p.DeliveryTimeId; - col++; - cells[row, col].Value = p.QuantityUnitId; - col++; - cells[row, col].Value = p.BasePriceEnabled; - col++; - cells[row, col].Value = p.BasePriceMeasureUnit; - col++; - cells[row, col].Value = p.BasePriceAmount; - col++; - cells[row, col].Value = p.BasePriceBaseAmount; - col++; - - cells[row, col].Value = p.BundleTitleText; - col++; - - cells[row, col].Value = p.BundlePerItemShipping; - col++; - - cells[row, col].Value = p.BundlePerItemPricing; - col++; - - cells[row, col].Value = p.BundlePerItemShoppingCart; - col++; - - string bundleItemSkus = ""; - - if (p.ProductType == ProductType.BundledProduct) - { - bundleItemSkus = string.Join(",", _productService.GetBundleItems(p.Id, true).Select(x => x.Item.Product.Sku)); - } - - cells[row, col].Value = bundleItemSkus; - col++; - - cells[row, col].Value = p.AvailableStartDateTimeUtc; - col++; - - cells[row, col].Value = p.AvailableEndDateTimeUtc; - col++; - - string storeIds = ""; - - if (p.LimitedToStores) - { - storeIds = string.Join(";", _storeMappingService.GetStoreMappings(p).Select(x => x.StoreId)); - } - cells[row, col].Value = storeIds; - col++; - - cells[row, col].Value = p.LimitedToStores; - col++; - - //BEGIN: export localized values - foreach (var lang in languages) - { - worksheet.Cells[row, col].Value = p.GetLocalized(x => x.Name, lang.Id, false, false); - col++; - - worksheet.Cells[row, col].Value = p.GetLocalized(x => x.ShortDescription, lang.Id, false, false); - col++; - - worksheet.Cells[row, col].Value = p.GetLocalized(x => x.FullDescription, lang.Id, false, false); - col++; - } - //END: export localized values - - row++; - } - - // we had better add some document properties to the spreadsheet - - // set some core property values - //var storeName = _storeInformationSettings.StoreName; - //var storeUrl = _storeInformationSettings.StoreUrl; - //xlPackage.Workbook.Properties.Title = string.Format("{0} products", storeName); - //xlPackage.Workbook.Properties.Author = storeName; - //xlPackage.Workbook.Properties.Subject = string.Format("{0} products", storeName); - //xlPackage.Workbook.Properties.Keywords = string.Format("{0} products", storeName); - //xlPackage.Workbook.Properties.Category = "Products"; - //xlPackage.Workbook.Properties.Comments = string.Format("{0} products", storeName); - - // set some extended property values - //xlPackage.Workbook.Properties.Company = storeName; - //xlPackage.Workbook.Properties.HyperlinkBase = new Uri(storeUrl); - - // save the new spreadsheet - xlPackage.Save(); - } - - // EPPLus had serious memory leak problems in V3. - // We enforce the garbage collector to release unused memory, - // it's not perfect, but better than nothing. - GC.Collect(); - } - /// /// Export customer list to XLSX /// diff --git a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs index 290f60dfe1..0700f4ead3 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs @@ -23,13 +23,6 @@ public interface IExportManager /// Result in XML format string ExportCategoriesToXml(); - /// - /// Export products to XLSX - /// - /// Stream - /// Products - void ExportProductsToXlsx(Stream stream, IList products); - /// /// Export customer list to XLSX /// diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 366942ced1..6c0abaf27f 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -171,6 +171,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index bf2ffe38ee..5f35f710ac 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net.Mime; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -23,7 +22,6 @@ using SmartStore.Core.Domain.Seo; using SmartStore.Core.Events; using SmartStore.Core.Infrastructure; -using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Services; using SmartStore.Services.Catalog; @@ -2804,65 +2802,22 @@ public ActionResult ExportXmlSelected(string selectedIds) return Export(ExportProductXmlProvider.SystemName, selectedIds); } + [Compress] public ActionResult ExportExcelAll() { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - try - { - using (var scope = new DbContextScope(_dbContext, autoDetectChanges: false, forceNoTracking: true)) - { - var ctx = new ProductSearchContext(); - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageSize = int.MaxValue; - ctx.ShowHidden = true; - - var products = _productService.SearchProducts(ctx); - - byte[] bytes = null; - using (var stream = new MemoryStream()) - { - _exportManager.ExportProductsToXlsx(stream, products); - bytes = stream.ToArray(); - } - return File(bytes, "text/xls", "products.xlsx"); - } - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } + return Export(ExportProductXlsxProvider.SystemName, null); } - [HttpPost] + [HttpPost, Compress] public ActionResult ExportExcelSelected(string selectedIds) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - using (var scope = new DbContextScope(_dbContext, autoDetectChanges: false, forceNoTracking: true)) - { - var products = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - products.AddRange(_productService.GetProductsByIds(ids)); - } - - byte[] bytes = null; - using (var stream = new MemoryStream()) - { - _exportManager.ExportProductsToXlsx(stream, products); - bytes = stream.ToArray(); - } - return File(bytes, "text/xls", "products.xlsx"); - } + return Export(ExportProductXlsxProvider.SystemName, selectedIds); } public ActionResult ExportPdf(bool all, string selectedIds = null) diff --git a/src/Tests/SmartStore.Services.Tests/ExportImport/ExportManagerTests.cs b/src/Tests/SmartStore.Services.Tests/ExportImport/ExportManagerTests.cs deleted file mode 100644 index dcdd328196..0000000000 --- a/src/Tests/SmartStore.Services.Tests/ExportImport/ExportManagerTests.cs +++ /dev/null @@ -1,237 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using SmartStore.Core.Domain; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Domain.Payments; -using SmartStore.Core.Domain.Shipping; -using SmartStore.Core.Domain.Tax; -using SmartStore.Services.Catalog; -using SmartStore.Services.ExportImport; -using SmartStore.Services.Localization; -using SmartStore.Services.Media; -using SmartStore.Services.Messages; -using SmartStore.Tests; -using NUnit.Framework; -using Rhino.Mocks; -using SmartStore.Core.Domain.Media; -using SmartStore.Services.Stores; - -namespace SmartStore.Services.Tests.ExportImport -{ - [TestFixture] - public class ExportManagerTests : ServiceTest - { - ICategoryService _categoryService; - IManufacturerService _manufacturerService; - IProductService _productService; - IProductTemplateService _productTemplateService; - IPictureService _pictureService; - INewsLetterSubscriptionService _newsLetterSubscriptionService; - IExportManager _exportManager; - ILanguageService _languageService; - MediaSettings _mediaSettings; - ICommonServices _services; - IStoreMappingService _storeMapping; - - [SetUp] - public new void SetUp() - { - _categoryService = MockRepository.GenerateMock(); - _manufacturerService = MockRepository.GenerateMock(); - _productService = MockRepository.GenerateMock(); - _productTemplateService = MockRepository.GenerateMock(); - _pictureService = MockRepository.GenerateMock(); - _newsLetterSubscriptionService = MockRepository.GenerateMock(); - _languageService = MockRepository.GenerateMock(); - _mediaSettings = MockRepository.GenerateMock(); - _services = MockRepository.GenerateMock(); - _storeMapping = MockRepository.GenerateMock(); - - _exportManager = new ExportManager(_categoryService, _manufacturerService, _productService, _productTemplateService, _pictureService, - _newsLetterSubscriptionService, _languageService, _mediaSettings, _services, _storeMapping); - } - - //[Test] - //public void Can_export_manufacturers_to_xml() - //{ - // var manufacturers = new List() - // { - // new Manufacturer() - // { - // Id = 1, - // Name = "Name", - // Description = "Description 1", - // MetaKeywords = "Meta keywords", - // MetaDescription = "Meta description", - // MetaTitle = "Meta title", - // PictureId = 0, - // PageSize = 4, - // PriceRanges = "1-3;", - // Published = true, - // Deleted = false, - // DisplayOrder = 5, - // CreatedOnUtc = new DateTime(2010, 01, 01), - // UpdatedOnUtc = new DateTime(2010, 01, 02), - // }, - // new Manufacturer() - // { - // Id = 2, - // Name = "Name 2", - // Description = "Description 2", - // MetaKeywords = "Meta keywords", - // MetaDescription = "Meta description", - // MetaTitle = "Meta title", - // PictureId = 0, - // PageSize = 4, - // PriceRanges = "1-3;", - // Published = true, - // Deleted = false, - // DisplayOrder = 5, - // CreatedOnUtc = new DateTime(2010, 01, 01), - // UpdatedOnUtc = new DateTime(2010, 01, 02), - // } - // }; - - // string result = _exportManager.ExportManufacturersToXml(manufacturers); - // //TODO test it - // String.IsNullOrEmpty(result).ShouldBeFalse(); - //} - - [Test] - public void Can_export_orders_xlsx() - { - var orders = new List() - { - new Order() - { - OrderGuid = Guid.NewGuid(), - Customer = GetTestCustomer(), - OrderStatus = OrderStatus.Complete, - ShippingStatus = ShippingStatus.Shipped, - PaymentStatus = PaymentStatus.Paid, - PaymentMethodSystemName = "PaymentMethodSystemName1", - CustomerCurrencyCode = "RUR", - CurrencyRate = 1.1M, - CustomerTaxDisplayType = TaxDisplayType.ExcludingTax, - VatNumber = "123456789", - OrderSubtotalInclTax = 2.1M, - OrderSubtotalExclTax = 3.1M, - OrderSubTotalDiscountInclTax = 4.1M, - OrderSubTotalDiscountExclTax = 5.1M, - OrderShippingInclTax = 6.1M, - OrderShippingExclTax = 7.1M, - OrderShippingTaxRate = 19.0M, - PaymentMethodAdditionalFeeInclTax = 8.1M, - PaymentMethodAdditionalFeeExclTax = 9.1M, - PaymentMethodAdditionalFeeTaxRate = 19.0M, - TaxRates = "1,3,5,7", - OrderTax = 10.1M, - OrderDiscount = 11.1M, - OrderTotal = 12.1M, - RefundedAmount = 13.1M, - CheckoutAttributeDescription = "CheckoutAttributeDescription1", - CheckoutAttributesXml = "CheckoutAttributesXml1", - CustomerLanguageId = 14, - AffiliateId= 15, - CustomerIp="CustomerIp1", - AllowStoringCreditCardNumber= true, - CardType= "Visa", - CardName = "John Smith", - CardNumber = "4111111111111111", - MaskedCreditCardNumber= "************1111", - CardCvv2= "123", - CardExpirationMonth= "12", - CardExpirationYear = "2010", - AuthorizationTransactionId = "AuthorizationTransactionId1", - AuthorizationTransactionCode="AuthorizationTransactionCode1", - AuthorizationTransactionResult="AuthorizationTransactionResult1", - CaptureTransactionId= "CaptureTransactionId1", - CaptureTransactionResult = "CaptureTransactionResult1", - SubscriptionTransactionId = "SubscriptionTransactionId1", - PurchaseOrderNumber= "PurchaseOrderNumber1", - PaidDateUtc= new DateTime(2010, 01, 01), - BillingAddress = GetTestBillingAddress(), - ShippingAddress = GetTestShippingAddress(), - ShippingMethod = "ShippingMethod1", - ShippingRateComputationMethodSystemName="ShippingRateComputationMethodSystemName1", - Deleted = false, - CreatedOnUtc = new DateTime(2010, 01, 04) - } - }; - string fileName = Path.GetTempFileName(); - //TODO uncomment - //_exportManager.ExportOrdersToXlsx(fileName, orders); - } - - protected Address GetTestBillingAddress() - { - return new Address() - { - FirstName = "FirstName 1", - LastName = "LastName 1", - Email = "Email 1", - Company = "Company 1", - City = "City 1", - Address1 = "Address1a", - Address2 = "Address1a", - ZipPostalCode = "ZipPostalCode 1", - PhoneNumber = "PhoneNumber 1", - FaxNumber = "FaxNumber 1", - CreatedOnUtc = new DateTime(2010, 01, 01), - Country = GetTestCountry() - }; - } - - protected Address GetTestShippingAddress() - { - return new Address() - { - FirstName = "FirstName 2", - LastName = "LastName 2", - Email = "Email 2", - Company = "Company 2", - City = "City 2", - Address1 = "Address2a", - Address2 = "Address2b", - ZipPostalCode = "ZipPostalCode 2", - PhoneNumber = "PhoneNumber 2", - FaxNumber = "FaxNumber 2", - CreatedOnUtc = new DateTime(2010, 01, 01), - Country = GetTestCountry() - }; - } - - protected Country GetTestCountry() - { - return new Country - { - Name = "United States", - AllowsBilling = true, - AllowsShipping = true, - TwoLetterIsoCode = "US", - ThreeLetterIsoCode = "USA", - NumericIsoCode = 1, - SubjectToVat = true, - Published = true, - DisplayOrder = 1 - }; - } - - protected Customer GetTestCustomer() - { - return new Customer - { - CustomerGuid = Guid.NewGuid(), - AdminComment = "some comment here", - Active = true, - Deleted = false, - CreatedOnUtc = new DateTime(2010, 01, 01) - }; - } - } -} diff --git a/src/Tests/SmartStore.Services.Tests/SmartStore.Services.Tests.csproj b/src/Tests/SmartStore.Services.Tests/SmartStore.Services.Tests.csproj index 69f97a9946..b57ac1295b 100644 --- a/src/Tests/SmartStore.Services.Tests/SmartStore.Services.Tests.csproj +++ b/src/Tests/SmartStore.Services.Tests/SmartStore.Services.Tests.csproj @@ -102,7 +102,6 @@ - From c87b17115d51e486be5d8c44ff98db675351b47a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 6 Oct 2015 14:23:57 +0200 Subject: [PATCH 107/874] Convert manufacturer XML export to support new export framework --- .../Domain/DataExchange/ExportCore.cs | 2 +- .../201509150931528_ExportFramework2.cs | 12 ++ .../Catalog/IManufacturerService.cs | 9 +- .../Catalog/ManufacturerService.cs | 15 ++ .../ExportManufacturerXmlProvider.cs | 113 ++++++++++++++ .../ExportProductXmlProvider.cs | 3 +- .../ExportDataContextManufacturer.cs | 72 +++++++++ ...taContext.cs => ExportDataContextOrder.cs} | 4 +- ...Context.cs => ExportDataContextProduct.cs} | 4 +- .../ExportTask/ExportProfileTask.cs | 139 ++++++++++++++---- .../ExportTask/ExportProfileTaskContext.cs | 39 +++-- .../Media/IPictureService.cs | 7 + .../Media/PictureService.cs | 11 +- .../SmartStore.Services.csproj | 6 +- .../Controllers/AdminControllerBase.cs | 14 +- .../Controllers/ManufacturerController.cs | 14 +- .../Controllers/OrderController.cs | 14 -- .../Controllers/ProductController.cs | 14 -- .../Administration/Views/Export/Edit.cshtml | 20 ++- .../Views/Export/Preview.cshtml | 2 +- .../Views/Export/_Tab.Filter.cshtml | 42 +++--- .../Views/Export/_Tab.Projection.cshtml | 9 +- 22 files changed, 446 insertions(+), 119 deletions(-) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextManufacturer.cs rename src/Libraries/SmartStore.Services/DataExchange/ExportTask/{ExportOrderDataContext.cs => ExportDataContextOrder.cs} (97%) rename src/Libraries/SmartStore.Services/DataExchange/ExportTask/{ExportProductDataContext.cs => ExportDataContextProduct.cs} (97%) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs index 578698e062..047ee5022e 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs @@ -64,7 +64,7 @@ public enum ExportAttributeValueMerging public enum ExportSupport { /// - /// Whether to get more data like localized properties, product tags, bundle items etc. + /// Whether to get more data like product tags, bundle items etc. /// HighDataDepth = 0, diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index 05eacb8d86..c564032315 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -68,6 +68,18 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "In the preview group products are always displayed regardless of this option. Therefore, the displayed product number may vary from the number of exported products.", "In der Vorschau werden Gruppenprodukte ungeachtet dieser Option immer angezeigt. Daher kann die dort angezeigte Produktanzahl von der Anzahl der exportierten Produkte abweichen."); + builder.AddOrUpdate("Admin.DataExchange.Export.NoFiltering", + "There is no filtering available.", + "Mglichkeiten der Filterung stehen nicht zur Verfgung."); + + builder.AddOrUpdate("Admin.DataExchange.Export.NoProjection", + "There is no projection available.", + "Mglichkeiten der Projektion stehen nicht zur Verfgung."); + + builder.AddOrUpdate("Admin.DataExchange.Export.NoPreview", + "There is no preview available for this entity type.", + "Eine Vorschau steht fr diesen Entittstyp nicht zur Verfgung."); + builder.Delete( "Plugins.Feed.Froogle.TaskEnabled", diff --git a/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs b/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs index 43813c2f58..bf9d8ac64e 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IManufacturerService.cs @@ -87,7 +87,14 @@ IPagedList GetProductManufacturersByManufacturerId(int manu IList GetProductManufacturersByProductId(int productId, bool showHidden = false); /// - /// Get product manufacturer by product identifiers + /// Get product manufacturers by manufacturer identifiers + /// + /// Manufacturer identifiers + /// Product manufacturers + Multimap GetProductManufacturersByManufacturerIds(int[] manufacturerIds); + + /// + /// Get product manufacturers by product identifiers /// /// Product identifiers /// Product manufacturers diff --git a/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs b/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs index d027ba24db..f1a4711174 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs @@ -337,6 +337,21 @@ orderby mGroup.Key }); } + public virtual Multimap GetProductManufacturersByManufacturerIds(int[] manufacturerIds) + { + Guard.ArgumentNotNull(() => manufacturerIds); + + var query = _productManufacturerRepository.TableUntracked + .Where(x => manufacturerIds.Contains(x.ManufacturerId)) + .OrderBy(x => x.DisplayOrder); + + var map = query + .ToList() + .ToMultimap(x => x.ManufacturerId, x => x); + + return map; + } + public virtual Multimap GetProductManufacturersByProductIds(int[] productIds) { Guard.ArgumentNotNull(() => productIds); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs new file mode 100644 index 0000000000..6e19fc737c --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs @@ -0,0 +1,113 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using System.Xml; +using SmartStore.Core; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.DataExchange.ExportProvider +{ + [SystemName("Exports.SmartStoreManufacturerXml")] + [FriendlyName("SmartStore XML manufacturer export")] + [IsHidden(true)] + public class ExportManufacturerXmlProvider : IExportProvider + { + public static string SystemName + { + get { return "Exports.SmartStoreManufacturerXml"; } + } + + public ExportConfigurationInfo ConfigurationInfo + { + get { return null; } + } + + public ExportEntityType EntityType + { + get { return ExportEntityType.Manufacturer; } + } + + public string FileExtension + { + get { return "XML"; } + } + + public void Execute(IExportExecuteContext context) + { + var settings = new XmlWriterSettings + { + Encoding = Encoding.UTF8, + CheckCharacters = false, + Indent = true, + IndentChars = "\t" + }; + + var path = context.FilePath; + + context.Log.Information("Creating file " + path); + + using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + using (var writer = XmlWriter.Create(stream, settings)) + { + var xmlHelper = new ExportXmlHelper(writer, CultureInfo.InvariantCulture); + + writer.WriteStartDocument(); + writer.WriteStartElement("Manufacturers"); + writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); + + while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + { + var segment = context.Data.CurrentSegment; + + foreach (dynamic manufacturer in segment) + { + if (context.Abort != ExportAbortion.None) + break; + + int manufacturerId = manufacturer.Id; + + writer.WriteStartElement("Manufacturer"); + + try + { + xmlHelper.WriteManufacturer(manufacturer, null); + + writer.WriteStartElement("ProductManufacturers"); + foreach (dynamic productManu in manufacturer.ProductManufacturers) + { + writer.WriteStartElement("ProductManufacturer"); + writer.Write("Id", ((int)productManu.Id).ToString()); + writer.Write("ProductId", ((int)productManu.ProductId).ToString()); + writer.Write("DisplayOrder", ((int)productManu.DisplayOrder).ToString()); + writer.Write("IsFeaturedProduct", ((int)productManu.IsFeaturedProduct).ToString()); + writer.Write("ManufacturerId", ((int)productManu.ManufacturerId).ToString()); + writer.WriteEndElement(); // ProductManufacturer + } + writer.WriteEndElement(); // ProductManufacturers + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.Log.Error("Error while processing manufacturer with id {0}: {1}".FormatInvariant(manufacturerId, exc.ToAllMessages()), exc); + ++context.RecordsFailed; + } + + writer.WriteEndElement(); // Manufacturer + } + } + + writer.WriteEndElement(); // Manufacturers + writer.WriteEndDocument(); + } + } + + public void ExecuteEnded(IExportExecuteContext context) + { + // nothing to do + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs index a2cdefcaeb..858aa7fae4 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs @@ -47,14 +47,13 @@ public void Execute(IExportExecuteContext context) }; var path = context.FilePath; - var invariantCulture = CultureInfo.InvariantCulture; context.Log.Information("Creating file " + path); using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) using (var writer = XmlWriter.Create(stream, settings)) { - var xmlHelper = new ExportXmlHelper(writer, invariantCulture); + var xmlHelper = new ExportXmlHelper(writer, CultureInfo.InvariantCulture); writer.WriteStartDocument(); writer.WriteStartElement("Products"); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextManufacturer.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextManufacturer.cs new file mode 100644 index 0000000000..79bd1558ca --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextManufacturer.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Collections; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Media; + +namespace SmartStore.Services.DataExchange.ExportTask +{ + internal class ExportDataContextManufacturer + { + protected List _manufacturerIds; + protected List _pictureIds; + + private Func> _funcProductManufacturers; + private Func> _funcPictures; + + private LazyMultimap _productManufacturers; + private LazyMultimap _pictures; + + public ExportDataContextManufacturer(IEnumerable manufacturers, + Func> productManufacturers, + Func> pictures) + { + if (manufacturers == null) + { + _manufacturerIds = new List(); + _pictureIds = new List(); + } + else + { + _manufacturerIds = new List(manufacturers.Select(x => x.Id)); + _pictureIds = new List(manufacturers.Where(x => (x.PictureId ?? 0) != 0).Select(x => x.PictureId ?? 0)); + } + + _funcProductManufacturers = productManufacturers; + _funcPictures = pictures; + } + + public void Clear() + { + if (_productManufacturers != null) + _productManufacturers.Clear(); + if (_pictures != null) + _pictures.Clear(); + } + + public LazyMultimap ProductManufacturers + { + get + { + if (_productManufacturers == null) + { + _productManufacturers = new LazyMultimap(keys => _funcProductManufacturers(keys), _manufacturerIds); + } + return _productManufacturers; + } + } + + public LazyMultimap Pictures + { + get + { + if (_pictures == null) + { + _pictures = new LazyMultimap(keys => _funcPictures(keys).ToMultimap(x => x.Id, x => x), _pictureIds); + } + return _pictures; + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportOrderDataContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextOrder.cs similarity index 97% rename from src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportOrderDataContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextOrder.cs index 150e6884c7..38fa4893ae 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportOrderDataContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextOrder.cs @@ -9,7 +9,7 @@ namespace SmartStore.Services.DataExchange.ExportTask { - internal class ExportOrderDataContext + internal class ExportDataContextOrder { protected List _orderIds; protected List _customerIds; @@ -27,7 +27,7 @@ internal class ExportOrderDataContext private LazyMultimap _orderItems; private LazyMultimap _shipments; - public ExportOrderDataContext(IEnumerable orders, + public ExportDataContextOrder(IEnumerable orders, Func> customers, Func> rewardPointsHistory, Func> addresses, diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProductDataContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextProduct.cs similarity index 97% rename from src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProductDataContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextProduct.cs index 512513e7e8..14f9168be4 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProductDataContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextProduct.cs @@ -8,7 +8,7 @@ namespace SmartStore.Services.DataExchange.ExportTask { - internal class ExportProductDataContext : PriceCalculationContext + internal class ExportDataContextProduct : PriceCalculationContext { private List _productIdsBundleItems; @@ -24,7 +24,7 @@ internal class ExportProductDataContext : PriceCalculationContext private LazyMultimap _productSpecificationAttributes; private LazyMultimap _productBundleItems; - public ExportProductDataContext(IEnumerable products, + public ExportDataContextProduct(IEnumerable products, Func> attributes, Func> attributeCombinations, Func> tierPrices, diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 901f867194..ef078edc4c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -130,7 +130,7 @@ private void InitDependencies(TaskExecutionContext context) private List GetLocalized(ExportProfileTaskContext ctx, T entity, params Expression>[] keySelectors) where T : BaseEntity, ILocalizedEntity { - if (ctx.Languages.Count <= 1 || !ctx.Supporting[ExportSupport.HighDataDepth]) + if (ctx.Languages.Count <= 1) return null; var localized = new List(); @@ -269,7 +269,7 @@ private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic exp else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription || ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ManufacturerAndNameAndDescription) { - var productManus = ctx.ProductDataContext.ProductManufacturers.Load(product.Id); + var productManus = ctx.DataContextProduct.ProductManufacturers.Load(product.Id); if (productManus != null && productManus.Any()) description = productManus.First().Manufacturer.GetLocalized(x => x.Name, languageId, true, false); @@ -346,7 +346,7 @@ private decimal CalculatePrice(ExportProfileTaskContext ctx, Product product, bo // price type if (ctx.Projection.PriceType.HasValue && !forAttributeCombination) { - var priceCalculationContext = ctx.ProductDataContext as PriceCalculationContext; + var priceCalculationContext = ctx.DataContextProduct as PriceCalculationContext; if (ctx.Projection.PriceType.Value == PriceDisplayType.LowestPrice) { @@ -987,6 +987,8 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, Manufacturer manufacture expando.CreatedOnUtc = manufacturer.CreatedOnUtc; expando.UpdatedOnUtc = manufacturer.UpdatedOnUtc; + expando.Picture = null; + expando._Localized = GetLocalized(ctx, manufacturer, x => x.Name, x => x.Description, @@ -1426,7 +1428,7 @@ private List GetProducts(ExportProfileTaskContext ctx, int pageIndex) } // load data behind navigation properties for current page in one go - ctx.ProductDataContext = new ExportProductDataContext(products, + ctx.DataContextProduct = new ExportDataContextProduct(products, x => _productAttributeService.GetProductVariantAttributesByProductIds(x, null), x => _productAttributeService.GetProductVariantAttributeCombinations(x), x => _productService.GetTierPricesByProductIds(x, (ctx.Projection.CurrencyId ?? 0) != 0 ? ctx.ContextCustomer : null, ctx.Store.Id), @@ -1483,7 +1485,7 @@ private List GetOrders(ExportProfileTaskContext ctx, int pageIndex, int p if (pageSize > 1) { - ctx.OrderDataContext = new ExportOrderDataContext(result, + ctx.DataContextOrder = new ExportDataContextOrder(result, x => _customerService.GetCustomersByIds(x), x => _customerService.GetRewardPointsHistoriesByCustomerIds(x), x => _addressesService.GetAddressByIds(x), @@ -1503,6 +1505,33 @@ private List GetOrders(ExportProfileTaskContext ctx, int pageIndex, int p return result; } + private List GetManufacturers(ExportProfileTaskContext ctx, int pageIndex, int pageSize, out int totalCount) + { + var manus = _manufacturerService.GetAllManufacturers(null, pageIndex, ctx.PageSize, true); + + totalCount = manus.TotalCount; + + var result = manus as List; + + if (pageSize > 1) + { + ctx.DataContextManufacturer = new ExportDataContextManufacturer(result, + x => _manufacturerService.GetProductManufacturersByManufacturerIds(x), + x => _pictureService.GetPicturesByIds(x) + ); + + SetProgress(ctx, manus.Count); + } + + try + { + _services.DbContext.DetachEntities(result); + } + catch { } + + return result; + } + private List ConvertToExpando(ExportProfileTaskContext ctx, Product product) { var result = new List(); @@ -1513,11 +1542,11 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro if (ctx.Supporting[ExportSupport.ProjectionMainPictureUrl] && ctx.Projection.PictureSize > 0) pictureSize = ctx.Projection.PictureSize; - var productPictures = ctx.ProductDataContext.ProductPictures.Load(product.Id); - var productManufacturers = ctx.ProductDataContext.ProductManufacturers.Load(product.Id); - var productCategories = ctx.ProductDataContext.ProductCategories.Load(product.Id); - var productAttributes = ctx.ProductDataContext.Attributes.Load(product.Id); - var productAttributeCombinations = ctx.ProductDataContext.AttributeCombinations.Load(product.Id); + var productPictures = ctx.DataContextProduct.ProductPictures.Load(product.Id); + var productManufacturers = ctx.DataContextProduct.ProductManufacturers.Load(product.Id); + var productCategories = ctx.DataContextProduct.ProductCategories.Load(product.Id); + var productAttributes = ctx.DataContextProduct.Attributes.Load(product.Id); + var productAttributeCombinations = ctx.DataContextProduct.AttributeCombinations.Load(product.Id); dynamic expando = ToExpando(ctx, product); @@ -1545,6 +1574,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro dynamic exp = new ExpandoObject(); exp._Entity = x; exp.Id = x.Id; + exp.ProductId = x.ProductId; exp.DisplayOrder = x.DisplayOrder; exp.PictureId = x.PictureId; exp.Picture = ToExpando(ctx, x.Picture, _mediaSettings.ProductThumbPictureSize, pictureSize); @@ -1560,6 +1590,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro dynamic exp = new ExpandoObject(); exp._Entity = x; exp.Id = x.Id; + exp.ProductId = x.ProductId; exp.DisplayOrder = x.DisplayOrder; exp.IsFeaturedProduct = x.IsFeaturedProduct; exp.ManufacturerId = x.ManufacturerId; @@ -1581,6 +1612,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro dynamic exp = new ExpandoObject(); exp._Entity = x; exp.Id = x.Id; + exp.ProductId = x.ProductId; exp.DisplayOrder = x.DisplayOrder; exp.IsFeaturedProduct = x.IsFeaturedProduct; exp.CategoryId = x.CategoryId; @@ -1626,7 +1658,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro if (product.HasTierPrices) { - var tierPrices = ctx.ProductDataContext.TierPrices.Load(product.Id) + var tierPrices = ctx.DataContextProduct.TierPrices.Load(product.Id) .RemoveDuplicatedQuantities(); expando.TierPrices = tierPrices @@ -1651,7 +1683,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro if (product.HasDiscountsApplied) { - var appliedDiscounts = ctx.ProductDataContext.AppliedDiscounts.Load(product.Id); + var appliedDiscounts = ctx.DataContextProduct.AppliedDiscounts.Load(product.Id); expando.AppliedDiscounts = appliedDiscounts .Select(x => ToExpando(ctx, x)) @@ -1668,8 +1700,8 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro if (ctx.Supporting[ExportSupport.HighDataDepth]) { - var productTags = ctx.ProductDataContext.ProductTags.Load(product.Id); - var specificationAttributes = ctx.ProductDataContext.ProductSpecificationAttributes.Load(product.Id); + var productTags = ctx.DataContextProduct.ProductTags.Load(product.Id); + var specificationAttributes = ctx.DataContextProduct.ProductSpecificationAttributes.Load(product.Id); expando.ProductTags = productTags .Select(x => @@ -1690,7 +1722,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro if (product.ProductType == ProductType.BundledProduct) { - var bundleItems = ctx.ProductDataContext.ProductBundleItems.Load(product.Id); + var bundleItems = ctx.DataContextProduct.ProductBundleItems.Load(product.Id); expando.ProductBundleItems = bundleItems .Select(x => @@ -1741,7 +1773,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro if (ctx.Supporting[ExportSupport.ProjectionBrand]) { string brand = null; - var productManus = ctx.ProductDataContext.ProductManufacturers.Load(product.Id); + var productManus = ctx.DataContextProduct.ProductManufacturers.Load(product.Id); if (productManus != null && productManus.Any()) brand = productManus.First().Manufacturer.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); @@ -1885,13 +1917,13 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Order order { var result = new List(); - ctx.OrderDataContext.Addresses.Collect(order.ShippingAddressId ?? 0); + ctx.DataContextOrder.Addresses.Collect(order.ShippingAddressId ?? 0); - var addresses = ctx.OrderDataContext.Addresses.Load(order.BillingAddressId); - var customers = ctx.OrderDataContext.Customers.Load(order.CustomerId); - var rewardPointsHistories = ctx.OrderDataContext.RewardPointsHistories.Load(order.CustomerId); - var orderItems = ctx.OrderDataContext.OrderItems.Load(order.Id); - var shipments = ctx.OrderDataContext.Shipments.Load(order.Id); + var addresses = ctx.DataContextOrder.Addresses.Load(order.BillingAddressId); + var customers = ctx.DataContextOrder.Customers.Load(order.CustomerId); + var rewardPointsHistories = ctx.DataContextOrder.RewardPointsHistories.Load(order.CustomerId); + var orderItems = ctx.DataContextOrder.OrderItems.Load(order.Id); + var shipments = ctx.DataContextOrder.Shipments.Load(order.Id); dynamic expando = ToExpando(ctx, order); @@ -1946,6 +1978,42 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Order order return result; } + private List ConvertToExpando(ExportProfileTaskContext ctx, Manufacturer manu) + { + var result = new List(); + + var productManufacturers = ctx.DataContextManufacturer.ProductManufacturers.Load(manu.Id); + + dynamic expando = ToExpando(ctx, manu); + + if (manu.PictureId.HasValue) + { + var pictures = ctx.DataContextManufacturer.Pictures.Load(manu.PictureId.Value); + + if (pictures.Count > 0) + expando.Picture = ToExpando(ctx, pictures.First(), _mediaSettings.ManufacturerThumbPictureSize, _mediaSettings.ManufacturerThumbPictureSize); + } + + expando.ProductManufacturers = productManufacturers + .OrderBy(x => x.DisplayOrder) + .Select(x => + { + dynamic exp = new ExpandoObject(); + exp._Entity = x; + exp.Id = x.Id; + exp.ProductId = x.ProductId; + exp.DisplayOrder = x.DisplayOrder; + exp.IsFeaturedProduct = x.IsFeaturedProduct; + exp.ManufacturerId = x.ManufacturerId; + + return exp; + }) + .ToList(); + + result.Add(expando); + return result; + } + #endregion private List Init(ExportProfileTaskContext ctx) @@ -2004,6 +2072,13 @@ private List Init(ExportProfileTaskContext ctx) var unused = GetOrders(ctx, 0, 1, out totalCount); ctx.RecordsPerStore.Add(store.Id, totalCount); } + else if (ctx.Provider.Value.EntityType == ExportEntityType.Manufacturer) + { + ctx.Store = store; + int totalCount = 0; + var unused = GetManufacturers(ctx, 0, 1, out totalCount); + ctx.RecordsPerStore.Add(store.Id, totalCount); + } } return result; @@ -2021,7 +2096,8 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) logHead.AppendLine(); logHead.AppendLine(new string('-', 40)); logHead.AppendLine("SmartStore.NET:\t\tv." + SmartStoreVersion.CurrentFullVersion); - logHead.AppendLine("Export profile:\t\t{0} (Id {1})".FormatInvariant(ctx.Profile.Name, ctx.Profile.Id == 0 ? "volatile" : ctx.Profile.Id.ToString())); + logHead.Append("Export profile:\t\t" + ctx.Profile.Name); + logHead.AppendLine(ctx.Profile.Id == 0 ? " volatile" : " (Id {0})".FormatInvariant(ctx.Profile.Id)); var plugin = ctx.Provider.Metadata.PluginDescriptor; logHead.Append("Plugin:\t\t\t\t"); @@ -2045,6 +2121,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.PublicFolderPath = (ctx.Export.HasPublicDeployment ? Path.Combine(HttpRuntime.AppDomainAppPath, PublicFolder) : null); + int unused; var totalCount = ctx.RecordsPerStore.First(x => x.Key == ctx.Store.Id).Value; if (ctx.Provider.Value.EntityType == ExportEntityType.Product) @@ -2058,8 +2135,6 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) } else if (ctx.Provider.Value.EntityType == ExportEntityType.Order) { - int unused; - ctx.Export.Data = new ExportSegmenter( pageIndex => GetOrders(ctx, pageIndex, ctx.PageSize, out unused), entity => ConvertToExpando(ctx, entity), @@ -2067,6 +2142,15 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.IsPreview ? 0 : ctx.Profile.BatchSize ); } + else if (ctx.Provider.Value.EntityType == ExportEntityType.Manufacturer) + { + ctx.Export.Data = new ExportSegmenter( + pageIndex => GetManufacturers(ctx, pageIndex, ctx.PageSize, out unused), + entity => ConvertToExpando(ctx, entity), + new PagedList(ctx.Profile.Offset, ctx.Profile.Limit, ctx.PageIndex, ctx.PageSize, totalCount), + ctx.IsPreview ? 0 : ctx.Profile.BatchSize + ); + } if (ctx.Export.Data == null) { @@ -2285,8 +2369,9 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) ctx.CategoryPathes.Clear(); ctx.Categories.Clear(); ctx.EntityIdsSelected.Clear(); - ctx.ProductDataContext = null; - ctx.OrderDataContext = null; + ctx.DataContextProduct = null; + ctx.DataContextOrder = null; + ctx.DataContextManufacturer = null; (ctx.Export.Data as IExportExecuter).Dispose(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs index 83d0b060ac..395ebc168a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs @@ -19,8 +19,9 @@ namespace SmartStore.Services.DataExchange.ExportTask { internal class ExportProfileTaskContext { - private ExportProductDataContext _productDataContext; - private ExportOrderDataContext _orderDataContext; + private ExportDataContextProduct _dataContextProduct; + private ExportDataContextOrder _dataContextOrder; + private ExportDataContextManufacturer _dataContextManufacturer; public ExportProfileTaskContext( TaskExecutionContext taskContext, @@ -139,32 +140,46 @@ public string[] GetDeploymentFiles(ExportDeployment deployment) public Dictionary ProductTemplates { get; set; } // data loaded once per page - public ExportProductDataContext ProductDataContext + public ExportDataContextProduct DataContextProduct { get { - return _productDataContext; + return _dataContextProduct; } set { - if (_productDataContext != null) - _productDataContext.Clear(); + if (_dataContextProduct != null) + _dataContextProduct.Clear(); - _productDataContext = value; + _dataContextProduct = value; } } - public ExportOrderDataContext OrderDataContext + public ExportDataContextOrder DataContextOrder { get { - return _orderDataContext; + return _dataContextOrder; } set { - if (_orderDataContext != null) - _orderDataContext.Clear(); + if (_dataContextOrder != null) + _dataContextOrder.Clear(); - _orderDataContext = value; + _dataContextOrder = value; + } + } + public ExportDataContextManufacturer DataContextManufacturer + { + get + { + return _dataContextManufacturer; + } + set + { + if (_dataContextManufacturer != null) + _dataContextManufacturer.Clear(); + + _dataContextManufacturer = value; } } diff --git a/src/Libraries/SmartStore.Services/Media/IPictureService.cs b/src/Libraries/SmartStore.Services/Media/IPictureService.cs index 216e7ee061..85bc1741b5 100644 --- a/src/Libraries/SmartStore.Services/Media/IPictureService.cs +++ b/src/Libraries/SmartStore.Services/Media/IPictureService.cs @@ -137,6 +137,13 @@ string GetPictureUrl(Picture picture, /// Pictures IList GetPicturesByProductId(int productId, int recordsToReturn = 0); + /// + /// Gets pictures by picture identifier + /// + /// Picture identifier + /// Pictures + IList GetPicturesByIds(int[] pictureIds); + /// /// Inserts a picture /// diff --git a/src/Libraries/SmartStore.Services/Media/PictureService.cs b/src/Libraries/SmartStore.Services/Media/PictureService.cs index 34cbdeaf6b..95c58fb845 100644 --- a/src/Libraries/SmartStore.Services/Media/PictureService.cs +++ b/src/Libraries/SmartStore.Services/Media/PictureService.cs @@ -15,7 +15,6 @@ using SmartStore.Core.Logging; using SmartStore.Services.Configuration; using SmartStore.Utilities; -using SmartStore.Data; namespace SmartStore.Services.Media { @@ -552,6 +551,16 @@ orderby pp.DisplayOrder return pics; } + public virtual IList GetPicturesByIds(int[] pictureIds) + { + Guard.ArgumentNotNull(() => pictureIds); + + var query = _pictureRepository.Table + .Where(x => pictureIds.Contains(x.Id)); + + return query.ToList(); + } + public virtual Picture InsertPicture(byte[] pictureBinary, string mimeType, string seoFilename, bool isNew, bool isTransient = true, bool validateBinary = true) { mimeType = mimeType.EmptyNull(); diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 6c0abaf27f..a7401ad996 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -171,16 +171,18 @@ + + - - + + diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/AdminControllerBase.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/AdminControllerBase.cs index 3f60fe76d0..d9f4215b53 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/AdminControllerBase.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/AdminControllerBase.cs @@ -4,10 +4,10 @@ using System.Web.Routing; using SmartStore.Core; using SmartStore.Core.Infrastructure; +using SmartStore.Services.DataExchange.ExportTask; using SmartStore.Services.Localization; using SmartStore.Web.Framework.Localization; using SmartStore.Web.Framework.Security; -using SmartStore.Web.Framework.Controllers; namespace SmartStore.Web.Framework.Controllers { @@ -82,5 +82,17 @@ protected ActionResult AccessDeniedPartialView() return PartialView("~/Administration/Views/Security/AccessDenied.cshtml"); } + protected ActionResult Export(string providerSystemName, string selectedIds) + { + string error = null; + var fileStreamResult = ExportProfileTask.Export(providerSystemName, selectedIds, null, out error); + + if (fileStreamResult != null) + return fileStreamResult; + + NotifyError(string.Concat("

    ", T("Admin.Common.UnknownError"), "

    ", error.NaIfEmpty())); + + return RedirectToAction("List"); + } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs index 873ef04b9a..d7b7c1079f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs @@ -10,6 +10,7 @@ using SmartStore.Core.Logging; using SmartStore.Services.Catalog; using SmartStore.Services.Common; +using SmartStore.Services.DataExchange.ExportProvider; using SmartStore.Services.ExportImport; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; @@ -19,7 +20,6 @@ using SmartStore.Services.Stores; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; -using SmartStore.Web.Framework.Mvc; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -445,17 +445,7 @@ public ActionResult ExportXml() if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - try - { - var manufacturers = _manufacturerService.GetAllManufacturers(true); - var xml = _exportManager.ExportManufacturersToXml(manufacturers); - return new XmlDownloadResult(xml, "manufacturers.xml"); - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } + return Export(ExportManufacturerXmlProvider.SystemName, null); } #endregion diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs index fec8cf03bd..7d602580e0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs @@ -21,7 +21,6 @@ using SmartStore.Services.Common; using SmartStore.Services.Customers; using SmartStore.Services.DataExchange.ExportProvider; -using SmartStore.Services.DataExchange.ExportTask; using SmartStore.Services.Directory; using SmartStore.Services.ExportImport; using SmartStore.Services.Helpers; @@ -773,19 +772,6 @@ private void PrepareOrderAddressModel(OrderAddressModel model, Address address) } } - private ActionResult Export(string providerSystemName, string selectedIds) - { - string error = null; - var fileStreamResult = ExportProfileTask.Export(providerSystemName, selectedIds, null, out error); - - if (fileStreamResult != null) - return fileStreamResult; - - NotifyError(string.Concat("

    ", T("Admin.Common.UnknownError"), "

    ", error.NaIfEmpty())); - - return RedirectToAction("List"); - } - #endregion #region Order list diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 5f35f710ac..5844ead483 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -28,7 +28,6 @@ using SmartStore.Services.Common; using SmartStore.Services.Customers; using SmartStore.Services.DataExchange.ExportProvider; -using SmartStore.Services.DataExchange.ExportTask; using SmartStore.Services.Directory; using SmartStore.Services.Discounts; using SmartStore.Services.ExportImport; @@ -828,19 +827,6 @@ private void PrepareProductPictureThumbnailModel(ProductModel model, Product pro } } - private ActionResult Export(string providerSystemName, string selectedIds) - { - string error = null; - var fileStreamResult = ExportProfileTask.Export(providerSystemName, selectedIds, null, out error); - - if (fileStreamResult != null) - return fileStreamResult; - - NotifyError(string.Concat("

    ", T("Admin.Common.UnknownError"), "

    ", error.NaIfEmpty())); - - return RedirectToAction("List"); - } - #endregion Utitilies #region Methods diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index dda87168cd..91b4f715d6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -200,12 +200,28 @@ @helper TabFilter() { - @Html.Partial("_Tab.Filter", Model) + if (Model.Provider.EntityType == ExportEntityType.Product || Model.Provider.EntityType == ExportEntityType.Order) + { +
    @Html.Raw(T("Admin.DataExchange.Export.Filter.Note"))
    + @Html.Partial("_Tab.Filter", Model) + } + else + { +
    @T("Admin.DataExchange.Export.NoFiltering")
    + } } @helper TabProjection() { - @Html.Partial("_Tab.Projection", Model) + if (Model.Provider.EntityType == ExportEntityType.Product || Model.Provider.EntityType == ExportEntityType.Order) + { +
    @Html.Raw(T("Admin.DataExchange.Export.Projection.Note"))
    + @Html.Partial("_Tab.Projection", Model) + } + else + { +
    @T("Admin.DataExchange.Export.NoProjection")
    + } } @helper TabConfiguration() diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml index c0e06aca48..1c0af5cf0a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml @@ -172,5 +172,5 @@ else if (Model.EntityType == ExportEntityType.Order) } else { -
    Unsupported entity type!
    +
    @T("Admin.DataExchange.Export.NoPreview")
    } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Filter.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Filter.cshtml index 21bc0c7efb..a55b17962c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Filter.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Filter.cshtml @@ -2,27 +2,21 @@ @using SmartStore.Core.Domain.DataExchange; @using SmartStore.Admin.Models.DataExchange; -
    - - @Html.Raw(T("Admin.DataExchange.Export.Filter.Note")) -
    - - @if (Model.StoreCount > 1) - { - - - - - } - @if (Model.Provider.EntityType == ExportEntityType.Product) { + if (Model.StoreCount > 1) + { + + + + + } + + + + } - - - - - - - @if (Model.Provider.Description.HasValue()) - { - - - - - } - @if (Model.Provider.Url.HasValue()) - { + if (Model.HasActiveProvider) + { +
    - @Html.SmartLabelFor(x => x.Filter.StoreId) - - @Html.DropDownListFor(x => x.Filter.StoreId, Model.Filter.AvailableStores, Model.AllString) - @Html.ValidationMessageFor(x => x.Filter.StoreId) -
    + @Html.SmartLabelFor(x => x.Filter.StoreId) + + @Html.DropDownListFor(x => x.Filter.StoreId, Model.Filter.AvailableStores, Model.AllString) + @Html.ValidationMessageFor(x => x.Filter.StoreId) +
    @Html.SmartLabelFor(x => x.Filter.ProductType) @@ -153,6 +147,18 @@ } else if (Model.Provider.EntityType == ExportEntityType.Order) { + if (Model.StoreCount > 1) + { +
    + @Html.SmartLabelFor(x => x.Filter.StoreId) + + @Html.DropDownListFor(x => x.Filter.StoreId, Model.Filter.AvailableStores, Model.AllString) + @Html.ValidationMessageFor(x => x.Filter.StoreId) +
    @Html.SmartLabelFor(x => x.Filter.CreatedFrom) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml index 5390cb529e..7c782ec5ae 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml @@ -2,11 +2,6 @@ @using SmartStore.Core.Domain.DataExchange; @using SmartStore.Admin.Models.DataExchange; -
    - - @Html.Raw(T("Admin.DataExchange.Export.Projection.Note")) -
    - @if (Model.Provider.EntityType == ExportEntityType.Product) - { + { if (Model.Provider.Supporting.Contains(ExportSupport.ProjectionAttributeCombinationAsProduct)) { @@ -226,7 +221,7 @@ } } else if (Model.Provider.EntityType == ExportEntityType.Order) - { + { @if (Model.Provider.Description.HasValue()) @@ -121,22 +128,28 @@ } - - - - - - - - + @if (Model.Provider.Author.HasValue()) + { + + + + + } + @if (Model.Provider.Version.HasValue()) + { + + + + + } + + + } if (Model.Provider.Supporting.Contains(ExportSupport.ProjectionNoGroupedProducts)) @@ -78,7 +85,7 @@ From e526959371d6613ad552d27836fb175141156e22 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 16 Oct 2015 15:09:35 +0200 Subject: [PATCH 126/874] Export provider selection via select2 templating --- .../Administration/Content/admin.less | 36 +++++++++ .../Controllers/ExportController.cs | 39 +++++----- .../Models/DataExchange/ExportProfileModel.cs | 15 +++- .../Administration/Views/Export/Create.cshtml | 22 ++++-- .../Administration/Views/Export/List.cshtml | 77 ++++++++++++++++++- 5 files changed, 159 insertions(+), 30 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index 895e5f636b..947ca2b377 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -426,6 +426,42 @@ td.adminData > input[type="checkbox"] { } +/* select2 with large, templated items +-------------------------------------------------------------- */ +.large-select2 { + .select2-choice { + height: 52px; + min-height: 52px; + padding-top: 4px; + + > div > b { + padding-top: 14px; + } + } +} + +.large-select2-item { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + img { + margin-right: 12px; + vertical-align: middle; + } + > div { + margin-right: 0px !important; + display: inline-block; + vertical-align: top; + line-height: normal; + } + > div > span { + display: block; + padding-bottom: 1px; + } +} + + /* SM Telerik tweaks & overrides -------------------------------------------------------------- */ diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 8a13db3fb3..07952a0a1d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -89,6 +89,9 @@ public ExportController( private string GetThumbnailUrl(Provider provider) { + if (provider == null) + return ""; + var url = _pluginMediator.GetIconUrl(provider.Metadata); if (url.IsEmpty()) @@ -480,40 +483,40 @@ public ActionResult Create() { if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return Content(T("Admin.AccessDenied.Description")); - + + var count = 0; + var allProviders = _exportService.LoadAllExportProviders(0, false); + var model = new ExportProfileModel(); model.UnspecifiedString = T("Common.Unspecified"); model.Provider = new ExportProfileModel.ProviderModel(); - model.Provider.ProviderDescriptions = new Dictionary(); - model.Provider.AvailableExportProviders = _exportService.LoadAllExportProviders(0, false) + model.Provider.AvailableProviders = allProviders .Select(x => { - var item = new SelectListItem + var item = new ExportProfileModel.ProviderSelectItem { - Text = "{0} ({1})".FormatInvariant(_pluginMediator.GetLocalizedFriendlyName(x.Metadata), x.Metadata.SystemName), - Value = x.Metadata.SystemName + Id = ++count, + SystemName = x.Metadata.SystemName, + FriendlyName = _pluginMediator.GetLocalizedFriendlyName(x.Metadata), + ImageUrl = GetThumbnailUrl(x), + Description = _pluginMediator.GetLocalizedDescription(x.Metadata) }; - - if (!model.Provider.ProviderDescriptions.ContainsKey(x.Metadata.SystemName)) - { - var description = _pluginMediator.GetLocalizedDescription(x.Metadata); - - model.Provider.ProviderDescriptions.Add(x.Metadata.SystemName, description.NaIfEmpty()); - } - return item; - }).ToList(); + }) + .ToList(); model.AvailableProfiles = _exportService.GetExportProfiles() .ToList() .Select(x => { - var item = new SelectListItem + var item = new ExportProfileModel.ProviderSelectItem { - Text = "{0} ({1})".FormatInvariant(x.Name, x.ProviderSystemName), - Value = x.Id.ToString() + Id = x.Id, + SystemName = x.ProviderSystemName, + FriendlyName = x.Name, + ImageUrl = GetThumbnailUrl(allProviders.FirstOrDefault(y => y.Metadata.SystemName.IsCaseInsensitiveEqual(x.ProviderSystemName))) }; return item; }) diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index a61989d0d5..08f7af67b3 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -70,7 +70,8 @@ public partial class ExportProfileModel : EntityModelBase [SmartResourceDisplayName("Admin.DataExchange.Export.CloneProfile")] public int? CloneProfileId { get; set; } - public List AvailableProfiles { get; set; } + //public List AvailableProfiles { get; set; } + public List AvailableProfiles { get; set; } public ProviderModel Provider { get; set; } @@ -101,8 +102,7 @@ public class ProviderModel [SmartResourceDisplayName("Admin.DataExchange.Export.ProviderSystemName")] public string SystemName { get; set; } - public List AvailableExportProviders { get; set; } - public Dictionary ProviderDescriptions { get; set; } + public List AvailableProviders { get; set; } [SmartResourceDisplayName("Common.Provider")] public string FriendlyName { get; set; } @@ -134,5 +134,14 @@ public bool IsFileBasedExport [SmartResourceDisplayName("Admin.DataExchange.Export.SupportedFileTypes")] public string SupportedFileTypes { get; set; } } + + public class ProviderSelectItem + { + public int Id { get; set; } + public string SystemName { get; set; } + public string FriendlyName { get; set; } + public string ImageUrl { get; set; } + public string Description { get; set; } + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml index b6baf86c64..3af31d9510 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml @@ -5,7 +5,7 @@ }
    - @if(Model.Provider.AvailableExportProviders.Count > 0) + @if(Model.Provider.AvailableProviders.Count > 0) {
    @@ -46,7 +41,7 @@
    @Html.SmartLabelFor(x => x.Projection.OrderStatusChangeId) From f2a904ac85f14f8ff55c3b74ac1eacc886689379 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 6 Oct 2015 15:53:18 +0200 Subject: [PATCH 108/874] Fixed several base price issues --- .../Catalog/ProductExtensions.cs | 18 ++++++++++++--- .../Controllers/CatalogHelper.cs | 22 +++++++++++++++---- .../Controllers/ShoppingCartController.cs | 10 ++++++--- 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs index 21d749a085..5516606fc4 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs @@ -1,13 +1,17 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Media; using SmartStore.Core.Infrastructure; +using SmartStore.Services.Directory; using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Seo; +using SmartStore.Services.Tax; namespace SmartStore.Services.Catalog { @@ -306,7 +310,8 @@ public static int[] ParseRequiredProductIds(this Product product) /// Whether the result string should be language independent /// The base price public static string GetBasePriceInfo(this Product product, ILocalizationService localizationService, IPriceFormatter priceFormatter, - decimal priceAdjustment = decimal.Zero, bool languageIndependent = false) + ICurrencyService currencyService, ITaxService taxService, IPriceCalculationService priceCalculationService, + Currency currency, decimal priceAdjustment = decimal.Zero, bool languageIndependent = false) { if (product == null) throw new ArgumentNullException("product"); @@ -316,10 +321,17 @@ public static string GetBasePriceInfo(this Product product, ILocalizationService if (product.BasePriceHasValue && product.BasePriceAmount != Decimal.Zero) { - decimal price = decimal.Add(product.Price, priceAdjustment); + var workContext = EngineContext.Current.Resolve(); + + var taxrate = decimal.Zero; + var currentPrice = priceCalculationService.GetFinalPrice(product, workContext.CurrentCustomer, true); + decimal price = taxService.GetProductPrice(product, decimal.Add(currentPrice, priceAdjustment), out taxrate); + + price = currencyService.ConvertFromPrimaryStoreCurrency(price, currency); + decimal basePriceValue = Convert.ToDecimal((price / product.BasePriceAmount) * product.BasePriceBaseAmount); - string basePrice = priceFormatter.FormatPrice(basePriceValue, true, false); + string basePrice = priceFormatter.FormatPrice(basePriceValue, true, currency); string unit = "{0} {1}".FormatWith(product.BasePriceBaseAmount, product.BasePriceMeasureUnit); if (languageIndependent) diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index ba36afd020..f733911e33 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -659,7 +659,7 @@ public ProductDetailsModel PrepareProductDetailModel( model.HasSampleDownload = product.IsDownload && product.HasSampleDownload; model.IsCurrentCustomerRegistered = _services.WorkContext.CurrentCustomer.IsRegistered(); model.IsBasePriceEnabled = product.BasePriceEnabled; - model.BasePriceInfo = product.GetBasePriceInfo(_localizationService, _priceFormatter); + model.BasePriceInfo = product.GetBasePriceInfo(_localizationService, _priceFormatter, _currencyService, _taxService, _priceCalculationService, _services.WorkContext.WorkingCurrency); model.ShowLegalInfo = _taxSettings.ShowLegalHintsInProductDetails; model.BundleTitleText = product.GetLocalized(x => x.BundleTitleText); model.BundlePerItemPricing = product.BundlePerItemPricing; @@ -843,7 +843,14 @@ public ProductDetailsModel PrepareProductDetailModel( model.ProductPrice.PriceValue = finalPriceWithoutDiscount; model.ProductPrice.PriceWithDiscountValue = finalPriceWithDiscount; - model.BasePriceInfo = product.GetBasePriceInfo(_localizationService, _priceFormatter, attributesTotalPriceBase); + model.BasePriceInfo = product.GetBasePriceInfo( + _localizationService, + _priceFormatter, + _currencyService, + _taxService, + _priceCalculationService, + _services.WorkContext.WorkingCurrency, + attributesTotalPriceBase); if (!string.IsNullOrWhiteSpace(model.ProductPrice.OldPrice) || !string.IsNullOrWhiteSpace(model.ProductPrice.PriceWithDiscount)) { @@ -856,7 +863,14 @@ public ProductDetailsModel PrepareProductDetailModel( { model.ProductPrice.NoteWithDiscount = T("Products.Bundle.PriceWithDiscount.Note"); } - model.BasePriceInfo = product.GetBasePriceInfo(_localizationService, _priceFormatter, (product.Price - finalPriceWithDiscount) * (-1)); + model.BasePriceInfo = product.GetBasePriceInfo( + _localizationService, + _priceFormatter, + _currencyService, + _taxService, + _priceCalculationService, + _services.WorkContext.WorkingCurrency, + (product.Price - finalPriceWithDiscount) * (-1)); } } } @@ -1320,7 +1334,7 @@ from a in colorAttribute.ProductVariantAttributeValues.Take(50) if (_catalogSettings.ShowBasePriceInProductLists) { - model.BasePriceInfo = contextProduct.GetBasePriceInfo(_localizationService, _priceFormatter); + model.BasePriceInfo = contextProduct.GetBasePriceInfo(_localizationService, _priceFormatter, _currencyService, _taxService, _priceCalculationService, workingCurrency); } var addShippingPrice = _currencyService.ConvertCurrency(contextProduct.AdditionalShippingCharge, currentStore.PrimaryStoreCurrency, workingCurrency); diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index 215e1d0e6a..e8b1e17fe1 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -235,7 +235,7 @@ private ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartItemModel(Org IsShipEnabled = product.IsShipEnabled, ShortDesc = product.GetLocalized(x => x.ShortDescription), ProductType = product.ProductType, - BasePrice = product.GetBasePriceInfo(_localizationService, _priceFormatter), + BasePrice = product.GetBasePriceInfo(_localizationService, _priceFormatter, _currencyService, _taxService, _priceCalculationService, _workContext.WorkingCurrency), Weight = product.Weight }; @@ -370,8 +370,12 @@ private ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartItemModel(Org model.BasePrice = product.GetBasePriceInfo( _localizationService, - _priceFormatter, - (product.Price - _taxService.GetProductPrice(product, _priceCalculationService.GetUnitPrice(sci, true), out taxRate)) * (-1) + _priceFormatter, + _currencyService, + _taxService, + _priceCalculationService, + _workContext.WorkingCurrency, + (product.Price - _priceCalculationService.GetUnitPrice(sci, true)) * (-1) ); } From db547dcf2b077c85997109bece39a758faf15155 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 6 Oct 2015 17:20:14 +0200 Subject: [PATCH 109/874] A tax provider should never be activated without any merchant interaction --- changelog.md | 3 ++- .../SmartStore.Services/Tax/TaxService.cs | 12 ++---------- .../Orders/OrderProcessingServiceTests.cs | 2 +- .../Orders/OrderTotalCalculationServiceTests.cs | 2 +- .../Tax/TaxServiceTests.cs | 17 ++++++----------- 5 files changed, 12 insertions(+), 24 deletions(-) diff --git a/changelog.md b/changelog.md index 011b50f127..f882c84de1 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ -# Release Notes +# Release Notes ## SmartStore.NET 2.5 @@ -56,6 +56,7 @@ * #770 Resizing browser with product details page causes product image to disappear * GMC feed: Availability value "available for order" deprecated * Mobile: Shopping cart warnings weren't displayed to customers +* Tax provider and payment method were automatically activated when there were no active provider\method ## SmartStore.NET 2.2.2 diff --git a/src/Libraries/SmartStore.Services/Tax/TaxService.cs b/src/Libraries/SmartStore.Services/Tax/TaxService.cs index 4bc52f11c6..056c456606 100644 --- a/src/Libraries/SmartStore.Services/Tax/TaxService.cs +++ b/src/Libraries/SmartStore.Services/Tax/TaxService.cs @@ -8,11 +8,9 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Tax; -using SmartStore.Core.Infrastructure; using SmartStore.Core.Plugins; using SmartStore.Services.Common; using SmartStore.Services.Directory; -using SmartStore.Services.Configuration; namespace SmartStore.Services.Tax { @@ -44,7 +42,6 @@ public TaxAddressKey(int customerId, bool productIsEsd) private readonly IPluginFinder _pluginFinder; private readonly IDictionary _cachedTaxRates; private readonly IDictionary _cachedTaxAddresses; - private readonly ISettingService _settingService; private readonly IProviderManager _providerManager; private readonly IGeoCountryLookup _geoCountryLookup; @@ -65,10 +62,8 @@ public TaxService( TaxSettings taxSettings, ShoppingCartSettings cartSettings, IPluginFinder pluginFinder, - ISettingService settingService, IGeoCountryLookup geoCountryLookup, - IProviderManager providerManager - ) + IProviderManager providerManager) { this._addressService = addressService; this._workContext = workContext; @@ -77,7 +72,6 @@ IProviderManager providerManager this._pluginFinder = pluginFinder; this._cachedTaxRates = new Dictionary(); this._cachedTaxAddresses = new Dictionary(); - this._settingService = settingService; this._providerManager = providerManager; this._geoCountryLookup = geoCountryLookup; } @@ -274,9 +268,7 @@ public virtual Provider LoadActiveTaxProvider() if (taxProvider == null) { taxProvider = LoadAllTaxProviders().FirstOrDefault(); - _taxSettings.ActiveTaxProviderSystemName = taxProvider.Metadata.SystemName; - _settingService.SaveSetting(_taxSettings); - } + } return taxProvider; } diff --git a/src/Tests/SmartStore.Services.Tests/Orders/OrderProcessingServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Orders/OrderProcessingServiceTests.cs index 10f30b2fe8..ba2ee83ac7 100644 --- a/src/Tests/SmartStore.Services.Tests/Orders/OrderProcessingServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Orders/OrderProcessingServiceTests.cs @@ -159,7 +159,7 @@ public class OrderProcessingServiceTests : ServiceTest _httpRequestBase = MockRepository.GenerateMock(); _geoCountryLookup = MockRepository.GenerateMock(); - _taxService = new TaxService(_addressService, _workContext, _taxSettings, _shoppingCartSettings, pluginFinder, _settingService, _geoCountryLookup, this.ProviderManager); + _taxService = new TaxService(_addressService, _workContext, _taxSettings, _shoppingCartSettings, pluginFinder, _geoCountryLookup, this.ProviderManager); _rewardPointsSettings = new RewardPointsSettings(); diff --git a/src/Tests/SmartStore.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs index 21affac7fe..ffab0f5e54 100644 --- a/src/Tests/SmartStore.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs @@ -133,7 +133,7 @@ public class OrderTotalCalculationServiceTests : ServiceTest _httpRequestBase = MockRepository.GenerateMock(); _geoCountryLookup = MockRepository.GenerateMock(); - _taxService = new TaxService(_addressService, _workContext, _taxSettings, _shoppingCartSettings, pluginFinder, _settingService, _geoCountryLookup, this.ProviderManager); + _taxService = new TaxService(_addressService, _workContext, _taxSettings, _shoppingCartSettings, pluginFinder, _geoCountryLookup, this.ProviderManager); _rewardPointsSettings = new RewardPointsSettings(); diff --git a/src/Tests/SmartStore.Services.Tests/Tax/TaxServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Tax/TaxServiceTests.cs index d5d9cb2725..ee80b8ede0 100644 --- a/src/Tests/SmartStore.Services.Tests/Tax/TaxServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Tax/TaxServiceTests.cs @@ -1,22 +1,19 @@ using System; -using System.Collections.Generic; using System.Linq; +using NUnit.Framework; +using Rhino.Mocks; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Tax; -using SmartStore.Core.Infrastructure; +using SmartStore.Core.Events; using SmartStore.Core.Plugins; using SmartStore.Services.Common; -using SmartStore.Core.Events; +using SmartStore.Services.Directory; using SmartStore.Services.Tax; using SmartStore.Tests; -using NUnit.Framework; -using Rhino.Mocks; -using SmartStore.Core.Domain.Orders; -using SmartStore.Services.Configuration; -using SmartStore.Services.Directory; namespace SmartStore.Services.Tests.Tax { @@ -29,7 +26,6 @@ public class TaxServiceTests : ServiceTest ShoppingCartSettings _cartSettings; IEventPublisher _eventPublisher; ITaxService _taxService; - ISettingService _settingService; IGeoCountryLookup _geoCountryLookup; [SetUp] @@ -51,10 +47,9 @@ public class TaxServiceTests : ServiceTest _eventPublisher = MockRepository.GenerateMock(); _eventPublisher.Expect(x => x.Publish(Arg.Is.Anything)); - _settingService = MockRepository.GenerateMock(); _geoCountryLookup = MockRepository.GenerateMock(); - _taxService = new TaxService(_addressService, _workContext, _taxSettings, _cartSettings, pluginFinder, _settingService, _geoCountryLookup, this.ProviderManager); + _taxService = new TaxService(_addressService, _workContext, _taxSettings, _cartSettings, pluginFinder, _geoCountryLookup, this.ProviderManager); } [Test] From 3ecc74a1b2d1623b0655189c6f7a19f0bf00df06 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 6 Oct 2015 20:34:31 +0200 Subject: [PATCH 110/874] Minor change --- .../ExportManufacturerXmlProvider.cs | 2 +- .../ExportImport/ExportManager.cs | 66 ------------------- .../ExportImport/IExportManager.cs | 7 -- .../Controllers/ManufacturerController.cs | 2 + 4 files changed, 3 insertions(+), 74 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs index 6e19fc737c..0941e4558c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs @@ -82,7 +82,7 @@ public void Execute(IExportExecuteContext context) writer.Write("Id", ((int)productManu.Id).ToString()); writer.Write("ProductId", ((int)productManu.ProductId).ToString()); writer.Write("DisplayOrder", ((int)productManu.DisplayOrder).ToString()); - writer.Write("IsFeaturedProduct", ((int)productManu.IsFeaturedProduct).ToString()); + writer.Write("IsFeaturedProduct", ((bool)productManu.IsFeaturedProduct).ToString()); writer.Write("ManufacturerId", ((int)productManu.ManufacturerId).ToString()); writer.WriteEndElement(); // ProductManufacturer } diff --git a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs index 5b43c9c921..8a48b9c584 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs @@ -114,72 +114,6 @@ protected virtual void WriteCategories(XmlWriter writer, int parentCategoryId) #region Methods - /// - /// Export manufacturer list to xml - /// - /// Manufacturers - /// Result in XML format - public virtual string ExportManufacturersToXml(IList manufacturers) - { - var sb = new StringBuilder(); - var stringWriter = new StringWriter(sb); - var xmlWriter = new XmlTextWriter(stringWriter); - xmlWriter.WriteStartDocument(); - xmlWriter.WriteStartElement("Manufacturers"); - xmlWriter.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - - foreach (var manufacturer in manufacturers) - { - xmlWriter.WriteStartElement("Manufacturer"); - - xmlWriter.WriteElementString("Id", null, manufacturer.Id.ToString()); - xmlWriter.WriteElementString("Name", null, manufacturer.Name); - xmlWriter.WriteElementString("SeName", null, manufacturer.GetSeName(0, true, false)); - xmlWriter.WriteElementString("Description", null, manufacturer.Description); - xmlWriter.WriteElementString("ManufacturerTemplateId", null, manufacturer.ManufacturerTemplateId.ToString()); - xmlWriter.WriteElementString("MetaKeywords", null, manufacturer.MetaKeywords); - xmlWriter.WriteElementString("MetaDescription", null, manufacturer.MetaDescription); - xmlWriter.WriteElementString("MetaTitle", null, manufacturer.MetaTitle); - xmlWriter.WriteElementString("PictureId", null, manufacturer.PictureId.ToString()); - xmlWriter.WriteElementString("PageSize", null, manufacturer.PageSize.ToString()); - xmlWriter.WriteElementString("AllowCustomersToSelectPageSize", null, manufacturer.AllowCustomersToSelectPageSize.ToString()); - xmlWriter.WriteElementString("PageSizeOptions", null, manufacturer.PageSizeOptions); - xmlWriter.WriteElementString("PriceRanges", null, manufacturer.PriceRanges); - xmlWriter.WriteElementString("Published", null, manufacturer.Published.ToString()); - xmlWriter.WriteElementString("Deleted", null, manufacturer.Deleted.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, manufacturer.DisplayOrder.ToString()); - xmlWriter.WriteElementString("CreatedOnUtc", null, manufacturer.CreatedOnUtc.ToString()); - xmlWriter.WriteElementString("UpdatedOnUtc", null, manufacturer.UpdatedOnUtc.ToString()); - - xmlWriter.WriteStartElement("Products"); - var productManufacturers = _manufacturerService.GetProductManufacturersByManufacturerId(manufacturer.Id, 0, int.MaxValue, true); - if (productManufacturers != null) - { - foreach (var productManufacturer in productManufacturers) - { - var product = productManufacturer.Product; - if (product != null && !product.Deleted) - { - xmlWriter.WriteStartElement("ProductManufacturer"); - xmlWriter.WriteElementString("Id", null, productManufacturer.Id.ToString()); - xmlWriter.WriteElementString("ProductId", null, productManufacturer.ProductId.ToString()); - xmlWriter.WriteElementString("IsFeaturedProduct", null, productManufacturer.IsFeaturedProduct.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, productManufacturer.DisplayOrder.ToString()); - xmlWriter.WriteEndElement(); - } - } - } - xmlWriter.WriteEndElement(); - - xmlWriter.WriteEndElement(); - } - - xmlWriter.WriteEndElement(); - xmlWriter.WriteEndDocument(); - xmlWriter.Close(); - return stringWriter.ToString(); - } - /// /// Export category list to xml /// diff --git a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs index 0700f4ead3..e9536d671f 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs @@ -10,13 +10,6 @@ namespace SmartStore.Services.ExportImport /// public interface IExportManager { - /// - /// Export manufacturer list to xml - /// - /// Manufacturers - /// Result in XML format - string ExportManufacturersToXml(IList manufacturers); - /// /// Export category list to xml /// diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs index d7b7c1079f..2543de3845 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs @@ -20,6 +20,7 @@ using SmartStore.Services.Stores; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Mvc; using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers @@ -440,6 +441,7 @@ public ActionResult Delete(int id) #region Export / Import + [Compress] public ActionResult ExportXml() { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) From dac44a85aac48f477ff5e2507c9f78517a4ea029 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 7 Oct 2015 13:59:30 +0200 Subject: [PATCH 111/874] Convert category XML export to support new export framework --- .../Domain/DataExchange/ExportFilter.cs | 5 - .../Catalog/CategoryService.cs | 15 +++ .../Catalog/ICategoryService.cs | 7 ++ .../ExportCategoryXmlProvider.cs | 116 ++++++++++++++++++ .../ExportManufacturerXmlProvider.cs | 3 + .../ExportProvider/ExportOrderXlsxProvider.cs | 3 + .../ExportProvider/ExportOrderXmlProvider.cs | 3 + .../ExportProductXlsxProvider.cs | 3 + .../ExportProductXmlProvider.cs | 3 + .../ExportTask/ExportDataContextCategory.cs | 72 +++++++++++ .../ExportTask/ExportProfileTask.cs | 104 ++++++++++++++-- .../ExportTask/ExportProfileTaskContext.cs | 15 +++ .../DataExchange/ExportXmlHelper.cs | 4 +- .../ExportImport/ExportManager.cs | 83 ------------- .../ExportImport/IExportManager.cs | 6 - .../SmartStore.Services.csproj | 2 + .../Controllers/CategoryController.cs | 20 +-- 17 files changed, 343 insertions(+), 121 deletions(-) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCategory.cs diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs index 5820606bf7..7f22f55302 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportFilter.cs @@ -10,11 +10,6 @@ namespace SmartStore.Core.Domain.DataExchange [Serializable] public class ExportFilter { - public ExportFilter() - { - IsPublished = true; - } - /// /// Store identifier; 0 to load all records /// diff --git a/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs b/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs index 4e735e594d..c99cf97ad4 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs @@ -678,6 +678,21 @@ orderby pc.DisplayOrder return map; } + public virtual Multimap GetProductCategoriesByCategoryIds(int[] categoryIds) + { + Guard.ArgumentNotNull(() => categoryIds); + + var query = _productCategoryRepository.TableUntracked + .Where(x => categoryIds.Contains(x.CategoryId)) + .OrderBy(x => x.DisplayOrder); + + var map = query + .ToList() + .ToMultimap(x => x.CategoryId, x => x); + + return map; + } + protected virtual IQueryable ApplyHiddenProductCategoriesFilter(IQueryable query, int storeId = 0) { bool group = false; diff --git a/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs b/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs index 7d05282278..3fc7378ac2 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ICategoryService.cs @@ -131,6 +131,13 @@ IPagedList GetProductCategoriesByCategoryId(int categoryId, /// Map with product category mappings Multimap GetProductCategoriesByProductIds(int[] productIds, bool? hasDiscountsApplied = null, bool showHidden = false); + /// + /// Gets product category mappings + /// + /// Category identifiers + /// Map with product category mappings + Multimap GetProductCategoriesByCategoryIds(int[] categoryIds); + /// /// Gets a product category mapping /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs new file mode 100644 index 0000000000..944e49ff7b --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs @@ -0,0 +1,116 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using System.Xml; +using SmartStore.Core; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.DataExchange.ExportProvider +{ + /// + /// Exports XML formatted category data to a file + /// + [SystemName("Exports.SmartStoreCategoryXml")] + [FriendlyName("SmartStore XML category export")] + [IsHidden(true)] + public class ExportCategoryXmlProvider : IExportProvider + { + public static string SystemName + { + get { return "Exports.SmartStoreCategoryXml"; } + } + + public ExportConfigurationInfo ConfigurationInfo + { + get { return null; } + } + + public ExportEntityType EntityType + { + get { return ExportEntityType.Category; } + } + + public string FileExtension + { + get { return "XML"; } + } + + public void Execute(IExportExecuteContext context) + { + var settings = new XmlWriterSettings + { + Encoding = Encoding.UTF8, + CheckCharacters = false, + Indent = true, + IndentChars = "\t" + }; + + var path = context.FilePath; + + context.Log.Information("Creating file " + path); + + using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + using (var writer = XmlWriter.Create(stream, settings)) + { + var xmlHelper = new ExportXmlHelper(writer, CultureInfo.InvariantCulture); + + writer.WriteStartDocument(); + writer.WriteStartElement("Categories"); + writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); + + while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + { + var segment = context.Data.CurrentSegment; + + foreach (dynamic category in segment) + { + if (context.Abort != ExportAbortion.None) + break; + + int categoryId = category.Id; + + writer.WriteStartElement("Category"); + + try + { + xmlHelper.WriteCategory(category, null); + + writer.WriteStartElement("ProductCategories"); + foreach (dynamic productCategory in category.ProductCategories) + { + writer.WriteStartElement("ProductCategory"); + writer.Write("Id", ((int)productCategory.Id).ToString()); + writer.Write("ProductId", ((int)productCategory.ProductId).ToString()); + writer.Write("DisplayOrder", ((int)productCategory.DisplayOrder).ToString()); + writer.Write("IsFeaturedProduct", ((bool)productCategory.IsFeaturedProduct).ToString()); + writer.Write("CategoryId", ((int)productCategory.CategoryId).ToString()); + writer.WriteEndElement(); // ProductCategory + } + writer.WriteEndElement(); // ProductCategories + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.Log.Error("Error while processing category with id {0}: {1}".FormatInvariant(categoryId, exc.ToAllMessages()), exc); + ++context.RecordsFailed; + } + + writer.WriteEndElement(); // Category + } + } + + writer.WriteEndElement(); // Categories + writer.WriteEndDocument(); + } + } + + public void ExecuteEnded(IExportExecuteContext context) + { + // nothing to do + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs index 0941e4558c..d23afb3916 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs @@ -10,6 +10,9 @@ namespace SmartStore.Services.DataExchange.ExportProvider { + /// + /// Exports XML formatted manufacturer data to a file + /// [SystemName("Exports.SmartStoreManufacturerXml")] [FriendlyName("SmartStore XML manufacturer export")] [IsHidden(true)] diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs index 47641ec41f..efb3667c0e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs @@ -9,6 +9,9 @@ namespace SmartStore.Services.DataExchange.ExportProvider { + /// + /// Exports Excel formatted order data to a file + /// [SystemName("Exports.SmartStoreOrderXlsx")] [FriendlyName("SmartStore Excel order export")] [IsHidden(true)] diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs index 4dbfd8ee28..87459365d1 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs @@ -10,6 +10,9 @@ namespace SmartStore.Services.DataExchange.ExportProvider { + /// + /// Exports XML formatted order data to a file + /// [SystemName("Exports.SmartStoreOrderXml")] [FriendlyName("SmartStore XML order export")] [IsHidden(true)] diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs index baff832560..2f956fb72f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs @@ -16,6 +16,9 @@ namespace SmartStore.Services.DataExchange.ExportProvider { + /// + /// Exports Excel formatted product data to a file + /// [SystemName("Exports.SmartStoreProductXlsx")] [FriendlyName("SmartStore Excel product export")] [IsHidden(true)] diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs index 858aa7fae4..268d3cdd91 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs @@ -10,6 +10,9 @@ namespace SmartStore.Services.DataExchange.ExportProvider { + /// + /// Exports XML formatted product data to a file + /// [SystemName("Exports.SmartStoreProductXml")] [FriendlyName("SmartStore XML product export")] [IsHidden(true)] diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCategory.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCategory.cs new file mode 100644 index 0000000000..f19fb56f47 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCategory.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Collections; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Media; + +namespace SmartStore.Services.DataExchange.ExportTask +{ + internal class ExportDataContextCategory + { + protected List _categoryIds; + protected List _pictureIds; + + private Func> _funcProductCategories; + private Func> _funcPictures; + + private LazyMultimap _productCategories; + private LazyMultimap _pictures; + + public ExportDataContextCategory(IEnumerable categories, + Func> productCategories, + Func> pictures) + { + if (categories == null) + { + _categoryIds = new List(); + _pictureIds = new List(); + } + else + { + _categoryIds = new List(categories.Select(x => x.Id)); + _pictureIds = new List(categories.Where(x => (x.PictureId ?? 0) != 0).Select(x => x.PictureId ?? 0)); + } + + _funcProductCategories = productCategories; + _funcPictures = pictures; + } + + public void Clear() + { + if (_productCategories != null) + _productCategories.Clear(); + if (_pictures != null) + _pictures.Clear(); + } + + public LazyMultimap ProductCategories + { + get + { + if (_productCategories == null) + { + _productCategories = new LazyMultimap(keys => _funcProductCategories(keys), _categoryIds); + } + return _productCategories; + } + } + + public LazyMultimap Pictures + { + get + { + if (_pictures == null) + { + _pictures = new LazyMultimap(keys => _funcPictures(keys).ToMultimap(x => x.Id, x => x), _pictureIds); + } + return _pictures; + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index ef078edc4c..19a0507cbc 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -1035,6 +1035,8 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, Category category) expando.Alias = category.Alias; expando.DefaultViewMode = category.DefaultViewMode; + expando.Picture = null; + expando._Localized = GetLocalized(ctx, category, x => x.Name, x => x.FullName, @@ -1507,7 +1509,8 @@ private List GetOrders(ExportProfileTaskContext ctx, int pageIndex, int p private List GetManufacturers(ExportProfileTaskContext ctx, int pageIndex, int pageSize, out int totalCount) { - var manus = _manufacturerService.GetAllManufacturers(null, pageIndex, ctx.PageSize, true); + var showHidden = !ctx.Filter.IsPublished.HasValue; + var manus = _manufacturerService.GetAllManufacturers(null, pageIndex, ctx.PageSize, showHidden); totalCount = manus.TotalCount; @@ -1532,6 +1535,36 @@ private List GetManufacturers(ExportProfileTaskContext ctx, int pa return result; } + private List GetCategories(ExportProfileTaskContext ctx, int pageIndex, int pageSize, out int totalCount) + { + var showHidden = !ctx.Filter.IsPublished.HasValue; + var storeId = (ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); + + var categories = _categoryService.GetAllCategories(null, pageIndex, pageSize, showHidden, null, true, false, storeId); + + totalCount = categories.TotalCount; + + var result = categories as List; + + if (pageSize > 1) + { + ctx.DataContextCategory = new ExportDataContextCategory(result, + x => _categoryService.GetProductCategoriesByCategoryIds(x), + x => _pictureService.GetPicturesByIds(x) + ); + + SetProgress(ctx, categories.Count); + } + + try + { + _services.DbContext.DetachEntities(result); + } + catch { } + + return result; + } + private List ConvertToExpando(ExportProfileTaskContext ctx, Product product) { var result = new List(); @@ -1620,8 +1653,6 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro if (x.Category != null && x.Category.PictureId.HasValue) exp.Category.Picture = ToExpando(ctx, x.Category.Picture, _mediaSettings.CategoryThumbPictureSize, _mediaSettings.CategoryThumbPictureSize); - else - exp.Category.Picture = null; if (expando._CategoryName == null) expando._CategoryName = (string)exp.Category.Name; @@ -2014,6 +2045,42 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Manufacture return result; } + private List ConvertToExpando(ExportProfileTaskContext ctx, Category category) + { + var result = new List(); + + var productCategories = ctx.DataContextCategory.ProductCategories.Load(category.Id); + + dynamic expando = ToExpando(ctx, category); + + if (category.PictureId.HasValue) + { + var pictures = ctx.DataContextCategory.Pictures.Load(category.PictureId.Value); + + if (pictures.Count > 0) + expando.Picture = ToExpando(ctx, pictures.First(), _mediaSettings.CategoryThumbPictureSize, _mediaSettings.CategoryThumbPictureSize); + } + + expando.ProductCategories = productCategories + .OrderBy(x => x.DisplayOrder) + .Select(x => + { + dynamic exp = new ExpandoObject(); + exp._Entity = x; + exp.Id = x.Id; + exp.ProductId = x.ProductId; + exp.DisplayOrder = x.DisplayOrder; + exp.IsFeaturedProduct = x.IsFeaturedProduct; + exp.CategoryId = x.CategoryId; + + return exp; + }) + .ToList(); + + result.Add(expando); + return result; + } + #endregion private List Init(ExportProfileTaskContext ctx) @@ -2079,6 +2146,13 @@ private List Init(ExportProfileTaskContext ctx) var unused = GetManufacturers(ctx, 0, 1, out totalCount); ctx.RecordsPerStore.Add(store.Id, totalCount); } + else if (ctx.Provider.Value.EntityType == ExportEntityType.Category) + { + ctx.Store = store; + int totalCount = 0; + var unused = GetCategories(ctx, 0, 1, out totalCount); + ctx.RecordsPerStore.Add(store.Id, totalCount); + } } return result; @@ -2122,15 +2196,17 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.PublicFolderPath = (ctx.Export.HasPublicDeployment ? Path.Combine(HttpRuntime.AppDomainAppPath, PublicFolder) : null); int unused; + var itemsPerFile = (ctx.IsPreview ? 0 : ctx.Profile.BatchSize); var totalCount = ctx.RecordsPerStore.First(x => x.Key == ctx.Store.Id).Value; + var pageable = new PagedList(ctx.Profile.Offset, ctx.Profile.Limit, ctx.PageIndex, ctx.PageSize, totalCount); if (ctx.Provider.Value.EntityType == ExportEntityType.Product) { ctx.Export.Data = new ExportSegmenter( pageIndex => GetProducts(ctx, pageIndex), entity => ConvertToExpando(ctx, entity), - new PagedList(ctx.Profile.Offset, ctx.Profile.Limit, ctx.PageIndex, ctx.PageSize, totalCount), - ctx.IsPreview ? 0 : ctx.Profile.BatchSize + pageable, + itemsPerFile ); } else if (ctx.Provider.Value.EntityType == ExportEntityType.Order) @@ -2138,8 +2214,8 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.Data = new ExportSegmenter( pageIndex => GetOrders(ctx, pageIndex, ctx.PageSize, out unused), entity => ConvertToExpando(ctx, entity), - new PagedList(ctx.Profile.Offset, ctx.Profile.Limit, ctx.PageIndex, ctx.PageSize, totalCount), - ctx.IsPreview ? 0 : ctx.Profile.BatchSize + pageable, + itemsPerFile ); } else if (ctx.Provider.Value.EntityType == ExportEntityType.Manufacturer) @@ -2147,8 +2223,17 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.Data = new ExportSegmenter( pageIndex => GetManufacturers(ctx, pageIndex, ctx.PageSize, out unused), entity => ConvertToExpando(ctx, entity), - new PagedList(ctx.Profile.Offset, ctx.Profile.Limit, ctx.PageIndex, ctx.PageSize, totalCount), - ctx.IsPreview ? 0 : ctx.Profile.BatchSize + pageable, + itemsPerFile + ); + } + else if (ctx.Provider.Value.EntityType == ExportEntityType.Category) + { + ctx.Export.Data = new ExportSegmenter( + pageIndex => GetCategories(ctx, pageIndex, ctx.PageSize, out unused), + entity => ConvertToExpando(ctx, entity), + pageable, + itemsPerFile ); } @@ -2372,6 +2457,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) ctx.DataContextProduct = null; ctx.DataContextOrder = null; ctx.DataContextManufacturer = null; + ctx.DataContextCategory = null; (ctx.Export.Data as IExportExecuter).Dispose(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs index 395ebc168a..92587e3685 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs @@ -22,6 +22,7 @@ internal class ExportProfileTaskContext private ExportDataContextProduct _dataContextProduct; private ExportDataContextOrder _dataContextOrder; private ExportDataContextManufacturer _dataContextManufacturer; + private ExportDataContextCategory _dataContextCategory; public ExportProfileTaskContext( TaskExecutionContext taskContext, @@ -182,6 +183,20 @@ public ExportDataContextManufacturer DataContextManufacturer _dataContextManufacturer = value; } } + public ExportDataContextCategory DataContextCategory + { + get + { + return _dataContextCategory; + } + set + { + if (_dataContextCategory != null) + _dataContextCategory.Clear(); + + _dataContextCategory = value; + } + } public ExportExecuteContext Export { get; set; } public ExportExecuteResult Result { get; set; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs index f65bfec11e..54a340407c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs @@ -320,10 +320,10 @@ public void WriteManufacturer(dynamic manufacturer, string node) _writer.Write("CreatedOnUtc", ((DateTime)manufacturer.CreatedOnUtc).ToString(_culture)); _writer.Write("UpdatedOnUtc", ((DateTime)manufacturer.UpdatedOnUtc).ToString(_culture)); - WriteLocalized(manufacturer); - WritePicture(manufacturer.Picture, "Picture"); + WriteLocalized(manufacturer); + if (node.HasValue()) { _writer.WriteEndElement(); diff --git a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs index 8a48b9c584..a35f876b36 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs @@ -48,91 +48,8 @@ public ExportManager(ICategoryService categoryService, #endregion - #region Utilities - - protected virtual void WriteCategories(XmlWriter writer, int parentCategoryId) - { - var categories = _categoryService.GetAllCategoriesByParentCategoryId(parentCategoryId, true); - if (categories != null && categories.Count > 0) - { - foreach (var category in categories) - { - writer.WriteStartElement("Category"); - writer.Write("Id", category.Id.ToString()); - writer.Write("Name", category.Name); - writer.Write("FullName", category.FullName); - writer.Write("Description", category.Description); - writer.Write("BottomDescription", category.BottomDescription); - writer.Write("CategoryTemplateId", category.CategoryTemplateId.ToString()); - writer.Write("MetaKeywords", category.MetaKeywords); - writer.Write("MetaDescription", category.MetaDescription); - writer.Write("MetaTitle", category.MetaTitle); - writer.Write("SeName", category.GetSeName(0, true, false)); - writer.Write("ParentCategoryId", category.ParentCategoryId.ToString()); - writer.Write("PageSize", category.PageSize.ToString()); - writer.Write("AllowCustomersToSelectPageSize", category.AllowCustomersToSelectPageSize.ToString()); - writer.Write("PageSizeOptions", category.PageSizeOptions); - writer.Write("PriceRanges", category.PriceRanges); - writer.Write("ShowOnHomePage", category.ShowOnHomePage.ToString()); - writer.Write("HasDiscountsApplied", category.HasDiscountsApplied.ToString()); - writer.Write("Published", category.Published.ToString()); - writer.Write("Deleted", category.Deleted.ToString()); - writer.Write("DisplayOrder", category.DisplayOrder.ToString()); - writer.Write("CreatedOnUtc", category.CreatedOnUtc.ToString()); - writer.Write("UpdatedOnUtc", category.UpdatedOnUtc.ToString()); - writer.Write("SubjectToAcl", category.SubjectToAcl.ToString()); - writer.Write("LimitedToStores", category.LimitedToStores.ToString()); - writer.Write("Alias", category.Alias); - writer.Write("DefaultViewMode", category.DefaultViewMode); - - writer.WriteStartElement("Products"); - var productCategories = _categoryService.GetProductCategoriesByCategoryId(category.Id, 0, int.MaxValue, true); - foreach (var productCategory in productCategories) - { - var product = productCategory.Product; - if (product != null && !product.Deleted) - { - writer.WriteStartElement("ProductCategory"); - writer.Write("ProductCategoryId", productCategory.Id.ToString()); - writer.Write("ProductId", productCategory.ProductId.ToString()); - writer.Write("IsFeaturedProduct", productCategory.IsFeaturedProduct.ToString()); - writer.Write("DisplayOrder", productCategory.DisplayOrder.ToString()); - writer.WriteEndElement(); - } - } - writer.WriteEndElement(); - - writer.WriteStartElement("SubCategories"); - WriteCategories(writer, category.Id); - writer.WriteEndElement(); - writer.WriteEndElement(); - } - } - } - - #endregion - #region Methods - /// - /// Export category list to xml - /// - /// Result in XML format - public virtual string ExportCategoriesToXml() - { - var sb = new StringBuilder(); - var stringWriter = new StringWriter(sb); - var xmlWriter = new XmlTextWriter(stringWriter); - xmlWriter.WriteStartDocument(); - xmlWriter.WriteStartElement("Categories"); - xmlWriter.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - WriteCategories(xmlWriter, 0); - xmlWriter.WriteEndElement(); - xmlWriter.WriteEndDocument(); - xmlWriter.Close(); - return stringWriter.ToString(); - } - /// /// Export customer list to XLSX /// diff --git a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs index e9536d671f..91f0801f2b 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs @@ -10,12 +10,6 @@ namespace SmartStore.Services.ExportImport /// public interface IExportManager { - /// - /// Export category list to xml - /// - /// Result in XML format - string ExportCategoriesToXml(); - /// /// Export customer list to XLSX /// diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index a7401ad996..64c7a1c439 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -170,11 +170,13 @@ + + diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs index b6bdee8bc0..76d0abdaf9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Catalog; @@ -14,8 +13,8 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; +using SmartStore.Services.DataExchange.ExportProvider; using SmartStore.Services.Discounts; -using SmartStore.Services.ExportImport; using SmartStore.Services.Filter; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; @@ -51,7 +50,6 @@ public partial class CategoryController : AdminControllerBase private readonly IAclService _aclService; private readonly IStoreService _storeService; private readonly IStoreMappingService _storeMappingService; - private readonly IExportManager _exportManager; private readonly IWorkContext _workContext; private readonly ICustomerActivityService _customerActivityService; private readonly IDateTimeHelper _dateTimeHelper; @@ -71,7 +69,7 @@ public CategoryController(ICategoryService categoryService, ICategoryTemplateSer ILocalizationService localizationService, ILocalizedEntityService localizedEntityService, IDiscountService discountService, IPermissionService permissionService, IAclService aclService, IStoreService storeService, IStoreMappingService storeMappingService, - IExportManager exportManager, IWorkContext workContext, + IWorkContext workContext, ICustomerActivityService customerActivityService, IDateTimeHelper dateTimeHelper, AdminAreaSettings adminAreaSettings, @@ -93,7 +91,6 @@ public CategoryController(ICategoryService categoryService, ICategoryTemplateSer this._aclService = aclService; this._storeService = storeService; this._storeMappingService = storeMappingService; - this._exportManager = exportManager; this._workContext = workContext; this._customerActivityService = customerActivityService; this._dateTimeHelper = dateTimeHelper; @@ -756,22 +753,13 @@ public ActionResult Delete(int id, string deleteType) #region Export / Import + [Compress] public ActionResult ExportXml() { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - try - { - var fileName = string.Format("categories_{0}.xml", DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")); - var xml = _exportManager.ExportCategoriesToXml(); - return new XmlDownloadResult(xml, "categories.xml"); - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } + return Export(ExportCategoryXmlProvider.SystemName, null); } #endregion From e4149ff7c9eb5a95c2a8a8112554e38f78cad265 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 8 Oct 2015 11:03:57 +0200 Subject: [PATCH 112/874] Convert customer XML export to support new export framework --- .../Common/GenericAttributeService.cs | 15 + .../Common/IGenericAttributeService.cs | 11 +- .../DataExchange/ExportExecuteContext.cs | 42 +- .../ExportCategoryXmlProvider.cs | 16 +- .../ExportCustomerXmlProvider.cs | 87 +++ .../ExportManufacturerXmlProvider.cs | 16 +- .../ExportProvider/ExportOrderXlsxProvider.cs | 22 +- .../ExportProvider/ExportOrderXmlProvider.cs | 50 +- .../ExportProductXlsxProvider.cs | 17 +- .../ExportProductXmlProvider.cs | 15 +- .../ExportTask/ExportDataContextCustomer.cs | 51 ++ .../ExportTask/ExportProfileTask.cs | 222 +++++--- .../ExportTask/ExportProfileTaskContext.cs | 21 +- .../DataExchange/ExportXmlHelper.cs | 505 +++++++++++------- .../ExportImport/ExportManager.cs | 135 ----- .../ExportImport/IExportManager.cs | 7 - .../SmartStore.Services.csproj | 2 + .../Providers/ProductExportXmlProvider.cs | 11 +- .../Controllers/AdminControllerBase.cs | 7 +- .../Controllers/CustomerController.cs | 31 +- 20 files changed, 712 insertions(+), 571 deletions(-) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCustomer.cs diff --git a/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs b/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs index f8d0ae9280..4c73f2e7ed 100644 --- a/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Common/GenericAttributeService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; @@ -165,6 +166,20 @@ public virtual IList GetAttributesForEntity(int entityId, stri }); } + public virtual Multimap GetAttributesForEntity(int[] entityIds, string keyGroup) + { + Guard.ArgumentNotNull(() => entityIds); + + var query = _genericAttributeRepository.TableUntracked + .Where(x => entityIds.Contains(x.EntityId) && x.KeyGroup == keyGroup); + + var map = query + .ToList() + .ToMultimap(x => x.EntityId, x => x); + + return map; + } + /// /// Get queryable attributes /// diff --git a/src/Libraries/SmartStore.Services/Common/IGenericAttributeService.cs b/src/Libraries/SmartStore.Services/Common/IGenericAttributeService.cs index 4aabb70b7b..cb837a6f74 100644 --- a/src/Libraries/SmartStore.Services/Common/IGenericAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Common/IGenericAttributeService.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain.Common; @@ -40,9 +41,17 @@ public partial interface IGenericAttributeService /// /// Entity identifier /// Key group - /// Get attributes + /// Generic attributes IList GetAttributesForEntity(int entityId, string keyGroup); + /// + /// Get attributes + /// + /// Entity identifiers + /// Key group + /// Generic attributes + Multimap GetAttributesForEntity(int[] entityIds, string keyGroup); + /// /// Get queryable attributes /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs index da0bb443f7..cc0ef940dd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs @@ -1,5 +1,5 @@ -using System.Collections.Generic; -using System.IO; +using System; +using System.Collections.Generic; using System.Threading; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Logging; @@ -97,25 +97,34 @@ public interface IExportExecuteContext Dictionary CustomProperties { get; set; } /// - /// Number of successful exported records. Should be incremented by the provider. Will be logged. + /// Number of successful processed records /// - int RecordsSucceeded { get; set; } + int RecordsSucceeded { get; } /// - /// Number of failed records. Should be incremented by the provider. Will be logged. + /// Number of failed records /// - int RecordsFailed { get; set; } + int RecordsFailed { get; } + + /// + /// Try-catch closure for exporting a single record + /// + /// Entity identifier + /// Callback + void ProcessRecord(int entityId, Action export); } public class ExportExecuteContext : IExportExecuteContext { + private ExportExecuteResult _result; private CancellationToken _cancellation; private IExportSegmenter _segmenter; private ExportAbortion _providerAbort; - internal ExportExecuteContext(CancellationToken cancellation, string folder) + internal ExportExecuteContext(ExportExecuteResult result, CancellationToken cancellation, string folder) { + _result = result; _cancellation = cancellation; Folder = folder; @@ -185,5 +194,24 @@ public bool IsMaxFailures public int RecordsSucceeded { get; set; } public int RecordsFailed { get; set; } + + public void ProcessRecord(int entityId, Action process) + { + try + { + process(); + + ++RecordsSucceeded; + } + catch (Exception exc) + { + ++RecordsFailed; + + Log.Error("Error while processing record with id {0}: {1}".FormatInvariant(entityId, exc.ToAllMessages()), exc); + + if (IsMaxFailures) + _result.LastError = exc.ToString(); + } + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs index 944e49ff7b..03cbb1630a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs @@ -1,5 +1,4 @@ -using System; -using System.Globalization; +using System.Globalization; using System.IO; using System.Text; using System.Xml; @@ -70,11 +69,9 @@ public void Execute(IExportExecuteContext context) if (context.Abort != ExportAbortion.None) break; - int categoryId = category.Id; - writer.WriteStartElement("Category"); - try + context.ProcessRecord((int)category.Id, () => { xmlHelper.WriteCategory(category, null); @@ -90,14 +87,7 @@ public void Execute(IExportExecuteContext context) writer.WriteEndElement(); // ProductCategory } writer.WriteEndElement(); // ProductCategories - - ++context.RecordsSucceeded; - } - catch (Exception exc) - { - context.Log.Error("Error while processing category with id {0}: {1}".FormatInvariant(categoryId, exc.ToAllMessages()), exc); - ++context.RecordsFailed; - } + }); writer.WriteEndElement(); // Category } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs new file mode 100644 index 0000000000..8b3c256f5d --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs @@ -0,0 +1,87 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using System.Xml; +using SmartStore.Core; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.DataExchange.ExportProvider +{ + /// + /// Exports XML formatted customer data to a file + /// + [SystemName("Exports.SmartStoreCustomerXml")] + [FriendlyName("SmartStore XML customer export")] + [IsHidden(true)] + public class ExportCustomerXmlProvider : IExportProvider + { + public static string SystemName + { + get { return "Exports.SmartStoreCustomerXml"; } + } + + public ExportConfigurationInfo ConfigurationInfo + { + get { return null; } + } + + public ExportEntityType EntityType + { + get { return ExportEntityType.Customer; } + } + + public string FileExtension + { + get { return "XML"; } + } + + public void Execute(IExportExecuteContext context) + { + var settings = new XmlWriterSettings + { + Encoding = Encoding.UTF8, + CheckCharacters = false, + Indent = true, + IndentChars = "\t" + }; + + var path = context.FilePath; + + context.Log.Information("Creating file " + path); + + using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + using (var writer = XmlWriter.Create(stream, settings)) + { + var xmlHelper = new ExportXmlHelper(writer, CultureInfo.InvariantCulture); + + writer.WriteStartDocument(); + writer.WriteStartElement("Customers"); + writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); + + while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + { + var segment = context.Data.CurrentSegment; + + foreach (dynamic customer in segment) + { + if (context.Abort != ExportAbortion.None) + break; + + context.ProcessRecord((int)customer.Id, () => xmlHelper.WriteCustomer(customer, "Customer")); + } + } + + writer.WriteEndElement(); // Customers + writer.WriteEndDocument(); + } + } + + public void ExecuteEnded(IExportExecuteContext context) + { + // nothing to do + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs index d23afb3916..15f03f4891 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs @@ -1,5 +1,4 @@ -using System; -using System.Globalization; +using System.Globalization; using System.IO; using System.Text; using System.Xml; @@ -70,11 +69,9 @@ public void Execute(IExportExecuteContext context) if (context.Abort != ExportAbortion.None) break; - int manufacturerId = manufacturer.Id; - writer.WriteStartElement("Manufacturer"); - try + context.ProcessRecord((int)manufacturer.Id, () => { xmlHelper.WriteManufacturer(manufacturer, null); @@ -90,14 +87,7 @@ public void Execute(IExportExecuteContext context) writer.WriteEndElement(); // ProductManufacturer } writer.WriteEndElement(); // ProductManufacturers - - ++context.RecordsSucceeded; - } - catch (Exception exc) - { - context.Log.Error("Error while processing manufacturer with id {0}: {1}".FormatInvariant(manufacturerId, exc.ToAllMessages()), exc); - ++context.RecordsFailed; - } + }); writer.WriteEndElement(); // Manufacturer } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs index efb3667c0e..6949198637 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs @@ -148,14 +148,13 @@ public void Execute(IExportExecuteContext context) if (context.Abort != ExportAbortion.None) break; - int column = 1; - int orderId = order.Id; - dynamic customer = order.Customer; - dynamic billingAddress = order.BillingAddress; - dynamic shippingAddress = order.ShippingAddress; - - try + context.ProcessRecord((int)order.Id, () => { + int column = 1; + dynamic customer = order.Customer; + dynamic billingAddress = order.BillingAddress; + dynamic shippingAddress = order.ShippingAddress; + int rewardPointsUsed = 0; int rewardPointsRemaining = customer._RewardPointsBalance; @@ -220,14 +219,7 @@ public void Execute(IExportExecuteContext context) WriteCell(worksheet, row, ref column, shippingAddress != null ? (string)shippingAddress.ZipPostalCode : ""); WriteCell(worksheet, row, ref column, shippingAddress != null ? (string)shippingAddress.PhoneNumber : ""); WriteCell(worksheet, row, ref column, shippingAddress != null ? (string)shippingAddress.FaxNumber : ""); - - ++context.RecordsSucceeded; - } - catch (Exception exc) - { - context.Log.Error("Error while processing order with id {0}: {1}".FormatInvariant(orderId, exc.ToAllMessages()), exc); - ++context.RecordsFailed; - } + }); ++row; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs index 87459365d1..7cc9e2dada 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs @@ -71,19 +71,16 @@ public void Execute(IExportExecuteContext context) if (context.Abort != ExportAbortion.None) break; - int orderId = order.Id; - dynamic customer = order.Customer; - dynamic store = order.Store; - writer.WriteStartElement("Order"); - try + context.ProcessRecord((int)order.Id, () => { + dynamic store = order.Store; int? shippingAddressId = order.ShippingAddressId; DateTime? paidDateUtc = order.PaidDateUtc; int? rewardPointsRemaining = order.RewardPointsRemaining; - writer.Write("Id", orderId.ToString()); + writer.Write("Id", ((int)order.Id).ToString()); writer.Write("OrderNumber", (string)order.OrderNumber); writer.Write("OrderGuid", ((Guid)order.OrderGuid).ToString()); writer.Write("StoreId", ((int)order.StoreId).ToString()); @@ -155,35 +152,7 @@ public void Execute(IExportExecuteContext context) writer.Write("PaymentStatus", (string)order.PaymentStatus); writer.Write("ShippingStatus", (string)order.ShippingStatus); - if (customer != null) - { - DateTime? lastLoginDateUtc = customer.LastLoginDateUtc; - - writer.WriteStartElement("Customer"); - writer.Write("Id", ((int)customer.Id).ToString()); - writer.Write("CustomerGuid", ((Guid)customer.CustomerGuid).ToString()); - writer.Write("Username", (string)customer.Username); - writer.Write("Email", (string)customer.Email); - writer.Write("PasswordFormatId", ((int)customer.PasswordFormatId).ToString()); - writer.Write("AdminComment", (string)customer.AdminComment); - writer.Write("IsTaxExempt", ((bool)customer.IsTaxExempt).ToString()); - writer.Write("AffiliateId", ((int)customer.AffiliateId).ToString()); - writer.Write("Active", ((bool)customer.Active).ToString()); - writer.Write("Deleted", ((bool)customer.Deleted).ToString()); - writer.Write("IsSystemAccount", ((bool)customer.IsSystemAccount).ToString()); - writer.Write("SystemName", (string)customer.SystemName); - writer.Write("LastIpAddress", (string)customer.LastIpAddress); - writer.Write("CreatedOnUtc", ((DateTime)customer.CreatedOnUtc).ToString(invariantCulture)); - writer.Write("LastLoginDateUtc", lastLoginDateUtc.HasValue ? lastLoginDateUtc.Value.ToString(invariantCulture) : ""); - writer.Write("LastActivityDateUtc", ((DateTime)customer.LastActivityDateUtc).ToString(invariantCulture)); - writer.Write("RewardPointsBalance", ((int)customer._RewardPointsBalance).ToString()); - - xmlHelper.WriteRewardPointsHistory(customer.RewardPointsHistory, "RewardPointsHistories"); - xmlHelper.WriteAddress(customer.BillingAddress, "BillingAddress"); - xmlHelper.WriteAddress(customer.ShippingAddress, "ShippingAddress"); - - writer.WriteEndElement(); // Customer - } + xmlHelper.WriteCustomer(order.Customer, "Customer"); xmlHelper.WriteAddress(order.BillingAddress, "BillingAddress"); xmlHelper.WriteAddress(order.ShippingAddress, "ShippingAddress"); @@ -216,7 +185,7 @@ public void Execute(IExportExecuteContext context) int? licenseDownloadId = orderItem.LicenseDownloadId; decimal? itemWeight = orderItem.ItemWeight; - writer.WriteStartElement("OrderItem"); + writer.WriteStartElement("OrderItem"); writer.Write("Id", ((int)orderItem.Id).ToString()); writer.Write("OrderItemGuid", ((Guid)orderItem.OrderItemGuid).ToString()); writer.Write("OrderId", ((int)orderItem.OrderId).ToString()); @@ -275,14 +244,7 @@ public void Execute(IExportExecuteContext context) writer.WriteEndElement(); // Shipment } writer.WriteEndElement(); // Shipments - - ++context.RecordsSucceeded; - } - catch (Exception exc) - { - context.Log.Error("Error while processing order with id {0}: {1}".FormatInvariant(orderId, exc.ToAllMessages()), exc); - ++context.RecordsFailed; - } + }); writer.WriteEndElement(); // Order } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs index 2f956fb72f..4ccd4ec32b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs @@ -233,11 +233,9 @@ public void Execute(IExportExecuteContext context) if (context.Abort != ExportAbortion.None) break; - int column = 1; - int productId = product.Id; - - try + context.ProcessRecord((int)product.Id, () => { + int column = 1; DateTime? specialPriceStartDateTimeUtc = product.SpecialPriceStartDateTimeUtc; DateTime? specialPriceEndDateTimeUtc = product.SpecialPriceEndDateTimeUtc; @@ -363,7 +361,7 @@ public void Execute(IExportExecuteContext context) if ((bool)product.LimitedToStores) { - var storeIds = _storeMappingService.GetStoreMappingsFor("Product", productId) + var storeIds = _storeMappingService.GetStoreMappingsFor("Product", (int)product.Id) .Select(x => x.StoreId) .ToList(); @@ -382,14 +380,7 @@ public void Execute(IExportExecuteContext context) WriteCell(worksheet, row, ref column, GetLocalized(localized, lang, "ShortDescription")); WriteCell(worksheet, row, ref column, GetLocalized(localized, lang, "FullDescription")); } - - ++context.RecordsSucceeded; - } - catch (Exception exc) - { - context.Log.Error("Error while processing product with id {0}: {1}".FormatInvariant(productId, exc.ToAllMessages()), exc); - ++context.RecordsFailed; - } + }); ++row; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs index 268d3cdd91..4e95c4a478 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs @@ -1,5 +1,4 @@ -using System; -using System.Globalization; +using System.Globalization; using System.IO; using System.Text; using System.Xml; @@ -73,17 +72,7 @@ public void Execute(IExportExecuteContext context) int productId = product.Id; - try - { - xmlHelper.WriteProduct(product, "Product"); - - ++context.RecordsSucceeded; - } - catch (Exception exc) - { - context.Log.Error("Error while processing product with id {0}: {1}".FormatInvariant(productId, exc.ToAllMessages()), exc); - ++context.RecordsFailed; - } + context.ProcessRecord((int)product.Id, () => xmlHelper.WriteProduct(product, "Product")); } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCustomer.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCustomer.cs new file mode 100644 index 0000000000..ac85860cc9 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCustomer.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SmartStore.Collections; +using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Customers; + +namespace SmartStore.Services.DataExchange.ExportTask +{ + public class ExportDataContextCustomer + { + protected List _customerIds; + + private Func> _funcGenericAttributes; + + private LazyMultimap _genericAttributes; + + public ExportDataContextCustomer(IEnumerable customers, + Func> genericAttributes) + { + if (customers == null) + { + _customerIds = new List(); + } + else + { + _customerIds = new List(customers.Select(x => x.Id)); + } + + _funcGenericAttributes = genericAttributes; + } + + public void Clear() + { + if (_genericAttributes != null) + _genericAttributes.Clear(); + } + + public LazyMultimap GenericAttributes + { + get + { + if (_genericAttributes == null) + { + _genericAttributes = new LazyMultimap(keys => _funcGenericAttributes(keys), _customerIds); + } + return _genericAttributes; + } + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 19a0507cbc..64a465f89d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -68,6 +68,7 @@ public class ExportProfileTask : ITask private ITaxService _taxService; private ICurrencyService _currencyService; private ICustomerService _customerService; + private IRepository _customerRepository; private ICategoryService _categoryService; private IPriceFormatter _priceFormatter; private IDateTimeHelper _dateTimeHelper; @@ -88,6 +89,7 @@ public class ExportProfileTask : ITask private IProductTemplateService _productTemplateService; private ILocalizedEntityService _localizedEntityService; private IUrlRecordService _urlRecordService; + private IGenericAttributeService _genericAttributeService; private void InitDependencies(TaskExecutionContext context) { @@ -101,6 +103,7 @@ private void InitDependencies(TaskExecutionContext context) _taxService = context.Resolve(); _currencyService = context.Resolve(); _customerService = context.Resolve(); + _customerRepository = context.Resolve>(); _categoryService = context.Resolve(); _priceFormatter = context.Resolve(); _dateTimeHelper = context.Resolve(); @@ -121,6 +124,7 @@ private void InitDependencies(TaskExecutionContext context) _productTemplateService = context.Resolve(); _localizedEntityService = context.Resolve(); _urlRecordService = context.Resolve(); + _genericAttributeService = context.Resolve(); } #endregion @@ -714,18 +718,25 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, Address address) expando.Country = ToExpando(ctx, address.Country); - dynamic sp = new ExpandoObject(); - sp._Entity = address.StateProvince; - sp.Id = address.StateProvince.Id; - sp.CountryId = address.StateProvince.CountryId; - sp.Name = address.StateProvince.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - sp.Abbreviation = address.StateProvince.Abbreviation; - sp.Published = address.StateProvince.Published; - sp.DisplayOrder = address.StateProvince.DisplayOrder; - - sp._Localized = GetLocalized(ctx, address.StateProvince, x => x.Name); - - expando.StateProvince = sp; + if (address.StateProvince != null) + { + dynamic sp = new ExpandoObject(); + sp._Entity = address.StateProvince; + sp.Id = address.StateProvince.Id; + sp.CountryId = address.StateProvince.CountryId; + sp.Name = address.StateProvince.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + sp.Abbreviation = address.StateProvince.Abbreviation; + sp.Published = address.StateProvince.Published; + sp.DisplayOrder = address.StateProvince.DisplayOrder; + + sp._Localized = GetLocalized(ctx, address.StateProvince, x => x.Name); + + expando.StateProvince = sp; + } + else + { + expando.StateProvince = null; + } return expando; } @@ -775,12 +786,15 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, Customer customer) expando.LastLoginDateUtc = customer.LastLoginDateUtc; expando.LastActivityDateUtc = customer.LastActivityDateUtc; - expando.BillingAddress = ToExpando(ctx, customer.BillingAddress); - expando.ShippingAddress = ToExpando(ctx, customer.ShippingAddress); + expando.BillingAddress = null; + expando.ShippingAddress = null; + expando.Addresses = null; expando.RewardPointsHistory = null; expando._RewardPointsBalance = 0; + expando._GenericAttributes = null; + return expando; } @@ -1158,6 +1172,17 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, Product product) expando.BundlePerItemPricing = product.BundlePerItemPricing; expando.BundlePerItemShoppingCart = product.BundlePerItemShoppingCart; + expando.AppliedDiscounts = null; + expando.TierPrices = null; + expando.ProductTags = null; + expando.ProductAttributes = null; + expando.ProductAttributeCombinations = null; + expando.ProductPictures = null; + expando.ProductCategories = null; + expando.ProductManufacturers = null; + expando.ProductSpecificationAttributes = null; + expando.ProductBundleItems = null; + expando._Localized = GetLocalized(ctx, product, x => x.Name, x => x.ShortDescription, @@ -1388,6 +1413,24 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, ProductSpecificationAttr return expando; } + private dynamic ToExpando(ExportProfileTaskContext ctx, GenericAttribute genericAttribute) + { + if (genericAttribute == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = genericAttribute; + + expando.Id = genericAttribute.Id; + expando.EntityId = genericAttribute.EntityId; + expando.KeyGroup = genericAttribute.KeyGroup; + expando.Key = genericAttribute.Key; + expando.Value = genericAttribute.Value; + expando.StoreId = genericAttribute.StoreId; + + return expando; + } + #endregion #region Segmenter callbacks @@ -1565,6 +1608,44 @@ private List GetCategories(ExportProfileTaskContext ctx, int pageIndex return result; } + private List GetCustomers(ExportProfileTaskContext ctx, int pageIndex, int pageSize, out int totalCount) + { + var query = _customerRepository.TableUntracked + .Expand(x => x.BillingAddress) + .Expand(x => x.ShippingAddress) + .Expand(x => x.Addresses.Select(y => y.Country)) + .Expand(x => x.Addresses.Select(y => y.StateProvince)) + .Where(x => !x.Deleted); + + if (ctx.EntityIdsSelected.Count > 0) + { + query = query.Where(x => ctx.EntityIdsSelected.Contains(x.Id)); + } + + var customers = new PagedList(query.OrderByDescending(x => x.CreatedOnUtc), pageIndex, pageSize); + + totalCount = customers.TotalCount; + + var result = customers as List; + + if (pageSize > 1) + { + ctx.DataContextCustomer = new ExportDataContextCustomer(result, + x => _genericAttributeService.GetAttributesForEntity(x, "Customer") + ); + + SetProgress(ctx, customers.Count); + } + + try + { + _services.DbContext.DetachEntities(result); + } + catch { } + + return result; + } + private List ConvertToExpando(ExportProfileTaskContext ctx, Product product) { var result = new List(); @@ -1707,10 +1788,6 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro }) .ToList(); } - else - { - expando.TierPrices = new List(); - } if (product.HasDiscountsApplied) { @@ -1720,10 +1797,6 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro .Select(x => ToExpando(ctx, x)) .ToList(); } - else - { - expando.AppliedDiscounts = new List(); - } #endregion @@ -1780,16 +1853,6 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro }) .ToList(); } - else - { - expando.ProductBundleItems = new List(); - } - } - else - { - expando.ProductTags = new List(); - expando.ProductSpecificationAttributes = new List(); - expando.ProductBundleItems = new List(); } #endregion @@ -2081,6 +2144,29 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Category ca return result; } + private List ConvertToExpando(ExportProfileTaskContext ctx, Customer customer) + { + var result = new List(); + + var genericAttributes = ctx.DataContextCustomer.GenericAttributes.Load(customer.Id); + + dynamic expando = ToExpando(ctx, customer); + + expando.BillingAddress = ToExpando(ctx, customer.BillingAddress); + expando.ShippingAddress = ToExpando(ctx, customer.ShippingAddress); + + expando.Addresses = customer.Addresses + .Select(x => ToExpando(ctx, x)) + .ToList(); + + expando._GenericAttributes = genericAttributes + .Select(x => ToExpando(ctx, x)) + .ToList(); + + result.Add(expando); + return result; + } + #endregion private List Init(ExportProfileTaskContext ctx) @@ -2126,32 +2212,37 @@ private List Init(ExportProfileTaskContext ctx) { ctx.RecordsPerStore.Add(store.Id, ctx.TotalRecords.Value); } - else if (ctx.Provider.Value.EntityType == ExportEntityType.Product) - { - ctx.Store = store; - var anySingleProduct = GetProductPage(ctx, ctx.Profile.Offset, 1); - ctx.RecordsPerStore.Add(store.Id, anySingleProduct.TotalCount); - } - else if (ctx.Provider.Value.EntityType == ExportEntityType.Order) - { - ctx.Store = store; - int totalCount = 0; - var unused = GetOrders(ctx, 0, 1, out totalCount); - ctx.RecordsPerStore.Add(store.Id, totalCount); - } - else if (ctx.Provider.Value.EntityType == ExportEntityType.Manufacturer) - { - ctx.Store = store; - int totalCount = 0; - var unused = GetManufacturers(ctx, 0, 1, out totalCount); - ctx.RecordsPerStore.Add(store.Id, totalCount); - } - else if (ctx.Provider.Value.EntityType == ExportEntityType.Category) + else { ctx.Store = store; + int totalCount = 0; - var unused = GetCategories(ctx, 0, 1, out totalCount); - ctx.RecordsPerStore.Add(store.Id, totalCount); + + if (ctx.Provider.Value.EntityType == ExportEntityType.Product) + { + var anySingleProduct = GetProductPage(ctx, ctx.Profile.Offset, 1); + ctx.RecordsPerStore.Add(store.Id, anySingleProduct.TotalCount); + } + else if (ctx.Provider.Value.EntityType == ExportEntityType.Order) + { + var unused = GetOrders(ctx, 0, 1, out totalCount); + ctx.RecordsPerStore.Add(store.Id, totalCount); + } + else if (ctx.Provider.Value.EntityType == ExportEntityType.Manufacturer) + { + var unused = GetManufacturers(ctx, 0, 1, out totalCount); + ctx.RecordsPerStore.Add(store.Id, totalCount); + } + else if (ctx.Provider.Value.EntityType == ExportEntityType.Category) + { + var unused = GetCategories(ctx, 0, 1, out totalCount); + ctx.RecordsPerStore.Add(store.Id, totalCount); + } + else if (ctx.Provider.Value.EntityType == ExportEntityType.Customer) + { + var unused = GetCustomers(ctx, 0, 1, out totalCount); + ctx.RecordsPerStore.Add(store.Id, totalCount); + } } } @@ -2205,8 +2296,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.Data = new ExportSegmenter( pageIndex => GetProducts(ctx, pageIndex), entity => ConvertToExpando(ctx, entity), - pageable, - itemsPerFile + pageable, itemsPerFile ); } else if (ctx.Provider.Value.EntityType == ExportEntityType.Order) @@ -2214,8 +2304,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.Data = new ExportSegmenter( pageIndex => GetOrders(ctx, pageIndex, ctx.PageSize, out unused), entity => ConvertToExpando(ctx, entity), - pageable, - itemsPerFile + pageable, itemsPerFile ); } else if (ctx.Provider.Value.EntityType == ExportEntityType.Manufacturer) @@ -2223,8 +2312,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.Data = new ExportSegmenter( pageIndex => GetManufacturers(ctx, pageIndex, ctx.PageSize, out unused), entity => ConvertToExpando(ctx, entity), - pageable, - itemsPerFile + pageable, itemsPerFile ); } else if (ctx.Provider.Value.EntityType == ExportEntityType.Category) @@ -2232,8 +2320,15 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.Data = new ExportSegmenter( pageIndex => GetCategories(ctx, pageIndex, ctx.PageSize, out unused), entity => ConvertToExpando(ctx, entity), - pageable, - itemsPerFile + pageable, itemsPerFile + ); + } + else if (ctx.Provider.Value.EntityType == ExportEntityType.Customer) + { + ctx.Export.Data = new ExportSegmenter( + pageIndex => GetCustomers(ctx, pageIndex, ctx.PageSize, out unused), + entity => ConvertToExpando(ctx, entity), + pageable, itemsPerFile ); } @@ -2458,6 +2553,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) ctx.DataContextOrder = null; ctx.DataContextManufacturer = null; ctx.DataContextCategory = null; + ctx.DataContextCustomer = null; (ctx.Export.Data as IExportExecuter).Dispose(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs index 92587e3685..97c0a5f926 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs @@ -23,6 +23,7 @@ internal class ExportProfileTaskContext private ExportDataContextOrder _dataContextOrder; private ExportDataContextManufacturer _dataContextManufacturer; private ExportDataContextCategory _dataContextCategory; + private ExportDataContextCustomer _dataContextCustomer; public ExportProfileTaskContext( TaskExecutionContext taskContext, @@ -60,13 +61,13 @@ public ExportProfileTaskContext( RecordsPerStore = new Dictionary(); EntityIdsLoaded = new List(); - Export = new ExportExecuteContext(TaskContext.CancellationToken, FolderContent); - Export.Projection = XmlHelper.Deserialize(profile.Projection); - Result = new ExportExecuteResult { FileFolder = (IsFileBasedExport ? FolderContent : null) }; + + Export = new ExportExecuteContext(Result, TaskContext.CancellationToken, FolderContent); + Export.Projection = XmlHelper.Deserialize(profile.Projection); } public List EntityIdsSelected { get; private set; } @@ -197,6 +198,20 @@ public ExportDataContextCategory DataContextCategory _dataContextCategory = value; } } + public ExportDataContextCustomer DataContextCustomer + { + get + { + return _dataContextCustomer; + } + set + { + if (_dataContextCustomer != null) + _dataContextCustomer.Clear(); + + _dataContextCustomer = value; + } + } public ExportExecuteContext Export { get; set; } public ExportExecuteResult Result { get; set; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs index 54a340407c..4835855ca4 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs @@ -33,6 +33,26 @@ public void WriteLocalized(dynamic parentNode) _writer.WriteEndElement(); // Localized } + public void WriteGenericAttributes(dynamic parentNode) + { + if (parentNode == null || parentNode._GenericAttributes == null) + return; + + _writer.WriteStartElement("GenericAttributes"); + foreach (dynamic genericAttribute in parentNode._GenericAttributes) + { + _writer.WriteStartElement("GenericAttribute"); + _writer.Write("Id", ((int)genericAttribute.Id).ToString()); + _writer.Write("EntityId", ((int)genericAttribute.EntityId).ToString()); + _writer.Write("KeyGroup", (string)genericAttribute.KeyGroup); + _writer.Write("Key", (string)genericAttribute.Key); + _writer.Write("Value", (string)genericAttribute.Value); + _writer.Write("StoreId", ((int)genericAttribute.StoreId).ToString()); + _writer.WriteEndElement(); // GenericAttribute + } + _writer.WriteEndElement(); // GenericAttributes + } + public void WriteAddress(dynamic address, string node) { if (address == null) @@ -457,265 +477,336 @@ public void WriteProduct(dynamic product, string node) WriteQuantityUnit(product.QuantityUnit, "QuantityUnit"); - - _writer.WriteStartElement("AppliedDiscounts"); - foreach (var discount in product.AppliedDiscounts) + if (product.AppliedDiscounts != null) { - DateTime? startDateUtc = discount.StartDateUtc; - DateTime? endDateUtc = discount.EndDateUtc; - - _writer.WriteStartElement("AppliedDiscount"); - _writer.Write("Id", ((int)discount.Id).ToString()); - _writer.Write("Name", (string)discount.Name); - _writer.Write("DiscountTypeId", ((int)discount.DiscountTypeId).ToString()); - _writer.Write("UsePercentage", ((bool)discount.UsePercentage).ToString()); - _writer.Write("DiscountPercentage", ((decimal)discount.DiscountPercentage).ToString(_culture)); - _writer.Write("DiscountAmount", ((decimal)discount.DiscountAmount).ToString(_culture)); - _writer.Write("StartDateUtc", startDateUtc.HasValue ? startDateUtc.Value.ToString(_culture) : ""); - _writer.Write("EndDateUtc", endDateUtc.HasValue ? endDateUtc.Value.ToString(_culture) : ""); - _writer.Write("RequiresCouponCode", ((bool)discount.RequiresCouponCode).ToString()); - _writer.Write("CouponCode", (string)discount.CouponCode); - _writer.Write("DiscountLimitationId", ((int)discount.DiscountLimitationId).ToString()); - _writer.Write("LimitationTimes", ((int)discount.LimitationTimes).ToString()); - _writer.WriteEndElement(); // AppliedDiscount + _writer.WriteStartElement("AppliedDiscounts"); + foreach (dynamic discount in product.AppliedDiscounts) + { + DateTime? startDateUtc = discount.StartDateUtc; + DateTime? endDateUtc = discount.EndDateUtc; + + _writer.WriteStartElement("AppliedDiscount"); + _writer.Write("Id", ((int)discount.Id).ToString()); + _writer.Write("Name", (string)discount.Name); + _writer.Write("DiscountTypeId", ((int)discount.DiscountTypeId).ToString()); + _writer.Write("UsePercentage", ((bool)discount.UsePercentage).ToString()); + _writer.Write("DiscountPercentage", ((decimal)discount.DiscountPercentage).ToString(_culture)); + _writer.Write("DiscountAmount", ((decimal)discount.DiscountAmount).ToString(_culture)); + _writer.Write("StartDateUtc", startDateUtc.HasValue ? startDateUtc.Value.ToString(_culture) : ""); + _writer.Write("EndDateUtc", endDateUtc.HasValue ? endDateUtc.Value.ToString(_culture) : ""); + _writer.Write("RequiresCouponCode", ((bool)discount.RequiresCouponCode).ToString()); + _writer.Write("CouponCode", (string)discount.CouponCode); + _writer.Write("DiscountLimitationId", ((int)discount.DiscountLimitationId).ToString()); + _writer.Write("LimitationTimes", ((int)discount.LimitationTimes).ToString()); + _writer.WriteEndElement(); // AppliedDiscount + } + _writer.WriteEndElement(); // AppliedDiscounts } - _writer.WriteEndElement(); // AppliedDiscounts - - _writer.WriteStartElement("TierPrices"); - foreach (dynamic tierPrice in product.TierPrices) + if (product.TierPrices != null) { - int? customerRoleId = tierPrice.CustomerRoleId; - - _writer.WriteStartElement("TierPrice"); - _writer.Write("Id", ((int)tierPrice.Id).ToString()); - _writer.Write("ProductId", ((int)tierPrice.ProductId).ToString()); - _writer.Write("StoreId", ((int)tierPrice.StoreId).ToString()); - _writer.Write("CustomerRoleId", customerRoleId.HasValue ? customerRoleId.Value.ToString() : ""); - _writer.Write("Quantity", ((int)tierPrice.Quantity).ToString()); - _writer.Write("Price", ((decimal)tierPrice.Price).ToString(_culture)); - _writer.WriteEndElement(); // TierPrice + _writer.WriteStartElement("TierPrices"); + foreach (dynamic tierPrice in product.TierPrices) + { + int? customerRoleId = tierPrice.CustomerRoleId; + + _writer.WriteStartElement("TierPrice"); + _writer.Write("Id", ((int)tierPrice.Id).ToString()); + _writer.Write("ProductId", ((int)tierPrice.ProductId).ToString()); + _writer.Write("StoreId", ((int)tierPrice.StoreId).ToString()); + _writer.Write("CustomerRoleId", customerRoleId.HasValue ? customerRoleId.Value.ToString() : ""); + _writer.Write("Quantity", ((int)tierPrice.Quantity).ToString()); + _writer.Write("Price", ((decimal)tierPrice.Price).ToString(_culture)); + _writer.WriteEndElement(); // TierPrice + } + _writer.WriteEndElement(); // TierPrices } - _writer.WriteEndElement(); // TierPrices - - _writer.WriteStartElement("ProductTags"); - foreach (dynamic tag in product.ProductTags) + if (product.ProductTags != null) { - _writer.WriteStartElement("ProductTag"); - _writer.Write("Id", ((int)tag.Id).ToString()); - _writer.Write("Name", (string)tag.Name); - _writer.Write("SeName", (string)tag.SeName); + _writer.WriteStartElement("ProductTags"); + foreach (dynamic tag in product.ProductTags) + { + _writer.WriteStartElement("ProductTag"); + _writer.Write("Id", ((int)tag.Id).ToString()); + _writer.Write("Name", (string)tag.Name); + _writer.Write("SeName", (string)tag.SeName); - WriteLocalized(tag); + WriteLocalized(tag); - _writer.WriteEndElement(); // ProductTag + _writer.WriteEndElement(); // ProductTag + } + _writer.WriteEndElement(); // ProductTags } - _writer.WriteEndElement(); // ProductTags - - _writer.WriteStartElement("ProductAttributes"); - foreach (var pa in product.ProductAttributes) + if (product.ProductAttributes != null) { - _writer.WriteStartElement("ProductAttribute"); - _writer.Write("Id", ((int)pa.Id).ToString()); - _writer.Write("TextPrompt", (string)pa.TextPrompt); - _writer.Write("IsRequired", ((bool)pa.IsRequired).ToString()); - _writer.Write("AttributeControlTypeId", ((int)pa.AttributeControlTypeId).ToString()); - _writer.Write("DisplayOrder", ((int)pa.DisplayOrder).ToString()); - - _writer.WriteStartElement("Attribute"); - _writer.Write("Id", ((int)pa.Attribute.Id).ToString()); - _writer.Write("Alias", (string)pa.Attribute.Alias); - _writer.Write("Name", (string)pa.Attribute.Name); - _writer.Write("Description", (string)pa.Attribute.Description); - - WriteLocalized(pa.Attribute); - - _writer.WriteEndElement(); // Attribute - - _writer.WriteStartElement("AttributeValues"); - foreach (var value in pa.Attribute.Values) + _writer.WriteStartElement("ProductAttributes"); + foreach (dynamic pa in product.ProductAttributes) { - _writer.WriteStartElement("AttributeValue"); - _writer.Write("Id", ((int)value.Id).ToString()); - _writer.Write("Alias", (string)value.Alias); - _writer.Write("Name", (string)value.Name); - _writer.Write("ColorSquaresRgb", (string)value.ColorSquaresRgb); - _writer.Write("PriceAdjustment", ((decimal)value.PriceAdjustment).ToString(_culture)); - _writer.Write("WeightAdjustment", ((decimal)value.WeightAdjustment).ToString(_culture)); - _writer.Write("IsPreSelected", ((bool)value.IsPreSelected).ToString()); - _writer.Write("DisplayOrder", ((int)value.DisplayOrder).ToString()); - _writer.Write("ValueTypeId", ((int)value.ValueTypeId).ToString()); - _writer.Write("LinkedProductId", ((int)value.LinkedProductId).ToString()); - _writer.Write("Quantity", ((int)value.Quantity).ToString()); - - WriteLocalized(value); - - _writer.WriteEndElement(); // AttributeValue + _writer.WriteStartElement("ProductAttribute"); + _writer.Write("Id", ((int)pa.Id).ToString()); + _writer.Write("TextPrompt", (string)pa.TextPrompt); + _writer.Write("IsRequired", ((bool)pa.IsRequired).ToString()); + _writer.Write("AttributeControlTypeId", ((int)pa.AttributeControlTypeId).ToString()); + _writer.Write("DisplayOrder", ((int)pa.DisplayOrder).ToString()); + + _writer.WriteStartElement("Attribute"); + _writer.Write("Id", ((int)pa.Attribute.Id).ToString()); + _writer.Write("Alias", (string)pa.Attribute.Alias); + _writer.Write("Name", (string)pa.Attribute.Name); + _writer.Write("Description", (string)pa.Attribute.Description); + + WriteLocalized(pa.Attribute); + + _writer.WriteEndElement(); // Attribute + + _writer.WriteStartElement("AttributeValues"); + foreach (dynamic value in pa.Attribute.Values) + { + _writer.WriteStartElement("AttributeValue"); + _writer.Write("Id", ((int)value.Id).ToString()); + _writer.Write("Alias", (string)value.Alias); + _writer.Write("Name", (string)value.Name); + _writer.Write("ColorSquaresRgb", (string)value.ColorSquaresRgb); + _writer.Write("PriceAdjustment", ((decimal)value.PriceAdjustment).ToString(_culture)); + _writer.Write("WeightAdjustment", ((decimal)value.WeightAdjustment).ToString(_culture)); + _writer.Write("IsPreSelected", ((bool)value.IsPreSelected).ToString()); + _writer.Write("DisplayOrder", ((int)value.DisplayOrder).ToString()); + _writer.Write("ValueTypeId", ((int)value.ValueTypeId).ToString()); + _writer.Write("LinkedProductId", ((int)value.LinkedProductId).ToString()); + _writer.Write("Quantity", ((int)value.Quantity).ToString()); + + WriteLocalized(value); + + _writer.WriteEndElement(); // AttributeValue + } + _writer.WriteEndElement(); // AttributeValues + + _writer.WriteEndElement(); // ProductAttribute } - _writer.WriteEndElement(); // AttributeValues - - _writer.WriteEndElement(); // ProductAttribute - } - _writer.WriteEndElement(); // ProductAttributes - - - _writer.WriteStartElement("ProductAttributeCombinations"); - foreach (dynamic combination in product.ProductAttributeCombinations) - { - decimal? price = combination.Price; - decimal? length = combination.Length; - decimal? width = combination.Width; - decimal? height = combination.Height; - decimal? bpAmount = combination.BasePriceAmount; - int? bpbAmount = combination.BasePriceBaseAmount; - int? dlvTimeId = combination.DeliveryTimeId; - - _writer.WriteStartElement("ProductAttributeCombination"); - _writer.Write("Id", ((int)combination.Id).ToString()); - _writer.Write("StockQuantity", ((int)combination.StockQuantity).ToString()); - _writer.Write("AllowOutOfStockOrders", ((bool)combination.AllowOutOfStockOrders).ToString()); - _writer.Write("AttributesXml", (string)combination.AttributesXml); - _writer.Write("Sku", (string)combination.Sku); - _writer.Write("Gtin", (string)combination.Gtin); - _writer.Write("ManufacturerPartNumber", (string)combination.ManufacturerPartNumber); - _writer.Write("Price", price.HasValue ? price.Value.ToString(_culture) : ""); - _writer.Write("Length", length.HasValue ? length.Value.ToString(_culture) : ""); - _writer.Write("Width", width.HasValue ? width.Value.ToString(_culture) : ""); - _writer.Write("Height", height.HasValue ? height.Value.ToString(_culture) : ""); - _writer.Write("BasePriceAmount", bpAmount.HasValue ? bpAmount.Value.ToString(_culture) : ""); - _writer.Write("BasePriceBaseAmount", bpbAmount.HasValue ? bpbAmount.Value.ToString() : ""); - _writer.Write("AssignedPictureIds", (string)combination.AssignedPictureIds); - _writer.Write("DeliveryTimeId", dlvTimeId.HasValue ? dlvTimeId.Value.ToString() : ""); - _writer.Write("IsActive", ((bool)combination.IsActive).ToString()); - - WriteDeliveryTime(combination.DeliveryTime, "DeliveryTime"); - - WriteQuantityUnit(combination.QuantityUnit, "QuantityUnit"); - - _writer.WriteStartElement("Pictures"); - foreach (dynamic assignedPicture in combination.Pictures) + _writer.WriteEndElement(); // ProductAttributes + } + + if (product.ProductAttributeCombinations != null) + { + _writer.WriteStartElement("ProductAttributeCombinations"); + foreach (dynamic combination in product.ProductAttributeCombinations) { - WritePicture(assignedPicture, "Picture"); + decimal? price = combination.Price; + decimal? length = combination.Length; + decimal? width = combination.Width; + decimal? height = combination.Height; + decimal? bpAmount = combination.BasePriceAmount; + int? bpbAmount = combination.BasePriceBaseAmount; + int? dlvTimeId = combination.DeliveryTimeId; + + _writer.WriteStartElement("ProductAttributeCombination"); + _writer.Write("Id", ((int)combination.Id).ToString()); + _writer.Write("StockQuantity", ((int)combination.StockQuantity).ToString()); + _writer.Write("AllowOutOfStockOrders", ((bool)combination.AllowOutOfStockOrders).ToString()); + _writer.Write("AttributesXml", (string)combination.AttributesXml); + _writer.Write("Sku", (string)combination.Sku); + _writer.Write("Gtin", (string)combination.Gtin); + _writer.Write("ManufacturerPartNumber", (string)combination.ManufacturerPartNumber); + _writer.Write("Price", price.HasValue ? price.Value.ToString(_culture) : ""); + _writer.Write("Length", length.HasValue ? length.Value.ToString(_culture) : ""); + _writer.Write("Width", width.HasValue ? width.Value.ToString(_culture) : ""); + _writer.Write("Height", height.HasValue ? height.Value.ToString(_culture) : ""); + _writer.Write("BasePriceAmount", bpAmount.HasValue ? bpAmount.Value.ToString(_culture) : ""); + _writer.Write("BasePriceBaseAmount", bpbAmount.HasValue ? bpbAmount.Value.ToString() : ""); + _writer.Write("AssignedPictureIds", (string)combination.AssignedPictureIds); + _writer.Write("DeliveryTimeId", dlvTimeId.HasValue ? dlvTimeId.Value.ToString() : ""); + _writer.Write("IsActive", ((bool)combination.IsActive).ToString()); + + WriteDeliveryTime(combination.DeliveryTime, "DeliveryTime"); + + WriteQuantityUnit(combination.QuantityUnit, "QuantityUnit"); + + _writer.WriteStartElement("Pictures"); + foreach (dynamic assignedPicture in combination.Pictures) + { + WritePicture(assignedPicture, "Picture"); + } + _writer.WriteEndElement(); // Pictures + + _writer.WriteEndElement(); // ProductAttributeCombination } - _writer.WriteEndElement(); // Pictures - - _writer.WriteEndElement(); // ProductAttributeCombination + _writer.WriteEndElement(); // ProductAttributeCombinations } - _writer.WriteEndElement(); // ProductAttributeCombinations - - _writer.WriteStartElement("ProductPictures"); - foreach (dynamic productPicture in product.ProductPictures) + if (product.ProductPictures != null) { - _writer.WriteStartElement("ProductPicture"); - _writer.Write("Id", ((int)productPicture.Id).ToString()); - _writer.Write("DisplayOrder", ((int)productPicture.DisplayOrder).ToString()); + _writer.WriteStartElement("ProductPictures"); + foreach (dynamic productPicture in product.ProductPictures) + { + _writer.WriteStartElement("ProductPicture"); + _writer.Write("Id", ((int)productPicture.Id).ToString()); + _writer.Write("DisplayOrder", ((int)productPicture.DisplayOrder).ToString()); - WritePicture(productPicture.Picture, "Picture"); + WritePicture(productPicture.Picture, "Picture"); - _writer.WriteEndElement(); // ProductPicture + _writer.WriteEndElement(); // ProductPicture + } + _writer.WriteEndElement(); // ProductPictures } - _writer.WriteEndElement(); // ProductPictures - - _writer.WriteStartElement("ProductCategories"); - foreach (dynamic productCategory in product.ProductCategories) + if (product.ProductCategories != null) { - _writer.WriteStartElement("ProductCategory"); - _writer.Write("Id", ((int)productCategory.Id).ToString()); - _writer.Write("DisplayOrder", ((int)productCategory.DisplayOrder).ToString()); - _writer.Write("IsFeaturedProduct", ((bool)productCategory.IsFeaturedProduct).ToString()); + _writer.WriteStartElement("ProductCategories"); + foreach (dynamic productCategory in product.ProductCategories) + { + _writer.WriteStartElement("ProductCategory"); + _writer.Write("Id", ((int)productCategory.Id).ToString()); + _writer.Write("DisplayOrder", ((int)productCategory.DisplayOrder).ToString()); + _writer.Write("IsFeaturedProduct", ((bool)productCategory.IsFeaturedProduct).ToString()); - WriteCategory(productCategory.Category, "Category"); + WriteCategory(productCategory.Category, "Category"); - _writer.WriteEndElement(); // ProductCategory + _writer.WriteEndElement(); // ProductCategory + } + _writer.WriteEndElement(); // ProductCategories } - _writer.WriteEndElement(); // ProductCategories - - _writer.WriteStartElement("ProductManufacturers"); - foreach (dynamic productManu in product.ProductManufacturers) + if (product.ProductManufacturers != null) { - _writer.WriteStartElement("ProductManufacturer"); + _writer.WriteStartElement("ProductManufacturers"); + foreach (dynamic productManu in product.ProductManufacturers) + { + _writer.WriteStartElement("ProductManufacturer"); - _writer.Write("Id", ((int)productManu.Id).ToString()); - _writer.Write("DisplayOrder", ((int)productManu.DisplayOrder).ToString()); - _writer.Write("IsFeaturedProduct", ((bool)productManu.IsFeaturedProduct).ToString()); + _writer.Write("Id", ((int)productManu.Id).ToString()); + _writer.Write("DisplayOrder", ((int)productManu.DisplayOrder).ToString()); + _writer.Write("IsFeaturedProduct", ((bool)productManu.IsFeaturedProduct).ToString()); - WriteManufacturer(productManu.Manufacturer, "Manufacturer"); + WriteManufacturer(productManu.Manufacturer, "Manufacturer"); - _writer.WriteEndElement(); // ProductManufacturer + _writer.WriteEndElement(); // ProductManufacturer + } + _writer.WriteEndElement(); // ProductManufacturers } - _writer.WriteEndElement(); // ProductManufacturers - - _writer.WriteStartElement("ProductSpecificationAttributes"); - foreach (dynamic psa in product.ProductSpecificationAttributes) + if (product.ProductSpecificationAttributes != null) { - _writer.WriteStartElement("ProductSpecificationAttribute"); + _writer.WriteStartElement("ProductSpecificationAttributes"); + foreach (dynamic psa in product.ProductSpecificationAttributes) + { + _writer.WriteStartElement("ProductSpecificationAttribute"); - _writer.Write("Id", ((int)psa.Id).ToString()); - _writer.Write("ProductId", ((int)psa.ProductId).ToString()); - _writer.Write("SpecificationAttributeOptionId", ((int)psa.SpecificationAttributeOptionId).ToString()); - _writer.Write("AllowFiltering", ((bool)psa.AllowFiltering).ToString()); - _writer.Write("ShowOnProductPage", ((bool)psa.ShowOnProductPage).ToString()); - _writer.Write("DisplayOrder", ((int)psa.DisplayOrder).ToString()); + _writer.Write("Id", ((int)psa.Id).ToString()); + _writer.Write("ProductId", ((int)psa.ProductId).ToString()); + _writer.Write("SpecificationAttributeOptionId", ((int)psa.SpecificationAttributeOptionId).ToString()); + _writer.Write("AllowFiltering", ((bool)psa.AllowFiltering).ToString()); + _writer.Write("ShowOnProductPage", ((bool)psa.ShowOnProductPage).ToString()); + _writer.Write("DisplayOrder", ((int)psa.DisplayOrder).ToString()); - dynamic option = psa.SpecificationAttributeOption; + dynamic option = psa.SpecificationAttributeOption; - _writer.WriteStartElement("SpecificationAttributeOption"); - _writer.Write("Id", ((int)option.Id).ToString()); - _writer.Write("SpecificationAttributeId", ((int)option.SpecificationAttributeId).ToString()); - _writer.Write("DisplayOrder", ((int)option.DisplayOrder).ToString()); - _writer.Write("Name", (string)option.Name); + _writer.WriteStartElement("SpecificationAttributeOption"); + _writer.Write("Id", ((int)option.Id).ToString()); + _writer.Write("SpecificationAttributeId", ((int)option.SpecificationAttributeId).ToString()); + _writer.Write("DisplayOrder", ((int)option.DisplayOrder).ToString()); + _writer.Write("Name", (string)option.Name); - WriteLocalized(option); + WriteLocalized(option); - _writer.WriteStartElement("SpecificationAttribute"); - _writer.Write("Id", ((int)option.SpecificationAttribute.Id).ToString()); - _writer.Write("Name", (string)option.SpecificationAttribute.Name); - _writer.Write("DisplayOrder", ((int)option.SpecificationAttribute.DisplayOrder).ToString()); + _writer.WriteStartElement("SpecificationAttribute"); + _writer.Write("Id", ((int)option.SpecificationAttribute.Id).ToString()); + _writer.Write("Name", (string)option.SpecificationAttribute.Name); + _writer.Write("DisplayOrder", ((int)option.SpecificationAttribute.DisplayOrder).ToString()); - WriteLocalized(option.SpecificationAttribute); + WriteLocalized(option.SpecificationAttribute); - _writer.WriteEndElement(); // SpecificationAttribute - _writer.WriteEndElement(); // SpecificationAttributeOption + _writer.WriteEndElement(); // SpecificationAttribute + _writer.WriteEndElement(); // SpecificationAttributeOption - _writer.WriteEndElement(); // ProductSpecificationAttribute + _writer.WriteEndElement(); // ProductSpecificationAttribute + } + _writer.WriteEndElement(); // ProductSpecificationAttributes } - _writer.WriteEndElement(); // ProductSpecificationAttributes + if (product.ProductBundleItems != null) + { + _writer.WriteStartElement("ProductBundleItems"); + foreach (dynamic bundleItem in product.ProductBundleItems) + { + decimal? bundleItemDiscount = bundleItem.Discount; + + _writer.WriteStartElement("ProductBundleItem"); + _writer.Write("Id", ((int)bundleItem.Id).ToString()); + _writer.Write("ProductId", ((int)bundleItem.ProductId).ToString()); + _writer.Write("BundleProductId", ((int)bundleItem.BundleProductId).ToString()); + _writer.Write("Quantity", ((int)bundleItem.Quantity).ToString()); + _writer.Write("Discount", bundleItemDiscount.HasValue ? bundleItemDiscount.Value.ToString(_culture) : ""); + _writer.Write("DiscountPercentage", ((bool)bundleItem.DiscountPercentage).ToString()); + _writer.Write("Name", (string)bundleItem.Name); + _writer.Write("ShortDescription", (string)bundleItem.ShortDescription); + _writer.Write("FilterAttributes", ((bool)bundleItem.FilterAttributes).ToString()); + _writer.Write("HideThumbnail", ((bool)bundleItem.HideThumbnail).ToString()); + _writer.Write("Visible", ((bool)bundleItem.Visible).ToString()); + _writer.Write("Published", ((bool)bundleItem.Published).ToString()); + _writer.Write("DisplayOrder", ((int)bundleItem.DisplayOrder).ToString()); + _writer.Write("CreatedOnUtc", ((DateTime)bundleItem.CreatedOnUtc).ToString(_culture)); + _writer.Write("UpdatedOnUtc", ((DateTime)bundleItem.UpdatedOnUtc).ToString(_culture)); + + WriteLocalized(bundleItem); + + _writer.WriteEndElement(); // ProductBundleItem + } + _writer.WriteEndElement(); // ProductBundleItems + } - _writer.WriteStartElement("ProductBundleItems"); - foreach (var bundleItem in product.ProductBundleItems) + if (node.HasValue()) { - decimal? bundleItemDiscount = bundleItem.Discount; + _writer.WriteEndElement(); + } + } - _writer.WriteStartElement("ProductBundleItem"); - _writer.Write("Id", ((int)bundleItem.Id).ToString()); - _writer.Write("ProductId", ((int)bundleItem.ProductId).ToString()); - _writer.Write("BundleProductId", ((int)bundleItem.BundleProductId).ToString()); - _writer.Write("Quantity", ((int)bundleItem.Quantity).ToString()); - _writer.Write("Discount", bundleItemDiscount.HasValue ? bundleItemDiscount.Value.ToString(_culture) : ""); - _writer.Write("DiscountPercentage", ((bool)bundleItem.DiscountPercentage).ToString()); - _writer.Write("Name", (string)bundleItem.Name); - _writer.Write("ShortDescription", (string)bundleItem.ShortDescription); - _writer.Write("FilterAttributes", ((bool)bundleItem.FilterAttributes).ToString()); - _writer.Write("HideThumbnail", ((bool)bundleItem.HideThumbnail).ToString()); - _writer.Write("Visible", ((bool)bundleItem.Visible).ToString()); - _writer.Write("Published", ((bool)bundleItem.Published).ToString()); - _writer.Write("DisplayOrder", ((int)bundleItem.DisplayOrder).ToString()); - _writer.Write("CreatedOnUtc", ((DateTime)bundleItem.CreatedOnUtc).ToString(_culture)); - _writer.Write("UpdatedOnUtc", ((DateTime)bundleItem.UpdatedOnUtc).ToString(_culture)); + public void WriteCustomer(dynamic customer, string node) + { + if (customer == null) + return; - WriteLocalized(bundleItem); + if (node.HasValue()) + { + _writer.WriteStartElement(node); + } - _writer.WriteEndElement(); // ProductBundleItem + DateTime? lastLoginDateUtc = customer.LastLoginDateUtc; + + _writer.Write("Id", ((int)customer.Id).ToString()); + _writer.Write("CustomerGuid", ((Guid)customer.CustomerGuid).ToString()); + _writer.Write("Username", (string)customer.Username); + _writer.Write("Email", (string)customer.Email); + _writer.Write("PasswordFormatId", ((int)customer.PasswordFormatId).ToString()); + _writer.Write("AdminComment", (string)customer.AdminComment); + _writer.Write("IsTaxExempt", ((bool)customer.IsTaxExempt).ToString()); + _writer.Write("AffiliateId", ((int)customer.AffiliateId).ToString()); + _writer.Write("Active", ((bool)customer.Active).ToString()); + _writer.Write("Deleted", ((bool)customer.Deleted).ToString()); + _writer.Write("IsSystemAccount", ((bool)customer.IsSystemAccount).ToString()); + _writer.Write("SystemName", (string)customer.SystemName); + _writer.Write("LastIpAddress", (string)customer.LastIpAddress); + _writer.Write("CreatedOnUtc", ((DateTime)customer.CreatedOnUtc).ToString(_culture)); + _writer.Write("LastLoginDateUtc", lastLoginDateUtc.HasValue ? lastLoginDateUtc.Value.ToString(_culture) : ""); + _writer.Write("LastActivityDateUtc", ((DateTime)customer.LastActivityDateUtc).ToString(_culture)); + _writer.Write("RewardPointsBalance", ((int)customer._RewardPointsBalance).ToString()); + + WriteRewardPointsHistory(customer.RewardPointsHistory, "RewardPointsHistories"); + WriteAddress(customer.BillingAddress, "BillingAddress"); + WriteAddress(customer.ShippingAddress, "ShippingAddress"); + + if (customer.Addresses != null) + { + _writer.WriteStartElement("Addresses"); + foreach (dynamic address in customer.Addresses) + { + WriteAddress(address, "Address"); + } + _writer.WriteEndElement(); // Addresses } - _writer.WriteEndElement(); // ProductBundleItems + WriteGenericAttributes(customer); if (node.HasValue()) { diff --git a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs index a35f876b36..374f0d39e6 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs @@ -302,141 +302,6 @@ public virtual void ExportCustomersToXlsx(Stream stream, IList custome } } - /// - /// Export customer list to xml - /// - /// Customers - /// Result in XML format - public virtual string ExportCustomersToXml(IList customers) - { - var sb = new StringBuilder(); - var stringWriter = new StringWriter(sb); - var xmlWriter = new XmlTextWriter(stringWriter); - xmlWriter.WriteStartDocument(); - xmlWriter.WriteStartElement("Customers"); - xmlWriter.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - - foreach (var customer in customers) - { - xmlWriter.WriteStartElement("Customer"); - - xmlWriter.WriteElementString("Id", null, customer.Id.ToString()); - xmlWriter.WriteElementString("CustomerNumber", null, customer.GetAttribute(SystemCustomerAttributeNames.CustomerNumber)); - xmlWriter.WriteElementString("CustomerGuid", null, customer.CustomerGuid.ToString()); - xmlWriter.WriteElementString("Email", null, customer.Email); - xmlWriter.WriteElementString("Username", null, customer.Username); - xmlWriter.WriteElementString("Password", null, customer.Password); - xmlWriter.WriteElementString("PasswordFormatId", null, customer.PasswordFormatId.ToString()); - xmlWriter.WriteElementString("PasswordSalt", null, customer.PasswordSalt); - xmlWriter.WriteElementString("AdminComment", null, customer.AdminComment); - xmlWriter.WriteElementString("IsTaxExempt", null, customer.IsTaxExempt.ToString()); - xmlWriter.WriteElementString("AffiliateId", null, customer.AffiliateId.ToString()); - xmlWriter.WriteElementString("Active", null, customer.Active.ToString()); - xmlWriter.WriteElementString("IsSystemAccount", null, customer.IsSystemAccount.ToString()); - xmlWriter.WriteElementString("SystemName", null, customer.SystemName); - xmlWriter.WriteElementString("LastIpAddress", null, customer.LastIpAddress); - xmlWriter.WriteElementString("CreatedOnUtc", null, customer.CreatedOnUtc.ToString()); - xmlWriter.WriteElementString("LastLoginDateUtc", null, customer.LastLoginDateUtc.HasValue ? customer.LastLoginDateUtc.Value.ToString() : ""); - xmlWriter.WriteElementString("LastActivityDateUtc", null, customer.LastActivityDateUtc.ToString()); - - xmlWriter.WriteElementString("IsGuest", null, customer.IsGuest().ToString()); - xmlWriter.WriteElementString("IsRegistered", null, customer.IsRegistered().ToString()); - xmlWriter.WriteElementString("IsAdministrator", null, customer.IsAdmin().ToString()); - xmlWriter.WriteElementString("IsForumModerator", null, customer.IsForumModerator().ToString()); - - xmlWriter.WriteElementString("FirstName", null, customer.GetAttribute(SystemCustomerAttributeNames.FirstName)); - xmlWriter.WriteElementString("LastName", null, customer.GetAttribute(SystemCustomerAttributeNames.LastName)); - xmlWriter.WriteElementString("Gender", null, customer.GetAttribute(SystemCustomerAttributeNames.Gender)); - xmlWriter.WriteElementString("Company", null, customer.GetAttribute(SystemCustomerAttributeNames.Company)); - - xmlWriter.WriteElementString("CountryId", null, customer.GetAttribute(SystemCustomerAttributeNames.CountryId).ToString()); - xmlWriter.WriteElementString("StreetAddress", null, customer.GetAttribute(SystemCustomerAttributeNames.StreetAddress)); - xmlWriter.WriteElementString("StreetAddress2", null, customer.GetAttribute(SystemCustomerAttributeNames.StreetAddress2)); - xmlWriter.WriteElementString("ZipPostalCode", null, customer.GetAttribute(SystemCustomerAttributeNames.ZipPostalCode)); - xmlWriter.WriteElementString("City", null, customer.GetAttribute(SystemCustomerAttributeNames.City)); - xmlWriter.WriteElementString("CountryId", null, customer.GetAttribute(SystemCustomerAttributeNames.CountryId).ToString()); - xmlWriter.WriteElementString("StateProvinceId", null, customer.GetAttribute(SystemCustomerAttributeNames.StateProvinceId).ToString()); - xmlWriter.WriteElementString("Phone", null, customer.GetAttribute(SystemCustomerAttributeNames.Phone)); - xmlWriter.WriteElementString("Fax", null, customer.GetAttribute(SystemCustomerAttributeNames.Fax)); - xmlWriter.WriteElementString("VatNumber", null, customer.GetAttribute(SystemCustomerAttributeNames.VatNumber)); - xmlWriter.WriteElementString("VatNumberStatusId", null, customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId).ToString()); - xmlWriter.WriteElementString("TimeZoneId", null, customer.GetAttribute(SystemCustomerAttributeNames.TimeZoneId)); - - var newsletter = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(customer.Email); - bool subscribedToNewsletters = newsletter != null && newsletter.Active; - xmlWriter.WriteElementString("Newsletter", null, subscribedToNewsletters.ToString()); - - xmlWriter.WriteElementString("AvatarPictureId", null, customer.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId).ToString()); - xmlWriter.WriteElementString("ForumPostCount", null, customer.GetAttribute(SystemCustomerAttributeNames.ForumPostCount).ToString()); - xmlWriter.WriteElementString("Signature", null, customer.GetAttribute(SystemCustomerAttributeNames.Signature)); - - xmlWriter.WriteStartElement("Addresses"); - - foreach (var address in customer.Addresses) - { - bool isCurrentBillingAddress = (customer.BillingAddress != null && customer.BillingAddress.Id == address.Id); - bool isCurrentShippingAddress = (customer.ShippingAddress != null && customer.ShippingAddress.Id == address.Id); - - xmlWriter.WriteStartElement("Address"); - xmlWriter.WriteElementString("IsCurrentBillingAddress", null, isCurrentBillingAddress.ToString()); - xmlWriter.WriteElementString("IsCurrentShippingAddress", null, isCurrentShippingAddress.ToString()); - - xmlWriter.WriteElementString("Id", null, address.Id.ToString()); - xmlWriter.WriteElementString("FirstName", null, address.FirstName); - xmlWriter.WriteElementString("LastName", null, address.LastName); - xmlWriter.WriteElementString("Email", null, address.Email); - xmlWriter.WriteElementString("Company", null, address.Company); - xmlWriter.WriteElementString("City", null, address.City); - xmlWriter.WriteElementString("Address1", null, address.Address1); - xmlWriter.WriteElementString("Address2", null, address.Address2); - xmlWriter.WriteElementString("ZipPostalCode", null, address.ZipPostalCode); - xmlWriter.WriteElementString("PhoneNumber", null, address.PhoneNumber); - xmlWriter.WriteElementString("FaxNumber", null, address.FaxNumber); - xmlWriter.WriteElementString("CreatedOnUtc", null, address.CreatedOnUtc.ToString()); - - if (address.Country != null) - { - xmlWriter.WriteStartElement("Country"); - xmlWriter.WriteElementString("Id", null, address.Country.Id.ToString()); - xmlWriter.WriteElementString("Name", null, address.Country.Name); - xmlWriter.WriteElementString("AllowsBilling", null, address.Country.AllowsBilling.ToString()); - xmlWriter.WriteElementString("AllowsShipping", null, address.Country.AllowsShipping.ToString()); - xmlWriter.WriteElementString("TwoLetterIsoCode", null, address.Country.TwoLetterIsoCode); - xmlWriter.WriteElementString("ThreeLetterIsoCode", null, address.Country.ThreeLetterIsoCode); - xmlWriter.WriteElementString("NumericIsoCode", null, address.Country.NumericIsoCode.ToString()); - xmlWriter.WriteElementString("SubjectToVat", null, address.Country.SubjectToVat.ToString()); - xmlWriter.WriteElementString("Published", null, address.Country.Published.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, address.Country.DisplayOrder.ToString()); - xmlWriter.WriteElementString("LimitedToStores", null, address.Country.LimitedToStores.ToString()); - xmlWriter.WriteEndElement(); // Country - } - - if (address.StateProvince != null) - { - xmlWriter.WriteStartElement("StateProvince"); - xmlWriter.WriteElementString("Id", null, address.StateProvince.Id.ToString()); - xmlWriter.WriteElementString("CountryId", null, address.StateProvince.CountryId.ToString()); - xmlWriter.WriteElementString("Name", null, address.StateProvince.Name); - xmlWriter.WriteElementString("Abbreviation", null, address.StateProvince.Abbreviation); - xmlWriter.WriteElementString("Published", null, address.StateProvince.Published.ToString()); - xmlWriter.WriteElementString("DisplayOrder", null, address.StateProvince.DisplayOrder.ToString()); - xmlWriter.WriteEndElement(); // StateProvince - } - - xmlWriter.WriteEndElement(); // Address - } - - xmlWriter.WriteEndElement(); // Addresses - - xmlWriter.WriteEndElement(); // Customer - } - - xmlWriter.WriteEndElement(); - xmlWriter.WriteEndDocument(); - xmlWriter.Close(); - return stringWriter.ToString(); - } - #endregion } } diff --git a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs index 91f0801f2b..21f0c155c3 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs @@ -16,12 +16,5 @@ public interface IExportManager /// Stream /// Customers void ExportCustomersToXlsx(Stream stream, IList customers); - - /// - /// Export customer list to xml - /// - /// Customers - /// Result in XML format - string ExportCustomersToXml(IList customers); } } diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 64c7a1c439..3c79f86603 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -170,6 +170,7 @@ + @@ -177,6 +178,7 @@ + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs index 380d866ec0..efb519fd1b 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs @@ -211,7 +211,7 @@ public void Execute(IExportExecuteContext context) writer.WriteStartElement("item"); - try + context.ProcessRecord((int)product.Id, () => { string category = (gmc == null ? null : gmc.Taxonomy); string productType = product._CategoryPath; @@ -381,14 +381,7 @@ public void Execute(IExportExecuteContext context) writer.WriteElementString("g", "unit_pricing_base_measure", _googleNamespace, basePriceBaseMeasure); } } - - ++context.RecordsSucceeded; - } - catch (Exception exc) - { - context.Log.Error("Error while processing product with id {0}: {1}".FormatInvariant(productId, exc.ToAllMessages()), exc); - ++context.RecordsFailed; - } + }); writer.WriteEndElement(); // item } diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/AdminControllerBase.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/AdminControllerBase.cs index d9f4215b53..04d5f38619 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/AdminControllerBase.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/AdminControllerBase.cs @@ -87,11 +87,14 @@ protected ActionResult Export(string providerSystemName, string selectedIds) string error = null; var fileStreamResult = ExportProfileTask.Export(providerSystemName, selectedIds, null, out error); + if (error.HasValue() || fileStreamResult == null) + { + NotifyError(string.Concat("

    ", T("Admin.Common.UnknownError"), "

    ", error.NaIfEmpty())); + } + if (fileStreamResult != null) return fileStreamResult; - NotifyError(string.Concat("

    ", T("Admin.Common.UnknownError"), "

    ", error.NaIfEmpty())); - return RedirectToAction("List"); } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs index 35f574834b..a27dbc1dac 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs @@ -25,6 +25,7 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; +using SmartStore.Services.DataExchange.ExportProvider; using SmartStore.Services.Directory; using SmartStore.Services.ExportImport; using SmartStore.Services.Forums; @@ -1779,44 +1780,22 @@ public ActionResult ExportExcelSelected(string selectedIds) return File(bytes, "text/xls", "customers.xlsx"); } + [Compress] public ActionResult ExportXmlAll() { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); - try - { - var customers = _customerService.GetAllCustomers(null, null, null, null, - null, null, null, 0, 0, null, null, null, - false, null, 0, int.MaxValue); - - var xml = _exportManager.ExportCustomersToXml(customers); - return new XmlDownloadResult(xml, "customers.xml"); - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } + return Export(ExportCustomerXmlProvider.SystemName, null); } + [Compress] public ActionResult ExportXmlSelected(string selectedIds) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); - var customers = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - customers.AddRange(_customerService.GetCustomersByIds(ids)); - } - - var xml = _exportManager.ExportCustomersToXml(customers); - return new XmlDownloadResult(xml, "customers.xml"); + return Export(ExportCustomerXmlProvider.SystemName, selectedIds); } #endregion From 0b9c99ea336e739411079dcb69f260739a9bed77 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 8 Oct 2015 15:04:32 +0200 Subject: [PATCH 113/874] Convert customer XLSX export to support new export framework Removed export manager because it is not used anymore --- .../ExportCustomerXlsxProvider.cs | 217 +++++++++++++ .../ExportTask/ExportProfileTask.cs | 46 ++- .../ExportTask/ExportProfileTaskContext.cs | 2 + .../DataExchange/ExportXmlHelper.cs | 21 ++ .../ExportImport/ExportManager.cs | 307 ------------------ .../ExportImport/IExportManager.cs | 20 -- .../SmartStore.Services.csproj | 3 +- .../DependencyRegistrar.cs | 6 - .../Controllers/CustomerController.cs | 45 +-- .../Controllers/ManufacturerController.cs | 5 +- .../Controllers/OrderController.cs | 5 +- .../Controllers/ProductController.cs | 3 - 12 files changed, 290 insertions(+), 390 deletions(-) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs delete mode 100644 src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs delete mode 100644 src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs new file mode 100644 index 0000000000..ecce56499b --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs @@ -0,0 +1,217 @@ +using System; +using System.Drawing; +using System.Globalization; +using System.IO; +using OfficeOpenXml; +using OfficeOpenXml.Style; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; +using SmartStore.Services.Customers; + +namespace SmartStore.Services.DataExchange.ExportProvider +{ + /// + /// Exports Excel formatted customer data to a file + /// + [SystemName("Exports.SmartStoreCustomerXlsx")] + [FriendlyName("SmartStore Excel customer export")] + [IsHidden(true)] + public class ExportCustomerXlsxProvider : IExportProvider + { + private string[] Properties + { + get + { + return new string[] + { + "Id", + "CustomerNumber", + "CustomerGuid", + "Email", + "Username", + "PasswordStr", + "PasswordFormatId", + "PasswordSalt", + "AdminComment", + "IsTaxExempt", + "AffiliateId", + "Active", + "IsSystemAccount", + "SystemName", + "LastIpAddress", + "CreatedOnUtc", + "LastLoginDateUtc", + "LastActivityDateUtc", + "IsGuest", + "IsRegistered", + "IsAdministrator", + "IsForumModerator", + "FirstName", + "LastName", + "Gender", + "Company", + "StreetAddress", + "StreetAddress2", + "ZipPostalCode", + "City", + "CountryId", + "StateProvinceId", + "Phone", + "Fax", + "VatNumber", + "VatNumberStatusId", + "TimeZoneId", + "Newsletter", + "AvatarPictureId", + "ForumPostCount", + "Signature" + }; + } + } + + private void WriteCell(ExcelWorksheet worksheet, int row, ref int column, object value) + { + worksheet.Cells[row, column].Value = value; + ++column; + } + + private string GetGenericAttributeValue(dynamic customer, string attributeName) + { + if (customer._GenericAttributes != null) + { + foreach (var attribute in customer._GenericAttributes) + { + if (attributeName.IsCaseInsensitiveEqual((string)attribute.Key)) + { + return (string)attribute.Value; + } + } + } + return null; + } + + public static string SystemName + { + get { return "Exports.SmartStoreCustomerXlsx"; } + } + + public ExportConfigurationInfo ConfigurationInfo + { + get { return null; } + } + + public ExportEntityType EntityType + { + get { return ExportEntityType.Customer; } + } + + public string FileExtension + { + get { return "XLSX"; } + } + + public void Execute(IExportExecuteContext context) + { + var invariantCulture = CultureInfo.InvariantCulture; + var path = context.FilePath; + + context.Log.Information("Creating file " + path); + + using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + using (var xlPackage = new ExcelPackage(stream)) + { + // uncomment this line if you want the XML written out to the outputDir + //xlPackage.DebugMode = true; + + // get handle to the existing worksheet + var worksheet = xlPackage.Workbook.Worksheets.Add("Customers"); + + // create headers and format them + string[] properties = Properties; + + for (int i = 0; i < properties.Length; ++i) + { + worksheet.Cells[1, i + 1].Value = properties[i]; + worksheet.Cells[1, i + 1].Style.Fill.PatternType = ExcelFillStyle.Solid; + worksheet.Cells[1, i + 1].Style.Fill.BackgroundColor.SetColor(Color.FromArgb(184, 204, 228)); + worksheet.Cells[1, i + 1].Style.Font.Bold = true; + } + + int row = 2; + + while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + { + var segment = context.Data.CurrentSegment; + + foreach (dynamic customer in segment) + { + if (context.Abort != ExportAbortion.None) + break; + + context.ProcessRecord((int)customer.Id, () => + { + int column = 1; + DateTime? lastLoginDateUtc = customer.LastLoginDateUtc; + Customer entity = customer._Entity; + + WriteCell(worksheet, row, ref column, (int)customer.Id); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.CustomerNumber)); + WriteCell(worksheet, row, ref column, (Guid)customer.CustomerGuid); + WriteCell(worksheet, row, ref column, (string)customer.Email); + WriteCell(worksheet, row, ref column, (string)customer.Username); + WriteCell(worksheet, row, ref column, (string)customer.Password); + WriteCell(worksheet, row, ref column, (int)customer.PasswordFormatId); + WriteCell(worksheet, row, ref column, (string)customer.PasswordSalt); + WriteCell(worksheet, row, ref column, (string)customer.AdminComment); + WriteCell(worksheet, row, ref column, (bool)customer.IsTaxExempt); + WriteCell(worksheet, row, ref column, (int)customer.AffiliateId); + WriteCell(worksheet, row, ref column, (bool)customer.Active); + WriteCell(worksheet, row, ref column, (bool)customer.IsSystemAccount); + WriteCell(worksheet, row, ref column, (string)customer.SystemName); + WriteCell(worksheet, row, ref column, (string)customer.LastIpAddress); + WriteCell(worksheet, row, ref column, ((DateTime)customer.CreatedOnUtc).ToString(invariantCulture)); + WriteCell(worksheet, row, ref column, lastLoginDateUtc.HasValue ? lastLoginDateUtc.Value.ToString(invariantCulture) : null); + WriteCell(worksheet, row, ref column, ((DateTime)customer.LastActivityDateUtc).ToString(invariantCulture)); + WriteCell(worksheet, row, ref column, entity.IsGuest()); + WriteCell(worksheet, row, ref column, entity.IsRegistered()); + WriteCell(worksheet, row, ref column, entity.IsAdmin()); + WriteCell(worksheet, row, ref column, entity.IsForumModerator()); + + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.FirstName)); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.LastName)); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.Gender)); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.Company)); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.StreetAddress)); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.StreetAddress2)); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.ZipPostalCode)); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.City)); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.CountryId)); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.StateProvinceId)); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.Phone)); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.Fax)); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.VatNumber)); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.VatNumberStatusId)); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.TimeZoneId)); + WriteCell(worksheet, row, ref column, (bool)customer._HasNewsletterSubscription); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.AvatarPictureId)); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.ForumPostCount)); + WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.Signature)); + }); + + ++row; + } + } + + // save the new spreadsheet + xlPackage.Save(); + } + } + + public void ExecuteEnded(IExportExecuteContext context) + { + // nothing to do + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 64a465f89d..1627caa2b5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -90,6 +90,7 @@ public class ExportProfileTask : ITask private ILocalizedEntityService _localizedEntityService; private IUrlRecordService _urlRecordService; private IGenericAttributeService _genericAttributeService; + private IRepository _subscriptionRepository; private void InitDependencies(TaskExecutionContext context) { @@ -125,6 +126,7 @@ private void InitDependencies(TaskExecutionContext context) _localizedEntityService = context.Resolve(); _urlRecordService = context.Resolve(); _genericAttributeService = context.Resolve(); + _subscriptionRepository = context.Resolve>(); } #endregion @@ -772,8 +774,9 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, Customer customer) expando.CustomerGuid = customer.CustomerGuid; expando.Username = customer.Username; expando.Email = customer.Email; - //Password... we do not provide that data + expando.Password = customer.Password; // not so good but referenced in Excel export expando.PasswordFormatId = customer.PasswordFormatId; + expando.PasswordSalt = customer.PasswordSalt; expando.AdminComment = customer.AdminComment; expando.IsTaxExempt = customer.IsTaxExempt; expando.AffiliateId = customer.AffiliateId; @@ -789,11 +792,13 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, Customer customer) expando.BillingAddress = null; expando.ShippingAddress = null; expando.Addresses = null; + expando.CustomerRoles = null; expando.RewardPointsHistory = null; expando._RewardPointsBalance = 0; expando._GenericAttributes = null; + expando._HasNewsletterSubscription = false; return expando; } @@ -1615,6 +1620,7 @@ private List GetCustomers(ExportProfileTaskContext ctx, int pageIndex, .Expand(x => x.ShippingAddress) .Expand(x => x.Addresses.Select(y => y.Country)) .Expand(x => x.Addresses.Select(y => y.StateProvince)) + .Expand(x => x.CustomerRoles) .Where(x => !x.Deleted); if (ctx.EntityIdsSelected.Count > 0) @@ -2159,10 +2165,30 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Customer cu .Select(x => ToExpando(ctx, x)) .ToList(); + expando.CustomerRoles = customer.CustomerRoles + .Select(x => + { + dynamic exp = new ExpandoObject(); + exp._Entity = x; + exp.Id = x.Id; + exp.Name = x.Name; + exp.FreeShipping = x.FreeShipping; + exp.TaxExempt = x.TaxExempt; + exp.TaxDisplayType = x.TaxDisplayType; + exp.Active = x.Active; + exp.IsSystemRole = x.IsSystemRole; + exp.SystemName = x.SystemName; + + return exp; + }) + .ToList(); + expando._GenericAttributes = genericAttributes .Select(x => ToExpando(ctx, x)) .ToList(); + expando._HasNewsletterSubscription = ctx.NewsletterSubscriptions.Contains(customer.Email, StringComparer.CurrentCultureIgnoreCase); + result.Add(expando); return result; } @@ -2264,13 +2290,15 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) logHead.Append("Export profile:\t\t" + ctx.Profile.Name); logHead.AppendLine(ctx.Profile.Id == 0 ? " volatile" : " (Id {0})".FormatInvariant(ctx.Profile.Id)); + logHead.AppendLine("Export provider:\t{0} ({1})".FormatInvariant(ctx.Provider.Metadata.FriendlyName, ctx.Profile.ProviderSystemName)); + var plugin = ctx.Provider.Metadata.PluginDescriptor; logHead.Append("Plugin:\t\t\t\t"); logHead.AppendLine(plugin == null ? "".NaIfEmpty() : "{0} ({1}) v.{2}".FormatInvariant(plugin.FriendlyName, plugin.SystemName, plugin.Version.ToString())); - logHead.AppendLine("Export provider:\t{0} ({1})".FormatInvariant(ctx.Provider == null ? "".NaIfEmpty() : ctx.Provider.Metadata.FriendlyName, ctx.Profile.ProviderSystemName)); + logHead.AppendLine("Entity:\t\t\t\t" + ctx.Provider.Value.EntityType.ToString()); - var storeInfo = (ctx.Profile.PerStore ? "{0} (Id {1})".FormatInvariant(ctx.Store.Name, ctx.Store.Id) : "all stores"); + var storeInfo = (ctx.Profile.PerStore ? "{0} (Id {1})".FormatInvariant(ctx.Store.Name, ctx.Store.Id) : "All stores"); logHead.Append("Store:\t\t\t\t" + storeInfo); ctx.Log.Information(logHead.ToString()); @@ -2461,6 +2489,17 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) ctx.Countries = _countryService.GetAllCountries(true).ToDictionary(x => x.Id, x => x); } + if (ctx.Provider.Value.EntityType == ExportEntityType.Customer) + { + var subscriptionEmails = _subscriptionRepository.TableUntracked + .Where(x => x.Active) + .Select(x => x.Email) + .Distinct() + .ToList(); + + ctx.NewsletterSubscriptions = new HashSet(subscriptionEmails, StringComparer.OrdinalIgnoreCase); + } + var stores = Init(ctx); ctx.Export.Language = ToExpando(ctx, ctx.ContextLanguage); @@ -2541,6 +2580,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) try { + ctx.NewsletterSubscriptions.Clear(); ctx.ProductTemplates.Clear(); ctx.Countries.Clear(); ctx.Stores.Clear(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs index 97c0a5f926..4387869a0f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs @@ -57,6 +57,7 @@ public ExportProfileTaskContext( CategoryPathes = new Dictionary(); Countries = new Dictionary(); ProductTemplates = new Dictionary(); + NewsletterSubscriptions = new HashSet(); RecordsPerStore = new Dictionary(); EntityIdsLoaded = new List(); @@ -140,6 +141,7 @@ public string[] GetDeploymentFiles(ExportDeployment deployment) public Dictionary Languages { get; set; } public Dictionary Countries { get; set; } public Dictionary ProductTemplates { get; set; } + public HashSet NewsletterSubscriptions { get; set; } // data loaded once per page public ExportDataContextProduct DataContextProduct diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs index 4835855ca4..8191f0d76e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs @@ -792,6 +792,27 @@ public void WriteCustomer(dynamic customer, string node) _writer.Write("LastActivityDateUtc", ((DateTime)customer.LastActivityDateUtc).ToString(_culture)); _writer.Write("RewardPointsBalance", ((int)customer._RewardPointsBalance).ToString()); + if (customer.CustomerRoles != null) + { + _writer.WriteStartElement("CustomerRoles"); + foreach (dynamic role in customer.CustomerRoles) + { + int? taxDisplayType = role.TaxDisplayType; + + _writer.WriteStartElement("CustomerRole"); + _writer.Write("Id", ((int)role.Id).ToString()); + _writer.Write("Name", (string)role.Name); + _writer.Write("FreeShipping", ((bool)role.FreeShipping).ToString()); + _writer.Write("TaxExempt", ((bool)role.TaxExempt).ToString()); + _writer.Write("TaxDisplayType", taxDisplayType.HasValue ? taxDisplayType.Value.ToString() : ""); + _writer.Write("Active", ((bool)role.Active).ToString()); + _writer.Write("IsSystemRole", ((bool)role.IsSystemRole).ToString()); + _writer.Write("SystemName", (string)role.SystemName); + _writer.WriteEndElement(); // CustomerRole + } + _writer.WriteEndElement(); // CustomerRoles + } + WriteRewardPointsHistory(customer.RewardPointsHistory, "RewardPointsHistories"); WriteAddress(customer.BillingAddress, "BillingAddress"); WriteAddress(customer.ShippingAddress, "ShippingAddress"); diff --git a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs deleted file mode 100644 index 374f0d39e6..0000000000 --- a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs +++ /dev/null @@ -1,307 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using System.Text; -using System.Xml; -using OfficeOpenXml; -using OfficeOpenXml.Style; -using SmartStore.Core; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Logging; -using SmartStore.Services.Catalog; -using SmartStore.Services.Common; -using SmartStore.Services.Customers; -using SmartStore.Services.Messages; -using SmartStore.Services.Seo; - -namespace SmartStore.Services.ExportImport -{ - /// - /// Export manager - /// - public partial class ExportManager : IExportManager - { - #region Fields - - private readonly ICategoryService _categoryService; - private readonly IManufacturerService _manufacturerService; - private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; - - #endregion - - #region Ctor - - public ExportManager(ICategoryService categoryService, - IManufacturerService manufacturerService, - INewsLetterSubscriptionService newsLetterSubscriptionService) - { - this._categoryService = categoryService; - this._manufacturerService = manufacturerService; - this._newsLetterSubscriptionService = newsLetterSubscriptionService; - - Logger = NullLogger.Instance; - } - - public ILogger Logger { get; set; } - - #endregion - - #region Methods - - /// - /// Export customer list to XLSX - /// - /// Stream - /// Customers - public virtual void ExportCustomersToXlsx(Stream stream, IList customers) - { - if (stream == null) - throw new ArgumentNullException("stream"); - - // ok, we can run the real code of the sample now - using (var xlPackage = new ExcelPackage(stream)) - { - // uncomment this line if you want the XML written out to the outputDir - //xlPackage.DebugMode = true; - - // get handle to the existing worksheet - var worksheet = xlPackage.Workbook.Worksheets.Add("Customers"); - //Create Headers and format them - var properties = new string[] - { - "Id", - "CustomerNumber", - "CustomerGuid", - "Email", - "Username", - "PasswordStr",//why can't we use 'Password' name? - "PasswordFormatId", - "PasswordSalt", - "AdminComment", - "IsTaxExempt", - "AffiliateId", - "Active", - "IsSystemAccount", - "SystemName", - "LastIpAddress", - "CreatedOnUtc", - "LastLoginDateUtc", - "LastActivityDateUtc", - - "IsGuest", - "IsRegistered", - "IsAdministrator", - "IsForumModerator", - "FirstName", - "LastName", - "Gender", - "Company", - "StreetAddress", - "StreetAddress2", - "ZipPostalCode", - "City", - "CountryId", - "StateProvinceId", - "Phone", - "Fax", - "VatNumber", - "VatNumberStatusId", - "TimeZoneId", - "Newsletter", - "AvatarPictureId", - "ForumPostCount", - "Signature", - }; - - for (int i = 0; i < properties.Length; i++) - { - worksheet.Cells[1, i + 1].Value = properties[i]; - worksheet.Cells[1, i + 1].Style.Fill.PatternType = ExcelFillStyle.Solid; - worksheet.Cells[1, i + 1].Style.Fill.BackgroundColor.SetColor(Color.FromArgb(184, 204, 228)); - worksheet.Cells[1, i + 1].Style.Font.Bold = true; - } - - - int row = 2; - foreach (var customer in customers) - { - int col = 1; - - worksheet.Cells[row, col].Value = customer.Id; - col++; - - worksheet.Cells[row, col].Value = customer.GetAttribute(SystemCustomerAttributeNames.CustomerNumber); - col++; - - worksheet.Cells[row, col].Value = customer.CustomerGuid; - col++; - - worksheet.Cells[row, col].Value = customer.Email; - col++; - - worksheet.Cells[row, col].Value = customer.Username; - col++; - - worksheet.Cells[row, col].Value = customer.Password; - col++; - - worksheet.Cells[row, col].Value = customer.PasswordFormatId; - col++; - - worksheet.Cells[row, col].Value = customer.PasswordSalt; - col++; - - worksheet.Cells[row, col].Value = customer.AdminComment; - col++; - - worksheet.Cells[row, col].Value = customer.IsTaxExempt; - col++; - - worksheet.Cells[row, col].Value = customer.AffiliateId; - col++; - - worksheet.Cells[row, col].Value = customer.Active; - col++; - - worksheet.Cells[row, col].Value = customer.IsSystemAccount; - col++; - - worksheet.Cells[row, col].Value = customer.SystemName; - col++; - - worksheet.Cells[row, col].Value = customer.LastIpAddress; - col++; - - worksheet.Cells[row, col].Value = customer.CreatedOnUtc.ToString(); - col++; - - worksheet.Cells[row, col].Value = (customer.LastLoginDateUtc.HasValue ? customer.LastLoginDateUtc.Value.ToString() : null); - col++; - - worksheet.Cells[row, col].Value = customer.LastActivityDateUtc.ToString(); - col++; - - - //roles - worksheet.Cells[row, col].Value = customer.IsGuest(); - col++; - - worksheet.Cells[row, col].Value = customer.IsRegistered(); - col++; - - worksheet.Cells[row, col].Value = customer.IsAdmin(); - col++; - - worksheet.Cells[row, col].Value = customer.IsForumModerator(); - col++; - - //attributes - var firstName = customer.GetAttribute(SystemCustomerAttributeNames.FirstName); - var lastName = customer.GetAttribute(SystemCustomerAttributeNames.LastName); - var gender = customer.GetAttribute(SystemCustomerAttributeNames.Gender); - var company = customer.GetAttribute(SystemCustomerAttributeNames.Company); - var streetAddress = customer.GetAttribute(SystemCustomerAttributeNames.StreetAddress); - var streetAddress2 = customer.GetAttribute(SystemCustomerAttributeNames.StreetAddress2); - var zipPostalCode = customer.GetAttribute(SystemCustomerAttributeNames.ZipPostalCode); - var city = customer.GetAttribute(SystemCustomerAttributeNames.City); - var countryId = customer.GetAttribute(SystemCustomerAttributeNames.CountryId); - var stateProvinceId = customer.GetAttribute(SystemCustomerAttributeNames.StateProvinceId); - var phone = customer.GetAttribute(SystemCustomerAttributeNames.Phone); - var fax = customer.GetAttribute(SystemCustomerAttributeNames.Fax); - var vatNumber = customer.GetAttribute(SystemCustomerAttributeNames.VatNumber); - var vatNumberStatusId = customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); - var timeZoneId = customer.GetAttribute(SystemCustomerAttributeNames.TimeZoneId); - - var newsletter = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(customer.Email); - bool subscribedToNewsletters = newsletter != null && newsletter.Active; - - var avatarPictureId = customer.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId); - var forumPostCount = customer.GetAttribute(SystemCustomerAttributeNames.ForumPostCount); - var signature = customer.GetAttribute(SystemCustomerAttributeNames.Signature); - - worksheet.Cells[row, col].Value = firstName; - col++; - - worksheet.Cells[row, col].Value = lastName; - col++; - - worksheet.Cells[row, col].Value = gender; - col++; - - worksheet.Cells[row, col].Value = company; - col++; - - worksheet.Cells[row, col].Value = streetAddress; - col++; - - worksheet.Cells[row, col].Value = streetAddress2; - col++; - - worksheet.Cells[row, col].Value = zipPostalCode; - col++; - - worksheet.Cells[row, col].Value = city; - col++; - - worksheet.Cells[row, col].Value = countryId; - col++; - - worksheet.Cells[row, col].Value = stateProvinceId; - col++; - - worksheet.Cells[row, col].Value = phone; - col++; - - worksheet.Cells[row, col].Value = fax; - col++; - - worksheet.Cells[row, col].Value = vatNumber; - col++; - - worksheet.Cells[row, col].Value = vatNumberStatusId; - col++; - - worksheet.Cells[row, col].Value = timeZoneId; - col++; - - worksheet.Cells[row, col].Value = subscribedToNewsletters; - col++; - - worksheet.Cells[row, col].Value = avatarPictureId; - col++; - - worksheet.Cells[row, col].Value = forumPostCount; - col++; - - worksheet.Cells[row, col].Value = signature; - col++; - - row++; - } - - // we had better add some document properties to the spreadsheet - - // set some core property values - //var storeName = _storeInformationSettings.StoreName; - //var storeUrl = _storeInformationSettings.StoreUrl; - //xlPackage.Workbook.Properties.Title = string.Format("{0} customers", storeName); - //xlPackage.Workbook.Properties.Author = storeName; - //xlPackage.Workbook.Properties.Subject = string.Format("{0} customers", storeName); - //xlPackage.Workbook.Properties.Keywords = string.Format("{0} customers", storeName); - //xlPackage.Workbook.Properties.Category = "Customers"; - //xlPackage.Workbook.Properties.Comments = string.Format("{0} customers", storeName); - - // set some extended property values - //xlPackage.Workbook.Properties.Company = storeName; - //xlPackage.Workbook.Properties.HyperlinkBase = new Uri(storeUrl); - - // save the new spreadsheet - xlPackage.Save(); - } - } - - #endregion - } -} diff --git a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs deleted file mode 100644 index 21f0c155c3..0000000000 --- a/src/Libraries/SmartStore.Services/ExportImport/IExportManager.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using System.IO; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Core.Domain.Customers; - -namespace SmartStore.Services.ExportImport -{ - /// - /// Export manager interface - /// - public interface IExportManager - { - /// - /// Export customer list to XLSX - /// - /// Stream - /// Customers - void ExportCustomersToXlsx(Stream stream, IList customers); - } -} diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 3c79f86603..242be95a9b 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -172,6 +172,7 @@ + @@ -329,7 +330,6 @@ - @@ -349,7 +349,6 @@ - diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index c250e6858f..d9b7ca9b43 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -218,12 +218,6 @@ public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, b builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As() - .WithParameter(ResolvedParameter.ForNamed("nocache")) - .WithParameter(ResolvedParameter.ForNamed("nocache")) - .WithParameter(ResolvedParameter.ForNamed("nocache")) - .InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs index a27dbc1dac..a609ee76e8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Text; using System.Web.ModelBinding; @@ -27,7 +26,6 @@ using SmartStore.Services.Customers; using SmartStore.Services.DataExchange.ExportProvider; using SmartStore.Services.Directory; -using SmartStore.Services.ExportImport; using SmartStore.Services.Forums; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; @@ -68,7 +66,6 @@ public partial class CustomerController : AdminControllerBase private readonly IStoreContext _storeContext; private readonly IPriceFormatter _priceFormatter; private readonly IOrderService _orderService; - private readonly IExportManager _exportManager; private readonly ICustomerActivityService _customerActivityService; private readonly IPriceCalculationService _priceCalculationService; private readonly IPermissionService _permissionService; @@ -102,7 +99,6 @@ public CustomerController(ICustomerService customerService, IWorkContext workContext, IStoreContext storeContext, IPriceFormatter priceFormatter, IOrderService orderService, - IExportManager exportManager, ICustomerActivityService customerActivityService, IPriceCalculationService priceCalculationService, IPermissionService permissionService, AdminAreaSettings adminAreaSettings, @@ -133,7 +129,6 @@ public CustomerController(ICustomerService customerService, this._storeContext = storeContext; this._priceFormatter = priceFormatter; this._orderService = orderService; - this._exportManager = exportManager; this._customerActivityService = customerActivityService; this._priceCalculationService = priceCalculationService; this._permissionService = permissionService; @@ -1730,54 +1725,22 @@ public JsonResult ListActivityLog(GridCommand command, int customerId) #region Export / Import + [Compress] public ActionResult ExportExcelAll() { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); - try - { - var customers = _customerService.GetAllCustomers(null, null, null, null, - null, null, null, 0, 0, null, null, null, - false, null, 0, int.MaxValue); - - byte[] bytes = null; - using (var stream = new MemoryStream()) - { - _exportManager.ExportCustomersToXlsx(stream, customers); - bytes = stream.ToArray(); - } - return File(bytes, "text/xls", "customers.xlsx"); - } - catch (Exception exc) - { - NotifyError(exc); - return RedirectToAction("List"); - } + return Export(ExportCustomerXlsxProvider.SystemName, null); } + [Compress] public ActionResult ExportExcelSelected(string selectedIds) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); - var customers = new List(); - if (selectedIds != null) - { - var ids = selectedIds - .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => Convert.ToInt32(x)) - .ToArray(); - customers.AddRange(_customerService.GetCustomersByIds(ids)); - } - - byte[] bytes = null; - using (var stream = new MemoryStream()) - { - _exportManager.ExportCustomersToXlsx(stream, customers); - bytes = stream.ToArray(); - } - return File(bytes, "text/xls", "customers.xlsx"); + return Export(ExportCustomerXlsxProvider.SystemName, selectedIds); } [Compress] diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs index 2543de3845..840cecc2e4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs @@ -11,7 +11,6 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.DataExchange.ExportProvider; -using SmartStore.Services.ExportImport; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Media; @@ -41,7 +40,6 @@ public partial class ManufacturerController : AdminControllerBase private readonly ILanguageService _languageService; private readonly ILocalizationService _localizationService; private readonly ILocalizedEntityService _localizedEntityService; - private readonly IExportManager _exportManager; private readonly IWorkContext _workContext; private readonly ICustomerActivityService _customerActivityService; private readonly IPermissionService _permissionService; @@ -58,7 +56,7 @@ public ManufacturerController(ICategoryService categoryService, IManufacturerSer IStoreService storeService, IStoreMappingService storeMappingService, IUrlRecordService urlRecordService, IPictureService pictureService, ILanguageService languageService, ILocalizationService localizationService, ILocalizedEntityService localizedEntityService, - IExportManager exportManager, IWorkContext workContext, + IWorkContext workContext, ICustomerActivityService customerActivityService, IPermissionService permissionService, IDateTimeHelper dateTimeHelper, AdminAreaSettings adminAreaSettings, CatalogSettings catalogSettings) @@ -74,7 +72,6 @@ public ManufacturerController(ICategoryService categoryService, IManufacturerSer this._languageService = languageService; this._localizationService = localizationService; this._localizedEntityService = localizedEntityService; - this._exportManager = exportManager; this._workContext = workContext; this._customerActivityService = customerActivityService; this._permissionService = permissionService; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs index 7d602580e0..186f1905b3 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs @@ -22,7 +22,6 @@ using SmartStore.Services.Customers; using SmartStore.Services.DataExchange.ExportProvider; using SmartStore.Services.Directory; -using SmartStore.Services.ExportImport; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Media; @@ -63,7 +62,6 @@ public partial class OrderController : AdminControllerBase private readonly ICountryService _countryService; private readonly IStateProvinceService _stateProvinceService; private readonly IProductService _productService; - private readonly IExportManager _exportManager; private readonly IPermissionService _permissionService; private readonly IWorkflowMessageService _workflowMessageService; private readonly ICategoryService _categoryService; @@ -107,7 +105,7 @@ public OrderController(IOrderService orderService, IMeasureService measureService, IAddressService addressService, ICountryService countryService, IStateProvinceService stateProvinceService, IProductService productService, - IExportManager exportManager, IPermissionService permissionService, + IPermissionService permissionService, IWorkflowMessageService workflowMessageService, ICategoryService categoryService, IManufacturerService manufacturerService, IProductAttributeService productAttributeService, IProductAttributeParser productAttributeParser, @@ -140,7 +138,6 @@ public OrderController(IOrderService orderService, this._countryService = countryService; this._stateProvinceService = stateProvinceService; this._productService = productService; - this._exportManager = exportManager; this._permissionService = permissionService; this._workflowMessageService = workflowMessageService; this._categoryService = categoryService; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 5844ead483..871dfbf644 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -68,7 +68,6 @@ public partial class ProductController : AdminControllerBase private readonly ITaxCategoryService _taxCategoryService; private readonly IProductTagService _productTagService; private readonly ICopyProductService _copyProductService; - private readonly IExportManager _exportManager; private readonly ICustomerActivityService _customerActivityService; private readonly IPermissionService _permissionService; private readonly IAclService _aclService; @@ -119,7 +118,6 @@ public ProductController( ITaxCategoryService taxCategoryService, IProductTagService productTagService, ICopyProductService copyProductService, - IExportManager exportManager, IImportManager importManager, ICustomerActivityService customerActivityService, IPermissionService permissionService, @@ -166,7 +164,6 @@ public ProductController( this._taxCategoryService = taxCategoryService; this._productTagService = productTagService; this._copyProductService = copyProductService; - this._exportManager = exportManager; this._customerActivityService = customerActivityService; this._permissionService = permissionService; this._aclService = aclService; From 44acebb51b460c8588356f67295a98be94176915 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 8 Oct 2015 18:03:43 +0200 Subject: [PATCH 114/874] Convert newsletter subscriber CSV export to support new export framework --- .../Domain/DataExchange/ExportCore.cs | 2 +- .../ExportCustomerXmlProvider.cs | 3 +- .../ExportNewsSubscriptionCsvProvider.cs | 75 +++++++++++++++++++ .../ExportTask/ExportProfileTask.cs | 74 +++++++++++++++++- .../SmartStore.Services.csproj | 1 + .../NewsLetterSubscriptionController.cs | 72 ++++++------------ 6 files changed, 174 insertions(+), 53 deletions(-) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs index 047ee5022e..cc0936f415 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs @@ -11,7 +11,7 @@ public enum ExportEntityType Manufacturer, Customer, Order, - NewsletterSubscriber + NewsLetterSubscription } /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs index 8b3c256f5d..30f83c0f3e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs @@ -1,5 +1,4 @@ -using System; -using System.Globalization; +using System.Globalization; using System.IO; using System.Text; using System.Xml; diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs new file mode 100644 index 0000000000..da07c4faff --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs @@ -0,0 +1,75 @@ +using System.IO; +using System.Text; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.DataExchange.ExportProvider +{ + /// + /// Exports CSV formatted newsletter subscription data to a file + /// + [SystemName("Exports.SmartStoreNewsSubscriptionCsv")] + [FriendlyName("SmartStore CSV newsletter subscription export")] + [IsHidden(true)] + public class ExportNewsSubscriptionCsvProvider : IExportProvider + { + public static string SystemName + { + get { return "Exports.SmartStoreNewsSubscriptionCsv"; } + } + + public ExportConfigurationInfo ConfigurationInfo + { + get { return null; } + } + + public ExportEntityType EntityType + { + get { return ExportEntityType.NewsLetterSubscription; } + } + + public string FileExtension + { + get { return "CSV"; } + } + + public void Execute(IExportExecuteContext context) + { + var path = context.FilePath; + + context.Log.Information("Creating file " + path); + + using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + using (var writer = new StreamWriter(stream, Encoding.UTF8)) + { + while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + { + var segment = context.Data.CurrentSegment; + + foreach (dynamic subscriber in segment) + { + if (context.Abort != ExportAbortion.None) + break; + + context.ProcessRecord((int)subscriber.Id, () => + { + var row = "{0},{1},{2}".FormatInvariant( + ((string)subscriber.Email).ReplaceCsvChars(), + ((bool)subscriber.Active).ToString(), + (int)subscriber.StoreId + ); + + writer.WriteLine(row); + }); + } + } + } + } + + public void ExecuteEnded(IExportExecuteContext context) + { + // nothing to do + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 1627caa2b5..4461e91182 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -1436,6 +1436,24 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, GenericAttribute generic return expando; } + private dynamic ToExpando(ExportProfileTaskContext ctx, NewsLetterSubscription subscription) + { + if (subscription == null) + return null; + + dynamic expando = new ExpandoObject(); + expando._Entity = subscription; + + expando.Id = subscription.Id; + expando.NewsLetterSubscriptionGuid = subscription.NewsLetterSubscriptionGuid; + expando.Email = subscription.Email; + expando.Active = subscription.Active; + expando.CreatedOnUtc = subscription.CreatedOnUtc; + expando.StoreId = subscription.StoreId; + + return expando; + } + #endregion #region Segmenter callbacks @@ -1652,6 +1670,37 @@ private List GetCustomers(ExportProfileTaskContext ctx, int pageIndex, return result; } + private List GetNewsLetterSubscriptions(ExportProfileTaskContext ctx, int pageIndex, int pageSize, out int totalCount) + { + var storeId = (ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); + + var query = _subscriptionRepository.TableUntracked; + + if (storeId > 0) + { + query = query.Where(x => x.StoreId == storeId); + } + + var subscriptions = new PagedList(query.OrderBy(x => x.StoreId).ThenBy(x => x.Email), pageIndex, pageSize); + + totalCount = subscriptions.TotalCount; + + var result = subscriptions as List; + + if (pageSize > 1) + { + SetProgress(ctx, subscriptions.Count); + } + + try + { + _services.DbContext.DetachEntities(result); + } + catch { } + + return result; + } + private List ConvertToExpando(ExportProfileTaskContext ctx, Product product) { var result = new List(); @@ -2193,6 +2242,16 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Customer cu return result; } + private List ConvertToExpando(ExportProfileTaskContext ctx, NewsLetterSubscription subscription) + { + var result = new List(); + + dynamic expando = ToExpando(ctx, subscription); + + result.Add(expando); + return result; + } + #endregion private List Init(ExportProfileTaskContext ctx) @@ -2269,6 +2328,11 @@ private List Init(ExportProfileTaskContext ctx) var unused = GetCustomers(ctx, 0, 1, out totalCount); ctx.RecordsPerStore.Add(store.Id, totalCount); } + else if (ctx.Provider.Value.EntityType == ExportEntityType.NewsLetterSubscription) + { + var unused = GetNewsLetterSubscriptions(ctx, 0, 1, out totalCount); + ctx.RecordsPerStore.Add(store.Id, totalCount); + } } } @@ -2359,6 +2423,14 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) pageable, itemsPerFile ); } + else if (ctx.Provider.Value.EntityType == ExportEntityType.NewsLetterSubscription) + { + ctx.Export.Data = new ExportSegmenter( + pageIndex => GetNewsLetterSubscriptions(ctx, pageIndex, ctx.PageSize, out unused), + entity => ConvertToExpando(ctx, entity), + pageable, itemsPerFile + ); + } if (ctx.Export.Data == null) { @@ -2781,7 +2853,7 @@ public ExportExecuteResult Execute(string providerSystemName, prefix = _services.Localization.GetResource("Admin.Catalog.Manufacturers"); else if (provider.Value.EntityType == ExportEntityType.Customer) prefix = _services.Localization.GetResource("Admin.Customers"); - else if (provider.Value.EntityType == ExportEntityType.NewsletterSubscriber) + else if (provider.Value.EntityType == ExportEntityType.NewsLetterSubscription) prefix = _services.Localization.GetResource("Admin.Promotions.NewsLetterSubscriptions"); else prefix = provider.Value.EntityType.ToString(); diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 242be95a9b..708211c879 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -170,6 +170,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/NewsLetterSubscriptionController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/NewsLetterSubscriptionController.cs index 732995c8a8..8ba0f92043 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/NewsLetterSubscriptionController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/NewsLetterSubscriptionController.cs @@ -1,18 +1,17 @@ using System; using System.Linq; -using System.Text; using System.Web.Mvc; -using Telerik.Web.Mvc; using SmartStore.Admin.Models.Messages; -using SmartStore.Web.Framework; -using SmartStore.Web.Framework.Controllers; using SmartStore.Core.Domain.Common; +using SmartStore.Services.DataExchange.ExportProvider; using SmartStore.Services.Helpers; -using SmartStore.Services.Localization; using SmartStore.Services.Messages; using SmartStore.Services.Security; using SmartStore.Services.Stores; -using SmartStore.Utilities; +using SmartStore.Web.Framework; +using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Mvc; +using Telerik.Web.Mvc; namespace SmartStore.Admin.Controllers { @@ -21,19 +20,18 @@ public class NewsLetterSubscriptionController : AdminControllerBase { private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; private readonly IDateTimeHelper _dateTimeHelper; - private readonly ILocalizationService _localizationService; private readonly IPermissionService _permissionService; private readonly AdminAreaSettings _adminAreaSettings; private readonly IStoreService _storeService; public NewsLetterSubscriptionController(INewsLetterSubscriptionService newsLetterSubscriptionService, - IDateTimeHelper dateTimeHelper,ILocalizationService localizationService, - IPermissionService permissionService, AdminAreaSettings adminAreaSettings, + IDateTimeHelper dateTimeHelper, + IPermissionService permissionService, + AdminAreaSettings adminAreaSettings, IStoreService storeService) { this._newsLetterSubscriptionService = newsLetterSubscriptionService; this._dateTimeHelper = dateTimeHelper; - this._localizationService = localizationService; this._permissionService = permissionService; this._adminAreaSettings = adminAreaSettings; this._storeService = storeService; @@ -45,9 +43,8 @@ private void PrepareNewsLetterSubscriptionListModel(NewsLetterSubscriptionListMo model.GridPageSize = _adminAreaSettings.GridPageSize; - model.AvailableStores.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + model.AvailableStores.Add(new SelectListItem { Text = T("Admin.Common.All"), Value = "0" }); model.AvailableStores.AddRange(stores.ToSelectListItems()); - } public ActionResult Index() @@ -153,34 +150,13 @@ public ActionResult SubscriptionDelete(int id, GridCommand command) return SubscriptionList(command, listModel); } - public ActionResult ExportCsv(NewsLetterSubscriptionListModel model) + [Compress] + public ActionResult ExportCsv() { if (!_permissionService.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers)) return AccessDeniedView(); - string fileName = String.Format("newsletter_emails_{0}_{1}.txt", DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss"), CommonHelper.GenerateRandomDigitCode(4)); - - var sb = new StringBuilder(); - var newsLetterSubscriptions = _newsLetterSubscriptionService.GetAllNewsLetterSubscriptions(model.SearchEmail, 0, int.MaxValue, true, model.StoreId); - - if (newsLetterSubscriptions.Count == 0) - { - NotifyInfo(_localizationService.GetResource("Admin.Common.ExportNoData")); - return RedirectToAction("List"); - } - - foreach (var subscription in newsLetterSubscriptions) - { - sb.Append(subscription.Email); - sb.Append(","); - sb.Append(subscription.Active); - sb.Append(","); - sb.Append(subscription.StoreId); - sb.Append(Environment.NewLine); - } - string result = sb.ToString(); - - return File(Encoding.UTF8.GetBytes(result), "text/csv", fileName); + return Export(ExportNewsSubscriptionCsvProvider.SystemName, null); } [HttpPost] @@ -192,25 +168,23 @@ public ActionResult ImportCsv(FormCollection form) try { var file = Request.Files["importcsvfile"]; - if (file != null && file.ContentLength > 0) - { - var result = _newsLetterSubscriptionService.ImportSubscribers(file.InputStream); - - NotifySuccess( - _localizationService.GetResource("Admin.Promotions.NewsLetterSubscriptions.ImportEmailsSuccess").FormatWith(result.NewRecords, result.ModifiedRecords) - ); - - return RedirectToAction("List"); - } + if (file != null && file.ContentLength > 0) + { + var result = _newsLetterSubscriptionService.ImportSubscribers(file.InputStream); - NotifyError(_localizationService.GetResource("Admin.Common.UploadFile")); - return RedirectToAction("List"); + NotifySuccess(T("Admin.Promotions.NewsLetterSubscriptions.ImportEmailsSuccess", result.NewRecords, result.ModifiedRecords)); + } + else + { + NotifyError(T("Admin.Common.UploadFile")); + } } catch (Exception exc) { NotifyError(exc); - return RedirectToAction("List"); } + + return RedirectToAction("List"); } } } From fc47d7c4ff83c38896ec2b4cba49c365c7911bb7 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 9 Oct 2015 01:03:39 +0200 Subject: [PATCH 115/874] Perf: AttributeCombinations (initial) --- src/Libraries/SmartStore.Core/PagedList`T.cs | 20 +++- .../SmartStore.Data/ObjectContextBase.cs | 5 +- .../Catalog/CopyProductService.cs | 2 +- .../Catalog/IProductAttributeParser.cs | 31 ++++-- .../Catalog/IProductAttributeService.cs | 27 ++++- .../Catalog/ProductAttributeParser.cs | 58 +++++++--- .../Catalog/ProductAttributeService.cs | 93 +++++++++++----- .../Catalog/ProductExtensions.cs | 33 +++--- .../Catalog/ProductService.cs | 17 +-- .../ExportImport/ExportManager.cs | 91 +++++++++------- .../Orders/IShoppingCartService.cs | 23 ++-- .../Orders/ShoppingCartService.cs | 47 ++++---- .../Controllers/OData/ProductsController.cs | 11 +- .../SmartStore.WebApi.csproj | 10 ++ src/Plugins/SmartStore.WebApi/packages.config | 1 + .../Controllers/ActivityLogController.cs | 5 +- .../Controllers/ProductController.cs | 45 ++++++-- .../Administration/SmartStore.Admin.csproj | 9 ++ .../Product/_CreateOrUpdate.Attributes.cshtml | 103 +++++++++--------- .../Administration/packages.config | 1 + .../Controllers/CatalogHelper.cs | 55 ++++++---- .../Controllers/ProductController.cs | 18 +-- .../Extensions/ProductDetailsExtensions.cs | 4 +- .../Models/Catalog/ProductDetailsModel.cs | 4 +- src/Presentation/SmartStore.Web/Web.config | 1 + ...ProductAttributeParserAndFormatterTests.cs | 4 +- .../ExportImport/ExportManagerTests.cs | 16 ++- 27 files changed, 464 insertions(+), 270 deletions(-) diff --git a/src/Libraries/SmartStore.Core/PagedList`T.cs b/src/Libraries/SmartStore.Core/PagedList`T.cs index 5f4cb4dc04..af5039d810 100644 --- a/src/Libraries/SmartStore.Core/PagedList`T.cs +++ b/src/Libraries/SmartStore.Core/PagedList`T.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Data.Entity; +using System.Data.Entity.Infrastructure; namespace SmartStore.Core { @@ -20,10 +22,24 @@ public PagedList(IQueryable source, int pageIndex, int pageSize) { Guard.ArgumentNotNull(source, "source"); - if (pageIndex == 0 && pageSize == int.MaxValue) // avoid unnecessary SQL + if (pageIndex == 0 && pageSize == int.MaxValue) + { + // avoid unnecessary SQL Init(source, pageIndex, pageSize, source.Count()); + } else - Init(source.Skip(pageIndex * pageSize).Take(pageSize), pageIndex, pageSize, source.Count()); + { + if (source.Provider is IDbAsyncQueryProvider) + { + // the Lambda overloads for Skip() and Take() let EF use cached query plans, thus slightly increasing performance. + var skip = pageIndex * pageSize; + Init(source.Skip(() => skip).Take(() => pageSize), pageIndex, pageSize, source.Count()); + } + else + { + Init(source.Skip(pageIndex * pageSize).Take(pageSize), pageIndex, pageSize, source.Count()); + } + } } /// diff --git a/src/Libraries/SmartStore.Data/ObjectContextBase.cs b/src/Libraries/SmartStore.Data/ObjectContextBase.cs index fdab31aaeb..da5cbe2425 100644 --- a/src/Libraries/SmartStore.Data/ObjectContextBase.cs +++ b/src/Libraries/SmartStore.Data/ObjectContextBase.cs @@ -7,19 +7,16 @@ using System.Data.Entity.Infrastructure; using System.Data.Entity.Validation; using System.Linq; +using System.Linq.Expressions; using System.Text; using System.Text.RegularExpressions; -using System.Threading; using System.Threading.Tasks; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Data.Hooks; -using Microsoft.SqlServer; using Microsoft.SqlServer.Management.Common; using Microsoft.SqlServer.Management.Smo; -using SmartStore.Core.Infrastructure; using SmartStore.Core.Events; -using System.Data.Entity.Core.Objects; namespace SmartStore.Data { diff --git a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs index 7e85a11c12..7028306416 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs @@ -411,7 +411,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli } // attribute combinations - foreach (var combination in _productAttributeService.GetAllProductVariantAttributeCombinations(product.Id)) + foreach (var combination in product.ProductVariantAttributeCombinations) { //generate new AttributesXml according to new value IDs string newAttributesXml = ""; diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs index 4ac7e9b945..b1da4c5396 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs @@ -58,21 +58,14 @@ public partial interface IProductAttributeParser /// Result bool AreProductAttributesEqual(string attributeXml1, string attributeXml2, IEnumerable attributes = null); - /// - /// Finds a product variant attribute combination by attributes stored in XML - /// - /// Product - /// XML formatted attributes - /// Found product variant attribute combination - ProductVariantAttributeCombination FindProductVariantAttributeCombination(Product product, string attributesXml); - /// /// Finds a product variant attribute combination by attributes stored in XML /// /// Product identifier /// XML formatted attributes + /// Collection of already loaded product attribute mappings to reduce database round trips /// Found product variant attribute combination - ProductVariantAttributeCombination FindProductVariantAttributeCombination(int productId, string attributesXml); + ProductVariantAttributeCombination FindProductVariantAttributeCombination(int productId, string attributesXml, IEnumerable attributes = null); /// /// Deserializes attribute data from an URL query string @@ -122,4 +115,24 @@ void GetGiftCardAttribute(string attributesXml, out string recipientName, #endregion } + + public static class IProductAttributeParserExtensions + { + /// + /// Finds a product variant attribute combination by attributes stored in XML + /// + /// Product + /// XML formatted attributes + /// Found product variant attribute combination + public static ProductVariantAttributeCombination FindProductVariantAttributeCombination( + this IProductAttributeParser service, + Product product, + string attributesXml, + IEnumerable attributes = null) + { + Guard.ArgumentNotNull(() => product); + + return service.FindProductVariantAttributeCombination(product.Id, attributesXml, attributes); + } + } } diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs index 36874a566a..dadef55189 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using SmartStore.Collections; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core; namespace SmartStore.Services.Catalog { @@ -147,12 +148,15 @@ public partial interface IProductAttributeService /// Product variant attribute combination void DeleteProductVariantAttributeCombination(ProductVariantAttributeCombination combination); - /// - /// Gets all product variant attribute combinations - /// + /// + /// Gets all product variant attribute combinations + /// /// Product identifier - /// Product variant attribute combination collection - IList GetAllProductVariantAttributeCombinations(int productId); + /// Page index + /// Page size + /// Specifies whether loaded entities should be tracked by the state manager + /// Product variant attribute combination collection + IPagedList GetAllProductVariantAttributeCombinations(int productId, int pageIndex, int pageSize, bool untracked = true); /// /// Gets product variant attribute combinations by multiple product identifiers @@ -247,4 +251,17 @@ public partial interface IProductAttributeService #endregion } + + public static class IProductAttributeServiceExtensions + { + /// + /// Gets all product variant attribute combinations + /// + /// Product identifier + /// Product variant attribute combination collection + public static IList GetAllProductVariantAttributeCombinations(this IProductAttributeService service, int productId) + { + return service.GetAllProductVariantAttributeCombinations(productId, 0, int.MaxValue, true); + } + } } diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs index 3d454b3822..c576ca7aed 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs @@ -7,6 +7,9 @@ using Newtonsoft.Json; using SmartStore.Collections; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Data; +using SmartStore.Core.Caching; +using System.Text; namespace SmartStore.Services.Catalog { @@ -15,11 +18,21 @@ namespace SmartStore.Services.Catalog /// public partial class ProductAttributeParser : IProductAttributeParser { - private readonly IProductAttributeService _productAttributeService; + // 0 = ProductId, 1 = AttributeXml Hash + private const string ATTRIBUTECOMBINATION_BY_ID_HASH = "SmartStore.parsedattributecombination.id-{0}-{1}"; - public ProductAttributeParser(IProductAttributeService productAttributeService) + private readonly IProductAttributeService _productAttributeService; + private readonly IRepository _pvacRepository; + private readonly ICacheManager _cacheManager; + + public ProductAttributeParser( + IProductAttributeService productAttributeService, + IRepository pvacRepository, + ICacheManager cacheManager) { - this._productAttributeService = productAttributeService; + _productAttributeService = productAttributeService; + _pvacRepository = pvacRepository; + _cacheManager = cacheManager; } #region Product attributes @@ -244,30 +257,41 @@ public virtual bool AreProductAttributesEqual(string attributeXml1, string attri return true; } - public virtual ProductVariantAttributeCombination FindProductVariantAttributeCombination(Product product, string attributesXml) - { - if (product == null) - throw new ArgumentNullException("product"); + public virtual ProductVariantAttributeCombination FindProductVariantAttributeCombination( + int productId, + string attributesXml, + IEnumerable attributes = null) + { + if (attributesXml.IsEmpty()) + return null; - return FindProductVariantAttributeCombination(product.Id, attributesXml); - } + var cacheKey = ATTRIBUTECOMBINATION_BY_ID_HASH.FormatInvariant(productId, attributesXml.Hash(Encoding.UTF8)); - public virtual ProductVariantAttributeCombination FindProductVariantAttributeCombination(int productId, string attributesXml) - { - if (attributesXml.HasValue()) + var result = _cacheManager.Get(cacheKey, () => { - var combinations = _productAttributeService.GetAllProductVariantAttributeCombinations(productId); + var query = from x in _pvacRepository.TableUntracked + where x.ProductId == productId + select new + { + x.Id, + x.AttributesXml + }; + + var combinations = query.ToList(); if (combinations.Count == 0) return null; foreach (var combination in combinations) { - bool attributesEqual = AreProductAttributesEqual(combination.AttributesXml, attributesXml); + bool attributesEqual = AreProductAttributesEqual(combination.AttributesXml, attributesXml, attributes); if (attributesEqual) - return combination; + return _productAttributeService.GetProductVariantAttributeCombinationById(combination.Id); } - } - return null; + + return null; + }); + + return result; } /// diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs index 47dafa0dfc..282b834630 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs @@ -7,28 +7,28 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Events; using SmartStore.Services.Media; +using SmartStore.Core; namespace SmartStore.Services.Catalog { public partial class ProductAttributeService : IProductAttributeService { - #region Constants private const string PRODUCTATTRIBUTES_ALL_KEY = "SmartStore.productattribute.all"; - private const string PRODUCTVARIANTATTRIBUTES_ALL_KEY = "SmartStore.productvariantattribute.all-{0}"; - private const string PRODUCTVARIANTATTRIBUTEVALUES_ALL_KEY = "SmartStore.productvariantattributevalue.all-{0}"; - private const string PRODUCTATTRIBUTES_PATTERN_KEY = "SmartStore.productattribute."; - private const string PRODUCTVARIANTATTRIBUTES_PATTERN_KEY = "SmartStore.productvariantattribute."; - private const string PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY = "SmartStore.productvariantattributevalue."; - private const string PRODUCTATTRIBUTES_BY_ID_KEY = "SmartStore.productattribute.id-{0}"; - private const string PRODUCTVARIANTATTRIBUTES_BY_ID_KEY = "SmartStore.productvariantattribute.id-{0}"; - private const string PRODUCTVARIANTATTRIBUTEVALUES_BY_ID_KEY = "SmartStore.productvariantattributevalue.id-{0}"; + private const string PRODUCTATTRIBUTES_BY_ID_KEY = "SmartStore.productattribute.id-{0}"; + private const string PRODUCTATTRIBUTES_PATTERN_KEY = "SmartStore.productattribute."; - #endregion + private const string PRODUCTVARIANTATTRIBUTES_ALL_KEY = "SmartStore.productvariantattribute.all-{0}"; + private const string PRODUCTVARIANTATTRIBUTES_BY_ID_KEY = "SmartStore.productvariantattribute.id-{0}"; + // 0 = ProductId, 1 = PageIndex, 2 = PageSize + private const string PRODUCTVARIANTATTRIBUTES_COMBINATIONS_BY_ID_KEY = "SmartStore.productvariantattribute.combinations.id-{0}-{1}-{2}"; + private const string PRODUCTVARIANTATTRIBUTES_PATTERN_KEY = "SmartStore.productvariantattribute."; - #region Fields + private const string PRODUCTVARIANTATTRIBUTEVALUES_ALL_KEY = "SmartStore.productvariantattributevalue.all-{0}"; + private const string PRODUCTVARIANTATTRIBUTEVALUES_BY_ID_KEY = "SmartStore.productvariantattributevalue.id-{0}"; + private const string PRODUCTVARIANTATTRIBUTEVALUES_PATTERN_KEY = "SmartStore.productvariantattributevalue."; - private readonly IRepository _productAttributeRepository; + private readonly IRepository _productAttributeRepository; private readonly IRepository _productVariantAttributeRepository; private readonly IRepository _productVariantAttributeCombinationRepository; private readonly IRepository _productVariantAttributeValueRepository; @@ -37,9 +37,6 @@ public partial class ProductAttributeService : IProductAttributeService private readonly ICacheManager _cacheManager; private readonly IPictureService _pictureService; - #endregion - - #region Ctor /// /// Ctor @@ -69,8 +66,6 @@ public ProductAttributeService(ICacheManager cacheManager, _pictureService = pictureService; } - #endregion - #region Utilities private IList GetSwitchedLoadedAttributeMappings(ICollection productVariantAttributeIds) @@ -416,19 +411,59 @@ public virtual void DeleteProductVariantAttributeCombination(ProductVariantAttri _eventPublisher.EntityDeleted(combination); } - public virtual IList GetAllProductVariantAttributeCombinations(int productId) - { + public virtual IPagedList GetAllProductVariantAttributeCombinations( + int productId, + int pageIndex, + int pageSize, + bool untracked = true) + { if (productId == 0) - return new List(); + { + return new PagedList(new List(), pageIndex, pageSize); + } - var query = from pvac in _productVariantAttributeCombinationRepository.Table - orderby pvac.Id - where pvac.ProductId == productId - select pvac; + string key = string.Format(PRODUCTVARIANTATTRIBUTES_COMBINATIONS_BY_ID_KEY, productId, 0, int.MaxValue); + return _cacheManager.Get(key, () => + { + var query = from pvac in (untracked ? _productVariantAttributeCombinationRepository.TableUntracked : _productVariantAttributeCombinationRepository.Table) + orderby pvac.Id + where pvac.ProductId == productId + select pvac; + + var combinations = new PagedList(query, pageIndex, pageSize); + return combinations; + }); + } - var combinations = query.ToList(); - return combinations; - } + //public virtual IList GetAllProductVariantAttributeCombinationPictureIds(int productId) + //{ + // var imageIds = new List(); + + // if (productId == 0) + // return imageIds; + + // var query = from pvac in _productVariantAttributeCombinationRepository.TableUntracked + // where + // pvac.ProductId == productId + // && pvac.IsActive + // && !String.IsNullOrEmpty(pvac.AssignedPictureIds) + // select pvac.AssignedPictureIds; + + // var data = query.ToList(); + // if (data.Any()) + // { + // int id; + // var ids = string.Join(",", data).SplitSafe(",").Distinct(); + + // foreach (string str in ids) + // { + // if (int.TryParse(str, out id) && !imageIds.Exists(i => i == id)) + // imageIds.Add(id); + // } + // } + + // return imageIds; + //} public virtual Multimap GetProductVariantAttributeCombinations(int[] productIds) { @@ -534,7 +569,7 @@ public virtual void UpdateProductVariantAttributeCombination(ProductVariantAttri public virtual void CreateAllProductVariantAttributeCombinations(Product product) { // delete all existing combinations - foreach(var itm in GetAllProductVariantAttributeCombinations(product.Id)) + foreach(var itm in product.ProductVariantAttributeCombinations) { DeleteProductVariantAttributeCombination(itm); } @@ -566,7 +601,7 @@ public virtual void CreateAllProductVariantAttributeCombinations(Product product attrXml = attributes[values.IndexOf(x)].AddProductAttribute(attrXml, x.Id.ToString()); } - var combination = new ProductVariantAttributeCombination() + var combination = new ProductVariantAttributeCombination { ProductId = product.Id, AttributesXml = attrXml, diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs index 5516606fc4..ec2b7fda41 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs @@ -30,12 +30,9 @@ public static ProductVariantAttributeCombination MergeWithCombination(this Produ return null; // let's find appropriate record - var combination = product - .ProductVariantAttributeCombinations - .Where(x => x.IsActive == true) - .FirstOrDefault(x => productAttributeParser.AreProductAttributesEqual(x.AttributesXml, selectedAttributes)); + var combination = productAttributeParser.FindProductVariantAttributeCombination(product.Id, selectedAttributes); - if (combination != null) + if (combination != null && combination.IsActive) { product.MergeWithCombination(combination); } @@ -91,9 +88,9 @@ public static void MergeWithCombination(this Product product, ProductVariantAttr product.MergedDataValues.Add("BasePriceBaseAmount", combination.BasePriceBaseAmount); } - public static void GetAllCombinationImageIds(this IList combinations, List imageIds) + public static IList GetAllCombinationPictureIds(this IEnumerable combinations) { - Guard.ArgumentNotNull(imageIds, "imageIds"); + var pictureIds = new List(); if (combinations != null) { @@ -109,21 +106,23 @@ public static void GetAllCombinationImageIds(this IList i == id)) - imageIds.Add(id); + if (int.TryParse(str, out id) && !pictureIds.Exists(i => i == id)) + pictureIds.Add(id); } } } + + return pictureIds; } - /// - /// Finds a related product item by specified identifiers - /// - /// Source - /// The first product identifier - /// The second product identifier - /// Related product - public static RelatedProduct FindRelatedProduct(this IList source, + /// + /// Finds a related product item by specified identifiers + /// + /// Source + /// The first product identifier + /// The second product identifier + /// Related product + public static RelatedProduct FindRelatedProduct(this IList source, int productId1, int productId2) { foreach (RelatedProduct relatedProduct in source) diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs index 953334a43c..e347939214 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs @@ -1015,24 +1015,25 @@ public virtual void UpdateProductReviewTotals(Product product) /// Result public virtual IList GetLowStockProducts() { - //Track inventory for product + // Track inventory for product var query1 = from p in _productRepository.Table orderby p.MinStockQuantity where !p.Deleted && - p.ManageInventoryMethodId == (int)ManageInventoryMethod.ManageStock && - p.MinStockQuantity >= p.StockQuantity + p.ManageInventoryMethodId == (int)ManageInventoryMethod.ManageStock && + p.MinStockQuantity >= p.StockQuantity select p; var products1 = query1.ToList(); - //Track inventory for product by product attributes + // Track inventory for product by product attributes var query2 = from p in _productRepository.Table from pvac in p.ProductVariantAttributeCombinations where !p.Deleted && - p.ManageInventoryMethodId == (int)ManageInventoryMethod.ManageStockByAttributes && - pvac.StockQuantity <= 0 + p.ManageInventoryMethodId == (int)ManageInventoryMethod.ManageStockByAttributes && + pvac.StockQuantity <= 0 select p; - //only distinct products (group by ID) - //if we use standard Distinct() method, then all fields will be compared (low performance) + + // only distinct products (group by ID) + // if we use standard Distinct() method, then all fields will be compared (low performance) query2 = from p in query2 group p by p.Id into pGroup orderby pGroup.Key diff --git a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs index 430a71ebad..4c48efc481 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs @@ -25,6 +25,7 @@ using SmartStore.Services.Messages; using SmartStore.Services.Seo; using SmartStore.Services.Stores; +using SmartStore.Core.Data; namespace SmartStore.Services.ExportImport { @@ -38,6 +39,7 @@ public partial class ExportManager : IExportManager private readonly ICategoryService _categoryService; private readonly IManufacturerService _manufacturerService; private readonly IProductService _productService; + private readonly IProductAttributeService _productAttributeService; private readonly IProductTemplateService _productTemplateService; private readonly IPictureService _pictureService; private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; @@ -53,7 +55,8 @@ public partial class ExportManager : IExportManager public ExportManager(ICategoryService categoryService, IManufacturerService manufacturerService, IProductService productService, - IProductTemplateService productTemplateService, + IProductAttributeService productAttributeService, + IProductTemplateService productTemplateService, IPictureService pictureService, INewsLetterSubscriptionService newsLetterSubscriptionService, ILanguageService languageService, @@ -61,16 +64,17 @@ public ExportManager(ICategoryService categoryService, ICommonServices services, IStoreMappingService storeMappingService) { - this._categoryService = categoryService; - this._manufacturerService = manufacturerService; - this._productService = productService; - this._productTemplateService = productTemplateService; - this._pictureService = pictureService; - this._newsLetterSubscriptionService = newsLetterSubscriptionService; - this._languageService = languageService; - this._mediaSettings = mediaSettings; - this._services = services; - this._storeMappingService = storeMappingService; + _categoryService = categoryService; + _manufacturerService = manufacturerService; + _productService = productService; + _productAttributeService = productAttributeService; + _productTemplateService = productTemplateService; + _pictureService = pictureService; + _newsLetterSubscriptionService = newsLetterSubscriptionService; + _languageService = languageService; + _mediaSettings = mediaSettings; + _services = services; + _storeMappingService = storeMappingService; Logger = NullLogger.Instance; } @@ -504,41 +508,46 @@ public virtual void WriteProductToXml(XmlWriter writer, Product product, XmlExpo writer.WriteEndElement(); // ProductAttribute } - writer.WriteEndElement(); // ProductAttributes + writer.WriteEndElement(); // ProductAttributes - writer.WriteStartElement("ProductAttributeCombinations"); - foreach (var combination in product.ProductVariantAttributeCombinations) + using (var scope = new DbContextScope(proxyCreation: false, forceNoTracking: true)) { - writer.WriteStartElement("ProductAttributeCombination"); - - writer.Write("Id", combination.Id.ToString()); - writer.Write("StockQuantity", combination.StockQuantity.ToString()); - writer.Write("AllowOutOfStockOrders", combination.AllowOutOfStockOrders.ToString()); - writer.Write("AttributesXml", combination.AttributesXml, null, true); - writer.Write("Sku", combination.Sku); - writer.Write("Gtin", combination.Gtin); - writer.Write("ManufacturerPartNumber", combination.ManufacturerPartNumber); - writer.Write("Price", combination.Price.HasValue ? combination.Price.Value.ToString(culture) : ""); - writer.Write("Length", combination.Length.HasValue ? combination.Length.Value.ToString(culture) : ""); - writer.Write("Width", combination.Width.HasValue ? combination.Width.Value.ToString(culture) : ""); - writer.Write("Height", combination.Height.HasValue ? combination.Height.Value.ToString(culture) : ""); - writer.Write("BasePriceAmount", combination.BasePriceAmount.HasValue ? combination.BasePriceAmount.Value.ToString(culture) : ""); - writer.Write("BasePriceBaseAmount", combination.BasePriceBaseAmount.HasValue ? combination.BasePriceBaseAmount.Value.ToString() : ""); - writer.Write("DeliveryTimeId", combination.DeliveryTimeId.HasValue ? combination.DeliveryTimeId.Value.ToString() : ""); - writer.Write("IsActive", combination.IsActive.ToString()); - - WriteQuantityUnit(writer, context, combination.QuantityUnit); - - writer.WriteStartElement("Pictures"); - foreach (int pictureId in combination.GetAssignedPictureIds()) + var allCombinations = product.ProductVariantAttributeCombinations; + + writer.WriteStartElement("ProductAttributeCombinations"); + foreach (var combination in allCombinations) { - WritePicture(writer, context, _pictureService.GetPictureById(pictureId), _mediaSettings.ProductThumbPictureSize, _mediaSettings.ProductDetailsPictureSize); - } - writer.WriteEndElement(); // Pictures + writer.WriteStartElement("ProductAttributeCombination"); + + writer.Write("Id", combination.Id.ToString()); + writer.Write("StockQuantity", combination.StockQuantity.ToString()); + writer.Write("AllowOutOfStockOrders", combination.AllowOutOfStockOrders.ToString()); + writer.Write("AttributesXml", combination.AttributesXml, null, true); + writer.Write("Sku", combination.Sku); + writer.Write("Gtin", combination.Gtin); + writer.Write("ManufacturerPartNumber", combination.ManufacturerPartNumber); + writer.Write("Price", combination.Price.HasValue ? combination.Price.Value.ToString(culture) : ""); + writer.Write("Length", combination.Length.HasValue ? combination.Length.Value.ToString(culture) : ""); + writer.Write("Width", combination.Width.HasValue ? combination.Width.Value.ToString(culture) : ""); + writer.Write("Height", combination.Height.HasValue ? combination.Height.Value.ToString(culture) : ""); + writer.Write("BasePriceAmount", combination.BasePriceAmount.HasValue ? combination.BasePriceAmount.Value.ToString(culture) : ""); + writer.Write("BasePriceBaseAmount", combination.BasePriceBaseAmount.HasValue ? combination.BasePriceBaseAmount.Value.ToString() : ""); + writer.Write("DeliveryTimeId", combination.DeliveryTimeId.HasValue ? combination.DeliveryTimeId.Value.ToString() : ""); + writer.Write("IsActive", combination.IsActive.ToString()); + + WriteQuantityUnit(writer, context, combination.QuantityUnit); + + writer.WriteStartElement("Pictures"); + foreach (int pictureId in combination.GetAssignedPictureIds()) + { + WritePicture(writer, context, _pictureService.GetPictureById(pictureId), _mediaSettings.ProductThumbPictureSize, _mediaSettings.ProductDetailsPictureSize); + } + writer.WriteEndElement(); // Pictures - writer.WriteEndElement(); // ProductAttributeCombination + writer.WriteEndElement(); // ProductAttributeCombination + } + writer.WriteEndElement(); // ProductAttributeCombinations } - writer.WriteEndElement(); // ProductAttributeCombinations writer.WriteStartElement("ProductPictures"); foreach (var productPicture in product.ProductPictures.OrderBy(x => x.DisplayOrder)) diff --git a/src/Libraries/SmartStore.Services/Orders/IShoppingCartService.cs b/src/Libraries/SmartStore.Services/Orders/IShoppingCartService.cs index 517811cc78..d8479eb218 100644 --- a/src/Libraries/SmartStore.Services/Orders/IShoppingCartService.cs +++ b/src/Libraries/SmartStore.Services/Orders/IShoppingCartService.cs @@ -58,18 +58,25 @@ IList GetStandardWarnings(Customer customer, ShoppingCartType shoppingCa Product product, string selectedAttributes, decimal customerEnteredPrice, int quantity); - /// - /// Validates shopping cart item attributes - /// + /// + /// Validates shopping cart item attributes + /// /// The customer - /// Shopping cart type + /// Shopping cart type /// Product - /// Selected attributes + /// Selected attributes + /// The product variant attribute combination instance (reduces database roundtrips) /// Quantity /// Product bundle item - /// Warnings - IList GetShoppingCartItemAttributeWarnings(Customer customer, ShoppingCartType shoppingCartType, - Product product, string selectedAttributes, int quantity = 1, ProductBundleItem bundleItem = null); + /// Warnings + IList GetShoppingCartItemAttributeWarnings( + Customer customer, + ShoppingCartType shoppingCartType, + Product product, + string selectedAttributes, + int quantity = 1, + ProductBundleItem bundleItem = null, + ProductVariantAttributeCombination combination = null); /// /// Validates shopping cart item (gift card) diff --git a/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs b/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs index ca699df3d0..b72ff7d9fa 100644 --- a/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs +++ b/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs @@ -71,8 +71,10 @@ public partial class ShoppingCartService : IShoppingCartService /// ACL service /// Store mapping service /// Generic attribute service - public ShoppingCartService(IRepository sciRepository, - IWorkContext workContext, IStoreContext storeContext, + public ShoppingCartService( + IRepository sciRepository, + IWorkContext workContext, + IStoreContext storeContext, ICurrencyService currencyService, IProductService productService, ILocalizationService localizationService, IProductAttributeParser productAttributeParser, @@ -423,10 +425,9 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart break; case ManageInventoryMethod.ManageStockByAttributes: { - var combination = product.ProductVariantAttributeCombinations - .FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, selectedAttributes)); + var combination = _productAttributeParser.FindProductVariantAttributeCombination(product.Id, selectedAttributes); - if (combination != null) + if (combination != null) { if (!combination.AllowOutOfStockOrders && combination.StockQuantity < quantity) { @@ -469,21 +470,16 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart return warnings; } - /// - /// Validates shopping cart item attributes - /// - /// The customer - /// Shopping cart type - /// Product - /// Selected attributes - /// Quantity - /// Product bundle item - /// Warnings - public virtual IList GetShoppingCartItemAttributeWarnings(Customer customer, ShoppingCartType shoppingCartType, - Product product, string selectedAttributes, int quantity = 1, ProductBundleItem bundleItem = null) + public virtual IList GetShoppingCartItemAttributeWarnings( + Customer customer, + ShoppingCartType shoppingCartType, + Product product, + string selectedAttributes, + int quantity = 1, + ProductBundleItem bundleItem = null, + ProductVariantAttributeCombination combination = null) { - if (product == null) - throw new ArgumentNullException("product"); + Guard.ArgumentNotNull(() => product); var warnings = new List(); @@ -546,9 +542,10 @@ public virtual IList GetShoppingCartItemAttributeWarnings(Customer custo // check if there is a selected attribute combination and if it is active if (warnings.Count == 0 && selectedAttributes.HasValue()) { - var combination = product - .ProductVariantAttributeCombinations - .FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, selectedAttributes, pva1Collection)); + if (combination == null) + { + combination = _productAttributeParser.FindProductVariantAttributeCombination(product.Id, selectedAttributes, pva1Collection); + } if (combination != null && !combination.IsActive) { @@ -599,8 +596,10 @@ public virtual IList GetShoppingCartItemAttributeWarnings(Customer custo /// bool public virtual bool AreAllAttributesForCombinationSelected(string selectedAttributes, Product product) { - if (product.ProductVariantAttributeCombinations.Count == 0) - return true; + Guard.ArgumentNotNull(() => product); + + if (!product.ProductVariantAttributeCombinations.Any()) + return true; //selected attributes var pva1Collection = _productAttributeParser.ParseProductVariantAttributes(selectedAttributes); diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductsController.cs index 232a326378..1729a87bcf 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductsController.cs @@ -5,6 +5,7 @@ using System.Net.Http; using System.Web.Http; using System.Web.Http.OData; +using System.Data.Entity; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Directory; @@ -224,8 +225,16 @@ public IQueryable GetProductVariantAttributes(int key) public IQueryable GetProductVariantAttributeCombinations(int key) { var entity = GetExpandedEntity>(key, x => x.ProductVariantAttributeCombinations); - return entity.ProductVariantAttributeCombinations.AsQueryable(); + + //var ctx = (DbContext)Repository.Context; + //var product = GetEntityByKey(key); + //var entry = ctx.Entry(product); + //var query = entry.Collection(p => p.ProductVariantAttributeCombinations).Query(); + //return query; + + //var query = GetEntitySet().Where(x => x.Id.Equals(key)); + //return query.SelectMany(x => x.ProductVariantAttributeCombinations); } [WebApiQueryable] diff --git a/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj b/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj index f3ad93ddb8..feaadf8fcc 100644 --- a/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj +++ b/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj @@ -49,6 +49,7 @@ + true @@ -92,6 +93,14 @@ ..\..\packages\AutoMapper.3.2.1\lib\net40\AutoMapper.Net4.dll + + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + True + + + ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + True + False ..\..\packages\Microsoft.Data.Edm.5.6.1\lib\net40\Microsoft.Data.Edm.dll @@ -107,6 +116,7 @@ ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + diff --git a/src/Plugins/SmartStore.WebApi/packages.config b/src/Plugins/SmartStore.WebApi/packages.config index 78cc3d0406..3e64ed0b12 100644 --- a/src/Plugins/SmartStore.WebApi/packages.config +++ b/src/Plugins/SmartStore.WebApi/packages.config @@ -4,6 +4,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ActivityLogController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ActivityLogController.cs index d6e205b7fe..67843f3301 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ActivityLogController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ActivityLogController.cs @@ -127,8 +127,9 @@ public JsonResult ListLogs(GridCommand command, ActivityLogSearchModel model) DateTime? startDateValue = (model.CreatedOnFrom == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnFrom.Value, _dateTimeHelper.CurrentTimeZone); - DateTime? endDateValue = (model.CreatedOnTo == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnTo.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + DateTime? endDateValue = (model.CreatedOnTo == null) + ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnTo.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); var activityLog = _customerActivityService.GetAllActivities(startDateValue, endDateValue,null, model.ActivityLogTypeId, command.Page - 1, command.PageSize); var gridModel = new GridModel diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 0aa775ff8f..a08835d4c2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using System.Web.Mvc; using System.Web.Routing; +using System.Data.Entity; using Autofac; using Newtonsoft.Json; using SmartStore.Admin.Models.Catalog; @@ -4064,11 +4065,14 @@ public ActionResult ProductAttributeValueLinkagePopup(ProductModel.ProductVarian #region Product variant attribute combinations - private void PrepareProductAttributeCombinationModel(ProductVariantAttributeCombinationModel model, ProductVariantAttributeCombination entity, + private void PrepareProductAttributeCombinationModel( + ProductVariantAttributeCombinationModel model, + ProductVariantAttributeCombination entity, Product product, bool formatAttributes = false) { if (model == null) throw new ArgumentNullException("model"); + if (product == null) throw new ArgumentNullException("variant"); @@ -4168,13 +4172,17 @@ public ActionResult ProductVariantAttributeCombinationList(GridCommand command, if (product == null) throw new ArgumentException("No product found with the specified id"); - var productVariantAttributeCombinations = _productAttributeService.GetAllProductVariantAttributeCombinations(productId); + ////var allCombinations = new PagedList(product.ProductVariantAttributeCombinations.AsQueryable(), command.Page - 1, command.PageSize); + //var take = command.PageSize; + //var skip = (command.Page - 1) * take; + //var allCombinations = product.ProductVariantAttributeCombinations.Take(take).Skip(skip); + var allCombinations = _productAttributeService.GetAllProductVariantAttributeCombinations(product.Id, command.Page - 1, command.PageSize); var productUrlTitle = _localizationService.GetResource("Common.OpenInShop"); var productUrl = Url.RouteUrl("Product", new { SeName = product.GetSeName() }); productUrl = "{0}{1}attributes=".FormatWith(productUrl, productUrl.Contains("?") ? "&" : "?"); - var productVariantAttributesModel = productVariantAttributeCombinations.Select(x => + var productVariantAttributesModel = allCombinations.Select(x => { var pvacModel = x.ToModel(); PrepareProductAttributeCombinationModel(pvacModel, x, product, true); @@ -4185,7 +4193,7 @@ public ActionResult ProductVariantAttributeCombinationList(GridCommand command, try { var firstAttribute = _productAttributeParser.DeserializeProductVariantAttributes(x.AttributesXml).FirstOrDefault(); - + var attribute = x.Product.ProductVariantAttributes.FirstOrDefault(y => y.Id == firstAttribute.Key); var attributeValue = attribute.ProductVariantAttributeValues.FirstOrDefault(y => y.Id == int.Parse(firstAttribute.Value.First())); @@ -4200,7 +4208,13 @@ public ActionResult ProductVariantAttributeCombinationList(GridCommand command, // pvacModel.AttributesXml = "{0}".FormatWith(pvacModel.AttributesXml); //warnings - var warnings = _shoppingCartService.GetShoppingCartItemAttributeWarnings(_workContext.CurrentCustomer, ShoppingCartType.ShoppingCart, x.Product, x.AttributesXml); + var warnings = _shoppingCartService.GetShoppingCartItemAttributeWarnings( + _workContext.CurrentCustomer, + ShoppingCartType.ShoppingCart, + x.Product, + x.AttributesXml, + combination: x); + pvacModel.Warnings.AddRange(warnings); return pvacModel; @@ -4210,7 +4224,8 @@ public ActionResult ProductVariantAttributeCombinationList(GridCommand command, var model = new GridModel { Data = productVariantAttributesModel, - Total = productVariantAttributesModel.Count + Total = allCombinations.TotalCount, + //Total = allCombinations.Count() }; return new JsonResult @@ -4273,12 +4288,20 @@ public ActionResult AttributeCombinationCreatePopup(string btnId, string formId, var warnings = new List(); var variantAttributes = _productAttributeService.GetProductVariantAttributesByProductId(product.Id); - string attributeXml = form.CreateSelectedAttributesXml(product.Id, variantAttributes, _productAttributeParser, _localizationService, - _downloadService, _catalogSettings, this.Request, warnings, false); + string attributeXml = form.CreateSelectedAttributesXml( + product.Id, + variantAttributes, + _productAttributeParser, + _localizationService, + _downloadService, + _catalogSettings, + this.Request, + warnings, + false); warnings.AddRange(_shoppingCartService.GetShoppingCartItemAttributeWarnings(_workContext.CurrentCustomer, ShoppingCartType.ShoppingCart, product, attributeXml)); - if (null != _productAttributeParser.FindProductVariantAttributeCombination(product, attributeXml)) + if (_productAttributeParser.FindProductVariantAttributeCombination(product, attributeXml) != null) { warnings.Add(_localizationService.GetResource("Admin.Catalog.Products.ProductVariantAttributes.AttributeCombinations.CombiExists")); } @@ -4388,7 +4411,7 @@ public ActionResult DeleteAllAttributeCombinations(ProductVariantAttributeCombin if (product == null) throw new ArgumentException("No product found with the specified id"); - foreach (var combination in _productAttributeService.GetAllProductVariantAttributeCombinations(product.Id)) + foreach (var combination in product.ProductVariantAttributeCombinations) { _productAttributeService.DeleteProductVariantAttributeCombination(combination); } @@ -4409,7 +4432,7 @@ public ActionResult CombinationExistenceNote(int productId, FormCollection form) string attributeXml = form.CreateSelectedAttributesXml(productId, attributes, _productAttributeParser, _localizationService, _downloadService, _catalogSettings, this.Request, warnings, false); - bool exists = (null != _productAttributeParser.FindProductVariantAttributeCombination(productId, attributeXml)); + bool exists = (_productAttributeParser.FindProductVariantAttributeCombination(productId, attributeXml) != null); if (!exists) { diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index 194adfe046..ca5d1ddbde 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -38,6 +38,7 @@ true true 1e5b888b + true @@ -79,6 +80,14 @@ False ..\..\..\packages\AutoMapper.3.2.1\lib\net40\AutoMapper.Net4.dll + + ..\..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll + True + + + ..\..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll + True + False ..\..\..\packages\FluentValidation.5.0.0.1\lib\Net40\FluentValidation.dll diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Attributes.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Attributes.cshtml index 9ba9831a5d..fc675e560f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Attributes.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Attributes.cshtml @@ -94,7 +94,7 @@ @T("Admin.Catalog.Products.ProductVariantAttributes.AttributeCombinations.Description") -
    +
    ") - // .Title(""); - columns.Command(commands => - { - commands.Delete().Localize(T); - }) - .Width(90); - }) - .ClientEvents(events => events.OnRowDataBound("onRowDataBound_Combinations")) - .EnableCustomBinding(true)) + columns.Bound(x => x.StockQuantity) + .Width(160) + .Centered(); + columns.Bound(x => x.AllowOutOfStockOrders) + .Width(60) + .HeaderTemplate("") + .Template(item => @Html.SymbolForBool(item.AllowOutOfStockOrders)) + .ClientTemplate(@Html.SymbolForBool("AllowOutOfStockOrders")) + .Centered(); + //columns.Bound(x => x.Id) + // .Width(90) + // .Centered() + // .ClientTemplate("") + // .Title(""); + columns.Command(commands => + { + commands.Delete().Localize(T); + }) + .Width(90); + }) + .Pageable(settings => settings.PageSize(100).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => + { + dataBinding.Ajax() + .Select("ProductVariantAttributeCombinationList", "Product", new { productId = Model.Id }) + .Delete("ProductVariantAttributeCombinationDelete", "Product"); + }) + .ClientEvents(events => events.OnRowDataBound("onRowDataBound_Combinations")) + .EnableCustomBinding(true))
    diff --git a/src/Presentation/SmartStore.Web/Administration/packages.config b/src/Presentation/SmartStore.Web/Administration/packages.config index a9d0066006..cf47679773 100644 --- a/src/Presentation/SmartStore.Web/Administration/packages.config +++ b/src/Presentation/SmartStore.Web/Administration/packages.config @@ -4,6 +4,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index f733911e33..1a1d5dff8e 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -11,7 +11,7 @@ using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Tax; -using SmartStore.Core.Localization; +using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Services; using SmartStore.Services.Catalog; @@ -20,7 +20,7 @@ using SmartStore.Services.Directory; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; -using SmartStore.Services.Media; +using SmartStore.Services.Media; using SmartStore.Services.Security; using SmartStore.Services.Seo; using SmartStore.Services.Tax; @@ -29,6 +29,7 @@ using SmartStore.Web.Infrastructure.Cache; using SmartStore.Web.Models.Catalog; using SmartStore.Web.Models.Media; +using SmartStore.Core.Data; namespace SmartStore.Web.Controllers { @@ -140,8 +141,12 @@ public CatalogHelper( public ILogger Logger { get; set; } - public ProductDetailsModel PrepareProductDetailsPageModel(Product product, bool isAssociatedProduct = false, - ProductBundleItemData productBundleItem = null, IList productBundleItems = null, FormCollection selectedAttributes = null) + public ProductDetailsModel PrepareProductDetailsPageModel( + Product product, + bool isAssociatedProduct = false, + ProductBundleItemData productBundleItem = null, + IList productBundleItems = null, + FormCollection selectedAttributes = null) { if (product == null) throw new ArgumentNullException("product"); @@ -200,11 +205,10 @@ public ProductDetailsModel PrepareProductDetailsPageModel(Product product, bool IList bundleItems = null; ProductVariantAttributeCombination combination = null; - var combinationImageIds = new List(); if (product.ProductType == ProductType.GroupedProduct && !isAssociatedProduct) // associated products { - var searchContext = new ProductSearchContext() + var searchContext = new ProductSearchContext { StoreId = _services.StoreContext.CurrentStore.Id, ParentGroupedProductId = product.Id, @@ -246,17 +250,18 @@ public ProductDetailsModel PrepareProductDetailsPageModel(Product product, bool model = PrepareProductDetailModel(model, product, isAssociatedProduct, productBundleItem, bundleItems, selectedAttributes); + IList combinationPictureIds = null; + if (productBundleItem == null) { - model.Combinations.GetAllCombinationImageIds(combinationImageIds); - - if (combination == null && model.CombinationSelected != null) - combination = model.CombinationSelected; + combinationPictureIds = product.ProductVariantAttributeCombinations.GetAllCombinationPictureIds(); + if (combination == null && model.SelectedCombination != null) + combination = model.SelectedCombination; } // pictures var pictures = _pictureService.GetPicturesByProductId(product.Id); - PrepareProductDetailsPictureModel(model.DetailsPictureModel, pictures, model.Name, combinationImageIds, isAssociatedProduct, productBundleItem, combination); + PrepareProductDetailsPictureModel(model.DetailsPictureModel, pictures, model.Name, combinationPictureIds, isAssociatedProduct, productBundleItem, combination); return model; } @@ -314,8 +319,14 @@ private PictureModel CreatePictureModel(ProductDetailsPictureModel model, Pictur return result; } - public void PrepareProductDetailsPictureModel(ProductDetailsPictureModel model, IList pictures, string name, List allCombinationImageIds, - bool isAssociatedProduct, ProductBundleItemData bundleItem = null, ProductVariantAttributeCombination combination = null) + public void PrepareProductDetailsPictureModel( + ProductDetailsPictureModel model, + IList pictures, + string name, + IList allCombinationImageIds, + bool isAssociatedProduct, + ProductBundleItemData bundleItem = null, + ProductVariantAttributeCombination combination = null) { model.Name = name; model.DefaultPictureZoomEnabled = _mediaSettings.DefaultPictureZoomEnabled; @@ -352,7 +363,8 @@ public void PrepareProductDetailsPictureModel(ProductDetailsPictureModel model, else { // images not belonging to any combination... - foreach (var picture in pictures.Where(p => !allCombinationImageIds.Contains(p.Id))) + allCombinationImageIds = allCombinationImageIds ?? new List(); + foreach (var picture in pictures.Where(p => !allCombinationImageIds.Contains(p.Id))) { model.PictureModels.Add(CreatePictureModel(model, picture, _mediaSettings.ProductDetailsPictureSize)); } @@ -383,7 +395,7 @@ public void PrepareProductDetailsPictureModel(ProductDetailsPictureModel model, // default picture if (defaultPicture == null) { - model.DefaultPictureModel = new PictureModel() + model.DefaultPictureModel = new PictureModel { ThumbImageUrl = _pictureService.GetDefaultPictureUrl(_mediaSettings.ProductThumbPictureSizeOnProductDetailsPage), ImageUrl = _pictureService.GetDefaultPictureUrl(defaultPictureSize), @@ -579,8 +591,6 @@ public ProductDetailsModel PrepareProductDetailModel( if (!isBundle) { - model.Combinations = _productAttributeService.GetAllProductVariantAttributeCombinations(product.Id); - if (selectedAttributes.Count > 0) { // merge with combination data if there's a match @@ -597,16 +607,15 @@ public ProductDetailsModel PrepareProductDetailModel( renderPrices: false, renderGiftCardAttributes: false, allowHyperlinks: false); } - model.CombinationSelected = model.Combinations - .FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, attributeXml, variantAttributes)); + model.SelectedCombination = _productAttributeParser.FindProductVariantAttributeCombination(product.Id, attributeXml, variantAttributes); - if (model.CombinationSelected != null && model.CombinationSelected.IsActive == false) + if (model.SelectedCombination != null && model.SelectedCombination.IsActive == false) { model.IsAvailable = false; model.StockAvailability = T("Products.Availability.IsNotActive"); } - product.MergeWithCombination(model.CombinationSelected); + product.MergeWithCombination(model.SelectedCombination); // mark explicitly selected as pre-selected foreach (var attribute in model.ProductVariantAttributes) @@ -632,8 +641,8 @@ public ProductDetailsModel PrepareProductDetailModel( { // cases where stock inventory is not functional. determined by what ShoppingCartService.GetStandardWarnings and ProductService.AdjustInventory is not handling. model.IsAvailable = true; - model.StockAvailability = product.ProductVariantAttributeCombinations.Count == 0 ? product.FormatStockMessage(_localizationService) : ""; - } + model.StockAvailability = !product.ProductVariantAttributeCombinations.Any() ? product.FormatStockMessage(_localizationService) : ""; + } else if (model.IsAvailable) { model.IsAvailable = product.IsAvailableByStock(); diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index f557e9ee81..0f3558f6ed 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -658,14 +658,16 @@ public ActionResult UpdateProductDetails(int productId, string itemType, int bun } else { - var allCombinationImageIds = new List(); - - _productAttributeService - .GetAllProductVariantAttributeCombinations(product.Id) - .GetAllCombinationImageIds(allCombinationImageIds); - - _helper.PrepareProductDetailsPictureModel(pictureModel, pictures, product.GetLocalized(x => x.Name), allCombinationImageIds, - false, bundleItem, m.CombinationSelected); + var allCombinationImageIds = product.ProductVariantAttributeCombinations.GetAllCombinationPictureIds(); + + _helper.PrepareProductDetailsPictureModel( + pictureModel, + pictures, + product.GetLocalized(x => x.Name), + allCombinationImageIds, + false, + bundleItem, + m.SelectedCombination); galleryStartIndex = pictureModel.GalleryStartIndex; galleryHtml = this.RenderPartialViewToString("_PictureGallery", pictureModel); diff --git a/src/Presentation/SmartStore.Web/Extensions/ProductDetailsExtensions.cs b/src/Presentation/SmartStore.Web/Extensions/ProductDetailsExtensions.cs index 173ab96308..18356aa3cb 100644 --- a/src/Presentation/SmartStore.Web/Extensions/ProductDetailsExtensions.cs +++ b/src/Presentation/SmartStore.Web/Extensions/ProductDetailsExtensions.cs @@ -33,10 +33,10 @@ public static bool RenderBundleTitle(this ProductDetailsModel model) public static Picture GetAssignedPicture(this ProductDetailsModel model, IPictureService pictureService, IList pictures, int productId = 0) { - if (model != null && model.CombinationSelected != null) + if (model != null && model.SelectedCombination != null) { Picture picture = null; - var combiAssignedImages = model.CombinationSelected.GetAssignedPictureIds(); + var combiAssignedImages = model.SelectedCombination.GetAssignedPictureIds(); if (combiAssignedImages.Length > 0) { diff --git a/src/Presentation/SmartStore.Web/Models/Catalog/ProductDetailsModel.cs b/src/Presentation/SmartStore.Web/Models/Catalog/ProductDetailsModel.cs index fdfce95aff..5aa0d0a7f5 100644 --- a/src/Presentation/SmartStore.Web/Models/Catalog/ProductDetailsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Catalog/ProductDetailsModel.cs @@ -21,7 +21,6 @@ public ProductDetailsModel() ProductPrice = new ProductPriceModel(); AddToCart = new AddToCartModel(); ProductVariantAttributes = new List(); - Combinations = new List(); AssociatedProducts = new List(); BundledItems = new List(); BundleItem = new ProductBundleItemModel(); @@ -100,8 +99,7 @@ public ProductDetailsPictureModel DetailsPictureModel public bool BundlePerItemPricing { get; set; } public bool BundlePerItemShoppingCart { get; set; } - public IList Combinations { get; set; } - public ProductVariantAttributeCombination CombinationSelected { get; set; } + public ProductVariantAttributeCombination SelectedCombination { get; set; } public IList Manufacturers { get; set; } public int ReviewCount { get; set; } diff --git a/src/Presentation/SmartStore.Web/Web.config b/src/Presentation/SmartStore.Web/Web.config index 16e1e0cdb6..f45ffa195f 100644 --- a/src/Presentation/SmartStore.Web/Web.config +++ b/src/Presentation/SmartStore.Web/Web.config @@ -191,6 +191,7 @@ + diff --git a/src/Tests/SmartStore.Services.Tests/Catalog/ProductAttributeParserAndFormatterTests.cs b/src/Tests/SmartStore.Services.Tests/Catalog/ProductAttributeParserAndFormatterTests.cs index b8ca866718..ee7ac3f754 100644 --- a/src/Tests/SmartStore.Services.Tests/Catalog/ProductAttributeParserAndFormatterTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Catalog/ProductAttributeParserAndFormatterTests.cs @@ -185,8 +185,8 @@ public class ProductAttributeParserTests : ServiceTest _productBundleItemAttributeFilter, _eventPublisher, _pictureService); - - _productAttributeParser = new ProductAttributeParser(_productAttributeService); + + _productAttributeParser = new ProductAttributeParser(_productAttributeService, new MemoryRepository(), NullCache.Instance); diff --git a/src/Tests/SmartStore.Services.Tests/ExportImport/ExportManagerTests.cs b/src/Tests/SmartStore.Services.Tests/ExportImport/ExportManagerTests.cs index dcdd328196..d401410a23 100644 --- a/src/Tests/SmartStore.Services.Tests/ExportImport/ExportManagerTests.cs +++ b/src/Tests/SmartStore.Services.Tests/ExportImport/ExportManagerTests.cs @@ -29,6 +29,7 @@ public class ExportManagerTests : ServiceTest ICategoryService _categoryService; IManufacturerService _manufacturerService; IProductService _productService; + IProductAttributeService _productAttributeService; IProductTemplateService _productTemplateService; IPictureService _pictureService; INewsLetterSubscriptionService _newsLetterSubscriptionService; @@ -44,6 +45,7 @@ public class ExportManagerTests : ServiceTest _categoryService = MockRepository.GenerateMock(); _manufacturerService = MockRepository.GenerateMock(); _productService = MockRepository.GenerateMock(); + _productAttributeService = MockRepository.GenerateMock(); _productTemplateService = MockRepository.GenerateMock(); _pictureService = MockRepository.GenerateMock(); _newsLetterSubscriptionService = MockRepository.GenerateMock(); @@ -52,8 +54,18 @@ public class ExportManagerTests : ServiceTest _services = MockRepository.GenerateMock(); _storeMapping = MockRepository.GenerateMock(); - _exportManager = new ExportManager(_categoryService, _manufacturerService, _productService, _productTemplateService, _pictureService, - _newsLetterSubscriptionService, _languageService, _mediaSettings, _services, _storeMapping); + _exportManager = new ExportManager( + _categoryService, + _manufacturerService, + _productService, + _productAttributeService, + _productTemplateService, + _pictureService, + _newsLetterSubscriptionService, + _languageService, + _mediaSettings, + _services, + _storeMapping); } //[Test] From 53fd0ccd8cb70887a6c6a838d8eb6641f8891ae3 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 9 Oct 2015 02:14:47 +0200 Subject: [PATCH 116/874] (Perf) WebApi/OData: Navigation properties are not eagerly loaded anymore. --- .../Controllers/OData/AddressesController.cs | 10 +-- .../Controllers/OData/CategoriesController.cs | 4 +- .../Controllers/OData/CountriesController.cs | 4 +- .../Controllers/OData/CustomersController.cs | 22 +++---- .../Controllers/OData/DiscountsController.cs | 4 +- .../OData/LocalizedPropertysController.cs | 5 +- .../Controllers/OData/OrderItemsController.cs | 10 +-- .../Controllers/OData/OrderNotesController.cs | 5 +- .../Controllers/OData/OrdersController.cs | 27 ++++---- .../Controllers/OData/PicturesController.cs | 4 +- .../OData/ProductBundleItemsController.cs | 12 ++-- ...tVariantAttributeCombinationsController.cs | 5 +- ...ProductVariantAttributeValuesController.cs | 5 +- .../ProductVariantAttributesController.cs | 9 ++- .../Controllers/OData/ProductsController.cs | 65 ++++++------------- .../OData/ReturnRequestsController.cs | 5 +- .../OData/ShippingMethodsController.cs | 4 +- ...SpecificationAttributeOptionsController.cs | 9 ++- .../SpecificationAttributesController.cs | 4 +- .../OData/StateProvincesController.cs | 5 +- .../WebApi/WebApiEntityController.cs | 50 ++++++++++++++ 21 files changed, 142 insertions(+), 126 deletions(-) diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/AddressesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/AddressesController.cs index 49785de800..3b7e734583 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/AddressesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/AddressesController.cs @@ -73,14 +73,16 @@ public SingleResult
    GetAddress(int key) // navigation properties - public Country GetCountry(int key) + [WebApiQueryable] + public SingleResult GetCountry(int key) { - return GetExpandedProperty(key, x => x.Country); + return GetRelatedEntity(key, x => x.Country); } - public StateProvince GetStateProvince(int key) + [WebApiQueryable] + public SingleResult GetStateProvince(int key) { - return GetExpandedProperty(key, x => x.StateProvince); + return GetRelatedEntity(key, x => x.StateProvince); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/CategoriesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/CategoriesController.cs index aa72f64388..9d252befa9 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/CategoriesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/CategoriesController.cs @@ -67,9 +67,7 @@ public SingleResult GetCategory(int key) [WebApiQueryable] public IQueryable GetAppliedDiscounts(int key) { - var entity = GetExpandedEntity>(key, x => x.AppliedDiscounts); - - return entity.AppliedDiscounts.AsQueryable(); + return GetRelatedCollection(key, x => x.AppliedDiscounts); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/CountriesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/CountriesController.cs index cf78613bd4..15f58522a4 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/CountriesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/CountriesController.cs @@ -36,9 +36,7 @@ public SingleResult GetCountry(int key) [WebApiQueryable] public IQueryable GetStateProvinces(int key) { - var entity = GetExpandedEntity>(key, x => x.StateProvinces); - - return entity.StateProvinces.AsQueryable(); + return GetRelatedCollection(key, x => x.StateProvinces); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/CustomersController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/CustomersController.cs index 317c3e09da..c811dd60cf 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/CustomersController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/CustomersController.cs @@ -44,14 +44,16 @@ public SingleResult GetCustomer(int key) // navigation properties - public Address GetBillingAddress(int key) + [WebApiQueryable] + public SingleResult
    GetBillingAddress(int key) { - return GetExpandedProperty
    (key, x => x.BillingAddress); + return GetRelatedEntity(key, x => x.BillingAddress); } - public Address GetShippingAddress(int key) + [WebApiQueryable] + public SingleResult
    GetShippingAddress(int key) { - return GetExpandedProperty
    (key, x => x.ShippingAddress); + return GetRelatedEntity(key, x => x.ShippingAddress); } //public Language GetLanguage(int key) @@ -67,25 +69,19 @@ public Address GetShippingAddress(int key) [WebApiQueryable] public IQueryable GetOrders(int key) { - var entity = GetExpandedEntity>(key, x => x.Orders); - - return entity.Orders.AsQueryable(); + return GetRelatedCollection(key, x => x.Orders); } [WebApiQueryable] public IQueryable GetReturnRequests(int key) { - var entity = GetExpandedEntity>(key, x => x.ReturnRequests); - - return entity.ReturnRequests.AsQueryable(); + return GetRelatedCollection(key, x => x.ReturnRequests); } [WebApiQueryable] public IQueryable
    GetAddresses(int key) { - var entity = GetExpandedEntity>(key, x => x.Addresses); - - return entity.Addresses.AsQueryable(); + return GetRelatedCollection(key, x => x.Addresses); } // actions diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/DiscountsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/DiscountsController.cs index de6badcc25..eeb0d3d448 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/DiscountsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/DiscountsController.cs @@ -37,9 +37,7 @@ public SingleResult GetDiscount(int key) [WebApiQueryable] public IQueryable GetAppliedToCategories(int key) { - var entity = GetExpandedEntity>(key, x => x.AppliedToCategories); - - return entity.AppliedToCategories.AsQueryable(); + return GetRelatedCollection(key, x => x.AppliedToCategories); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/LocalizedPropertysController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/LocalizedPropertysController.cs index 8aa403ce14..9f9c52edca 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/LocalizedPropertysController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/LocalizedPropertysController.cs @@ -31,9 +31,10 @@ public SingleResult GetLocalizedProperty(int key) // navigation properties - public Language GetLanguage(int key) + [WebApiQueryable] + public SingleResult GetLanguage(int key) { - return GetExpandedProperty(key, x => x.Language); + return GetRelatedEntity(key, x => x.Language); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderItemsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderItemsController.cs index bdbe03a781..2e608f15bc 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderItemsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderItemsController.cs @@ -41,14 +41,16 @@ public SingleResult GetOrderItem(int key) // navigation properties - public Order GetOrder(int key) + [WebApiQueryable] + public SingleResult GetOrder(int key) { - return GetExpandedProperty(key, x => x.Order); + return GetRelatedEntity(key, x => x.Order); } - public Product GetProduct(int key) + [WebApiQueryable] + public SingleResult GetProduct(int key) { - return GetExpandedProperty(key, x => x.Product); + return GetRelatedEntity(key, x => x.Product); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderNotesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderNotesController.cs index 99bc1502c3..354204fb0e 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderNotesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrderNotesController.cs @@ -23,9 +23,10 @@ public SingleResult GetOrderNote(int key) // navigation properties - public Order GetOrder(int key) + [WebApiQueryable] + public SingleResult GetOrder(int key) { - return GetExpandedProperty(key, x => x.Order); + return GetRelatedEntity(key, x => x.Order); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs index f86745f368..d633274612 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs @@ -55,44 +55,41 @@ public SingleResult GetOrder(int key) // navigation properties - public Customer GetCustomer(int key) + [WebApiQueryable] + public SingleResult GetCustomer(int key) { - return GetExpandedProperty(key, x => x.Customer); + return GetRelatedEntity(key, x => x.Customer); } - public Address GetBillingAddress(int key) + [WebApiQueryable] + public SingleResult
    GetBillingAddress(int key) { - return GetExpandedProperty
    (key, x => x.BillingAddress); + return GetRelatedEntity(key, x => x.BillingAddress); } - public Address GetShippingAddress(int key) + [WebApiQueryable] + public SingleResult
    GetShippingAddress(int key) { - return GetExpandedProperty
    (key, x => x.ShippingAddress); + return GetRelatedEntity(key, x => x.ShippingAddress); } [WebApiQueryable] public IQueryable GetOrderNotes(int key) { - var entity = GetExpandedEntity>(key, x => x.OrderNotes); //var entity = GetEntityByKeyNotNull(key); // if ProxyCreationEnabled = true - - return entity.OrderNotes.AsQueryable(); + return GetRelatedCollection(key, x => x.OrderNotes); } [WebApiQueryable] public IQueryable GetShipments(int key) { - var entity = GetExpandedEntity>(key, x => x.Shipments); - - return entity.Shipments.AsQueryable(); + return GetRelatedCollection(key, x => x.Shipments); } [WebApiQueryable] public IQueryable GetOrderItems(int key) { - var entity = GetExpandedEntity>(key, x => x.OrderItems); - - return entity.OrderItems.AsQueryable(); + return GetRelatedCollection(key, x => x.OrderItems); } // actions diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/PicturesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/PicturesController.cs index 26e4025c27..9e23c2959f 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/PicturesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/PicturesController.cs @@ -37,9 +37,7 @@ public SingleResult GetPicture(int key) [WebApiQueryable] public IQueryable GetProductPictures(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductPictures); - - return entity.ProductPictures.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductPictures); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductBundleItemsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductBundleItemsController.cs index 3d86242624..a24d207821 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductBundleItemsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductBundleItemsController.cs @@ -27,18 +27,20 @@ protected override void Delete(ProductBundleItem entity) public SingleResult GetProductBundleItem(int key) { return GetSingleResult(key); - } + } // navigation properties - public Product GetProduct(int key) + [WebApiQueryable] + public SingleResult GetProduct(int key) { - return GetExpandedProperty(key, x => x.Product); + return GetRelatedEntity(key, x => x.Product); } - public Product GetBundleProduct(int key) + [WebApiQueryable] + public SingleResult GetBundleProduct(int key) { - return GetExpandedProperty(key, x => x.BundleProduct); + return GetRelatedEntity(key, x => x.BundleProduct); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeCombinationsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeCombinationsController.cs index a7e393e205..822b0d4d62 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeCombinationsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeCombinationsController.cs @@ -32,9 +32,10 @@ public SingleResult GetProductVariantAttribu // navigation properties - public DeliveryTime GetDeliveryTime(int key) + [WebApiQueryable] + public SingleResult GetDeliveryTime(int key) { - return GetExpandedProperty(key, x => x.DeliveryTime); + return GetRelatedEntity(key, x => x.DeliveryTime); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeValuesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeValuesController.cs index b2f6a9e3ff..ad1f54efe7 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeValuesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributeValuesController.cs @@ -31,9 +31,10 @@ public SingleResult GetProductVariantAttributeValu // navigation properties - public ProductVariantAttribute GetProductVariantAttribute(int key) + [WebApiQueryable] + public SingleResult GetProductVariantAttribute(int key) { - return GetExpandedProperty(key, x => x.ProductVariantAttribute); + return GetRelatedEntity(key, x => x.ProductVariantAttribute); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributesController.cs index 9218f73e6d..487f636560 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductVariantAttributesController.cs @@ -33,17 +33,16 @@ public SingleResult GetProductVariantAttribute(int key) // navigation properties - public ProductAttribute GetProductAttribute(int key) + [WebApiQueryable] + public SingleResult GetProductAttribute(int key) { - return GetExpandedProperty(key, x => x.ProductAttribute); + return GetRelatedEntity(key, x => x.ProductAttribute); } [WebApiQueryable] public IQueryable GetProductVariantAttributeValues(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductVariantAttributeValues); - - return entity.ProductVariantAttributeValues.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductVariantAttributeValues); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductsController.cs index 1729a87bcf..84d3f2fc09 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ProductsController.cs @@ -142,107 +142,82 @@ public HttpResponseMessage NavigationProductManufacturers(int key, int relatedKe return Request.CreateResponseForEntity(productManufacturer, relatedKey); } - public DeliveryTime GetDeliveryTime(int key) + [WebApiQueryable] + public SingleResult GetDeliveryTime(int key) { - return GetExpandedProperty(key, x => x.DeliveryTime); + return GetRelatedEntity(key, x => x.DeliveryTime); } - public QuantityUnit GetQuantityUnit(int key) + [WebApiQueryable] + public SingleResult GetQuantityUnit(int key) { - return GetExpandedProperty(key, x => x.QuantityUnit); + return GetRelatedEntity(key, x => x.QuantityUnit); } - public Download GetSampleDownload(int key) + [WebApiQueryable] + public SingleResult GetSampleDownload(int key) { - return GetExpandedProperty(key, x => x.SampleDownload); + return GetRelatedEntity(key, x => x.SampleDownload); } [WebApiQueryable] public IQueryable GetProductCategories(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductCategories); - - return entity.ProductCategories.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductCategories); } [WebApiQueryable] public IQueryable GetProductManufacturers(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductManufacturers); - - return entity.ProductManufacturers.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductManufacturers); } [WebApiQueryable] public IQueryable GetProductPictures(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductPictures); - - return entity.ProductPictures.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductPictures); } [WebApiQueryable] public IQueryable GetProductSpecificationAttributes(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductSpecificationAttributes); - - return entity.ProductSpecificationAttributes.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductSpecificationAttributes); } [WebApiQueryable] public IQueryable GetProductTags(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductTags); - - return entity.ProductTags.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductTags); } [WebApiQueryable] public IQueryable GetTierPrices(int key) { - var entity = GetExpandedEntity>(key, x => x.TierPrices); - - return entity.TierPrices.AsQueryable(); + return GetRelatedCollection(key, x => x.TierPrices); } [WebApiQueryable] public IQueryable GetAppliedDiscounts(int key) { - var entity = GetExpandedEntity>(key, x => x.AppliedDiscounts); - - return entity.AppliedDiscounts.AsQueryable(); + return GetRelatedCollection(key, x => x.AppliedDiscounts); } [WebApiQueryable] public IQueryable GetProductVariantAttributes(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductVariantAttributes); - - return entity.ProductVariantAttributes.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductVariantAttributes); } [WebApiQueryable] public IQueryable GetProductVariantAttributeCombinations(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductVariantAttributeCombinations); - return entity.ProductVariantAttributeCombinations.AsQueryable(); - - //var ctx = (DbContext)Repository.Context; - //var product = GetEntityByKey(key); - //var entry = ctx.Entry(product); - //var query = entry.Collection(p => p.ProductVariantAttributeCombinations).Query(); - //return query; - - //var query = GetEntitySet().Where(x => x.Id.Equals(key)); - //return query.SelectMany(x => x.ProductVariantAttributeCombinations); + return GetRelatedCollection(key, x => x.ProductVariantAttributeCombinations); } [WebApiQueryable] public IQueryable GetProductBundleItems(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductBundleItems); - - return entity.ProductBundleItems.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductBundleItems); } // actions @@ -259,7 +234,7 @@ public IQueryable GetProductBundleItems(int key) { if (entity.ProductType == ProductType.GroupedProduct) { - var searchContext = new ProductSearchContext() + var searchContext = new ProductSearchContext { Query = this.GetExpandedEntitySet(requiredProperties), ParentGroupedProductId = entity.Id, diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ReturnRequestsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ReturnRequestsController.cs index 949b9b067b..fbcdd389b0 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ReturnRequestsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ReturnRequestsController.cs @@ -24,9 +24,10 @@ public SingleResult GetReturnRequest(int key) // navigation properties - public Customer GetCustomer(int key) + [WebApiQueryable] + public SingleResult GetCustomer(int key) { - return GetExpandedProperty(key, x => x.Customer); + return GetRelatedEntity(key, x => x.Customer); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ShippingMethodsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ShippingMethodsController.cs index 0f4f6aef90..ab13899ff9 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/ShippingMethodsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ShippingMethodsController.cs @@ -37,9 +37,7 @@ public SingleResult GetShippingMethod(int key) [WebApiQueryable] public IQueryable GetRestrictedCountries(int key) { - var entity = GetExpandedEntity>(key, x => x.RestrictedCountries); - - return entity.RestrictedCountries.AsQueryable(); + return GetRelatedCollection(key, x => x.RestrictedCountries); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributeOptionsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributeOptionsController.cs index f4e66b5ddd..e377bb9fff 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributeOptionsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributeOptionsController.cs @@ -33,17 +33,16 @@ public SingleResult GetSpecificationAttributeOptio // navigation properties - public SpecificationAttribute GetSpecificationAttribute(int key) + [WebApiQueryable] + public SingleResult GetSpecificationAttribute(int key) { - return GetExpandedProperty(key, x => x.SpecificationAttribute); + return GetRelatedEntity(key, x => x.SpecificationAttribute); } [WebApiQueryable] public IQueryable GetProductSpecificationAttributes(int key) { - var entity = GetExpandedEntity>(key, x => x.ProductSpecificationAttributes); - - return entity.ProductSpecificationAttributes.AsQueryable(); + return GetRelatedCollection(key, x => x.ProductSpecificationAttributes); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributesController.cs index 7e9c084a9d..3990c280d8 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/SpecificationAttributesController.cs @@ -36,9 +36,7 @@ public SingleResult GetSpecificationAttribute(int key) [WebApiQueryable] public IQueryable GetSpecificationAttributeOptions(int key) { - var entity = GetExpandedEntity>(key, x => x.SpecificationAttributeOptions); - - return entity.SpecificationAttributeOptions.AsQueryable(); + return GetRelatedCollection(key, x => x.SpecificationAttributeOptions); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/StateProvincesController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/StateProvincesController.cs index 1c26bdacb6..f01dccfec4 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/StateProvincesController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/StateProvincesController.cs @@ -31,9 +31,10 @@ public SingleResult GetStateProvince(int key) // navigation properties - public Country GetCountry(int key) + [WebApiQueryable] + public SingleResult GetCountry(int key) { - return GetExpandedProperty(key, x => x.Country); + return GetRelatedEntity(key, x => x.Country); } } } diff --git a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs index 370b667b19..894b9d9400 100644 --- a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs +++ b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs @@ -9,11 +9,13 @@ using System.Web.Http; using System.Web.Http.OData; using System.Web.Http.OData.Routing; +using System.Data.Entity; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Infrastructure; using SmartStore.Services.Directory; using SmartStore.Services.Localization; +using System.Collections.Generic; namespace SmartStore.Web.Framework.WebApi { @@ -269,6 +271,54 @@ protected internal virtual TProperty GetExpandedProperty(int key, Exp return property; } + protected internal virtual IQueryable GetRelatedCollection( + int key, + Expression>> navigationProperty) + { + Guard.ArgumentNotNull(() => navigationProperty); + + var query = GetEntitySet().Where(x => x.Id.Equals(key)); + return query.SelectMany(navigationProperty); + } + + protected internal virtual IQueryable GetRelatedCollection( + int key, + string navigationProperty) + { + Guard.ArgumentNotEmpty(() => navigationProperty); + + var ctx = (DbContext)Repository.Context; + var product = GetEntityByKey(key); + var entry = ctx.Entry(product); + var query = entry.Collection(navigationProperty).Query(); + + return query.Cast(); + } + + protected internal virtual SingleResult GetRelatedEntity( + int key, + Expression> navigationProperty) + { + Guard.ArgumentNotNull(() => navigationProperty); + + var query = GetEntitySet().Where(x => x.Id.Equals(key)).Select(navigationProperty); + return SingleResult.Create(query); + } + + protected internal virtual SingleResult GetRelatedEntity( + int key, + string navigationProperty) + { + Guard.ArgumentNotEmpty(() => navigationProperty); + + var ctx = (DbContext)Repository.Context; + var product = GetEntityByKey(key); + var entry = ctx.Entry(product); + var query = entry.Reference(navigationProperty).Query().Cast(); + + return SingleResult.Create(query); + } + public override HttpResponseMessage Post(TEntity entity) { return Request.CreateResponse(HttpStatusCode.OK, CreateEntity(entity)); From 0964c0b7dc1d5c33d8135082124631fc3bfb40c8 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 9 Oct 2015 10:58:10 +0200 Subject: [PATCH 117/874] Removed high data depth flag --- .../Domain/DataExchange/ExportCore.cs | 7 +- .../ExportProductXlsxProvider.cs | 1 - .../ExportProductXmlProvider.cs | 1 - .../ExportTask/ExportProfileTask.cs | 83 +++++++++---------- 4 files changed, 39 insertions(+), 53 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs index cc0936f415..f9a5dcc18e 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs @@ -63,15 +63,10 @@ public enum ExportAttributeValueMerging ///
    public enum ExportSupport { - /// - /// Whether to get more data like product tags, bundle items etc. - /// - HighDataDepth = 0, - /// /// Whether to automatically create a file based public deployment when an export profile is created /// - CreateInitialPublicDeployment, + CreateInitialPublicDeployment = 0, /// /// Whether to offer option to include\exclude grouped products diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs index 4ccd4ec32b..ce3d9dc7df 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs @@ -22,7 +22,6 @@ namespace SmartStore.Services.DataExchange.ExportProvider [SystemName("Exports.SmartStoreProductXlsx")] [FriendlyName("SmartStore Excel product export")] [IsHidden(true)] - [ExportSupporting(ExportSupport.HighDataDepth)] public class ExportProductXlsxProvider : IExportProvider { private readonly ILanguageService _languageService; diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs index 4e95c4a478..632d5c979a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs @@ -15,7 +15,6 @@ namespace SmartStore.Services.DataExchange.ExportProvider [SystemName("Exports.SmartStoreProductXml")] [FriendlyName("SmartStore XML product export")] [IsHidden(true)] - [ExportSupporting(ExportSupport.HighDataDepth)] public class ExportProductXmlProvider : IExportProvider { public static string SystemName diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 4461e91182..5f927e2383 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -1705,6 +1705,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro { var result = new List(); + var languageId = (ctx.Projection.LanguageId ?? 0); var productTemplate = ctx.ProductTemplates.FirstOrDefault(x => x.Key == product.ProductTemplateId); var pictureSize = _mediaSettings.ProductDetailsPictureSize; @@ -1716,6 +1717,8 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro var productCategories = ctx.DataContextProduct.ProductCategories.Load(product.Id); var productAttributes = ctx.DataContextProduct.Attributes.Load(product.Id); var productAttributeCombinations = ctx.DataContextProduct.AttributeCombinations.Load(product.Id); + var productTags = ctx.DataContextProduct.ProductTags.Load(product.Id); + var specificationAttributes = ctx.DataContextProduct.ProductSpecificationAttributes.Load(product.Id); dynamic expando = ToExpando(ctx, product); @@ -1853,61 +1856,51 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro .ToList(); } - #endregion + expando.ProductTags = productTags + .Select(x => + { + dynamic exp = new ExpandoObject(); + exp._Entity = x; + exp.Id = x.Id; + exp.Name = x.GetLocalized(y => y.Name, languageId, true, false); + exp.SeName = x.GetSeName(languageId); + exp._Localized = GetLocalized(ctx, x, y => y.Name); + return exp; + }) + .ToList(); - #region high data depth + expando.ProductSpecificationAttributes = specificationAttributes + .Select(x => ToExpando(ctx, x)) + .ToList(); - if (ctx.Supporting[ExportSupport.HighDataDepth]) + if (product.ProductType == ProductType.BundledProduct) { - var productTags = ctx.DataContextProduct.ProductTags.Load(product.Id); - var specificationAttributes = ctx.DataContextProduct.ProductSpecificationAttributes.Load(product.Id); + var bundleItems = ctx.DataContextProduct.ProductBundleItems.Load(product.Id); - expando.ProductTags = productTags + expando.ProductBundleItems = bundleItems .Select(x => { dynamic exp = new ExpandoObject(); exp._Entity = x; exp.Id = x.Id; - exp.Name = x.GetLocalized(y => y.Name, ctx.Projection.LanguageId ?? 0, true, false); - exp.SeName = x.GetSeName(ctx.Projection.LanguageId ?? 0); - exp._Localized = GetLocalized(ctx, x, y => y.Name); + exp.ProductId = x.ProductId; + exp.BundleProductId = x.BundleProductId; + exp.Quantity = x.Quantity; + exp.Discount = x.Discount; + exp.DiscountPercentage = x.DiscountPercentage; + exp.Name = x.GetLocalized(y => y.Name, languageId, true, false); + exp.ShortDescription = x.GetLocalized(y => y.ShortDescription, languageId, true, false); + exp.FilterAttributes = x.FilterAttributes; + exp.HideThumbnail = x.HideThumbnail; + exp.Visible = x.Visible; + exp.Published = x.Published; + exp.DisplayOrder = x.DisplayOrder; + exp.CreatedOnUtc = x.CreatedOnUtc; + exp.UpdatedOnUtc = x.UpdatedOnUtc; + exp._Localized = GetLocalized(ctx, x, y => y.Name, y => y.ShortDescription); return exp; }) .ToList(); - - expando.ProductSpecificationAttributes = specificationAttributes - .Select(x => ToExpando(ctx, x)) - .ToList(); - - if (product.ProductType == ProductType.BundledProduct) - { - var bundleItems = ctx.DataContextProduct.ProductBundleItems.Load(product.Id); - - expando.ProductBundleItems = bundleItems - .Select(x => - { - dynamic exp = new ExpandoObject(); - exp._Entity = x; - exp.Id = x.Id; - exp.ProductId = x.ProductId; - exp.BundleProductId = x.BundleProductId; - exp.Quantity = x.Quantity; - exp.Discount = x.Discount; - exp.DiscountPercentage = x.DiscountPercentage; - exp.Name = x.GetLocalized(y => y.Name, ctx.Projection.LanguageId ?? 0, true, false); - exp.ShortDescription = x.GetLocalized(y => y.ShortDescription, ctx.Projection.LanguageId ?? 0, true, false); - exp.FilterAttributes = x.FilterAttributes; - exp.HideThumbnail = x.HideThumbnail; - exp.Visible = x.Visible; - exp.Published = x.Published; - exp.DisplayOrder = x.DisplayOrder; - exp.CreatedOnUtc = x.CreatedOnUtc; - exp.UpdatedOnUtc = x.UpdatedOnUtc; - exp._Localized = GetLocalized(ctx, x, y => y.Name, y => y.ShortDescription); - return exp; - }) - .ToList(); - } } #endregion @@ -1925,7 +1918,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro var productManus = ctx.DataContextProduct.ProductManufacturers.Load(product.Id); if (productManus != null && productManus.Any()) - brand = productManus.First().Manufacturer.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + brand = productManus.First().Manufacturer.GetLocalized(x => x.Name, languageId, true, false); if (brand.IsEmpty()) brand = ctx.Projection.Brand; @@ -1965,7 +1958,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro if (combination != null && ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) { - var values = _productAttributeParser.ParseProductVariantAttributeValues(combination.AttributesXml, productAttributes, ctx.Projection.LanguageId ?? 0); + var values = _productAttributeParser.ParseProductVariantAttributeValues(combination.AttributesXml, productAttributes, languageId); exp.Name = ((string)exp.Name).Grow(string.Join(", ", values), " "); } From bb0fed753d06f40ee942dd4e886e7bb3ba916d83 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 9 Oct 2015 12:18:48 +0200 Subject: [PATCH 118/874] Testing export framework --- .../DataExchange/ExportExecuteContext.cs | 35 ++++++------------- .../ExportCategoryXmlProvider.cs | 13 +++++-- .../ExportCustomerXlsxProvider.cs | 10 ++++-- .../ExportCustomerXmlProvider.cs | 14 ++++++-- .../ExportManufacturerXmlProvider.cs | 13 +++++-- .../ExportNewsSubscriptionCsvProvider.cs | 13 +++++-- .../ExportProvider/ExportOrderXlsxProvider.cs | 10 ++++-- .../ExportProvider/ExportOrderXmlProvider.cs | 10 ++++-- .../ExportProductXlsxProvider.cs | 10 ++++-- .../ExportProductXmlProvider.cs | 14 ++++++-- .../ExportTask/ExportProfileTask.cs | 17 +++++++-- .../Providers/ProductExportXmlProvider.cs | 14 ++++++-- 12 files changed, 122 insertions(+), 51 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs index cc0ef940dd..139d2381a1 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs @@ -99,19 +99,18 @@ public interface IExportExecuteContext /// /// Number of successful processed records /// - int RecordsSucceeded { get; } + int RecordsSucceeded { get; set; } /// /// Number of failed records /// - int RecordsFailed { get; } + int RecordsFailed { get; set; } /// - /// Try-catch closure for exporting a single record + /// Processes an exception that occurred while exporting a record /// - /// Entity identifier - /// Callback - void ProcessRecord(int entityId, Action export); + /// Exception + void RecordException(Exception exc, int entityId); } @@ -158,11 +157,8 @@ public ExportAbortion Abort { get { - if (_cancellation.IsCancellationRequested) + if (_cancellation.IsCancellationRequested || IsMaxFailures) return ExportAbortion.Hard; - - if (IsMaxFailures) - return ExportAbortion.Soft; return _providerAbort; } @@ -195,23 +191,14 @@ public bool IsMaxFailures public int RecordsSucceeded { get; set; } public int RecordsFailed { get; set; } - public void ProcessRecord(int entityId, Action process) + public void RecordException(Exception exc, int entityId) { - try - { - process(); + ++RecordsFailed; - ++RecordsSucceeded; - } - catch (Exception exc) - { - ++RecordsFailed; + Log.Error("Error while processing record with id {0}: {1}".FormatInvariant(entityId, exc.ToAllMessages()), exc); - Log.Error("Error while processing record with id {0}: {1}".FormatInvariant(entityId, exc.ToAllMessages()), exc); - - if (IsMaxFailures) - _result.LastError = exc.ToString(); - } + if (IsMaxFailures) + _result.LastError = exc.ToString(); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs index 03cbb1630a..b9aea0d2fd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System; +using System.Globalization; using System.IO; using System.Text; using System.Xml; @@ -71,7 +72,7 @@ public void Execute(IExportExecuteContext context) writer.WriteStartElement("Category"); - context.ProcessRecord((int)category.Id, () => + try { xmlHelper.WriteCategory(category, null); @@ -87,7 +88,13 @@ public void Execute(IExportExecuteContext context) writer.WriteEndElement(); // ProductCategory } writer.WriteEndElement(); // ProductCategories - }); + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.RecordException(exc, (int)category.Id); + } writer.WriteEndElement(); // Category } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs index ecce56499b..4c911111c0 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs @@ -150,7 +150,7 @@ public void Execute(IExportExecuteContext context) if (context.Abort != ExportAbortion.None) break; - context.ProcessRecord((int)customer.Id, () => + try { int column = 1; DateTime? lastLoginDateUtc = customer.LastLoginDateUtc; @@ -198,7 +198,13 @@ public void Execute(IExportExecuteContext context) WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.AvatarPictureId)); WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.ForumPostCount)); WriteCell(worksheet, row, ref column, GetGenericAttributeValue(customer, SystemCustomerAttributeNames.Signature)); - }); + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.RecordException(exc, (int)customer.Id); + } ++row; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs index 30f83c0f3e..9833dd47d2 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System; +using System.Globalization; using System.IO; using System.Text; using System.Xml; @@ -69,7 +70,16 @@ public void Execute(IExportExecuteContext context) if (context.Abort != ExportAbortion.None) break; - context.ProcessRecord((int)customer.Id, () => xmlHelper.WriteCustomer(customer, "Customer")); + try + { + xmlHelper.WriteCustomer(customer, "Customer"); + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.RecordException(exc, (int)customer.Id); + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs index 15f03f4891..3a04473ac7 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System; +using System.Globalization; using System.IO; using System.Text; using System.Xml; @@ -71,7 +72,7 @@ public void Execute(IExportExecuteContext context) writer.WriteStartElement("Manufacturer"); - context.ProcessRecord((int)manufacturer.Id, () => + try { xmlHelper.WriteManufacturer(manufacturer, null); @@ -87,7 +88,13 @@ public void Execute(IExportExecuteContext context) writer.WriteEndElement(); // ProductManufacturer } writer.WriteEndElement(); // ProductManufacturers - }); + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.RecordException(exc, (int)manufacturer.Id); + } writer.WriteEndElement(); // Manufacturer } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs index da07c4faff..1c46311f8d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; using System.Text; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Logging; @@ -52,7 +53,7 @@ public void Execute(IExportExecuteContext context) if (context.Abort != ExportAbortion.None) break; - context.ProcessRecord((int)subscriber.Id, () => + try { var row = "{0},{1},{2}".FormatInvariant( ((string)subscriber.Email).ReplaceCsvChars(), @@ -61,7 +62,13 @@ public void Execute(IExportExecuteContext context) ); writer.WriteLine(row); - }); + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.RecordException(exc, (int)subscriber.Id); + } } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs index 6949198637..b5d215ef71 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs @@ -148,7 +148,7 @@ public void Execute(IExportExecuteContext context) if (context.Abort != ExportAbortion.None) break; - context.ProcessRecord((int)order.Id, () => + try { int column = 1; dynamic customer = order.Customer; @@ -219,7 +219,13 @@ public void Execute(IExportExecuteContext context) WriteCell(worksheet, row, ref column, shippingAddress != null ? (string)shippingAddress.ZipPostalCode : ""); WriteCell(worksheet, row, ref column, shippingAddress != null ? (string)shippingAddress.PhoneNumber : ""); WriteCell(worksheet, row, ref column, shippingAddress != null ? (string)shippingAddress.FaxNumber : ""); - }); + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.RecordException(exc, (int)order.Id); + } ++row; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs index 7cc9e2dada..89e07613eb 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs @@ -73,7 +73,7 @@ public void Execute(IExportExecuteContext context) writer.WriteStartElement("Order"); - context.ProcessRecord((int)order.Id, () => + try { dynamic store = order.Store; int? shippingAddressId = order.ShippingAddressId; @@ -244,7 +244,13 @@ public void Execute(IExportExecuteContext context) writer.WriteEndElement(); // Shipment } writer.WriteEndElement(); // Shipments - }); + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.RecordException(exc, (int)order.Id); + } writer.WriteEndElement(); // Order } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs index ce3d9dc7df..a5bef8cec9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs @@ -232,7 +232,7 @@ public void Execute(IExportExecuteContext context) if (context.Abort != ExportAbortion.None) break; - context.ProcessRecord((int)product.Id, () => + try { int column = 1; DateTime? specialPriceStartDateTimeUtc = product.SpecialPriceStartDateTimeUtc; @@ -379,7 +379,13 @@ public void Execute(IExportExecuteContext context) WriteCell(worksheet, row, ref column, GetLocalized(localized, lang, "ShortDescription")); WriteCell(worksheet, row, ref column, GetLocalized(localized, lang, "FullDescription")); } - }); + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.RecordException(exc, (int)product.Id); + } ++row; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs index 632d5c979a..5e8e648f46 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs @@ -1,4 +1,5 @@ -using System.Globalization; +using System; +using System.Globalization; using System.IO; using System.Text; using System.Xml; @@ -69,9 +70,16 @@ public void Execute(IExportExecuteContext context) if (context.Abort != ExportAbortion.None) break; - int productId = product.Id; + try + { + xmlHelper.WriteProduct(product, "Product"); - context.ProcessRecord((int)product.Id, () => xmlHelper.WriteProduct(product, "Product")); + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.RecordException(exc, (int)product.Id); + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 5f927e2383..0764201d9e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -50,12 +50,15 @@ using SmartStore.Services.Tasks; using SmartStore.Services.Tax; using SmartStore.Utilities; +using SmartStore.Utilities.Threading; // note: namespace persisted in ScheduleTask.Type namespace SmartStore.Services.DataExchange.ExportTask { public class ExportProfileTask : ITask { + private static readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); + #region Dependencies private DataExchangeSettings _dataExchangeSettings; @@ -1179,12 +1182,12 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, Product product) expando.AppliedDiscounts = null; expando.TierPrices = null; - expando.ProductTags = null; expando.ProductAttributes = null; expando.ProductAttributeCombinations = null; expando.ProductPictures = null; expando.ProductCategories = null; expando.ProductManufacturers = null; + expando.ProductTags = null; expando.ProductSpecificationAttributes = null; expando.ProductBundleItems = null; @@ -2450,7 +2453,17 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) try { - ctx.Provider.Value.Execute(ctx.Export); + if (ctx.IsFileBasedExport) + { + using (_rwLock.GetWriteLock()) + { + ctx.Provider.Value.Execute(ctx.Export); + } + } + else + { + ctx.Provider.Value.Execute(ctx.Export); + } ctx.Log.Information("Provider reports {0} successful exported record(s)".FormatInvariant(ctx.Export.RecordsSucceeded)); diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs index efb519fd1b..e4dde5e6a8 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs @@ -169,14 +169,16 @@ public void Execute(IExportExecuteContext context) }; var path = context.FilePath; - var config = context.ConfigurationData as ProfileConfigurationModel; dynamic currency = context.Currency; string measureWeightSystemKey = ""; + var measureWeight = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId); if (measureWeight != null) measureWeightSystemKey = measureWeight.SystemKeyword; + var config = (context.ConfigurationData as ProfileConfigurationModel) ?? new ProfileConfigurationModel(); + context.Log.Information("Creating file " + path); using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) @@ -211,7 +213,7 @@ public void Execute(IExportExecuteContext context) writer.WriteStartElement("item"); - context.ProcessRecord((int)product.Id, () => + try { string category = (gmc == null ? null : gmc.Taxonomy); string productType = product._CategoryPath; @@ -381,7 +383,13 @@ public void Execute(IExportExecuteContext context) writer.WriteElementString("g", "unit_pricing_base_measure", _googleNamespace, basePriceBaseMeasure); } } - }); + + ++context.RecordsSucceeded; + } + catch (Exception exc) + { + context.RecordException(exc, productId); + } writer.WriteEndElement(); // item } From 89416d05971946debaf3afa652a8ec2756502670 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 9 Oct 2015 17:29:16 +0200 Subject: [PATCH 119/874] Make profiles with hidden export providers editable and executable through profile grid (for future use, actually not required cause merchant cannot select hidden providers) --- .../Controllers/ExportController.cs | 115 ++++++++++-------- .../Administration/Views/Export/Edit.cshtml | 51 +++++--- 2 files changed, 95 insertions(+), 71 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 4fb97abe32..e49bb7b0da 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -123,15 +123,20 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile if (provider != null) { + var descriptor = provider.Metadata.PluginDescriptor; + model.Provider.ThumbnailUrl = GetThumbnailUrl(provider); - model.Provider.Url = provider.Metadata.PluginDescriptor.Url; - model.Provider.ConfigurationUrl = Url.Action("ConfigurePlugin", "Plugin", new { systemName = provider.Metadata.PluginDescriptor.SystemName, area = "Admin" }); + if (descriptor != null) + { + model.Provider.Url = descriptor.Url; + model.Provider.ConfigurationUrl = Url.Action("ConfigurePlugin", "Plugin", new { systemName = descriptor.SystemName, area = "Admin" }); + model.Provider.Author = descriptor.Author; + model.Provider.Version = descriptor.Version.ToString(); + } + model.Provider.FriendlyName = _pluginMediator.GetLocalizedFriendlyName(provider.Metadata); - model.Provider.Author = provider.Metadata.PluginDescriptor.Author; - model.Provider.Version = provider.Metadata.PluginDescriptor.Version.ToString(); 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.FileExtension = provider.Value.FileExtension; @@ -602,59 +607,65 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) profile.Name = provider.Metadata.SystemName; // projection - var projection = new ExportProjection + if (model.Projection != null) { - StoreId = model.Projection.StoreId, - LanguageId = model.Projection.LanguageId, - CurrencyId = model.Projection.CurrencyId, - CustomerId = model.Projection.CustomerId, - DescriptionMergingId = model.Projection.DescriptionMergingId, - DescriptionToPlainText = model.Projection.DescriptionToPlainText, - AppendDescriptionText = model.Projection.AppendDescriptionText, - RemoveCriticalCharacters = model.Projection.RemoveCriticalCharacters, - CriticalCharacters = model.Projection.CriticalCharacters, - PriceType = model.Projection.PriceType, - ConvertNetToGrossPrices = model.Projection.ConvertNetToGrossPrices, - Brand = model.Projection.Brand, - PictureSize = model.Projection.PictureSize, - ShippingTime = model.Projection.ShippingTime, - ShippingCosts = model.Projection.ShippingCosts, - FreeShippingThreshold = model.Projection.FreeShippingThreshold, - AttributeCombinationAsProduct = model.Projection.AttributeCombinationAsProduct, - AttributeCombinationValueMergingId = model.Projection.AttributeCombinationValueMergingId, - NoGroupedProducts = model.Projection.NoGroupedProducts, - OrderStatusChangeId = model.Projection.OrderStatusChangeId - }; + var projection = new ExportProjection + { + StoreId = model.Projection.StoreId, + LanguageId = model.Projection.LanguageId, + CurrencyId = model.Projection.CurrencyId, + CustomerId = model.Projection.CustomerId, + DescriptionMergingId = model.Projection.DescriptionMergingId, + DescriptionToPlainText = model.Projection.DescriptionToPlainText, + AppendDescriptionText = model.Projection.AppendDescriptionText, + RemoveCriticalCharacters = model.Projection.RemoveCriticalCharacters, + CriticalCharacters = model.Projection.CriticalCharacters, + PriceType = model.Projection.PriceType, + ConvertNetToGrossPrices = model.Projection.ConvertNetToGrossPrices, + Brand = model.Projection.Brand, + PictureSize = model.Projection.PictureSize, + ShippingTime = model.Projection.ShippingTime, + ShippingCosts = model.Projection.ShippingCosts, + FreeShippingThreshold = model.Projection.FreeShippingThreshold, + AttributeCombinationAsProduct = model.Projection.AttributeCombinationAsProduct, + AttributeCombinationValueMergingId = model.Projection.AttributeCombinationValueMergingId, + NoGroupedProducts = model.Projection.NoGroupedProducts, + OrderStatusChangeId = model.Projection.OrderStatusChangeId + }; - profile.Projection = XmlHelper.Serialize(projection); + profile.Projection = XmlHelper.Serialize(projection); + } // filtering - var filter = new ExportFilter + if (model.Filter != null) { - StoreId = model.Filter.StoreId ?? 0, - CreatedFrom = model.Filter.CreatedFrom, - CreatedTo = model.Filter.CreatedTo, - PriceMinimum = model.Filter.PriceMinimum, - PriceMaximum = model.Filter.PriceMaximum, - AvailabilityMinimum = model.Filter.AvailabilityMinimum, - AvailabilityMaximum = model.Filter.AvailabilityMaximum, - IsPublished = model.Filter.IsPublished, - CategoryIds = model.Filter.CategoryIds, - WithoutCategories = model.Filter.WithoutCategories, - ManufacturerId = model.Filter.ManufacturerId, - WithoutManufacturers = model.Filter.WithoutManufacturers, - ProductTagId = model.Filter.ProductTagId, - FeaturedProducts = model.Filter.FeaturedProducts, - ProductType = model.Filter.ProductType, - IdMinimum = model.Filter.IdMinimum, - IdMaximum = model.Filter.IdMaximum, - OrderStatusIds = model.Filter.OrderStatusIds, - PaymentStatusIds = model.Filter.PaymentStatusIds, - ShippingStatusIds = model.Filter.ShippingStatusIds, - CustomerRoleIds = model.Filter.CustomerRoleIds - }; + var filter = new ExportFilter + { + StoreId = model.Filter.StoreId ?? 0, + CreatedFrom = model.Filter.CreatedFrom, + CreatedTo = model.Filter.CreatedTo, + PriceMinimum = model.Filter.PriceMinimum, + PriceMaximum = model.Filter.PriceMaximum, + AvailabilityMinimum = model.Filter.AvailabilityMinimum, + AvailabilityMaximum = model.Filter.AvailabilityMaximum, + IsPublished = model.Filter.IsPublished, + CategoryIds = model.Filter.CategoryIds, + WithoutCategories = model.Filter.WithoutCategories, + ManufacturerId = model.Filter.ManufacturerId, + WithoutManufacturers = model.Filter.WithoutManufacturers, + ProductTagId = model.Filter.ProductTagId, + FeaturedProducts = model.Filter.FeaturedProducts, + ProductType = model.Filter.ProductType, + IdMinimum = model.Filter.IdMinimum, + IdMaximum = model.Filter.IdMaximum, + OrderStatusIds = model.Filter.OrderStatusIds, + PaymentStatusIds = model.Filter.PaymentStatusIds, + ShippingStatusIds = model.Filter.ShippingStatusIds, + CustomerRoleIds = model.Filter.CustomerRoleIds + }; - profile.Filtering = XmlHelper.Serialize(filter); + profile.Filtering = XmlHelper.Serialize(filter); + } // provider configuration profile.ProviderConfigData = null; diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 91b4f715d6..d7d900f71c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -94,9 +94,16 @@ @Html.SmartLabelFor(x => x.Provider.FriendlyName)
    - - @Model.Provider.FriendlyName.NaIfEmpty() - (@(Model.Provider.SystemName.NaIfEmpty())) + @if (Model.Provider.ConfigurationUrl.HasValue()) + { + + @Model.Provider.FriendlyName.NaIfEmpty() + (@(Model.Provider.SystemName.NaIfEmpty())) + } + else + { + @Model.Provider.FriendlyName.NaIfEmpty() (@(Model.Provider.SystemName.NaIfEmpty())) + }
    - @Html.SmartLabelFor(x => x.Provider.Author) - - @Html.DisplayFor(x => x.Provider.Author) -
    - @Html.SmartLabelFor(x => x.Provider.Version) - - @Html.DisplayFor(x => x.Provider.Version) -
    + @Html.SmartLabelFor(x => x.Provider.Author) + + @Html.DisplayFor(x => x.Provider.Author) +
    + @Html.SmartLabelFor(x => x.Provider.Version) + + @Html.DisplayFor(x => x.Provider.Version) +
    @Html.SmartLabelFor(x => x.Provider.EntityTypeName) From dd16042663fbc42f2d7d0a1540ee57d2872cca2a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 9 Oct 2015 18:10:52 +0200 Subject: [PATCH 120/874] Unit testing --- .../Infrastructure/AutoMapperStartupTask.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs index a4257da318..b8b44b146e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs @@ -616,11 +616,12 @@ public void Execute() Mapper.CreateMap() .ForMember(dest => dest.ActiveShippingRateComputationMethodSystemNames, mo => mo.Ignore()) .ForMember(dest => dest.ReturnValidOptionsIfThereAreAny, mo => mo.Ignore()); - Mapper.CreateMap() + Mapper.CreateMap() .ForMember(dest => dest.AvailableSubCategoryDisplayTypes, mo => mo.Ignore()) .ForMember(dest => dest.AvailablePriceDisplayTypes, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableDefaultViewModes, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableDeliveryTimes, mo => mo.Ignore()); + .ForMember(dest => dest.AvailableDefaultViewModes, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableDeliveryTimes, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableSortOrderModes, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.PageShareCode, mo => mo.Ignore()) .ForMember(dest => dest.DefaultProductRatingValue, mo => mo.Ignore()) @@ -660,7 +661,9 @@ public void Execute() .ForMember(dest => dest.DefaultImageQuality, mo => mo.Ignore()) .ForMember(dest => dest.MultipleThumbDirectories, mo => mo.Ignore()) .ForMember(dest => dest.AutoCompleteSearchThumbPictureSize, mo => mo.Ignore()); - Mapper.CreateMap(); + Mapper.CreateMap() + .ForMember(dest => dest.AvailableCustomerNumberMethods, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableCustomerNumberVisibilities, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.HashedPasswordFormat, mo => mo.Ignore()) .ForMember(dest => dest.PasswordMinLength, mo => mo.Ignore()) From 2dda8b36eba139a5dfbdbbec822e2d00a590a3e7 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 10 Oct 2015 02:22:30 +0200 Subject: [PATCH 121/874] (Perf) Operating on large sets of AttributeCombinations (> 10.000) MUCH faster now --- .../SmartStore.Core/Caching/AspNetCache.cs | 15 +- .../Caching/DefaultCacheManager.cs | 26 ++- .../SmartStore.Core/Caching/RequestCache.cs | 11 +- .../SmartStore.Core/Data/DbContextScope.cs | 45 ++++- .../SmartStore.Core/Data/IDbContext.cs | 8 +- .../Data/IDbContextExtensions.cs | 122 ++++++++++++ .../SmartStore.Data/ObjectContextBase.cs | 14 +- .../Catalog/CategoryService.cs | 2 +- .../Catalog/CopyProductService.cs | 8 +- .../Catalog/IProductAttributeParser.cs | 26 +-- .../Catalog/IProductAttributeService.cs | 8 + .../Catalog/PriceCalculationService.cs | 7 +- .../Catalog/ProductAttributeParser.cs | 176 +++++++++++------- .../Catalog/ProductAttributeService.cs | 126 +++++++------ .../Catalog/ProductExtensions.cs | 2 +- .../Catalog/ProductService.cs | 2 +- .../ExportImport/ExportManager.cs | 66 +++---- .../Orders/ShoppingCartExtensions.cs | 2 +- .../Orders/ShoppingCartService.cs | 7 +- .../Shipping/ShippingService.cs | 2 +- .../Controllers/ProductController.cs | 18 +- .../Controllers/CatalogHelper.cs | 8 +- .../Controllers/ProductController.cs | 4 +- 23 files changed, 459 insertions(+), 246 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Caching/AspNetCache.cs b/src/Libraries/SmartStore.Core/Caching/AspNetCache.cs index e50f69ed08..e75ef70b3b 100644 --- a/src/Libraries/SmartStore.Core/Caching/AspNetCache.cs +++ b/src/Libraries/SmartStore.Core/Caching/AspNetCache.cs @@ -13,12 +13,6 @@ public partial class AspNetCache : ICache { private const string REGION_NAME = "$$SmartStoreNET$$"; - // AspNetCache object does not have a ContainsKey() method: - // Therefore we put a special string into cache if value is null, - // otherwise our 'Contains()' would always return false, - // which is bad if we intentionally wanted to save NULL values. - private const string FAKE_NULL = "__[NULL]__"; - public IEnumerable> Entries { get @@ -40,12 +34,7 @@ public object Get(string key) if (HttpRuntime.Cache == null) return null; - var value = HttpRuntime.Cache.Get(BuildKey(key)); - - if (value.Equals(FAKE_NULL)) - return null; - - return value; + return HttpRuntime.Cache.Get(BuildKey(key)); } public void Set(string key, object value, int? cacheTime) @@ -62,7 +51,7 @@ public void Set(string key, object value, int? cacheTime) absoluteExpiration = DateTime.UtcNow + TimeSpan.FromMinutes(cacheTime.Value); } - HttpRuntime.Cache.Insert(key, value ?? FAKE_NULL, null, absoluteExpiration, Cache.NoSlidingExpiration); + HttpRuntime.Cache.Insert(key, value, null, absoluteExpiration, Cache.NoSlidingExpiration); } public bool Contains(string key) diff --git a/src/Libraries/SmartStore.Core/Caching/DefaultCacheManager.cs b/src/Libraries/SmartStore.Core/Caching/DefaultCacheManager.cs index 9ecc1425fa..ed49180ca8 100644 --- a/src/Libraries/SmartStore.Core/Caching/DefaultCacheManager.cs +++ b/src/Libraries/SmartStore.Core/Caching/DefaultCacheManager.cs @@ -21,7 +21,12 @@ public partial class CacheManager : ICacheManager where TCache : ICache private readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); private readonly ICache _cache; - public CacheManager(Func fn) + // Wwe put a special string into cache if value is null, + // otherwise our 'Contains()' would always return false, + // which is bad if we intentionally wanted to save NULL values. + private const string FAKE_NULL = "__[NULL]__"; + + public CacheManager(Func fn) { this._cache = fn(typeof(TCache)); } @@ -32,7 +37,7 @@ public T Get(string key, Func acquirer, int? cacheTime = null) if (_cache.Contains(key)) { - return (T)_cache.Get(key); + return GetExisting(key); } using (EnterReadLock()) @@ -46,19 +51,26 @@ public T Get(string key, Func acquirer, int? cacheTime = null) } } + return GetExisting(key); + } + + private T GetExisting(string key) + { + var value = _cache.Get(key); + + if (value.Equals(FAKE_NULL)) + return default(T); + return (T)_cache.Get(key); - } + } public void Set(string key, object value, int? cacheTime = null) { Guard.ArgumentNotEmpty(() => key); - - if (value == null) - return; using (EnterWriteLock()) { - _cache.Set(key, value, cacheTime); + _cache.Set(key, value ?? FAKE_NULL, cacheTime); } } diff --git a/src/Libraries/SmartStore.Core/Caching/RequestCache.cs b/src/Libraries/SmartStore.Core/Caching/RequestCache.cs index a293c6cd4f..a509f3d76b 100644 --- a/src/Libraries/SmartStore.Core/Caching/RequestCache.cs +++ b/src/Libraries/SmartStore.Core/Caching/RequestCache.cs @@ -64,13 +64,10 @@ public void Set(string key, object value, int? cacheTime) key = BuildKey(key); - if (value != null) - { - if (items.Contains(key)) - items[key] = value; - else - items.Add(key, value); - } + if (items.Contains(key)) + items[key] = value; + else + items.Add(key, value); } public bool Contains(string key) diff --git a/src/Libraries/SmartStore.Core/Data/DbContextScope.cs b/src/Libraries/SmartStore.Core/Data/DbContextScope.cs index 9113be55d5..93a7552c50 100644 --- a/src/Libraries/SmartStore.Core/Data/DbContextScope.cs +++ b/src/Libraries/SmartStore.Core/Data/DbContextScope.cs @@ -1,17 +1,22 @@ using System; using SmartStore.Core.Infrastructure; +using System.Linq.Expressions; +using System.Collections.Generic; +using System.Linq; namespace SmartStore.Core.Data { public class DbContextScope : IDisposable { - private readonly bool _autoDetectChangesEnabled; + private readonly IDbContext _ctx; + private readonly bool _autoDetectChangesEnabled; private readonly bool _proxyCreationEnabled; private readonly bool _validateOnSaveEnabled; private readonly bool _forceNoTracking; private readonly bool _hooksEnabled; private readonly bool _autoCommit; - private readonly IDbContext _ctx; + private readonly bool _lazyLoading; + public DbContextScope(IDbContext ctx = null, bool? autoDetectChanges = null, @@ -19,7 +24,8 @@ public DbContextScope(IDbContext ctx = null, bool? validateOnSave = null, bool? forceNoTracking = null, bool? hooksEnabled = null, - bool? autoCommit = null) + bool? autoCommit = null, + bool? lazyLoading = null) { _ctx = ctx ?? EngineContext.Current.Resolve(); _autoDetectChangesEnabled = _ctx.AutoDetectChangesEnabled; @@ -28,6 +34,7 @@ public DbContextScope(IDbContext ctx = null, _forceNoTracking = _ctx.ForceNoTracking; _hooksEnabled = _ctx.HooksEnabled; _autoCommit = _ctx.AutoCommitEnabled; + _lazyLoading = _ctx.LazyLoadingEnabled; if (autoDetectChanges.HasValue) _ctx.AutoDetectChangesEnabled = autoDetectChanges.Value; @@ -46,7 +53,36 @@ public DbContextScope(IDbContext ctx = null, if (autoCommit.HasValue) _ctx.AutoCommitEnabled = autoCommit.Value; - } + + if (lazyLoading.HasValue) + _ctx.LazyLoadingEnabled = lazyLoading.Value; + } + + public IDbContext DbContext + { + get { return _ctx; } + } + + public void LoadCollection( + TEntity entity, + Expression>> navigationProperty, + bool force = false, + Func, IQueryable> queryAction = null) + where TEntity : BaseEntity + where TCollection : BaseEntity + { + _ctx.LoadCollection(entity, navigationProperty, force, queryAction); + } + + public void LoadReference( + TEntity entity, + Expression> navigationProperty, + bool force = false) + where TEntity : BaseEntity + where TProperty : BaseEntity + { + _ctx.LoadReference(entity, navigationProperty, force); + } public int Commit() { @@ -61,6 +97,7 @@ public void Dispose() _ctx.ForceNoTracking = _forceNoTracking; _ctx.HooksEnabled = _hooksEnabled; _ctx.AutoCommitEnabled = _autoCommit; + _ctx.LazyLoadingEnabled = _lazyLoading; } } diff --git a/src/Libraries/SmartStore.Core/Data/IDbContext.cs b/src/Libraries/SmartStore.Core/Data/IDbContext.cs index 4d3c85dc05..cec8a1a2ce 100644 --- a/src/Libraries/SmartStore.Core/Data/IDbContext.cs +++ b/src/Libraries/SmartStore.Core/Data/IDbContext.cs @@ -1,7 +1,10 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Data.Entity; +using System.Linq; +using System.Linq.Expressions; using System.Threading.Tasks; namespace SmartStore.Core.Data @@ -42,7 +45,8 @@ IList ExecuteStoredProcedureList(string commandText, params ob // increasing performance on bulk operations bool ProxyCreationEnabled { get; set; } - bool AutoDetectChangesEnabled { get; set; } + bool LazyLoadingEnabled { get; set; } + bool AutoDetectChangesEnabled { get; set; } bool ValidateOnSaveEnabled { get; set; } bool HooksEnabled { get; set; } bool HasChanges { get; } diff --git a/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs b/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs index f0357af6f9..0889cd6684 100644 --- a/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs +++ b/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using SmartStore.Core; using SmartStore.Core.Data; +using System.Linq; +using System.Linq.Expressions; +using System.Data.Entity; namespace SmartStore { @@ -45,5 +48,124 @@ public static bool SetToUnchanged(this IDbContext ctx, TEntity entity) } } + public static IQueryable QueryForCollection( + this IDbContext ctx, + TEntity entity, + Expression>> navigationProperty) + where TEntity : BaseEntity + where TCollection : BaseEntity + { + Guard.ArgumentNotNull(() => entity); + Guard.ArgumentNotNull(() => navigationProperty); + + var dbContext = ctx as DbContext; + if (dbContext == null) + { + throw new NotSupportedException("The IDbContext instance does not inherit from DbContext (EF)"); + } + + return dbContext.Entry(entity).Collection(navigationProperty).Query(); + } + + public static IQueryable QueryForReference( + this IDbContext ctx, + TEntity entity, + Expression> navigationProperty) + where TEntity : BaseEntity + where TProperty : BaseEntity + { + Guard.ArgumentNotNull(() => entity); + Guard.ArgumentNotNull(() => navigationProperty); + + var dbContext = ctx as DbContext; + if (dbContext == null) + { + throw new NotSupportedException("The IDbContext instance does not inherit from DbContext (EF)"); + } + + return dbContext.Entry(entity).Reference(navigationProperty).Query(); + } + + public static void LoadCollection( + this IDbContext ctx, + TEntity entity, + Expression>> navigationProperty, + bool force = false, + Func, IQueryable> queryAction = null) + where TEntity : BaseEntity + where TCollection : BaseEntity + { + Guard.ArgumentNotNull(() => entity); + Guard.ArgumentNotNull(() => navigationProperty); + + var dbContext = ctx as DbContext; + if (dbContext == null) + { + throw new NotSupportedException("The IDbContext instance does not inherit from DbContext (EF)"); + } + + var entry = dbContext.Entry(entity); + var collection = entry.Collection(navigationProperty); + + if (force) + { + collection.IsLoaded = false; + } + + if (!collection.IsLoaded) + { + if (queryAction != null || ctx.ForceNoTracking) + { + var query = !ctx.ForceNoTracking + ? collection.Query() + : collection.Query().AsNoTracking(); + + var myQuery = queryAction != null + ? queryAction(query) + : query; + + collection.CurrentValue = myQuery.ToList(); + } + else + { + collection.Load(); + } + + collection.IsLoaded = true; + } + } + + public static void LoadReference( + this IDbContext ctx, + TEntity entity, + Expression> navigationProperty, + bool force = false) + where TEntity : BaseEntity + where TProperty : BaseEntity + { + Guard.ArgumentNotNull(() => entity); + Guard.ArgumentNotNull(() => navigationProperty); + + var dbContext = ctx as DbContext; + if (dbContext == null) + { + throw new NotSupportedException("The IDbContext instance does not inherit from DbContext (EF)"); + } + + var entry = dbContext.Entry(entity); + var reference = entry.Reference(navigationProperty); + + if (force) + { + reference.IsLoaded = false; + } + + if (!reference.IsLoaded) + { + reference.Load(); + reference.IsLoaded = true; + } + } + } } diff --git a/src/Libraries/SmartStore.Data/ObjectContextBase.cs b/src/Libraries/SmartStore.Data/ObjectContextBase.cs index da5cbe2425..5fff7f7588 100644 --- a/src/Libraries/SmartStore.Data/ObjectContextBase.cs +++ b/src/Libraries/SmartStore.Data/ObjectContextBase.cs @@ -422,6 +422,18 @@ public bool ProxyCreationEnabled } } + public bool LazyLoadingEnabled + { + get + { + return this.Configuration.LazyLoadingEnabled; + } + set + { + this.Configuration.LazyLoadingEnabled = value; + } + } + public bool ForceNoTracking { get; set; } public bool AutoCommitEnabled { get; set; } @@ -551,7 +563,7 @@ public void ReloadEntity(TEntity entity) where TEntity : BaseEntity this.Entry(entity).Reload(); } - private string FormatValidationExceptionMessage(IEnumerable results) + private string FormatValidationExceptionMessage(IEnumerable results) { var sb = new StringBuilder(); sb.Append("Entity validation failed" + Environment.NewLine); diff --git a/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs b/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs index 03a746bd57..1828ab79c7 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs @@ -120,7 +120,7 @@ private void DeleteAllCategories(IList categories, bool delete) var childCategories = GetAllCategoriesByParentCategoryId(category.Id, true); DeleteAllCategories(childCategories, delete); - } + } } #endregion diff --git a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs index 7028306416..cbf04229fb 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs @@ -6,6 +6,7 @@ using SmartStore.Services.Media; using SmartStore.Services.Seo; using SmartStore.Services.Stores; +using SmartStore.Core.Data; namespace SmartStore.Services.Catalog { @@ -411,6 +412,11 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli } // attribute combinations + using (var scope = new DbContextScope(lazyLoading: false, forceNoTracking: true)) + { + scope.LoadCollection(product, (Product p) => p.ProductVariantAttributeCombinations); + } + foreach (var combination in product.ProductVariantAttributeCombinations) { //generate new AttributesXml according to new value IDs @@ -471,7 +477,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli BasePriceAmount = combination.BasePriceAmount, BasePriceBaseAmount = combination.BasePriceBaseAmount, DeliveryTimeId = combination.DeliveryTimeId, - QuantityUnitId = combination.QuantityUnitId, + QuantityUnitId = combination.QuantityUnitId, IsActive = combination.IsActive //IsDefaultCombination = combination.IsDefaultCombination }; diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs index b1da4c5396..bb3bab22f4 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs @@ -54,18 +54,16 @@ public partial interface IProductAttributeParser /// /// The attributes of the first product /// The attributes of the second product - /// Collection of already loaded product attribute mappings to reduce database round trips /// Result - bool AreProductAttributesEqual(string attributeXml1, string attributeXml2, IEnumerable attributes = null); + bool AreProductAttributesEqual(string attributeXml1, string attributeXml2); /// /// Finds a product variant attribute combination by attributes stored in XML /// /// Product identifier /// XML formatted attributes - /// Collection of already loaded product attribute mappings to reduce database round trips /// Found product variant attribute combination - ProductVariantAttributeCombination FindProductVariantAttributeCombination(int productId, string attributesXml, IEnumerable attributes = null); + ProductVariantAttributeCombination FindProductVariantAttributeCombination(int productId, string attributesXml); /// /// Deserializes attribute data from an URL query string @@ -115,24 +113,4 @@ void GetGiftCardAttribute(string attributesXml, out string recipientName, #endregion } - - public static class IProductAttributeParserExtensions - { - /// - /// Finds a product variant attribute combination by attributes stored in XML - /// - /// Product - /// XML formatted attributes - /// Found product variant attribute combination - public static ProductVariantAttributeCombination FindProductVariantAttributeCombination( - this IProductAttributeParser service, - Product product, - string attributesXml, - IEnumerable attributes = null) - { - Guard.ArgumentNotNull(() => product); - - return service.FindProductVariantAttributeCombination(product.Id, attributesXml, attributes); - } - } } diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs index dadef55189..eacf65250e 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeService.cs @@ -158,6 +158,14 @@ public partial interface IProductAttributeService /// Product variant attribute combination collection IPagedList GetAllProductVariantAttributeCombinations(int productId, int pageIndex, int pageSize, bool untracked = true); + /// + /// Gets a distinct list of picture identifiers. + /// Only pictures that are explicitly assigned to combinations are taken into account. + /// + /// Product id + /// Picture ids + IList GetAllProductVariantAttributeCombinationPictureIds(int productId); + /// /// Gets product variant attribute combinations by multiple product identifiers /// diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs index 498f356868..656abef121 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs @@ -235,7 +235,7 @@ protected virtual decimal GetPreselectedPrice(Product product, PriceCalculationC var combinations = context.AttributeCombinations.Load(product.Id); - var selectedCombination = combinations.FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, attributeXml, attributes)); + var selectedCombination = combinations.FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, attributeXml)); if (selectedCombination != null && selectedCombination.IsActive && selectedCombination.Price.HasValue) { @@ -575,6 +575,7 @@ public virtual decimal GetProductCost(Product product, string attributesXml) _productAttributeParser .ParseProductVariantAttributeValues(attributesXml) .Where(x => x.ValueType == ProductVariantAttributeValueType.ProductLinkage) + .ToList() .Each(x => { var linkedProduct = _productService.GetProductById(x.LinkedProductId); @@ -743,7 +744,7 @@ public virtual decimal GetUnitPrice(OrganizedShoppingCartItem shoppingCartItem, product.MergeWithCombination(shoppingCartItem.Item.AttributesXml, _productAttributeParser); decimal attributesTotalPrice = decimal.Zero; - var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml); + var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml).ToList(); if (pvaValues != null) { @@ -790,7 +791,7 @@ public virtual decimal GetDiscountAmount(OrganizedShoppingCartItem shoppingCartI { decimal attributesTotalPrice = decimal.Zero; - var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml); + var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml).ToList(); foreach (var pvaValue in pvaValues) { attributesTotalPrice += GetProductVariantAttributeValuePriceAdjustment(pvaValue); diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs index c576ca7aed..7aa38efe7f 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Web; using System.Xml; +using System.Xml.Linq; using Newtonsoft.Json; using SmartStore.Collections; using SmartStore.Core.Domain.Catalog; @@ -70,50 +71,86 @@ private IEnumerable ParseProductVariantAttributeIds(string attributesXml) finally { } } - public virtual Multimap DeserializeProductVariantAttributes(string attributesXml) - { - var attrs = new Multimap(); - if (String.IsNullOrEmpty(attributesXml)) - return attrs; + //public virtual Multimap DeserializeProductVariantAttributes(string attributesXml) + //{ + // var attrs = new Multimap(); + // if (String.IsNullOrEmpty(attributesXml)) + // return attrs; + + // try + // { + // var xmlDoc = new XmlDocument(); + // xmlDoc.LoadXml(attributesXml); + + // var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductVariantAttribute"); + // foreach (var node1 in nodeList1.Cast()) + // { + // string sid = node1.GetAttribute("ID").Trim(); + // if (sid.HasValue()) + // { + // int id = 0; + // if (int.TryParse(sid, out id)) + // { + + // var nodeList2 = node1.SelectNodes(@"ProductVariantAttributeValue/Value").Cast(); + // foreach (var node2 in nodeList2) + // { + // string value = node2.InnerText.Trim(); + // attrs.Add(id, value); + // } + // } + // } + // } + // } + // catch (Exception exc) + // { + // Debug.Write(exc.ToString()); + // } + + // return attrs; + //} + + public virtual Multimap DeserializeProductVariantAttributes(string attributesXml) + { + var attrs = new Multimap(); + if (String.IsNullOrEmpty(attributesXml)) + return attrs; - try - { - var xmlDoc = new XmlDocument(); - xmlDoc.LoadXml(attributesXml); + try + { + var doc = XDocument.Parse(attributesXml); - var nodeList1 = xmlDoc.SelectNodes(@"//Attributes/ProductVariantAttribute"); - foreach (var node1 in nodeList1.Cast()) - { - string sid = node1.GetAttribute("ID").Trim(); - if (sid.HasValue()) - { - int id = 0; - if (int.TryParse(sid, out id)) - { - - var nodeList2 = node1.SelectNodes(@"ProductVariantAttributeValue/Value").Cast(); - foreach (var node2 in nodeList2) - { - string value = node2.InnerText.Trim(); - attrs.Add(id, value); - } - } - } - } - } - catch (Exception exc) - { - Debug.Write(exc.ToString()); - } + // Attributes/ProductVariantAttribute + foreach (var node1 in doc.Descendants("ProductVariantAttribute")) + { + string sid = node1.Attribute("ID").Value; + if (sid.HasValue()) + { + int id = 0; + if (int.TryParse(sid, out id)) + { + // ProductVariantAttributeValue/Value + foreach (var node2 in node1.Descendants("Value")) + { + attrs.Add(id, node2.Value); + } + } + } + } + } + catch (Exception exc) + { + Debug.Write(exc.ToString()); + } - return attrs; - } + return attrs; + } - /// - /// Gets selected product variant attributes - /// - /// Attributes - /// Selected product variant attributes + /// + /// Gets selected product variant attributes + /// + /// Attributes + /// Selected product variant attributes public virtual IList ParseProductVariantAttributes(string attributesXml) { var ids = ParseProductVariantAttributeIds(attributesXml); @@ -210,46 +247,43 @@ public virtual string AddProductAttribute(string attributesXml, ProductVariantAt return pva.AddProductAttribute(attributesXml, value); } - public virtual bool AreProductAttributesEqual(string attributeXml1, string attributeXml2, IEnumerable attributes = null) + public virtual bool AreProductAttributesEqual(string attributeXml1, string attributeXml2) { if (attributeXml1.IsCaseInsensitiveEqual(attributeXml2)) return true; - var attributes1 = DeserializeProductVariantAttributes(attributeXml1); + var attributes1 = DeserializeProductVariantAttributes(attributeXml1); var attributes2 = DeserializeProductVariantAttributes(attributeXml2); if (attributes1.Count != attributes2.Count) - return false; - - IEnumerable pvaCollection1 = null; - IEnumerable pvaCollection2 = null; - - pvaCollection1 = _productAttributeService.GetProductVariantAttributesByIds(attributes2.Keys, attributes); - - if (attributes2.Keys.SequenceEqual(attributes1.Keys)) // often the case - pvaCollection2 = pvaCollection1; - else - pvaCollection2 = _productAttributeService.GetProductVariantAttributesByIds(attributes1.Keys, attributes); + return false; - foreach (var pva1 in pvaCollection1) + foreach (var kvp in attributes1) { - foreach (var pva2 in pvaCollection2) + if (!attributes2.ContainsKey(kvp.Key)) { - if (pva1.Id == pva2.Id) - { - var pvaValues1 = attributes2[pva1.Id]; - var pvaValues2 = attributes1[pva2.Id]; + // the second list does not contain this id: not equal! + return false; + } - if (pvaValues1.Count != pvaValues2.Count) - return false; + // compare the values + var values1 = kvp.Value; + var values2 = attributes2[kvp.Key]; - foreach (string value1 in pvaValues1) - { - string str1 = value1.TrimSafe(); + if (values1.Count != values2.Count) + { + // number of values differ: not equal! + return false; + } - if (!pvaValues2.Any(x => x.TrimSafe().IsCaseInsensitiveEqual(str1))) - return false; - } + foreach (var value1 in values1) + { + var str1 = value1.TrimSafe(); + + if (!values2.Any(x => x.TrimSafe().IsCaseInsensitiveEqual(str1))) + { + // the second values list for this attribute does not contain this value: not equal! + return false; } } } @@ -259,13 +293,13 @@ public virtual bool AreProductAttributesEqual(string attributeXml1, string attri public virtual ProductVariantAttributeCombination FindProductVariantAttributeCombination( int productId, - string attributesXml, - IEnumerable attributes = null) + string attributesXml) { if (attributesXml.IsEmpty()) return null; - var cacheKey = ATTRIBUTECOMBINATION_BY_ID_HASH.FormatInvariant(productId, attributesXml.Hash(Encoding.UTF8)); + var attributesHash = attributesXml.Hash(Encoding.UTF8); + var cacheKey = ATTRIBUTECOMBINATION_BY_ID_HASH.FormatInvariant(productId, attributesHash); var result = _cacheManager.Get(cacheKey, () => { @@ -283,7 +317,7 @@ public virtual ProductVariantAttributeCombination FindProductVariantAttributeCom foreach (var combination in combinations) { - bool attributesEqual = AreProductAttributesEqual(combination.AttributesXml, attributesXml, attributes); + bool attributesEqual = AreProductAttributesEqual(combination.AttributesXml, attributesXml); if (attributesEqual) return _productAttributeService.GetProductVariantAttributeCombinationById(combination.Id); } diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs index 282b834630..704ab41c3f 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs @@ -30,7 +30,7 @@ public partial class ProductAttributeService : IProductAttributeService private readonly IRepository _productAttributeRepository; private readonly IRepository _productVariantAttributeRepository; - private readonly IRepository _productVariantAttributeCombinationRepository; + private readonly IRepository _pvacRepository; private readonly IRepository _productVariantAttributeValueRepository; private readonly IRepository _productBundleItemAttributeFilterRepository; private readonly IEventPublisher _eventPublisher; @@ -44,13 +44,13 @@ public partial class ProductAttributeService : IProductAttributeService /// Cache manager /// Product attribute repository /// Product variant attribute mapping repository - /// Product variant attribute combination repository + /// Product variant attribute combination repository /// Product variant attribute value repository /// Event published public ProductAttributeService(ICacheManager cacheManager, IRepository productAttributeRepository, IRepository productVariantAttributeRepository, - IRepository productVariantAttributeCombinationRepository, + IRepository pvacRepository, IRepository productVariantAttributeValueRepository, IRepository productBundleItemAttributeFilterRepository, IEventPublisher eventPublisher, @@ -59,7 +59,7 @@ public ProductAttributeService(ICacheManager cacheManager, _cacheManager = cacheManager; _productAttributeRepository = productAttributeRepository; _productVariantAttributeRepository = productVariantAttributeRepository; - _productVariantAttributeCombinationRepository = productVariantAttributeCombinationRepository; + _pvacRepository = pvacRepository; _productVariantAttributeValueRepository = productVariantAttributeValueRepository; _productBundleItemAttributeFilterRepository = productBundleItemAttributeFilterRepository; _eventPublisher = eventPublisher; @@ -405,7 +405,7 @@ public virtual void DeleteProductVariantAttributeCombination(ProductVariantAttri if (combination == null) throw new ArgumentNullException("combination"); - _productVariantAttributeCombinationRepository.Delete(combination); + _pvacRepository.Delete(combination); //event notification _eventPublisher.EntityDeleted(combination); @@ -425,7 +425,7 @@ public virtual IPagedList GetAllProductVaria string key = string.Format(PRODUCTVARIANTATTRIBUTES_COMBINATIONS_BY_ID_KEY, productId, 0, int.MaxValue); return _cacheManager.Get(key, () => { - var query = from pvac in (untracked ? _productVariantAttributeCombinationRepository.TableUntracked : _productVariantAttributeCombinationRepository.Table) + var query = from pvac in (untracked ? _pvacRepository.TableUntracked : _pvacRepository.Table) orderby pvac.Id where pvac.ProductId == productId select pvac; @@ -435,42 +435,42 @@ orderby pvac.Id }); } - //public virtual IList GetAllProductVariantAttributeCombinationPictureIds(int productId) - //{ - // var imageIds = new List(); + public virtual IList GetAllProductVariantAttributeCombinationPictureIds(int productId) + { + var pictureIds = new List(); - // if (productId == 0) - // return imageIds; + if (productId == 0) + return pictureIds; - // var query = from pvac in _productVariantAttributeCombinationRepository.TableUntracked - // where - // pvac.ProductId == productId - // && pvac.IsActive - // && !String.IsNullOrEmpty(pvac.AssignedPictureIds) - // select pvac.AssignedPictureIds; + var query = from pvac in _pvacRepository.TableUntracked + where + pvac.ProductId == productId + && pvac.IsActive + && !String.IsNullOrEmpty(pvac.AssignedPictureIds) + select pvac.AssignedPictureIds; - // var data = query.ToList(); - // if (data.Any()) - // { - // int id; - // var ids = string.Join(",", data).SplitSafe(",").Distinct(); + var data = query.ToList(); + if (data.Any()) + { + int id; + var ids = string.Join(",", data).SplitSafe(",").Distinct(); - // foreach (string str in ids) - // { - // if (int.TryParse(str, out id) && !imageIds.Exists(i => i == id)) - // imageIds.Add(id); - // } - // } + foreach (string str in ids) + { + if (int.TryParse(str, out id) && !pictureIds.Exists(i => i == id)) + pictureIds.Add(id); + } + } - // return imageIds; - //} + return pictureIds; + } public virtual Multimap GetProductVariantAttributeCombinations(int[] productIds) { Guard.ArgumentNotNull(() => productIds); var query = - from pvac in _productVariantAttributeCombinationRepository.TableUntracked + from pvac in _pvacRepository.TableUntracked where productIds.Contains(pvac.ProductId) select pvac; @@ -488,7 +488,7 @@ where productIds.Contains(pvac.ProductId) return null; var query = - from pvac in _productVariantAttributeCombinationRepository.Table + from pvac in _pvacRepository.Table where pvac.ProductId == productId && pvac.Price != null && pvac.IsActive orderby pvac.Price ascending select pvac.Price; @@ -502,7 +502,7 @@ public virtual ProductVariantAttributeCombination GetProductVariantAttributeComb if (productVariantAttributeCombinationId == 0) return null; - var combination = _productVariantAttributeCombinationRepository.GetById(productVariantAttributeCombinationId); + var combination = _pvacRepository.GetById(productVariantAttributeCombinationId); return combination; } @@ -511,7 +511,7 @@ public virtual ProductVariantAttributeCombination GetProductVariantAttributeComb if (sku.IsEmpty()) return null; - var combination = _productVariantAttributeCombinationRepository.Table.FirstOrDefault(x => x.Sku == sku); + var combination = _pvacRepository.Table.FirstOrDefault(x => x.Sku == sku); return combination; } @@ -525,7 +525,7 @@ public virtual void InsertProductVariantAttributeCombination(ProductVariantAttri // EnsureSingleDefaultVariant(combination); //} - _productVariantAttributeCombinationRepository.Insert(combination); + _pvacRepository.Insert(combination); //event notification _eventPublisher.EntityInserted(combination); @@ -560,7 +560,7 @@ public virtual void UpdateProductVariantAttributeCombination(ProductVariantAttri // } //} - _productVariantAttributeCombinationRepository.Update(combination); + _pvacRepository.Update(combination); //event notification _eventPublisher.EntityUpdated(combination); @@ -569,10 +569,7 @@ public virtual void UpdateProductVariantAttributeCombination(ProductVariantAttri public virtual void CreateAllProductVariantAttributeCombinations(Product product) { // delete all existing combinations - foreach(var itm in product.ProductVariantAttributeCombinations) - { - DeleteProductVariantAttributeCombination(itm); - } + _pvacRepository.DeleteAll(); var attributes = GetProductVariantAttributesByProductId(product.Id); if (attributes == null || attributes.Count <= 0) @@ -593,26 +590,43 @@ public virtual void CreateAllProductVariantAttributeCombinations(Product product { CombineAll(toCombine, resultMatrix, 0, tmp); - foreach (var values in resultMatrix) + using (var scope = new DbContextScope(ctx: _pvacRepository.Context, autoCommit: false, autoDetectChanges: false, validateOnSave: false, hooksEnabled: false)) { - string attrXml = ""; - foreach (var x in values) - { - attrXml = attributes[values.IndexOf(x)].AddProductAttribute(attrXml, x.Id.ToString()); + ProductVariantAttributeCombination combination = null; + + var idx = 0; + foreach (var values in resultMatrix) + { + idx++; + + string attrXml = ""; + for (var i = 0; i < values.Count; ++i) + { + var value = values[i]; + attrXml = attributes[i].AddProductAttribute(attrXml, value.Id.ToString()); + } + + combination = new ProductVariantAttributeCombination + { + ProductId = product.Id, + AttributesXml = attrXml, + StockQuantity = 10000, + AllowOutOfStockOrders = true, + IsActive = true + }; + + _pvacRepository.Insert(combination); } - var combination = new ProductVariantAttributeCombination + scope.Commit(); + + if (combination != null) { - ProductId = product.Id, - AttributesXml = attrXml, - StockQuantity = 10000, - AllowOutOfStockOrders = true, - IsActive = true - }; - - _productVariantAttributeCombinationRepository.Insert(combination); - _eventPublisher.EntityInserted(combination); + // Perf: publish event for last one only + _eventPublisher.EntityInserted(combination); + } } + } //foreach (var y in resultMatrix) { @@ -630,7 +644,7 @@ public virtual bool VariantHasAttributeCombinations(int productId) return false; var query = - from c in _productVariantAttributeCombinationRepository.Table + from c in _pvacRepository.Table where c.ProductId == productId select c; diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs index ec2b7fda41..1237446455 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductExtensions.cs @@ -29,7 +29,7 @@ public static ProductVariantAttributeCombination MergeWithCombination(this Produ if (selectedAttributes.IsEmpty()) return null; - // let's find appropriate record + // let's find appropriate record var combination = productAttributeParser.FindProductVariantAttributeCombination(product.Id, selectedAttributes); if (combination != null && combination.IsActive) diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs index e347939214..71fad8bab0 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs @@ -1211,7 +1211,7 @@ public virtual AdjustInventoryResult AdjustInventory(Product product, bool decre break; case ManageInventoryMethod.ManageStockByAttributes: { - var combination = _productAttributeParser.FindProductVariantAttributeCombination(product, attributesXml); + var combination = _productAttributeParser.FindProductVariantAttributeCombination(product.Id, attributesXml); if (combination != null) { result.StockQuantityOld = combination.StockQuantity; diff --git a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs index 4c48efc481..7a7a1dcd94 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/ExportManager.cs @@ -510,44 +510,44 @@ public virtual void WriteProductToXml(XmlWriter writer, Product product, XmlExpo } writer.WriteEndElement(); // ProductAttributes - using (var scope = new DbContextScope(proxyCreation: false, forceNoTracking: true)) + using (var scope = new DbContextScope(proxyCreation: false, forceNoTracking: true, lazyLoading: false)) { - var allCombinations = product.ProductVariantAttributeCombinations; + scope.LoadCollection(product, (Product p) => p.ProductVariantAttributeCombinations); + } - writer.WriteStartElement("ProductAttributeCombinations"); - foreach (var combination in allCombinations) + writer.WriteStartElement("ProductAttributeCombinations"); + foreach (var combination in product.ProductVariantAttributeCombinations) + { + writer.WriteStartElement("ProductAttributeCombination"); + + writer.Write("Id", combination.Id.ToString()); + writer.Write("StockQuantity", combination.StockQuantity.ToString()); + writer.Write("AllowOutOfStockOrders", combination.AllowOutOfStockOrders.ToString()); + writer.Write("AttributesXml", combination.AttributesXml, null, true); + writer.Write("Sku", combination.Sku); + writer.Write("Gtin", combination.Gtin); + writer.Write("ManufacturerPartNumber", combination.ManufacturerPartNumber); + writer.Write("Price", combination.Price.HasValue ? combination.Price.Value.ToString(culture) : ""); + writer.Write("Length", combination.Length.HasValue ? combination.Length.Value.ToString(culture) : ""); + writer.Write("Width", combination.Width.HasValue ? combination.Width.Value.ToString(culture) : ""); + writer.Write("Height", combination.Height.HasValue ? combination.Height.Value.ToString(culture) : ""); + writer.Write("BasePriceAmount", combination.BasePriceAmount.HasValue ? combination.BasePriceAmount.Value.ToString(culture) : ""); + writer.Write("BasePriceBaseAmount", combination.BasePriceBaseAmount.HasValue ? combination.BasePriceBaseAmount.Value.ToString() : ""); + writer.Write("DeliveryTimeId", combination.DeliveryTimeId.HasValue ? combination.DeliveryTimeId.Value.ToString() : ""); + writer.Write("IsActive", combination.IsActive.ToString()); + + WriteQuantityUnit(writer, context, combination.QuantityUnit); + + writer.WriteStartElement("Pictures"); + foreach (int pictureId in combination.GetAssignedPictureIds()) { - writer.WriteStartElement("ProductAttributeCombination"); - - writer.Write("Id", combination.Id.ToString()); - writer.Write("StockQuantity", combination.StockQuantity.ToString()); - writer.Write("AllowOutOfStockOrders", combination.AllowOutOfStockOrders.ToString()); - writer.Write("AttributesXml", combination.AttributesXml, null, true); - writer.Write("Sku", combination.Sku); - writer.Write("Gtin", combination.Gtin); - writer.Write("ManufacturerPartNumber", combination.ManufacturerPartNumber); - writer.Write("Price", combination.Price.HasValue ? combination.Price.Value.ToString(culture) : ""); - writer.Write("Length", combination.Length.HasValue ? combination.Length.Value.ToString(culture) : ""); - writer.Write("Width", combination.Width.HasValue ? combination.Width.Value.ToString(culture) : ""); - writer.Write("Height", combination.Height.HasValue ? combination.Height.Value.ToString(culture) : ""); - writer.Write("BasePriceAmount", combination.BasePriceAmount.HasValue ? combination.BasePriceAmount.Value.ToString(culture) : ""); - writer.Write("BasePriceBaseAmount", combination.BasePriceBaseAmount.HasValue ? combination.BasePriceBaseAmount.Value.ToString() : ""); - writer.Write("DeliveryTimeId", combination.DeliveryTimeId.HasValue ? combination.DeliveryTimeId.Value.ToString() : ""); - writer.Write("IsActive", combination.IsActive.ToString()); - - WriteQuantityUnit(writer, context, combination.QuantityUnit); - - writer.WriteStartElement("Pictures"); - foreach (int pictureId in combination.GetAssignedPictureIds()) - { - WritePicture(writer, context, _pictureService.GetPictureById(pictureId), _mediaSettings.ProductThumbPictureSize, _mediaSettings.ProductDetailsPictureSize); - } - writer.WriteEndElement(); // Pictures - - writer.WriteEndElement(); // ProductAttributeCombination + WritePicture(writer, context, _pictureService.GetPictureById(pictureId), _mediaSettings.ProductThumbPictureSize, _mediaSettings.ProductDetailsPictureSize); } - writer.WriteEndElement(); // ProductAttributeCombinations + writer.WriteEndElement(); // Pictures + + writer.WriteEndElement(); // ProductAttributeCombination } + writer.WriteEndElement(); // ProductAttributeCombinations writer.WriteStartElement("ProductPictures"); foreach (var productPicture in product.ProductPictures.OrderBy(x => x.DisplayOrder)) diff --git a/src/Libraries/SmartStore.Services/Orders/ShoppingCartExtensions.cs b/src/Libraries/SmartStore.Services/Orders/ShoppingCartExtensions.cs index 9054c7ffe7..84dad37b83 100644 --- a/src/Libraries/SmartStore.Services/Orders/ShoppingCartExtensions.cs +++ b/src/Libraries/SmartStore.Services/Orders/ShoppingCartExtensions.cs @@ -177,7 +177,7 @@ public static IList Organize(this IList GetShoppingCartItemAttributeWarnings( { if (combination == null) { - combination = _productAttributeParser.FindProductVariantAttributeCombination(product.Id, selectedAttributes, pva1Collection); + combination = _productAttributeParser.FindProductVariantAttributeCombination(product.Id, selectedAttributes); } if (combination != null && !combination.IsActive) @@ -555,7 +555,7 @@ public virtual IList GetShoppingCartItemAttributeWarnings( if (warnings.Count == 0) { - var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(selectedAttributes); + var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(selectedAttributes).ToList(); foreach (var pvaValue in pvaValues) { if (pvaValue.ValueType == ProductVariantAttributeValueType.ProductLinkage) @@ -598,7 +598,8 @@ public virtual bool AreAllAttributesForCombinationSelected(string selectedAttrib { Guard.ArgumentNotNull(() => product); - if (!product.ProductVariantAttributeCombinations.Any()) + var hasAttributeCombinations = _sciRepository.Context.QueryForCollection(product, (Product p) => p.ProductVariantAttributeCombinations).Any(); + if (!hasAttributeCombinations) return true; //selected attributes diff --git a/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs b/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs index 553156b912..1959d39d27 100644 --- a/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs +++ b/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs @@ -285,7 +285,7 @@ public virtual decimal GetShoppingCartItemWeight(OrganizedShoppingCartItem shopp if (!String.IsNullOrEmpty(shoppingCartItem.Item.AttributesXml)) { - var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml); + var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml).ToList(); foreach (var pvaValue in pvaValues) { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index a08835d4c2..9e6fe5df7b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Linq.Expressions; using System.Net.Mime; using System.Text; using System.Threading; @@ -81,6 +82,7 @@ public partial class ProductController : AdminControllerBase private readonly IDateTimeHelper _dateTimeHelper; private readonly IDiscountService _discountService; private readonly IProductAttributeService _productAttributeService; + private readonly IRepository _pvacRepository; private readonly IBackInStockSubscriptionService _backInStockSubscriptionService; private readonly IShoppingCartService _shoppingCartService; private readonly IProductAttributeFormatter _productAttributeFormatter; @@ -133,7 +135,8 @@ public ProductController( IDateTimeHelper dateTimeHelper, IDiscountService discountService, IProductAttributeService productAttributeService, - IBackInStockSubscriptionService backInStockSubscriptionService, + IRepository pvacRepository, + IBackInStockSubscriptionService backInStockSubscriptionService, IShoppingCartService shoppingCartService, IProductAttributeFormatter productAttributeFormatter, IProductAttributeParser productAttributeParser, @@ -179,6 +182,7 @@ public ProductController( this._dateTimeHelper = dateTimeHelper; this._discountService = discountService; this._productAttributeService = productAttributeService; + this._pvacRepository = pvacRepository; this._backInStockSubscriptionService = backInStockSubscriptionService; this._shoppingCartService = shoppingCartService; this._productAttributeFormatter = productAttributeFormatter; @@ -4172,10 +4176,6 @@ public ActionResult ProductVariantAttributeCombinationList(GridCommand command, if (product == null) throw new ArgumentException("No product found with the specified id"); - ////var allCombinations = new PagedList(product.ProductVariantAttributeCombinations.AsQueryable(), command.Page - 1, command.PageSize); - //var take = command.PageSize; - //var skip = (command.Page - 1) * take; - //var allCombinations = product.ProductVariantAttributeCombinations.Take(take).Skip(skip); var allCombinations = _productAttributeService.GetAllProductVariantAttributeCombinations(product.Id, command.Page - 1, command.PageSize); var productUrlTitle = _localizationService.GetResource("Common.OpenInShop"); @@ -4225,7 +4225,6 @@ public ActionResult ProductVariantAttributeCombinationList(GridCommand command, { Data = productVariantAttributesModel, Total = allCombinations.TotalCount, - //Total = allCombinations.Count() }; return new JsonResult @@ -4301,7 +4300,7 @@ public ActionResult AttributeCombinationCreatePopup(string btnId, string formId, warnings.AddRange(_shoppingCartService.GetShoppingCartItemAttributeWarnings(_workContext.CurrentCustomer, ShoppingCartType.ShoppingCart, product, attributeXml)); - if (_productAttributeParser.FindProductVariantAttributeCombination(product, attributeXml) != null) + if (_productAttributeParser.FindProductVariantAttributeCombination(product.Id, attributeXml) != null) { warnings.Add(_localizationService.GetResource("Admin.Catalog.Products.ProductVariantAttributes.AttributeCombinations.CombiExists")); } @@ -4411,10 +4410,7 @@ public ActionResult DeleteAllAttributeCombinations(ProductVariantAttributeCombin if (product == null) throw new ArgumentException("No product found with the specified id"); - foreach (var combination in product.ProductVariantAttributeCombinations) - { - _productAttributeService.DeleteProductVariantAttributeCombination(combination); - } + _pvacRepository.DeleteAll(); _productService.UpdateLowestAttributeCombinationPriceProperty(product); diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index 1a1d5dff8e..83d17f1efa 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Linq.Expressions; using System.Web; using System.Web.Mvc; using SmartStore.Collections; @@ -254,7 +255,7 @@ public ProductDetailsModel PrepareProductDetailsPageModel( if (productBundleItem == null) { - combinationPictureIds = product.ProductVariantAttributeCombinations.GetAllCombinationPictureIds(); + combinationPictureIds = _productAttributeService.GetAllProductVariantAttributeCombinationPictureIds(product.Id); if (combination == null && model.SelectedCombination != null) combination = model.SelectedCombination; } @@ -607,7 +608,7 @@ public ProductDetailsModel PrepareProductDetailModel( renderPrices: false, renderGiftCardAttributes: false, allowHyperlinks: false); } - model.SelectedCombination = _productAttributeParser.FindProductVariantAttributeCombination(product.Id, attributeXml, variantAttributes); + model.SelectedCombination = _productAttributeParser.FindProductVariantAttributeCombination(product.Id, attributeXml); if (model.SelectedCombination != null && model.SelectedCombination.IsActive == false) { @@ -641,7 +642,8 @@ public ProductDetailsModel PrepareProductDetailModel( { // cases where stock inventory is not functional. determined by what ShoppingCartService.GetStandardWarnings and ProductService.AdjustInventory is not handling. model.IsAvailable = true; - model.StockAvailability = !product.ProductVariantAttributeCombinations.Any() ? product.FormatStockMessage(_localizationService) : ""; + var hasAttributeCombinations = _services.DbContext.QueryForCollection(product, (Product p) => p.ProductVariantAttributeCombinations).Any(); + model.StockAvailability = !hasAttributeCombinations ? product.FormatStockMessage(_localizationService) : ""; } else if (model.IsAvailable) { diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index 0f3558f6ed..048274fc77 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -658,13 +658,13 @@ public ActionResult UpdateProductDetails(int productId, string itemType, int bun } else { - var allCombinationImageIds = product.ProductVariantAttributeCombinations.GetAllCombinationPictureIds(); + var allCombinationPictureIds = _productAttributeService.GetAllProductVariantAttributeCombinationPictureIds(product.Id); _helper.PrepareProductDetailsPictureModel( pictureModel, pictures, product.GetLocalized(x => x.Name), - allCombinationImageIds, + allCombinationPictureIds, false, bundleItem, m.SelectedCombination); From 1db1f108509d0ac28d7e375a3733ce09bb1a8a92 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 10 Oct 2015 11:56:59 +0200 Subject: [PATCH 122/874] Minor change --- src/Libraries/SmartStore.Core/Collections/LazyMultimap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Collections/LazyMultimap.cs b/src/Libraries/SmartStore.Core/Collections/LazyMultimap.cs index 59f22fa43e..3e1aada593 100644 --- a/src/Libraries/SmartStore.Core/Collections/LazyMultimap.cs +++ b/src/Libraries/SmartStore.Core/Collections/LazyMultimap.cs @@ -6,7 +6,7 @@ namespace SmartStore.Collections { /// - /// Structure that manages data keys and offers the combination of early and lazy data loading + /// Manages data keys and offers a combination of eager and lazy data loading /// public class LazyMultimap : Multimap { @@ -19,7 +19,7 @@ public class LazyMultimap : Multimap /// Constructor /// /// int[] keys like Entity.Id, Multimap> delegate to load data - /// Keys of early loaded data + /// Keys of eager loaded data public LazyMultimap(Func> load, IEnumerable collect = null) { _load = load; From dbf553fcce0bac5808091e13569786c7229f6256 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 12 Oct 2015 12:49:22 +0200 Subject: [PATCH 123/874] ExportSegmenter did not work as expected. Requires data queuing. --- src/Libraries/SmartStore.Core/PagedList.cs | 9 - .../DataExchange/ExportExecuteContext.cs | 18 +- .../ExportCategoryXmlProvider.cs | 4 +- .../ExportCustomerXlsxProvider.cs | 4 +- .../ExportCustomerXmlProvider.cs | 4 +- .../ExportManufacturerXmlProvider.cs | 4 +- .../ExportNewsSubscriptionCsvProvider.cs | 4 +- .../ExportProvider/ExportOrderXlsxProvider.cs | 4 +- .../ExportProvider/ExportOrderXmlProvider.cs | 4 +- .../ExportProductXlsxProvider.cs | 4 +- .../ExportProductXmlProvider.cs | 4 +- .../DataExchange/ExportSegmenter.cs | 307 ++++++++--------- .../ExportTask/ExportProfileTask.cs | 309 +++++++++--------- .../Providers/ProductExportXmlProvider.cs | 4 +- 14 files changed, 320 insertions(+), 363 deletions(-) diff --git a/src/Libraries/SmartStore.Core/PagedList.cs b/src/Libraries/SmartStore.Core/PagedList.cs index 51b876d568..ac5f185ef3 100644 --- a/src/Libraries/SmartStore.Core/PagedList.cs +++ b/src/Libraries/SmartStore.Core/PagedList.cs @@ -148,15 +148,6 @@ public PagedList(int pageIndex, int pageSize, int totalItemsCount) : base(pageIndex, pageSize, totalItemsCount) { } - - public PagedList(int offset, int limit, int pageIndex, int pageSize, int totalItemsCount) - : base( - offset > 0 && pageSize > 0 ? (offset / pageSize) + pageIndex : pageIndex, - pageSize, - limit > 0 ? limit : totalItemsCount - ) - { - } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs index 139d2381a1..1459463bfb 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs @@ -11,7 +11,7 @@ public interface IExportExecuteContext /// /// Provides the data to be exported /// - IExportSegmenter Data { get; } + IExportSegmenterConsumer Segmenter { get; } /// /// The store context to be used for the export @@ -118,7 +118,6 @@ public class ExportExecuteContext : IExportExecuteContext { private ExportExecuteResult _result; private CancellationToken _cancellation; - private IExportSegmenter _segmenter; private ExportAbortion _providerAbort; internal ExportExecuteContext(ExportExecuteResult result, CancellationToken cancellation, string folder) @@ -130,20 +129,7 @@ internal ExportExecuteContext(ExportExecuteResult result, CancellationToken canc CustomProperties = new Dictionary(); } - public IExportSegmenter Data - { - get - { - return _segmenter; - } - internal set - { - if (_segmenter != null) - (_segmenter as IExportExecuter).Dispose(); - - _segmenter = value; - } - } + public IExportSegmenterConsumer Segmenter { get; set; } public dynamic Store { get; internal set; } public dynamic Customer { get; internal set; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs index b9aea0d2fd..7e4e6f9fba 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs @@ -61,9 +61,9 @@ public void Execute(IExportExecuteContext context) writer.WriteStartElement("Categories"); writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + while (context.Abort == ExportAbortion.None && context.Segmenter.ReadNextSegment()) { - var segment = context.Data.CurrentSegment; + var segment = context.Segmenter.CurrentSegment; foreach (dynamic category in segment) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs index 4c911111c0..c2f4722f1a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs @@ -141,9 +141,9 @@ public void Execute(IExportExecuteContext context) int row = 2; - while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + while (context.Abort == ExportAbortion.None && context.Segmenter.ReadNextSegment()) { - var segment = context.Data.CurrentSegment; + var segment = context.Segmenter.CurrentSegment; foreach (dynamic customer in segment) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs index 9833dd47d2..cbf1c80719 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs @@ -61,9 +61,9 @@ public void Execute(IExportExecuteContext context) writer.WriteStartElement("Customers"); writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + while (context.Abort == ExportAbortion.None && context.Segmenter.ReadNextSegment()) { - var segment = context.Data.CurrentSegment; + var segment = context.Segmenter.CurrentSegment; foreach (dynamic customer in segment) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs index 3a04473ac7..ebe52eb5f9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs @@ -61,9 +61,9 @@ public void Execute(IExportExecuteContext context) writer.WriteStartElement("Manufacturers"); writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + while (context.Abort == ExportAbortion.None && context.Segmenter.ReadNextSegment()) { - var segment = context.Data.CurrentSegment; + var segment = context.Segmenter.CurrentSegment; foreach (dynamic manufacturer in segment) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs index 1c46311f8d..00400d64e9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs @@ -44,9 +44,9 @@ public void Execute(IExportExecuteContext context) using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) using (var writer = new StreamWriter(stream, Encoding.UTF8)) { - while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + while (context.Abort == ExportAbortion.None && context.Segmenter.ReadNextSegment()) { - var segment = context.Data.CurrentSegment; + var segment = context.Segmenter.CurrentSegment; foreach (dynamic subscriber in segment) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs index b5d215ef71..4cd8a5c5e2 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs @@ -139,9 +139,9 @@ public void Execute(IExportExecuteContext context) int row = 2; - while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + while (context.Abort == ExportAbortion.None && context.Segmenter.ReadNextSegment()) { - var segment = context.Data.CurrentSegment; + var segment = context.Segmenter.CurrentSegment; foreach (dynamic order in segment) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs index 89e07613eb..e04e6829bf 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs @@ -62,9 +62,9 @@ public void Execute(IExportExecuteContext context) writer.WriteStartElement("Orders"); writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + while (context.Abort == ExportAbortion.None && context.Segmenter.ReadNextSegment()) { - var segment = context.Data.CurrentSegment; + var segment = context.Segmenter.CurrentSegment; foreach (dynamic order in segment) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs index a5bef8cec9..516d05488a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs @@ -223,9 +223,9 @@ public void Execute(IExportExecuteContext context) cells[1, i + 1].Style.Font.Bold = true; } - while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + while (context.Abort == ExportAbortion.None && context.Segmenter.ReadNextSegment()) { - var segment = context.Data.CurrentSegment; + var segment = context.Segmenter.CurrentSegment; foreach (dynamic product in segment) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs index 5e8e648f46..a6894b4242 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs @@ -61,9 +61,9 @@ public void Execute(IExportExecuteContext context) writer.WriteStartElement("Products"); writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); - while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + while (context.Abort == ExportAbortion.None && context.Segmenter.ReadNextSegment()) { - var segment = context.Data.CurrentSegment; + var segment = context.Segmenter.CurrentSegment; foreach (dynamic product in segment) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs index eaf24b7505..65f62dc7f9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs @@ -6,238 +6,215 @@ namespace SmartStore.Services.DataExchange { - public interface IExportSegmenter + public interface IExportSegmenterConsumer { - /// - /// Number of processed records - /// - int RecordsCount { get; } - /// /// Total number of records /// - int RecordsTotal { get; } - - /// - /// Total number of segments - /// - int SegmentsTotal { get; } + int RecordTotal { get; } /// - /// Index of current segment + /// Gets current data segment /// - int SegmentIndex { get; } + List CurrentSegment { get; } /// - /// Index of current file + /// Reads the next segment /// - int FileIndex { get; } + /// + bool ReadNextSegment(); + } + internal interface IExportSegmenterProvider : IExportSegmenterConsumer, IDisposable + { /// - /// Read next data segment + /// Whether there is segmented data available /// - /// - bool ReadNextSegment(); + bool HasData { get; } /// - /// Gets the data of the current segment + /// Record per segment counter /// - ICollection CurrentSegment { get; } + int RecordPerSegmentCount { get; set; } } - internal interface IExportExecuter + public class ExportSegmenter : IExportSegmenterProvider where T : BaseEntity { - void Start(Func callback); - void Dispose(); - } + private Func> _load; + private Action> _loaded; + private Func> _convert; + private int _offset; + private int _limit; + private int _recordsPerSegment; - public class ExportSegmenter : IExportSegmenter, IExportExecuter, IDisposable where T : BaseEntity - { - private Func> _loadData; - private Func> _convertData; - private List _currentData; - private List _currentSegment; - private IPageable _pageable; - private int _pageIndexReset; - private bool _isBeginOfSegmentation; - - private int _recordsPerFile; - private int _fileIndex; - private int _fileRecordsSkip; - private bool _fileCaptured; - - private int _recordsPerFileCount; - private int _recordsCount; + private bool _isDataLoaded; + private int _countRecords; + + private IPageable _pages; + private Queue _data; public ExportSegmenter( - Func> loadData, - Func> convertData, - PagedList pageable, - int itemsPerFile) + Func> load, + Action> loaded, + Func> convert, + PagedList pages, + int offset, + int limit, + int recordsPerSegment) { - _loadData = loadData; - _convertData = convertData; - _pageable = pageable; - _recordsPerFile = itemsPerFile; - - _pageIndexReset = pageable.PageIndex; - _isBeginOfSegmentation = true; + _load = load; + _loaded = loaded; + _convert = convert; + _pages = pages; + _offset = offset; + _limit = limit; + _recordsPerSegment = recordsPerSegment; } - protected void ClearSegment() + /// + /// Whether there is segmented data available + /// + public bool HasData { - if (_currentSegment != null) + get { - _currentSegment.Clear(); - _currentSegment = null; - } + if (RecordCount >= _limit && _limit > 0) + return false; - if (_currentData != null) - { - _currentData.Clear(); - _currentData = null; - } - } + if (_data != null && _data.Count > 0) + return true; - public void Dispose() - { - ClearSegment(); - } + if (_data == null && _pages.IsFirstPage) + return true; - public int RecordsCount - { - get { return _recordsCount; } - } + if (_pages.HasNextPage) + return true; - public int RecordsTotal - { - get { return _pageable.TotalCount; } + return false; + } } - public int SegmentsTotal - { - get { return _pageable.TotalPages; } - } + /// + /// Record per segment counter + /// + public int RecordPerSegmentCount { get; set; } - public int SegmentIndex + /// + /// Number of processed records + /// + public int RecordCount { - get { return _isBeginOfSegmentation ? 0 : _pageable.PageIndex; } + get { return _countRecords - _offset; } } - public int FileIndex + /// + /// Total number of records + /// + public int RecordTotal { - get { return _fileIndex; } + get + { + if (_limit != 0 && _limit < _pages.TotalCount) + return _limit; + + return _pages.TotalCount - _offset; + } } - public void Start(Func callback) + /// + /// Gets current data segment + /// + public List CurrentSegment { - for (int oldFileIndex = 0; oldFileIndex < 9999999; ++oldFileIndex) + get { - _fileCaptured = false; + var records = new List(); - if (!callback()) - break; + while (_data.Count > 0) + { + var entity = _data.Dequeue(); + var skip = (++_countRecords < _offset && _offset > 0); - if (_fileIndex <= oldFileIndex) - break; - } + if (!skip) + { + foreach (var record in _convert(entity)) + { + records.Add(record); + } - Dispose(); - } + if (++RecordPerSegmentCount >= _recordsPerSegment && _recordsPerSegment > 0) + return records; + } - public void Reset() - { - //if (_pageable.PageIndex != 0 && _currentSegment != null) - //{ - // _currentSegment.Clear(); - // _currentSegment = null; - //} - - ClearSegment(); - - _isBeginOfSegmentation = true; - _pageable.PageIndex = _pageIndexReset; - _fileIndex = 0; - _fileRecordsSkip = 0; - _fileCaptured = false; - _recordsPerFileCount = 0; + if (RecordCount >= _limit && _limit > 0) + return records; + } + + return records; + } } + /// + /// Reads the next segment + /// + /// public bool ReadNextSegment() { - if (_fileCaptured) // do not write to current file anymore - { + if (RecordCount >= _limit && _limit > 0) return false; - } - if (_fileRecordsSkip > 0) // read rest of last segment for new file - { + if (RecordPerSegmentCount >= _recordsPerSegment && _recordsPerSegment > 0) + return false; + + // do not make the queue longer than necessary + if (_recordsPerSegment > 0 && _data != null && _data.Count >= _recordsPerSegment) return true; + + if (_isDataLoaded) + { + if (!_pages.HasNextPage) + return (_data != null && _data.Count > 0); + + ++_pages.PageIndex; + } + else + { + _isDataLoaded = true; } - ClearSegment(); + if (_data != null && _data.Count > 0) + { + var data = new List(_data); + data.AddRange(_load(_pages.PageIndex)); - if (_isBeginOfSegmentation) + _data = new Queue(data); + } + else { - _isBeginOfSegmentation = false; - _recordsCount = 0; - return _pageable.TotalCount > 0; + _data = new Queue(_load(_pages.PageIndex)); } - if (_pageable.HasNextPage) + // give provider the opportunity to make something with entity ids + if (_loaded != null) { - _pageable.PageIndex++; - return true; + _loaded(_data.AsReadOnly()); } - Reset(); - return false; + return (_data.Count > 0); } - public ICollection CurrentSegment + /// + /// Dispose and reset segmenter instance + /// + public void Dispose() { - get - { - var tmpCount = 0; - - if (_currentSegment == null) - { - _currentSegment = new List(); - } - - if (_currentData == null) - { - _currentData = _loadData(_pageable.PageIndex); - } - - foreach (var record in _currentData.Skip(_fileRecordsSkip)) - { - if (_recordsPerFile > 0 && _recordsPerFileCount >= _recordsPerFile) - { - _fileCaptured = true; - _fileIndex++; - _recordsPerFileCount = 0; - _fileRecordsSkip = tmpCount; - - return _currentSegment.AsReadOnly(); - } - - foreach (var obj in _convertData(record)) - { - _currentSegment.Add(obj); + if (_data != null) + _data.Clear(); - ++_recordsPerFileCount; - ++_recordsCount; - } - - ++tmpCount; - } - - _fileRecordsSkip = 0; - - return _currentSegment.AsReadOnly(); - } + _isDataLoaded = false; + _countRecords = 0; + RecordPerSegmentCount = 0; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 0764201d9e..734aefb360 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -240,6 +240,125 @@ private IPagedList GetProductPage(ExportProfileTaskContext ctx, int pag } } + private IExportSegmenterProvider CreateSegmenter(ExportProfileTaskContext ctx) + { + int unused; + var recordsPerFile = (ctx.IsPreview ? 0 : ctx.Profile.BatchSize); + var totalCount = ctx.RecordsPerStore.First(x => x.Key == ctx.Store.Id).Value; + + var pageable = new PagedList(ctx.PageIndex, ctx.PageSize, totalCount); + + switch (ctx.Provider.Value.EntityType) + { + case ExportEntityType.Product: + ctx.Export.Segmenter = new ExportSegmenter + ( + pageIndex => GetProducts(ctx, pageIndex), + entities => + { + // load data behind navigation properties for current queue in one go + ctx.DataContextProduct = new ExportDataContextProduct(entities, + x => _productAttributeService.GetProductVariantAttributesByProductIds(x, null), + x => _productAttributeService.GetProductVariantAttributeCombinations(x), + x => _productService.GetTierPricesByProductIds(x, (ctx.Projection.CurrencyId ?? 0) != 0 ? ctx.ContextCustomer : null, ctx.Store.Id), + x => _categoryService.GetProductCategoriesByProductIds(x), + x => _manufacturerService.GetProductManufacturersByProductIds(x), + x => _productService.GetProductPicturesByProductIds(x), + x => _productService.GetProductTagsByProductIds(x), + x => _productService.GetAppliedDiscountsByProductIds(x), + x => _productService.GetProductSpecificationAttributesByProductIds(x), + x => _productService.GetBundleItemsByProductIds(x, true) + ); + }, + entity => ConvertToExpando(ctx, entity), + pageable, ctx.Profile.Offset, ctx.Profile.Limit, recordsPerFile + ); + break; + + case ExportEntityType.Order: + ctx.Export.Segmenter = new ExportSegmenter + ( + pageIndex => GetOrders(ctx, pageIndex, ctx.PageSize, out unused), + entities => + { + ctx.DataContextOrder = new ExportDataContextOrder(entities, + x => _customerService.GetCustomersByIds(x), + x => _customerService.GetRewardPointsHistoriesByCustomerIds(x), + x => _addressesService.GetAddressByIds(x), + x => _orderService.GetOrderItemsByOrderIds(x), + x => _shipmentService.GetShipmentsByOrderIds(x) + ); + }, + entity => ConvertToExpando(ctx, entity), + pageable, ctx.Profile.Offset, ctx.Profile.Limit, recordsPerFile + ); + break; + + case ExportEntityType.Manufacturer: + ctx.Export.Segmenter = new ExportSegmenter + ( + pageIndex => GetManufacturers(ctx, pageIndex, ctx.PageSize, out unused), + entities => + { + ctx.DataContextManufacturer = new ExportDataContextManufacturer(entities, + x => _manufacturerService.GetProductManufacturersByManufacturerIds(x), + x => _pictureService.GetPicturesByIds(x) + ); + }, + entity => ConvertToExpando(ctx, entity), + pageable, ctx.Profile.Offset, ctx.Profile.Limit, recordsPerFile + ); + break; + + case ExportEntityType.Category: + ctx.Export.Segmenter = new ExportSegmenter + ( + pageIndex => GetCategories(ctx, pageIndex, ctx.PageSize, out unused), + entities => + { + ctx.DataContextCategory = new ExportDataContextCategory(entities, + x => _categoryService.GetProductCategoriesByCategoryIds(x), + x => _pictureService.GetPicturesByIds(x) + ); + }, + entity => ConvertToExpando(ctx, entity), + pageable, ctx.Profile.Offset, ctx.Profile.Limit, recordsPerFile + ); + break; + + case ExportEntityType.Customer: + ctx.Export.Segmenter = new ExportSegmenter + ( + pageIndex => GetCustomers(ctx, pageIndex, ctx.PageSize, out unused), + entities => + { + ctx.DataContextCustomer = new ExportDataContextCustomer(entities, + x => _genericAttributeService.GetAttributesForEntity(x, "Customer") + ); + }, + entity => ConvertToExpando(ctx, entity), + pageable, ctx.Profile.Offset, ctx.Profile.Limit, recordsPerFile + ); + break; + + case ExportEntityType.NewsLetterSubscription: + ctx.Export.Segmenter = new ExportSegmenter + ( + pageIndex => GetNewsLetterSubscriptions(ctx, pageIndex, ctx.PageSize, out unused), + null, + entity => ConvertToExpando(ctx, entity), + pageable, ctx.Profile.Offset, ctx.Profile.Limit, recordsPerFile + ); + break; + + default: + ctx.Export.Segmenter = null; + break; + } + + return ctx.Export.Segmenter as IExportSegmenterProvider; + } + private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic expando, Product product) { try @@ -390,7 +509,7 @@ private void GetDeliveryTimeAndQuantityUnit(ExportProfileTaskContext ctx, dynami private void SetProgress(ExportProfileTaskContext ctx, int loadedRecords) { - if (!ctx.IsPreview && loadedRecords > 0 && ctx.TaskContext.ScheduleTask != null) + if (!ctx.IsPreview && ctx.PageSize > 1 && loadedRecords > 0 && ctx.TaskContext.ScheduleTask != null) { int totalRecords = ctx.RecordsPerStore.Sum(x => x.Value); @@ -1498,24 +1617,10 @@ private List GetProducts(ExportProfileTaskContext ctx, int pageIndex) } } - // load data behind navigation properties for current page in one go - ctx.DataContextProduct = new ExportDataContextProduct(products, - x => _productAttributeService.GetProductVariantAttributesByProductIds(x, null), - x => _productAttributeService.GetProductVariantAttributeCombinations(x), - x => _productService.GetTierPricesByProductIds(x, (ctx.Projection.CurrencyId ?? 0) != 0 ? ctx.ContextCustomer : null, ctx.Store.Id), - x => _categoryService.GetProductCategoriesByProductIds(x), - x => _manufacturerService.GetProductManufacturersByProductIds(x), - x => _productService.GetProductPicturesByProductIds(x), - x => _productService.GetProductTagsByProductIds(x), - x => _productService.GetAppliedDiscountsByProductIds(x), - x => _productService.GetProductSpecificationAttributesByProductIds(x), - x => _productService.GetBundleItemsByProductIds(x, true) - ); - - SetProgress(ctx, products.Count); - try { + SetProgress(ctx, products.Count); + _services.DbContext.DetachEntities(result); } catch { } @@ -1554,21 +1659,10 @@ private List GetOrders(ExportProfileTaskContext ctx, int pageIndex, int p var result = orders as List; - if (pageSize > 1) + try { - ctx.DataContextOrder = new ExportDataContextOrder(result, - x => _customerService.GetCustomersByIds(x), - x => _customerService.GetRewardPointsHistoriesByCustomerIds(x), - x => _addressesService.GetAddressByIds(x), - x => _orderService.GetOrderItemsByOrderIds(x), - x => _shipmentService.GetShipmentsByOrderIds(x) - ); - SetProgress(ctx, orders.Count); - } - try - { _services.DbContext.DetachEntities(result); } catch { } @@ -1585,18 +1679,10 @@ private List GetManufacturers(ExportProfileTaskContext ctx, int pa var result = manus as List; - if (pageSize > 1) + try { - ctx.DataContextManufacturer = new ExportDataContextManufacturer(result, - x => _manufacturerService.GetProductManufacturersByManufacturerIds(x), - x => _pictureService.GetPicturesByIds(x) - ); - SetProgress(ctx, manus.Count); - } - try - { _services.DbContext.DetachEntities(result); } catch { } @@ -1615,18 +1701,10 @@ private List GetCategories(ExportProfileTaskContext ctx, int pageIndex var result = categories as List; - if (pageSize > 1) + try { - ctx.DataContextCategory = new ExportDataContextCategory(result, - x => _categoryService.GetProductCategoriesByCategoryIds(x), - x => _pictureService.GetPicturesByIds(x) - ); - SetProgress(ctx, categories.Count); - } - try - { _services.DbContext.DetachEntities(result); } catch { } @@ -1655,17 +1733,10 @@ private List GetCustomers(ExportProfileTaskContext ctx, int pageIndex, var result = customers as List; - if (pageSize > 1) + try { - ctx.DataContextCustomer = new ExportDataContextCustomer(result, - x => _genericAttributeService.GetAttributesForEntity(x, "Customer") - ); - SetProgress(ctx, customers.Count); - } - try - { _services.DbContext.DetachEntities(result); } catch { } @@ -1690,13 +1761,10 @@ private List GetNewsLetterSubscriptions(ExportProfileTas var result = subscriptions as List; - if (pageSize > 1) + try { SetProgress(ctx, subscriptions.Count); - } - try - { _services.DbContext.DetachEntities(result); } catch { } @@ -2340,6 +2408,8 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) if (ctx.Export.Abort != ExportAbortion.None) return; + int fileIndex = 0; + ctx.Store = store; { @@ -2374,82 +2444,25 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Export.PublicFolderPath = (ctx.Export.HasPublicDeployment ? Path.Combine(HttpRuntime.AppDomainAppPath, PublicFolder) : null); - int unused; - var itemsPerFile = (ctx.IsPreview ? 0 : ctx.Profile.BatchSize); - var totalCount = ctx.RecordsPerStore.First(x => x.Key == ctx.Store.Id).Value; - var pageable = new PagedList(ctx.Profile.Offset, ctx.Profile.Limit, ctx.PageIndex, ctx.PageSize, totalCount); - if (ctx.Provider.Value.EntityType == ExportEntityType.Product) - { - ctx.Export.Data = new ExportSegmenter( - pageIndex => GetProducts(ctx, pageIndex), - entity => ConvertToExpando(ctx, entity), - pageable, itemsPerFile - ); - } - else if (ctx.Provider.Value.EntityType == ExportEntityType.Order) - { - ctx.Export.Data = new ExportSegmenter( - pageIndex => GetOrders(ctx, pageIndex, ctx.PageSize, out unused), - entity => ConvertToExpando(ctx, entity), - pageable, itemsPerFile - ); - } - else if (ctx.Provider.Value.EntityType == ExportEntityType.Manufacturer) + using (var segmenter = CreateSegmenter(ctx)) { - ctx.Export.Data = new ExportSegmenter( - pageIndex => GetManufacturers(ctx, pageIndex, ctx.PageSize, out unused), - entity => ConvertToExpando(ctx, entity), - pageable, itemsPerFile - ); - } - else if (ctx.Provider.Value.EntityType == ExportEntityType.Category) - { - ctx.Export.Data = new ExportSegmenter( - pageIndex => GetCategories(ctx, pageIndex, ctx.PageSize, out unused), - entity => ConvertToExpando(ctx, entity), - pageable, itemsPerFile - ); - } - else if (ctx.Provider.Value.EntityType == ExportEntityType.Customer) - { - ctx.Export.Data = new ExportSegmenter( - pageIndex => GetCustomers(ctx, pageIndex, ctx.PageSize, out unused), - entity => ConvertToExpando(ctx, entity), - pageable, itemsPerFile - ); - } - else if (ctx.Provider.Value.EntityType == ExportEntityType.NewsLetterSubscription) - { - ctx.Export.Data = new ExportSegmenter( - pageIndex => GetNewsLetterSubscriptions(ctx, pageIndex, ctx.PageSize, out unused), - entity => ConvertToExpando(ctx, entity), - pageable, itemsPerFile - ); - } - - if (ctx.Export.Data == null) - { - throw new SmartException("Unsupported entity type '{0}'".FormatInvariant(ctx.Provider.Value.EntityType.ToString())); - } - else - { - (ctx.Export.Data as IExportExecuter).Start(() => + if (segmenter == null) { - ctx.Export.RecordsSucceeded = 0; + throw new SmartException("Unsupported entity type '{0}'".FormatInvariant(ctx.Provider.Value.EntityType.ToString())); + } - if (!ctx.IsPreview) + if (ctx.IsPreview) + { + if (segmenter.ReadNextSegment()) + segmenter.CurrentSegment.ForEach(x => ctx.PreviewData(x)); + } + else + { + while (ctx.Export.Abort == ExportAbortion.None && segmenter.HasData) { - if (ctx.IsFileBasedExport) - { - var resolvedPattern = ctx.Profile.ResolveFileNamePattern(ctx.Store, ctx.Export.Data.FileIndex + 1, ctx.Export.MaxFileNameLength); - - ctx.Export.FileName = resolvedPattern + ctx.Export.FileExtension; - ctx.Export.FilePath = Path.Combine(ctx.Export.Folder, ctx.Export.FileName); - - if (ctx.Export.HasPublicDeployment) - ctx.Export.PublicFileUrl = ctx.Store.Url.EnsureEndsWith("/") + PublicFolder.EnsureEndsWith("/") + ctx.Export.FileName; - } + segmenter.RecordPerSegmentCount = 0; + ctx.Export.RecordsSucceeded = 0; try { @@ -2457,6 +2470,14 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) { using (_rwLock.GetWriteLock()) { + var resolvedPattern = ctx.Profile.ResolveFileNamePattern(ctx.Store, ++fileIndex, ctx.Export.MaxFileNameLength); + + ctx.Export.FileName = resolvedPattern + ctx.Export.FileExtension; + ctx.Export.FilePath = Path.Combine(ctx.Export.Folder, ctx.Export.FileName); + + if (ctx.Export.HasPublicDeployment) + ctx.Export.PublicFileUrl = ctx.Store.Url.EnsureEndsWith("/") + PublicFolder.EnsureEndsWith("/") + ctx.Export.FileName; + ctx.Provider.Value.Execute(ctx.Export); } } @@ -2483,29 +2504,13 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Log.Error("The provider failed to execute the export: " + exc.ToAllMessages(), exc); ctx.Result.LastError = exc.ToString(); } - } - else if (ctx.Export.Data.ReadNextSegment()) - { - var segment = ctx.Export.Data.CurrentSegment; - - foreach (dynamic record in segment) - { - ctx.PreviewData(record); - } - } - if (ctx.Export.IsMaxFailures) - ctx.Log.Warning("Export aborted. The maximum number of failures has been reached"); + if (ctx.Export.IsMaxFailures) + ctx.Log.Warning("Export aborted. The maximum number of failures has been reached"); - if (ctx.TaskContext.CancellationToken.IsCancellationRequested) - ctx.Log.Warning("Export aborted. A cancellation has been requested"); - - return (ctx.Export.Abort == ExportAbortion.None); - }); - - if (ctx.Export.Abort != ExportAbortion.Hard) - { - ctx.Provider.Value.ExecuteEnded(ctx.Export); + if (ctx.TaskContext.CancellationToken.IsCancellationRequested) + ctx.Log.Warning("Export aborted. A cancellation has been requested"); + } } } } @@ -2673,8 +2678,6 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) ctx.DataContextCategory = null; ctx.DataContextCustomer = null; - (ctx.Export.Data as IExportExecuter).Dispose(); - ctx.Export.CustomProperties.Clear(); ctx.Export.Log = null; ctx.Log = null; diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs index e4dde5e6a8..ed7eb62390 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs @@ -193,9 +193,9 @@ public void Execute(IExportExecuteContext context) writer.WriteElementString("link", "http://base.google.com/base/"); writer.WriteElementString("description", "Information about products"); - while (context.Abort == ExportAbortion.None && context.Data.ReadNextSegment()) + while (context.Abort == ExportAbortion.None && context.Segmenter.ReadNextSegment()) { - var segment = context.Data.CurrentSegment; + var segment = context.Segmenter.CurrentSegment; int[] productIds = segment.Select(x => (int)((dynamic)x).Id).ToArray(); var googleProducts = _googleFeedService.GetGoogleProductRecords(productIds); From f82fda5e165cc0aedc459902bbbe5439bce2f107 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 14 Oct 2015 12:06:12 +0200 Subject: [PATCH 124/874] Added noindex meta tag to AskQuestion & EmailAFriend views --- src/Presentation/SmartStore.Web/Views/Product/AskQuestion.cshtml | 1 + .../SmartStore.Web/Views/Product/EmailAFriend.cshtml | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Presentation/SmartStore.Web/Views/Product/AskQuestion.cshtml b/src/Presentation/SmartStore.Web/Views/Product/AskQuestion.cshtml index 1a38db604d..ee471a1c1f 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/AskQuestion.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/AskQuestion.cshtml @@ -6,6 +6,7 @@ //title Html.AddTitleParts(Model.ProductName); Html.AddTitleParts(T("Products.AskQuestion.Title").Text); + Html.AddCustomHeadParts(""); }
    diff --git a/src/Presentation/SmartStore.Web/Views/Product/EmailAFriend.cshtml b/src/Presentation/SmartStore.Web/Views/Product/EmailAFriend.cshtml index 0e9690d304..5b5ea6fd88 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/EmailAFriend.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/EmailAFriend.cshtml @@ -5,6 +5,7 @@ Html.AddTitleParts(Model.ProductName); Html.AddTitleParts(T("PageTitle.ProductEmailAFriend").Text); + Html.AddCustomHeadParts(""); }
    @@ -191,9 +191,11 @@ else // start task $(document).on('click', '.btn-run-task', function (e) { e.preventDefault(); - var form = $('#ProfileExportForm'); - form.find('input[name=Id]').val($(this).attr('data-id')); - form.submit(); + if (confirm('@T("Admin.Common.AreYouSure")')) { + var form = $('#ProfileExportForm'); + form.find('input[name=Id]').val($(this).attr('data-id')); + form.submit(); + } return false; }); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml index 7c782ec5ae..73113cc2a5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml @@ -62,6 +62,13 @@ @Html.ValidationMessageFor(x => x.Projection.AttributeCombinationValueMergingId)
    +
    + @T("Admin.DataExchange.Export.NotPreviewCompatible") +
    +
    - @T("Admin.DataExchange.Export.Projection.NoGroupedProducts.Note") + @T("Admin.DataExchange.Export.NotPreviewCompatible")
    @@ -14,7 +14,12 @@ @Html.SmartLabelFor(model => model.Provider.SystemName) @if(Model.AvailableProfiles.Count > 0) @@ -24,7 +29,14 @@ @Html.SmartLabelFor(model => model.CloneProfileId) } @@ -33,9 +45,9 @@   diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 16e213c48b..0704662b78 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -143,9 +143,21 @@ else smallIcon: dialog.find('.modal-body'), callbackSuccess: function (resp) { dialog.find('.modal-body').html(resp); - $('select[name="Provider.SystemName"]').selectWrapper(); - $('select[name="CloneProfileId"]').selectWrapper(); + + $('select[name="Provider.SystemName"]').select2({ + minimumResultsForSearch: 16, + formatResult: providerSelectItemFormatting, + formatSelection: providerSelectItemFormatting + }); + toggleProviderDescription(); + + $('select[name="CloneProfileId"]').select2({ + allowClear: true, + minimumResultsForSearch: 16, + formatResult: profileSelectItemFormatting, + formatSelection: profileSelectItemFormatting + }); }, callbackComplete: function () { showPrimaryButton(true); @@ -182,9 +194,66 @@ else } function toggleProviderDescription() { - var val = dialog.find('select[name="Provider.SystemName"]').val().replace(/\./g, ''); dialog.find('.export-provider-description').hide(); - $('#ExportProviderDescription' + val).fadeIn('fast'); + + var option = dialog.find('select[name="Provider.SystemName"]').find(':selected'); + $('#ExportProviderDescription' + option.attr('data-id')).fadeIn('fast'); + } + + function providerSelectItemFormatting(item) { + try { + var option = $(item.element); + + // item.element is undefined for selection formatting + if (option.length === 0) { + option = $('select[name="Provider.SystemName"]').find('option[value="' + item.id + '"]'); + } + + var html = '
    ', + imageUrl = option.data('imageurl'); + + if (imageUrl) + html += ''; + + html += '
    '; + html += '' + item.text + ''; + html += '' + option.attr('value') + ''; + html += '
    '; + html += '
    '; + + return html; + } + catch (e) { } + + return item.text; + } + + function profileSelectItemFormatting(item) { + try { + var option = $(item.element); + + // item.element is undefined for selection formatting + if (option.length === 0) { + option = $('select[name="CloneProfileId"]').find('option[value="' + item.id + '"]'); + } + + var html = '
    ', + imageUrl = option.data('imageurl'); + + if (imageUrl) + html += ''; + + html += '
    '; + html += '' + item.text + ''; + html += '' + option.attr('data-systemname') + ''; + html += '
    '; + html += '
    '; + + return html; + } + catch (e) { } + + return item.text; } From 4e61fb8a2e14854cb9b66815ee685b1c328e49e0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 16 Oct 2015 21:20:24 +0200 Subject: [PATCH 127/874] Minor change --- .../SmartStore.Services/DataExchange/ExportSegmenter.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs index 68ef6d442c..b86df2cb45 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs @@ -120,10 +120,7 @@ public bool HasData if (_skip >= _totalRecords) return false; - if (_data == null && _skip == _offset) - return true; - - return false; + return true; } } From 2dfae891dab05504026b44ea2aaffe2ebe1701e9 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 17 Oct 2015 12:49:02 +0200 Subject: [PATCH 128/874] Minor change --- .../DataExchange/ExportTask/ExportProfileTask.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index ececcc6193..f0b413f591 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -2493,7 +2493,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) logHead.AppendLine(new string('-', 40)); logHead.AppendLine("SmartStore.NET:\t\tv." + SmartStoreVersion.CurrentFullVersion); logHead.Append("Export profile:\t\t" + ctx.Profile.Name); - logHead.AppendLine(ctx.Profile.Id == 0 ? " volatile" : " (Id {0})".FormatInvariant(ctx.Profile.Id)); + logHead.AppendLine(ctx.Profile.Id == 0 ? " (volatile)" : " (Id {0})".FormatInvariant(ctx.Profile.Id)); logHead.AppendLine("Export provider:\t{0} ({1})".FormatInvariant(ctx.Provider.Metadata.FriendlyName, ctx.Profile.ProviderSystemName)); @@ -2527,6 +2527,11 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) throw new SmartException("Unsupported entity type '{0}'".FormatInvariant(ctx.Provider.Value.EntityType.ToString())); } + if (segmenter.RecordTotal <= 0) + { + ctx.Log.Information("There are no records to export"); + } + while (ctx.Export.Abort == ExportAbortion.None && segmenter.HasData) { segmenter.RecordPerSegmentCount = 0; From 603cb9b39e4ff389d452322138e7aa84a65ac9ca Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 19 Oct 2015 11:25:18 +0200 Subject: [PATCH 129/874] Testing export deployment --- .../201508091512101_ExportFramework.cs | 4 +- .../201509021536425_ExportFramework1.cs | 4 +- .../ExportTask/ExportProfileTask.cs | 59 ++++++++++++------- .../Controllers/Api/UploadsController.cs | 4 +- .../Export/_CreateOrUpdate.Deployment.cshtml | 9 +-- 5 files changed, 49 insertions(+), 31 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs index ae7954120b..795d74921a 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs @@ -485,8 +485,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Legt die URL bzw. den Host-Namen fest, an die die Daten bermittelt werden sollen."); builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.EmailAddresses", - "Email addresses", - "E-Mail-Addressen", + "Email addresses to", + "E-Mail-Addressen an", "Specifies the email addresses where to send the data.", "Legt die E-Mail Addressen fest, an die die Daten verschickt werden soll."); diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs index b163b3cbea..4f7df13d97 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs @@ -80,8 +80,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Legt das E-Mail Konto fest, ber welches eine Benachrichtigung ber die Fertigstellung des Exports verschickt werden soll."); builder.AddOrUpdate("Admin.DataExchange.Export.CompletedEmailAddresses", - "Email addresses", - "E-Mail-Addressen", + "Email addresses to", + "E-Mail-Addressen an", "Specifies the email addresses where to send the notification message.", "Legt die E-Mail Addressen fest, an die die Benachrichtigung geschickt werden soll."); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index b6c48707cf..4bd5e5c56e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -466,7 +466,7 @@ private void GetDeliveryTimeAndQuantityUnit(ExportProfileTaskContext ctx, dynami private void SetProgress(ExportProfileTaskContext ctx, int loadedRecords) { - if (!ctx.IsPreview && loadedRecords > 0 && ctx.TaskContext.ScheduleTask != null) + if (!ctx.IsPreview && ctx.TaskContext.ScheduleTask != null && loadedRecords > 0) { int totalRecords = ctx.RecordsPerStore.Sum(x => x.Value); @@ -481,6 +481,14 @@ private void SetProgress(ExportProfileTaskContext ctx, int loadedRecords) } } + private void SetProgress(ExportProfileTaskContext ctx, string message) + { + if (!ctx.IsPreview && ctx.TaskContext.ScheduleTask != null && message.HasValue()) + { + ctx.TaskContext.SetProgress(null, message, true); + } + } + private void SendCompletionEmail(ExportProfileTaskContext ctx) { var emailAccount = _emailAccountService.Value.GetEmailAccountById(ctx.Profile.EmailAccountId); @@ -577,10 +585,10 @@ private void DeployEmail(ExportProfileTaskContext ctx, ExportDeployment deployme ++count; } - ctx.Log.Information("{0} email(s) created and queued.".FormatInvariant(count)); + ctx.Log.Information("{0} email(s) created and queued for deployment.".FormatInvariant(count)); } - private async void DeployHttp(ExportProfileTaskContext ctx, ExportDeployment deployment) + private void DeployHttp(ExportProfileTaskContext ctx, ExportDeployment deployment) { var succeeded = 0; var url = deployment.Url; @@ -606,7 +614,7 @@ private async void DeployHttp(ExportProfileTaskContext ctx, ExportDeployment dep formData.Add(new ByteArrayContent(fileData), "file {0}".FormatInvariant(++count), Path.GetFileName(path)); } - var response = await client.PostAsync(url, formData); + var response = client.PostAsync(url, formData).Result; if (response.IsSuccessStatusCode) { @@ -614,7 +622,7 @@ private async void DeployHttp(ExportProfileTaskContext ctx, ExportDeployment dep } else if (response.Content != null) { - var content = await response.Content.ReadAsStringAsync(); + 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, "...")); @@ -632,15 +640,17 @@ private async void DeployHttp(ExportProfileTaskContext ctx, ExportDeployment dep foreach (var path in ctx.GetDeploymentFiles(deployment)) { - await webClient.UploadFileTaskAsync(url, path); + webClient.UploadFile(url, path); + + ++succeeded; } } } - ctx.Log.Information("{0} file(s) successfully uploaded via HTTP.".FormatInvariant(succeeded)); + ctx.Log.Information("{0} file(s) successfully uploaded via HTTP ({1}).".FormatInvariant(succeeded, deployment.HttpTransmissionType.ToString())); } - private async void DeployFtp(ExportProfileTaskContext ctx, ExportDeployment deployment) + private void DeployFtp(ExportProfileTaskContext ctx, ExportDeployment deployment) { var bytesRead = 0; var succeededFiles = 0; @@ -670,31 +680,33 @@ private async void DeployFtp(ExportProfileTaskContext ctx, ExportDeployment depl request.ContentLength = (new FileInfo(path)).Length; - var requestStream = await request.GetRequestStreamAsync(); + var requestStream = request.GetRequestStream(); using (var stream = new FileStream(path, FileMode.Open)) { while ((bytesRead = stream.Read(buff, 0, buffLength)) != 0) { - await requestStream.WriteAsync(buff, 0, bytesRead); + requestStream.Write(buff, 0, bytesRead); } } requestStream.Close(); - var response = (FtpWebResponse)await request.GetResponseAsync(); - var statusCode = (int)response.StatusCode; - - if (statusCode >= 200 && statusCode <= 299) - { - ++succeededFiles; - } - else + using (var response = (FtpWebResponse)request.GetResponse()) { - var msg = "The FTP transfer might fail. {0} ({1}), {2}. File {3}".FormatInvariant( - response.StatusCode.ToString(), statusCode, response.StatusDescription.NaIfEmpty(), path); + var statusCode = (int)response.StatusCode; - ctx.Log.Error(msg); + if (statusCode >= 200 && statusCode <= 299) + { + ++succeededFiles; + } + else + { + var msg = "The FTP transfer might fail. {0} ({1}), {2}. File {3}".FormatInvariant( + response.StatusCode.ToString(), statusCode, response.StatusDescription.NaIfEmpty(), path); + + ctx.Log.Error(msg); + } } } @@ -2671,6 +2683,8 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) ZipFile.CreateFromDirectory(ctx.FolderContent, ctx.ZipPath, CompressionLevel.Fastest, true); } + SetProgress(ctx, _services.Localization.GetResource("Common.Deployment")); + foreach (var deployment in ctx.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) { try @@ -2693,7 +2707,8 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) } catch (Exception exc) { - logger.Error("Deployment \"{0}\" of type {1} failed.".FormatInvariant(deployment.Name, deployment.DeploymentType.ToString()), exc); + logger.Error("Deployment \"{0}\" of type {1} failed: {2}".FormatInvariant( + deployment.Name, deployment.DeploymentType.ToString(), exc.Message), exc); } } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/Api/UploadsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/Api/UploadsController.cs index 8cc5b1ecaf..2d4e879f22 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/Api/UploadsController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/Api/UploadsController.cs @@ -101,10 +101,12 @@ public async Task> PostProductImages() { FileName = file.Headers.ContentDisposition.FileName.ToUnquoted(), Name = file.Headers.ContentDisposition.Name.ToUnquoted(), - MediaType = file.Headers.ContentType.MediaType.ToUnquoted(), ContentDisposition = file.Headers.ContentDisposition.Parameters }; + if (file.Headers.ContentType != null) + image.MediaType = file.Headers.ContentType.MediaType.ToUnquoted(); + if (image.FileName.IsEmpty()) image.FileName = entity.Name; 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 6bfaa071d9..3b79b6fdeb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml @@ -92,7 +92,7 @@ - + @@ -116,7 +116,7 @@ @Html.SmartLabelFor(x => x.Username) @@ -125,7 +125,7 @@ @Html.SmartLabelFor(x => x.Password) @@ -165,7 +165,8 @@ // show\hide file system path $('#@(Html.FieldIdFor(x => x.IsPublic))').change(function () { - $('#FileSystemPathContainer').toggle(!$(this).is(':checked')); + var val = !$(this).is(':checked') && $('#@(Html.FieldIdFor(x => x.DeploymentType))').val() === @((int)ExportDeploymentType.FileSystem); + $('#FileSystemPathContainer').toggle(val); }).trigger('change'); }); \ No newline at end of file From d63b35d7ac970dff1c6c6925595ca865d2621c96 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 19 Oct 2015 11:49:10 +0200 Subject: [PATCH 130/874] Updated changelog --- changelog.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index f882c84de1..f27d3c2d7e 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,7 @@ ## SmartStore.NET 2.5 ### New Features +* #701 Implement new export framework * #141 Payment and shipping methods by customer roles * #67 Restrict payment methods to countries * #94 Restrict payment methods to shipping methods @@ -21,7 +22,8 @@ * #607 HTML capable full description for payment methods displayed in checkout * #732 Product list: Option to display the pre-selected price instead of the lowest price * New payment provider for Offline Payment Plugin: Purchase Order Number -* #202 Implement option for product list 'default sort order' +* #202 Implement option for product list 'default sort order' +* #360 Import & export product variant combinations ### Improvements * (Perf) Implemented static caches for URL aliases and localized properties. Increases app startup and request speed by up to 30%. From 5e9aecaa920c2f035c7766485a7b91ad4ee0f931 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 19 Oct 2015 13:01:53 +0200 Subject: [PATCH 131/874] Resolves #776 Preview: Manufacturer and Product in Multi Store --- changelog.md | 2 ++ .../ExportTask/ExportProfileTask.cs | 2 +- .../Controllers/ProductController.cs | 34 ++++++++++++++----- .../Administration/Views/Product/Edit.cshtml | 2 +- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/changelog.md b/changelog.md index f27d3c2d7e..4016ba6a97 100644 --- a/changelog.md +++ b/changelog.md @@ -59,6 +59,8 @@ * GMC feed: Availability value "available for order" deprecated * Mobile: Shopping cart warnings weren't displayed to customers * Tax provider and payment method were automatically activated when there were no active provider\method +* #784 Biz-Importer: Name of delivery time must not be imported empty +* #776 Preview: Manufacturer and Product in Multi Store ## SmartStore.NET 2.2.2 diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 4bd5e5c56e..1bab65a88e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -1574,7 +1574,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro expando._ProductTemplateViewPath = (productTemplate.Value == null ? "" : productTemplate.Value.ViewPath); - expando._DetailUrl = ctx.Store.Url + (string)expando.SeName; + expando._DetailUrl = ctx.Store.Url.EnsureEndsWith("/") + (string)expando.SeName; expando._CategoryName = null; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index d3e2112a83..32c19c01c6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -656,7 +656,25 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set model.CreatedOn = _dateTimeHelper.ConvertToUserTime(product.CreatedOnUtc, DateTimeKind.Utc); model.UpdatedOn = _dateTimeHelper.ConvertToUserTime(product.UpdatedOnUtc, DateTimeKind.Utc); - model.ProductUrl = Url.RouteUrl("Product", new { SeName = product.GetSeName() }, Request.Url.Scheme); + if (product.LimitedToStores) + { + var storeMappings = _storeMappingService.GetStoreMappings(product); + if (storeMappings.FirstOrDefault(x => x.StoreId == _services.StoreContext.CurrentStore.Id) == null) + { + var storeMapping = storeMappings.FirstOrDefault(); + if (storeMapping != null) + { + var store = _services.StoreService.GetStoreById(storeMapping.StoreId); + if (store != null) + model.ProductUrl = store.Url.EnsureEndsWith("/") + product.GetSeName(); + } + } + } + + if (model.ProductUrl.IsEmpty()) + { + model.ProductUrl = Url.RouteUrl("Product", new { SeName = product.GetSeName() }, Request.Url.Scheme); + } } model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; @@ -680,7 +698,7 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set var templates = _productTemplateService.GetAllProductTemplates(); foreach (var template in templates) { - model.AvailableProductTemplates.Add(new SelectListItem() + model.AvailableProductTemplates.Add(new SelectListItem { Text = template.Name, Value = template.Id.ToString() @@ -691,7 +709,7 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set var allTags = _productTagService.GetAllProductTagNames(); foreach (var tag in allTags) { - model.AvailableProductTags.Add(new SelectListItem() { Text = tag, Value = tag }); + model.AvailableProductTags.Add(new SelectListItem { Text = tag, Value = tag }); } if (product != null) @@ -704,7 +722,7 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set var taxCategories = _taxCategoryService.GetAllTaxCategories(); foreach (var tc in taxCategories) { - model.AvailableTaxCategories.Add(new SelectListItem() + model.AvailableTaxCategories.Add(new SelectListItem { Text = tc.Name, Value = tc.Id.ToString(), @@ -716,7 +734,7 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set var deliveryTimes = _deliveryTimesService.GetAllDeliveryTimes(); foreach (var dt in deliveryTimes) { - model.AvailableDeliveryTimes.Add(new SelectListItem() + model.AvailableDeliveryTimes.Add(new SelectListItem { Text = dt.Name, Value = dt.Id.ToString(), @@ -728,7 +746,7 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set var quantityUnits = _quantityUnitService.GetAllQuantityUnits(); foreach (var mu in quantityUnits) { - model.AvailableQuantityUnits.Add(new SelectListItem() + model.AvailableQuantityUnits.Add(new SelectListItem { Text = mu.Name, Value = mu.Id.ToString(), @@ -748,7 +766,7 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set foreach (var mu in measureUnits) { - model.AvailableMeasureUnits.Add(new SelectListItem() + model.AvailableMeasureUnits.Add(new SelectListItem { Text = mu, Value = mu, @@ -788,7 +806,7 @@ protected void PrepareProductModel(ProductModel model, Product product, bool set foreach (var inventoryMethod in inventoryMethods) { - model.AvailableManageInventoryMethods.Add(new SelectListItem() + model.AvailableManageInventoryMethods.Add(new SelectListItem { Value = ((int)inventoryMethod).ToString(), Text = inventoryMethod.GetLocalizedEnum(_localizationService, _workContext), diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/Edit.cshtml index 386b95834f..a655777c17 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/Edit.cshtml @@ -14,7 +14,7 @@ (@T("Admin.Catalog.Products.BackToList"))
    - From 05c320eb5362ba6935a5967006b33c8c6ba549ab Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 19 Oct 2015 13:58:44 +0200 Subject: [PATCH 132/874] Minor change --- .../DataExchange/ExportTask/ExportProfileTask.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 1bab65a88e..bcfadc73df 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -2555,18 +2555,15 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) { if (ctx.IsFileBasedExport) { - using (_rwLock.GetWriteLock()) - { - var resolvedPattern = ctx.Profile.ResolveFileNamePattern(ctx.Store, ++fileIndex, ctx.Export.MaxFileNameLength); + var resolvedPattern = ctx.Profile.ResolveFileNamePattern(ctx.Store, ++fileIndex, ctx.Export.MaxFileNameLength); - ctx.Export.FileName = resolvedPattern + ctx.Export.FileExtension; - ctx.Export.FilePath = Path.Combine(ctx.Export.Folder, ctx.Export.FileName); + ctx.Export.FileName = resolvedPattern + ctx.Export.FileExtension; + ctx.Export.FilePath = Path.Combine(ctx.Export.Folder, ctx.Export.FileName); - if (ctx.Export.HasPublicDeployment) - ctx.Export.PublicFileUrl = ctx.Store.Url.EnsureEndsWith("/") + PublicFolder.EnsureEndsWith("/") + ctx.Export.FileName; + if (ctx.Export.HasPublicDeployment) + ctx.Export.PublicFileUrl = ctx.Store.Url.EnsureEndsWith("/") + PublicFolder.EnsureEndsWith("/") + ctx.Export.FileName; - ctx.Provider.Value.Execute(ctx.Export); - } + ctx.Provider.Value.Execute(ctx.Export); // create info for deployment list in profile edit if (File.Exists(ctx.Export.FilePath)) @@ -2610,6 +2607,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) FileSystemHelper.ClearDirectory(ctx.FolderContent, false); FileSystemHelper.Delete(ctx.ZipPath); + using (_rwLock.GetWriteLock()) using (var logger = new TraceLogger(ctx.LogPath)) { try From 2322c7418bac2e206f1c12495b22499503a106d0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 19 Oct 2015 14:04:20 +0200 Subject: [PATCH 133/874] Perf product filter by using untracked product table --- .../SmartStore.Services/Filter/FilterService.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Filter/FilterService.cs b/src/Libraries/SmartStore.Services/Filter/FilterService.cs index c96a3f23bf..fad58fc5c8 100644 --- a/src/Libraries/SmartStore.Services/Filter/FilterService.cs +++ b/src/Libraries/SmartStore.Services/Filter/FilterService.cs @@ -165,6 +165,7 @@ private IQueryable AllProducts(List categoryIds) { var searchContext = new ProductSearchContext() { + Query = _productRepository.TableUntracked, FeaturedProducts = (_catalogSettings.IncludeFeaturedProductsInNormalLists ? null : (bool?)false), StoreId = _services.StoreContext.CurrentStoreIdIfMultiStoreMode, VisibleIndividuallyOnly = true @@ -191,17 +192,6 @@ join x in distinctIds on p.Id equals x _products = _productService.PrepareProductSearchQuery(searchContext); } - - //string.Join(", ", distinctIds.ToList()).Dump(); - - //_products - // .Select(x => new { x.Id, x.Name }) - // .ToList() - // .ForEach(x => { - // "{0} {1}".FormatWith(x.Id, x.Name).Dump(); - // }); - - //_products.ToString().Dump(true); } return _products; } From f999743d77f5ed9d70bcf5b0f375f80e899a9a8e Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 19 Oct 2015 14:53:07 +0200 Subject: [PATCH 134/874] Resolves #755 Some methods still loading all products in one go --- changelog.md | 1 + .../Seo/SitemapGenerator.cs | 30 ++++++++----- .../Controllers/ProductController.cs | 44 +++++++++++-------- 3 files changed, 46 insertions(+), 29 deletions(-) diff --git a/changelog.md b/changelog.md index 4016ba6a97..b1a1c00208 100644 --- a/changelog.md +++ b/changelog.md @@ -61,6 +61,7 @@ * Tax provider and payment method were automatically activated when there were no active provider\method * #784 Biz-Importer: Name of delivery time must not be imported empty * #776 Preview: Manufacturer and Product in Multi Store +* #755 Some methods still loading all products in one go ## SmartStore.NET 2.2.2 diff --git a/src/Libraries/SmartStore.Services/Seo/SitemapGenerator.cs b/src/Libraries/SmartStore.Services/Seo/SitemapGenerator.cs index 776d424311..497555026d 100644 --- a/src/Libraries/SmartStore.Services/Seo/SitemapGenerator.cs +++ b/src/Libraries/SmartStore.Services/Seo/SitemapGenerator.cs @@ -96,23 +96,31 @@ private void WriteManufacturers(UrlHelper urlHelper) private void WriteProducts(UrlHelper urlHelper) { - var ctx = new ProductSearchContext() + var protocol = _securitySettings.ForceSslForAllPages ? "https" : "http"; + + var ctx = new ProductSearchContext { OrderBy = ProductSortingEnum.CreatedOn, - PageSize = int.MaxValue, + PageSize = 500, StoreId = _storeContext.CurrentStoreIdIfMultiStoreMode, VisibleIndividuallyOnly = true }; - var products = _productService.SearchProducts(ctx); - var protocol = _securitySettings.ForceSslForAllPages ? "https" : "http"; - foreach (var product in products) - { - var url = urlHelper.RouteUrl("Product", new { SeName = product.GetSeName() }, protocol); - var updateFrequency = UpdateFrequency.Weekly; - var updateTime = product.UpdatedOnUtc; - WriteUrlLocation(url, updateFrequency, updateTime); - } + for (ctx.PageIndex = 0; ctx.PageIndex < 9999999; ++ctx.PageIndex) + { + var products = _productService.SearchProducts(ctx); + + foreach (var product in products) + { + var url = urlHelper.RouteUrl("Product", new { SeName = product.GetSeName() }, protocol); + var updateFrequency = UpdateFrequency.Weekly; + var updateTime = product.UpdatedOnUtc; + WriteUrlLocation(url, updateFrequency, updateTime); + } + + if (!products.HasNextPage) + break; + } } private void WriteTopics(UrlHelper urlHelper) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 32c19c01c6..f4d1bc8e35 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -2,13 +2,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Linq.Expressions; -using System.Net.Mime;using System.Text; +using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web.Mvc; using System.Web.Routing; -using System.Data.Entity; using Autofac; using Newtonsoft.Json; using SmartStore.Admin.Models.Catalog; @@ -2836,35 +2834,45 @@ public ActionResult ExportPdf(bool all, string selectedIds = null) NotifyInfo(T("Admin.Common.ExportNoData")); return RedirectToAction("List"); } - - IList products; + + const int maxProducts = 500; + int totalCount = 0; + IPagedList products; using (var scope = new DbContextScope(_dbContext, autoDetectChanges: false, forceNoTracking: true)) { - if (all) + var searchContext = new ProductSearchContext { - products = _productService.SearchProducts(new ProductSearchContext - { - LanguageId = _workContext.WorkingLanguage.Id, - OrderBy = ProductSortingEnum.Position, - PageSize = int.MaxValue, - ShowHidden = true - }); + LanguageId = _workContext.WorkingLanguage.Id, + OrderBy = ProductSortingEnum.Position, + PageSize = 1, + ShowHidden = true + }; + + if (!all) + { + searchContext.ProductIds = selectedIds.ToIntArray().ToList(); } - else + + products = _productService.SearchProducts(searchContext); + + totalCount = products.TotalCount; + + if (totalCount > 0 && totalCount <= maxProducts) { - var ids = selectedIds.ToIntArray(); - products = _productService.GetProductsByIds(ids); + searchContext.PageSize = int.MaxValue; + + products = _productService.SearchProducts(searchContext); } } - if (products.Count == 0) + if (totalCount == 0) { NotifyInfo(T("Admin.Common.ExportNoData")); return RedirectToAction("List"); } - if (products.Count > 500) + if (totalCount > maxProducts) { NotifyWarning(T("Admin.Common.ExportToPdf.TooManyItems")); return RedirectToAction("List"); From f1f3b314e91b27ee6b7054012549214fdaf33784 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 19 Oct 2015 16:34:58 +0200 Subject: [PATCH 135/874] Resolves #778 Web-API: Increase MaxExpansionDepth for using expand paths --- changelog.md | 1 + .../SmartStore.Web.Framework/WebApi/WebApiCore.cs | 1 + .../WebApi/WebApiStartupTask.cs | 11 ++++++----- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index b1a1c00208..715ade0eaa 100644 --- a/changelog.md +++ b/changelog.md @@ -49,6 +49,7 @@ * Automatically redirect to referrer after login * #249 Make UI editor for 'SeoSettings.ExtraRobotsDisallows' * Debitoor: Customer VAT number not transmitted anymore because it appears on the Debitoor invoice. +* #778 Web-API: Increase MaxExpansionDepth for using expand pathes ### Bugfixes * #523 Redirecting to payment provider performed by core instead of plugin diff --git a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiCore.cs b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiCore.cs index 7d9db0fd44..993c7624e9 100644 --- a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiCore.cs +++ b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiCore.cs @@ -6,6 +6,7 @@ public static class WebApiGlobal { public static int MaxApiVersion { get { return 1; } } public static int MaxTop { get { return 120; } } + public static int MaxExpansionDepth { get { return 8; } } public static int DefaultTimePeriodMinutes { get { return 15; } } public static string RouteNameDefaultApi { get { return "WebApi.Default"; } } public static string RouteNameDefaultOdata { get { return "WebApi.OData.Default"; } } diff --git a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiStartupTask.cs b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiStartupTask.cs index 5d9ec60a7a..0cb709e658 100644 --- a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiStartupTask.cs +++ b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiStartupTask.cs @@ -1,13 +1,13 @@ using System; using System.Net.Http.Formatting; using System.Web.Http; +using System.Web.Http.Cors; using System.Web.Http.OData.Builder; using System.Web.Http.OData.Routing; +using System.Web.Http.OData.Routing.Conventions; using SmartStore.Core.Infrastructure; using SmartStore.Web.Framework.WebApi.Configuration; -using System.Web.Http.OData.Routing.Conventions; using SmartStore.Web.Framework.WebApi.OData; -using System.Web.Http.Cors; namespace SmartStore.Web.Framework.WebApi { @@ -18,7 +18,7 @@ public void Execute() { var config = GlobalConfiguration.Configuration; - var configBroadcaster = new WebApiConfigurationBroadcaster() + var configBroadcaster = new WebApiConfigurationBroadcaster { ModelBuilder = new ODataConventionModelBuilder(), RoutingConventions = ODataRoutingConventions.CreateDefault(), @@ -31,9 +31,10 @@ public void Execute() config.Formatters.JsonFormatter.MediaTypeMappings.Add(new QueryStringMapping("format", "json", "application/json")); config.Formatters.XmlFormatter.MediaTypeMappings.Add(new QueryStringMapping("format", "xml", "application/xml")); - var queryAttribute = new WebApiQueryableAttribute() + var queryAttribute = new WebApiQueryableAttribute { - MaxTop = WebApiGlobal.MaxTop + MaxTop = WebApiGlobal.MaxTop, + MaxExpansionDepth = WebApiGlobal.MaxExpansionDepth }; config.EnableQuerySupport(queryAttribute); From 1cfa949596f7e311965d73a327a19a9a9e4d0910 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 20 Oct 2015 12:03:17 +0200 Subject: [PATCH 136/874] Resolves #767 Remove assignments to a grouped product if the grouped product is deleted --- changelog.md | 1 + .../SmartStore.Services/Catalog/ProductService.cs | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/changelog.md b/changelog.md index 715ade0eaa..d81dd8aecb 100644 --- a/changelog.md +++ b/changelog.md @@ -50,6 +50,7 @@ * #249 Make UI editor for 'SeoSettings.ExtraRobotsDisallows' * Debitoor: Customer VAT number not transmitted anymore because it appears on the Debitoor invoice. * #778 Web-API: Increase MaxExpansionDepth for using expand pathes +* #767 Remove assignments to a grouped product if the grouped product is deleted ### Bugfixes * #523 Redirecting to payment provider performed by core instead of plugin diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs index c74ec355f9..c18f230bf5 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs @@ -251,6 +251,17 @@ public virtual void DeleteProduct(Product product) product.QuantityUnitId = null; UpdateProduct(product); + + if (product.ProductType == ProductType.GroupedProduct) + { + var associatedProducts = _productRepository.Table + .Where(x => x.ParentGroupedProductId == product.Id) + .ToList(); + + associatedProducts.ForEach(x => x.ParentGroupedProductId = 0); + + _dbContext.SaveChanges(); + } } /// From fbc1ed68b56c6977329a6c1393c5184a3a0131d4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 20 Oct 2015 13:09:51 +0200 Subject: [PATCH 137/874] Resolves #773 Reduce number of guest records created by search engine requests --- changelog.md | 1 + .../Customers/CustomerService.cs | 20 ++++++++++++++++++- .../CustomerRegistrationServiceTests.cs | 4 +++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index d81dd8aecb..3b88652893 100644 --- a/changelog.md +++ b/changelog.md @@ -51,6 +51,7 @@ * Debitoor: Customer VAT number not transmitted anymore because it appears on the Debitoor invoice. * #778 Web-API: Increase MaxExpansionDepth for using expand pathes * #767 Remove assignments to a grouped product if the grouped product is deleted +* #773 Reduce number of guest records created by search engine requests ### Bugfixes * #523 Redirecting to payment provider performed by core instead of plugin diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerService.cs b/src/Libraries/SmartStore.Services/Customers/CustomerService.cs index cd2e68d0cf..e27636dc9d 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerService.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerService.cs @@ -44,6 +44,7 @@ public partial class CustomerService : ICustomerService private readonly ICacheManager _cacheManager; private readonly IEventPublisher _eventPublisher; private readonly RewardPointsSettings _rewardPointsSettings; + private readonly IWebHelper _webHelper; #endregion @@ -56,7 +57,8 @@ public CustomerService(ICacheManager cacheManager, IRepository rewardPointsHistoryRepository, IGenericAttributeService genericAttributeService, IEventPublisher eventPublisher, - RewardPointsSettings rewardPointsSettings) + RewardPointsSettings rewardPointsSettings, + IWebHelper webHelper) { this._cacheManager = cacheManager; this._customerRepository = customerRepository; @@ -66,6 +68,7 @@ public CustomerService(ICacheManager cacheManager, this._genericAttributeService = genericAttributeService; this._eventPublisher = eventPublisher; this._rewardPointsSettings = rewardPointsSettings; + this._webHelper = webHelper; T = NullLocalizer.Instance; } @@ -346,6 +349,20 @@ orderby c.Id public virtual Customer InsertGuestCustomer() { + var ip = _webHelper.GetCurrentIpAddress(); + + if (ip.HasValue()) + { + var existingCustomer = _customerRepository.Table + .Where(x => !x.Deleted && x.Active && !x.IsSystemAccount && x.LastIpAddress == ip && x.CustomerRoles.Any(y => y.Active && y.SystemName == SystemCustomerRoleNames.Guests)) + .FirstOrDefault(); + + if (existingCustomer != null) + { + return existingCustomer; + } + } + var customer = new Customer { CustomerGuid = Guid.NewGuid(), @@ -358,6 +375,7 @@ public virtual Customer InsertGuestCustomer() var guestRole = GetCustomerRoleBySystemName(SystemCustomerRoleNames.Guests); if (guestRole == null) throw new SmartException("'Guests' role could not be loaded"); + customer.CustomerRoles.Add(guestRole); _customerRepository.Insert(customer); diff --git a/src/Tests/SmartStore.Services.Tests/Customers/CustomerRegistrationServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Customers/CustomerRegistrationServiceTests.cs index c3dfb206de..c589a427c4 100644 --- a/src/Tests/SmartStore.Services.Tests/Customers/CustomerRegistrationServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Customers/CustomerRegistrationServiceTests.cs @@ -36,6 +36,7 @@ public class CustomerRegistrationServiceTests : ServiceTest RewardPointsSettings _rewardPointsSettings; SecuritySettings _securitySettings; IStoreContext _storeContext; + IWebHelper _webHelper; [SetUp] public new void SetUp() @@ -119,9 +120,10 @@ public class CustomerRegistrationServiceTests : ServiceTest _localizationService = MockRepository.GenerateMock(); _storeContext = MockRepository.GenerateMock(); + _webHelper = MockRepository.GenerateMock(); _customerService = new CustomerService(new NullCache(), _customerRepo, _customerRoleRepo, - _genericAttributeRepo, _rewardPointsHistoryRepo, _genericAttributeService, _eventPublisher, _rewardPointsSettings); + _genericAttributeRepo, _rewardPointsHistoryRepo, _genericAttributeService, _eventPublisher, _rewardPointsSettings, _webHelper); _customerRegistrationService = new CustomerRegistrationService(_customerService, _encryptionService, _newsLetterSubscriptionService, _localizationService, From d7c7303da9d0f1d83cefebae640eea28607e10f1 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 20 Oct 2015 14:59:03 +0200 Subject: [PATCH 138/874] Closes #779 Sorting of filter values (backend). DisplayOrder should have always priority. --- src/Libraries/SmartStore.Services/Catalog/CategoryService.cs | 3 ++- .../SmartStore.Services/Catalog/ManufacturerService.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs b/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs index 5934f148d0..a525580283 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CategoryService.cs @@ -379,7 +379,8 @@ public virtual IPagedList GetAllCategories(string categoryName = "", i query = query .OrderBy(x => x.ParentCategoryId) - .ThenBy(x => x.DisplayOrder); + .ThenBy(x => x.DisplayOrder) + .ThenBy(x => x.Name); var unsortedCategories = query.ToList(); diff --git a/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs b/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs index 345ca078fe..e616f8cb2b 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ManufacturerService.cs @@ -142,7 +142,8 @@ public virtual IList GetAllManufacturers(string manufacturerName, if (manufacturerName.HasValue()) query = query.Where(m => m.Name.Contains(manufacturerName)); - query = query.OrderBy(m => m.DisplayOrder); + query = query.OrderBy(m => m.DisplayOrder) + .ThenBy(m => m.Name); var manufacturers = query.ToList(); return manufacturers; From a16fd015d13ca510f9cf00c1a59fcd34affff82d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 20 Oct 2015 17:48:57 +0200 Subject: [PATCH 139/874] Fixed: CreateAllProductVariantAttributeCombinations empties the whole combination table --- .../SmartStore.Services/Catalog/ProductAttributeService.cs | 2 +- .../Administration/Controllers/ProductController.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs index 1d889b5552..3a886d0913 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeService.cs @@ -570,7 +570,7 @@ public virtual void UpdateProductVariantAttributeCombination(ProductVariantAttri public virtual void CreateAllProductVariantAttributeCombinations(Product product) { // delete all existing combinations - _pvacRepository.DeleteAll(); + _pvacRepository.DeleteAll(x => x.ProductId == product.Id); var attributes = GetProductVariantAttributesByProductId(product.Id); if (attributes == null || attributes.Count <= 0) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index f4d1bc8e35..1671bfd387 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -4335,7 +4335,7 @@ public ActionResult DeleteAllAttributeCombinations(ProductVariantAttributeCombin if (product == null) throw new ArgumentException("No product found with the specified id"); - _pvacRepository.DeleteAll(); + _pvacRepository.DeleteAll(x => x.ProductId == product.Id); _productService.UpdateLowestAttributeCombinationPriceProperty(product); From c31f25be09189719dd6f1e94e2b6578e9c6e6b4c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 20 Oct 2015 20:50:34 +0200 Subject: [PATCH 140/874] Added a missing statement --- .../DataExchange/ExportTask/ExportProfileTask.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index bcfadc73df..0d4d486c2e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -2595,6 +2595,11 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) if (ctx.TaskContext.CancellationToken.IsCancellationRequested) ctx.Log.Warning("Export aborted. A cancellation has been requested"); } + + if (ctx.Export.Abort != ExportAbortion.Hard) + { + ctx.Provider.Value.ExecuteEnded(ctx.Export); + } } } From 0f8ddbf8b56a525bad65acbc54337657fa9edccf Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 22 Oct 2015 10:47:03 +0200 Subject: [PATCH 141/874] Resolves #791 Preselected attributes or attribute combinations should always be appended as querystring to product page links (part 1) Perf: Flyout shopping cart must not round-trip the database for bundle item data, OrganizedShoppingCart already provides it Flyout shopping cart should consider ShoppingCartSettings.ShowProductBundleImagesOnShoppingCart --- changelog.md | 1 + .../Controllers/ProductController.cs | 2 +- .../Controllers/ShoppingCartController.cs | 62 +++++++++++++------ .../ShoppingCart/MiniShoppingCartModel.cs | 3 + .../Models/ShoppingCart/ShoppingCartModel.cs | 2 + .../Models/ShoppingCart/WishlistModel.cs | 3 + .../ShoppingCart/FlyoutShoppingCart.cshtml | 11 ++-- .../Views/ShoppingCart/FlyoutWishlist.cshtml | 4 +- .../ShoppingCart/OrderSummary.Mobile.cshtml | 4 +- .../Views/ShoppingCart/OrderSummary.cshtml | 6 +- .../Views/ShoppingCart/Wishlist.Mobile.cshtml | 4 +- .../Views/ShoppingCart/Wishlist.cshtml | 4 +- 12 files changed, 67 insertions(+), 39 deletions(-) diff --git a/changelog.md b/changelog.md index 3b88652893..f72b36b86e 100644 --- a/changelog.md +++ b/changelog.md @@ -52,6 +52,7 @@ * #778 Web-API: Increase MaxExpansionDepth for using expand pathes * #767 Remove assignments to a grouped product if the grouped product is deleted * #773 Reduce number of guest records created by search engine requests +* #791 Preselected attributes or attribute combinations should always be appended as querystring to product page links ### Bugfixes * #523 Redirecting to payment provider performed by core instead of plugin diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 1671bfd387..3f2466d5e6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -4105,7 +4105,7 @@ public ActionResult ProductVariantAttributeCombinationList(GridCommand command, var productUrlTitle = _localizationService.GetResource("Common.OpenInShop"); var productUrl = Url.RouteUrl("Product", new { SeName = product.GetSeName() }); - productUrl = "{0}{1}attributes=".FormatWith(productUrl, productUrl.Contains("?") ? "&" : "?"); + productUrl = "{0}{1}attributes=".FormatInvariant(productUrl, productUrl.Contains("?") ? "&" : "?"); var productVariantAttributesModel = allCombinations.Select(x => { diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index e8b1e17fe1..5752b73a42 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -223,7 +223,7 @@ private ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartItemModel(Org product.MergeWithCombination(item.AttributesXml); - var model = new ShoppingCartModel.ShoppingCartItemModel() + var model = new ShoppingCartModel.ShoppingCartItemModel { Id = item.Id, Sku = product.Sku, @@ -235,10 +235,12 @@ private ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartItemModel(Org IsShipEnabled = product.IsShipEnabled, ShortDesc = product.GetLocalized(x => x.ShortDescription), ProductType = product.ProductType, - BasePrice = product.GetBasePriceInfo(_localizationService, _priceFormatter, _currencyService, _taxService, _priceCalculationService, _workContext.WorkingCurrency), - Weight = product.Weight + BasePrice = product.GetBasePriceInfo(_localizationService, _priceFormatter, _currencyService, _taxService, _priceCalculationService, _workContext.WorkingCurrency), + Weight = product.Weight }; + model.ProductUrl = GetProductUrl(item, model.ProductSeName); + if (item.BundleItem != null) { model.BundlePerItemPricing = item.BundleItem.BundleProduct.BundlePerItemPricing; @@ -419,7 +421,7 @@ private WishlistModel.ShoppingCartItemModel PrepareWishlistCartItemModel(Organiz product.MergeWithCombination(item.AttributesXml); - var model = new WishlistModel.ShoppingCartItemModel() + var model = new WishlistModel.ShoppingCartItemModel { Id = item.Id, Sku = product.Sku, @@ -432,6 +434,8 @@ private WishlistModel.ShoppingCartItemModel PrepareWishlistCartItemModel(Organiz VisibleIndividually = product.VisibleIndividually }; + model.ProductUrl = GetProductUrl(item, model.ProductSeName); + if (item.BundleItem != null) { model.BundlePerItemPricing = item.BundleItem.BundleProduct.BundlePerItemPricing; @@ -587,6 +591,20 @@ private void PrepareButtonPaymentMethodModel(ButtonPaymentMethodModel model, ILi } } + private string GetProductUrl(ShoppingCartItem cartItem, string productSeName) + { + var url = Url.RouteUrl("Product", new { SeName = productSeName }); + + var queryString = _productAttributeParser.SerializeQueryData(cartItem.ProductId, cartItem.AttributesXml); + + if (queryString.HasValue()) + { + url = string.Concat(url, url.Contains("?") ? "&" : "?", "attributes=", queryString); + } + + return url; + } + /// /// Prepare shopping cart model /// @@ -960,7 +978,7 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() .Take(_shoppingCartSettings.MiniShoppingCartProductNumber) .ToList()) { - var cartItemModel = new MiniShoppingCartModel.ShoppingCartItemModel() + var cartItemModel = new MiniShoppingCartModel.ShoppingCartItemModel { Id = sci.Item.Id, ProductId = sci.Item.Product.Id, @@ -977,23 +995,27 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() allowHyperlinks: false) }; - if (sci.Item.Product.ProductType == ProductType.BundledProduct) - { - var bundleItems = _productService.GetBundleItems(sci.Item.Product.Id); - foreach (var bundleItem in bundleItems) - { - var bundleItemModel = new MiniShoppingCartModel.ShoppingCartItemBundleItem(); - bundleItemModel.ProductName = bundleItem.Item.Product.GetLocalized(x => x.Name); - var bundlePic = _pictureService.GetPicturesByProductId(bundleItem.Item.ProductId).FirstOrDefault(); - if(bundlePic != null) - bundleItemModel.PictureUrl = _pictureService.GetPictureUrl(bundlePic.Id, 32); + cartItemModel.ProductUrl = GetProductUrl(sci.Item, cartItemModel.ProductSeName); - bundleItemModel.ProductSeName = bundleItem.Item.Product.GetSeName(); + if (sci.ChildItems != null && _shoppingCartSettings.ShowProductBundleImagesOnShoppingCart) + { + foreach (var childItem in sci.ChildItems.Where(x => x.Item.Id != sci.Item.Id && x.Item.BundleItem != null && !x.Item.BundleItem.HideThumbnail)) + { + var bundleItemModel = new MiniShoppingCartModel.ShoppingCartItemBundleItem + { + ProductName = childItem.Item.Product.GetLocalized(x => x.Name), + ProductSeName = childItem.Item.Product.GetSeName(), + }; - if (!bundleItem.Item.HideThumbnail) - cartItemModel.BundleItems.Add(bundleItemModel); - } - } + bundleItemModel.ProductUrl = GetProductUrl(childItem.Item, bundleItemModel.ProductSeName); + + var itemPicture = _pictureService.GetPicturesByProductId(childItem.Item.ProductId, 1).FirstOrDefault(); + if (itemPicture != null) + bundleItemModel.PictureUrl = _pictureService.GetPictureUrl(itemPicture.Id, 32); + + cartItemModel.BundleItems.Add(bundleItemModel); + } + } //unit prices if (sci.Item.Product.CallForPrice) diff --git a/src/Presentation/SmartStore.Web/Models/ShoppingCart/MiniShoppingCartModel.cs b/src/Presentation/SmartStore.Web/Models/ShoppingCart/MiniShoppingCartModel.cs index d7093304de..8a2aac28bc 100644 --- a/src/Presentation/SmartStore.Web/Models/ShoppingCart/MiniShoppingCartModel.cs +++ b/src/Presentation/SmartStore.Web/Models/ShoppingCart/MiniShoppingCartModel.cs @@ -40,6 +40,8 @@ public ShoppingCartItemModel() public string ProductSeName { get; set; } + public string ProductUrl { get; set; } + public int Quantity { get; set; } public string UnitPrice { get; set; } @@ -57,6 +59,7 @@ public partial class ShoppingCartItemBundleItem : ModelBase public string PictureUrl { get; set; } public string ProductName { get; set; } public string ProductSeName { get; set; } + public string ProductUrl { get; set; } } #endregion diff --git a/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs b/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs index 6416310328..35e9ab459e 100644 --- a/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs +++ b/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs @@ -73,6 +73,8 @@ public ShoppingCartItemModel() public string ProductSeName { get; set; } + public string ProductUrl { get; set; } + public bool VisibleIndividually { get; set; } public ProductType ProductType { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/ShoppingCart/WishlistModel.cs b/src/Presentation/SmartStore.Web/Models/ShoppingCart/WishlistModel.cs index f1d0053cf7..dec0395d63 100644 --- a/src/Presentation/SmartStore.Web/Models/ShoppingCart/WishlistModel.cs +++ b/src/Presentation/SmartStore.Web/Models/ShoppingCart/WishlistModel.cs @@ -51,6 +51,7 @@ public ShoppingCartItemModel() ChildItems = new List(); BundleItem = new BundleItemModel(); } + public string Sku { get; set; } public PictureModel Picture {get;set;} @@ -61,6 +62,8 @@ public ShoppingCartItemModel() public string ProductSeName { get; set; } + public string ProductUrl { get; set; } + public bool VisibleIndividually { get; set; } public ProductType ProductType { get; set; } diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/FlyoutShoppingCart.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/FlyoutShoppingCart.cshtml index 6b8df722bd..4af27a5a06 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/FlyoutShoppingCart.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/FlyoutShoppingCart.cshtml @@ -32,7 +32,7 @@ @if (Model.ShowProductImages) {
    - + @item.Picture.AlternateText
    @@ -41,7 +41,7 @@ @(item.Quantity)x
    - @item.ProductName + @item.ProductName @if (item.AttributeInfo.HasValue()) {
    @@ -71,19 +71,16 @@ { var bundleItem = item.BundleItems[j]; - - @bundleItem.ProductName - + + @bundleItem.ProductName if (bundlelength != (j + 1)) { } - }
    } - } @if (Model.IgnoredProductsCount > 0) diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/FlyoutWishlist.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/FlyoutWishlist.cshtml index 20ecf55aad..a2a157d2d9 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/FlyoutWishlist.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/FlyoutWishlist.cshtml @@ -28,7 +28,7 @@ @if (Model.ShowProductImages) {
    - + @item.Picture.AlternateText
    @@ -37,7 +37,7 @@ @(item.Quantity)x
    - @item.ProductName + @item.ProductName @if (item.AttributeInfo.HasValue()) {
    diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.Mobile.cshtml index 04c707cab1..8f3dccf235 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.Mobile.cshtml @@ -13,7 +13,7 @@
    @if (item.VisibleIndividually) { - @item.ProductName + @item.ProductName } else { @@ -81,7 +81,7 @@
  • - @item.ProductName + @item.ProductName @if (!String.IsNullOrEmpty(item.AttributeInfo)) {
    diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.cshtml index ad8359513a..a89ac3e95b 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.cshtml @@ -25,7 +25,7 @@ }
  • } } } } @if (Model.Provider.Description.HasValue()) 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 3b79b6fdeb..c1e5c4abc5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml @@ -165,7 +165,7 @@ // show\hide file system path $('#@(Html.FieldIdFor(x => x.IsPublic))').change(function () { - var val = !$(this).is(':checked') && $('#@(Html.FieldIdFor(x => x.DeploymentType))').val() === @((int)ExportDeploymentType.FileSystem); + var val = !$(this).is(':checked') && $('#@(Html.FieldIdFor(x => x.DeploymentType))').val() == @((int)ExportDeploymentType.FileSystem); $('#FileSystemPathContainer').toggle(val); }).trigger('change'); }); From 04ed9722af36f27a9de0fc606c38b901f403a323 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 25 Oct 2015 14:02:57 +0100 Subject: [PATCH 146/874] Fixed "The object in the 'ProductVariantAttributeCombination_Product_Source' role cannot be automatically added to the context because it was retrieved using the NoTracking merge option" --- .../Catalog/CopyProductService.cs | 73 ++++++++++--------- .../Controllers/ProductController.cs | 2 + 2 files changed, 40 insertions(+), 35 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs index cbf04229fb..71792bc582 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; +using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Media; using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Seo; using SmartStore.Services.Stores; -using SmartStore.Core.Data; namespace SmartStore.Services.Catalog { @@ -103,6 +103,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli Extension = download.Extension, IsNew = download.IsNew, }; + _downloadService.InsertDownload(downloadCopy); downloadId = downloadCopy.Id; } @@ -123,6 +124,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli Extension = sampleDownload.Extension, IsNew = sampleDownload.IsNew }; + _downloadService.InsertDownload(sampleDownloadCopy); sampleDownloadId = sampleDownloadCopy.Id; } @@ -280,6 +282,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli true, false, false); + _productService.InsertProductPicture(new ProductPicture { ProductId = productCopy.Id, @@ -292,7 +295,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli // product <-> categories mappings foreach (var productCategory in product.ProductCategories) { - var productCategoryCopy = new ProductCategory() + var productCategoryCopy = new ProductCategory { ProductId = productCopy.Id, CategoryId = productCategory.CategoryId, @@ -306,7 +309,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli // product <-> manufacturers mappings foreach (var productManufacturers in product.ProductManufacturers) { - var productManufacturerCopy = new ProductManufacturer() + var productManufacturerCopy = new ProductManufacturer { ProductId = productCopy.Id, ManufacturerId = productManufacturers.ManufacturerId, @@ -320,30 +323,28 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli // product <-> releated products mappings foreach (var relatedProduct in _productService.GetRelatedProductsByProductId1(product.Id, true)) { - _productService.InsertRelatedProduct( - new RelatedProduct() - { - ProductId1 = productCopy.Id, - ProductId2 = relatedProduct.ProductId2, - DisplayOrder = relatedProduct.DisplayOrder - }); + _productService.InsertRelatedProduct(new RelatedProduct + { + ProductId1 = productCopy.Id, + ProductId2 = relatedProduct.ProductId2, + DisplayOrder = relatedProduct.DisplayOrder + }); } // product <-> cross sells mappings foreach (var csProduct in _productService.GetCrossSellProductsByProductId1(product.Id, true)) { - _productService.InsertCrossSellProduct( - new CrossSellProduct() - { - ProductId1 = productCopy.Id, - ProductId2 = csProduct.ProductId2, - }); + _productService.InsertCrossSellProduct(new CrossSellProduct + { + ProductId1 = productCopy.Id, + ProductId2 = csProduct.ProductId2, + }); } // product specifications foreach (var productSpecificationAttribute in product.ProductSpecificationAttributes) { - var psaCopy = new ProductSpecificationAttribute() + var psaCopy = new ProductSpecificationAttribute { ProductId = productCopy.Id, SpecificationAttributeOptionId = productSpecificationAttribute.SpecificationAttributeOptionId, @@ -351,6 +352,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli ShowOnProductPage = productSpecificationAttribute.ShowOnProductPage, DisplayOrder = productSpecificationAttribute.DisplayOrder }; + _specificationAttributeService.InsertProductSpecificationAttribute(psaCopy); } @@ -364,9 +366,10 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli // product <-> attributes mappings var associatedAttributes = new Dictionary(); var associatedAttributeValues = new Dictionary(); + foreach (var productVariantAttribute in _productAttributeService.GetProductVariantAttributesByProductId(product.Id)) { - var productVariantAttributeCopy = new ProductVariantAttribute() + var productVariantAttributeCopy = new ProductVariantAttribute { ProductId = productCopy.Id, ProductAttributeId = productVariantAttribute.ProductAttributeId, @@ -375,15 +378,17 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli AttributeControlTypeId = productVariantAttribute.AttributeControlTypeId, DisplayOrder = productVariantAttribute.DisplayOrder }; + _productAttributeService.InsertProductVariantAttribute(productVariantAttributeCopy); //save associated value (used for combinations copying) associatedAttributes.Add(productVariantAttribute.Id, productVariantAttributeCopy.Id); // product variant attribute values var productVariantAttributeValues = _productAttributeService.GetProductVariantAttributeValues(productVariantAttribute.Id); + foreach (var productVariantAttributeValue in productVariantAttributeValues) { - var pvavCopy = new ProductVariantAttributeValue() + var pvavCopy = new ProductVariantAttributeValue { ProductVariantAttributeId = productVariantAttributeCopy.Id, Name = productVariantAttributeValue.Name, @@ -396,6 +401,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli LinkedProductId = productVariantAttributeValue.LinkedProductId, Quantity = productVariantAttributeValue.Quantity, }; + _productAttributeService.InsertProductVariantAttributeValue(pvavCopy); //save associated value (used for combinations copying) @@ -412,7 +418,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli } // attribute combinations - using (var scope = new DbContextScope(lazyLoading: false, forceNoTracking: true)) + using (var scope = new DbContextScope(lazyLoading: false, forceNoTracking: false)) { scope.LoadCollection(product, (Product p) => p.ProductVariantAttributeCombinations); } @@ -443,22 +449,20 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli var newPvav = _productAttributeService.GetProductVariantAttributeValueById(newPvavId); if (newPvav != null) { - newAttributesXml = _productAttributeParser.AddProductAttribute(newAttributesXml, - newPva, newPvav.Id.ToString()); + newAttributesXml = _productAttributeParser.AddProductAttribute(newAttributesXml, newPva, newPvav.Id.ToString()); } } } else { //just a text - newAttributesXml = _productAttributeParser.AddProductAttribute(newAttributesXml, - newPva, oldPvaValueStr); + newAttributesXml = _productAttributeParser.AddProductAttribute(newAttributesXml, newPva, oldPvaValueStr); } } } } } - var combinationCopy = new ProductVariantAttributeCombination() + var combinationCopy = new ProductVariantAttributeCombination { ProductId = productCopy.Id, AttributesXml = newAttributesXml, @@ -487,15 +491,14 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli // tier prices foreach (var tierPrice in product.TierPrices) { - _productService.InsertTierPrice( - new TierPrice() - { - ProductId = productCopy.Id, - StoreId = tierPrice.StoreId, - CustomerRoleId = tierPrice.CustomerRoleId, - Quantity = tierPrice.Quantity, - Price = tierPrice.Price - }); + _productService.InsertTierPrice(new TierPrice + { + ProductId = productCopy.Id, + StoreId = tierPrice.StoreId, + CustomerRoleId = tierPrice.CustomerRoleId, + Quantity = tierPrice.Quantity, + Price = tierPrice.Price + }); } // product <-> discounts mapping @@ -513,7 +516,7 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli // associated products if (copyAssociatedProducts && product.ProductType != ProductType.BundledProduct) { - var searchContext = new ProductSearchContext() + var searchContext = new ProductSearchContext { ParentGroupedProductId = product.Id, PageSize = int.MaxValue, diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 3f9fa79477..5b79e00770 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -1271,7 +1271,9 @@ public ActionResult CopyProduct(ProductModel model) { var product = _productService.GetProductById(copyModel.Id); var newProduct = _copyProductService.CopyProduct(product, copyModel.Name, copyModel.Published, copyModel.CopyImages); + NotifySuccess("The product is copied"); + return RedirectToAction("Edit", new { id = newProduct.Id }); } catch (Exception exc) From 83b197b9654d9fd5b5e2ffcbeda8f4284bc2c273 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 25 Oct 2015 14:28:32 +0100 Subject: [PATCH 147/874] Resolves #797 Payment method couldn't be loaded --- .../SmartStore.Services/Payments/PaymentService.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs index 7505bcea30..c029fc051b 100644 --- a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs +++ b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs @@ -381,10 +381,14 @@ public virtual ProcessPaymentResult ProcessPayment(ProcessPaymentRequest process /// Payment info required for an order processing public virtual void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest) { - var paymentMethod = LoadPaymentMethodBySystemName(postProcessPaymentRequest.Order.PaymentMethodSystemName); - if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); - paymentMethod.Value.PostProcessPayment(postProcessPaymentRequest); + if (postProcessPaymentRequest.Order.PaymentMethodSystemName.HasValue()) + { + var paymentMethod = LoadPaymentMethodBySystemName(postProcessPaymentRequest.Order.PaymentMethodSystemName); + if (paymentMethod == null) + throw new SmartException("Payment method couldn't be loaded"); + + paymentMethod.Value.PostProcessPayment(postProcessPaymentRequest); + } } /// From 88c296460f97a56bb8819e97cb66c434cc8f6f4c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 26 Oct 2015 12:24:50 +0100 Subject: [PATCH 148/874] Proper handling of deactivated export providers due to uninstalled plugins --- .../201508091512101_ExportFramework.cs | 4 - .../201509150931528_ExportFramework2.cs | 4 + .../ExportTask/ExportProfileTask.cs | 16 +- .../ExportTask/ExportProfileTaskContext.cs | 2 +- .../Controllers/ExportController.cs | 196 ++++++++---------- .../Models/DataExchange/ExportProfileModel.cs | 9 +- .../Administration/Views/Export/Create.cshtml | 10 +- .../Administration/Views/Export/Edit.cshtml | 166 ++++++++------- .../Administration/Views/Export/List.cshtml | 17 +- 9 files changed, 219 insertions(+), 205 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs index 65999838ab..64bb41f4d8 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs @@ -112,10 +112,6 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Validation.EmailAddress", "Please enter a valid email address", "Bitte geben Sie eine gltige E-Mail Adresse ein"); - builder.AddOrUpdate("Admin.DataExchange.Export.ProviderSystemName.Validate", - "There were no export provider found for system name \"{0}\". A provider is mandatory for an export profile.", - "Es wurde kein Export-Provider mit dem Systemnamen \"{0}\" gefunden. Ein Provider ist fr ein Exportprofil zwingend erforderlich."); - builder.AddOrUpdate("Admin.DataExchange.Export.NoProfiles", "There were no export profiles found.", "Es wurden keine Exportprofile gefunden."); diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index 5e3a2a8714..195519dbc5 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -48,6 +48,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "{0} folders were deleted", "{0} Verzeichnisse wurden gelscht"); + builder.AddOrUpdate("Admin.Common.ProviderNotLoaded", + "Cannot load the provider {0}.", + "Der Provider {0} konnte nicht geladen werden."); + builder.AddOrUpdate("Admin.DataExchange.Export.NotPreviewCompatible", "This option is not taken into account in the preview.", "Diese Option wird in der Vorschau nicht bercksichtigt."); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 0d4d486c2e..66698ab1a3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -2617,11 +2617,6 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) { try { - if (ctx.Provider == null) - { - throw new SmartException("Export aborted because the export provider cannot be loaded"); - } - if (!ctx.Provider.IsValid()) { throw new SmartException("Export aborted because the export provider is not valid"); @@ -2887,7 +2882,12 @@ public void Execute(TaskExecutionContext taskContext) var selectedIdsCacheKey = profile.GetSelectedEntityIdsCacheKey(); var selectedEntityIds = HttpRuntime.Cache[selectedIdsCacheKey] as string; - var ctx = new ExportProfileTaskContext(taskContext, profile, _exportService.Value.LoadProvider(profile.ProviderSystemName), selectedEntityIds); + var provider = _exportService.Value.LoadProvider(profile.ProviderSystemName); + + if (provider == null) + throw new SmartException(_services.Localization.GetResource("Admin.Common.ProviderNotLoaded").FormatInvariant(profile.ProviderSystemName.NaIfEmpty())); + + var ctx = new ExportProfileTaskContext(taskContext, profile, provider, selectedEntityIds); HttpRuntime.Cache.Remove(selectedIdsCacheKey); @@ -2925,6 +2925,9 @@ public ExportExecuteResult Execute(string providerSystemName, var provider = _exportService.Value.LoadProvider(providerSystemName); + if (provider == null) + throw new SmartException(_services.Localization.GetResource("Admin.Common.ProviderNotLoaded").FormatInvariant(providerSystemName.NaIfEmpty())); + if (profile == null) profile = _exportService.Value.CreateVolatileProfile(provider); @@ -2982,6 +2985,7 @@ public ExportExecuteResult Execute(string providerSystemName, public void Preview(ExportProfile profile, Provider provider, IComponentContext context, int pageIndex, int totalRecords, Action previewData) { Guard.ArgumentNotNull(() => profile); + Guard.ArgumentNotNull(() => provider); Guard.ArgumentNotNull(() => context); var taskContext = new TaskExecutionContext(context, null); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs index 6e120e1866..2beab3db1b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs @@ -115,7 +115,7 @@ public string LogPath public bool IsFileBasedExport { - get { return Provider.Value.FileExtension.HasValue(); } + get { return Provider == null || Provider.Value == null || Provider.Value.FileExtension.HasValue(); } } public string[] GetDeploymentFiles(ExportDeployment deployment) { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 07952a0a1d..33d35d9fbb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -24,7 +24,6 @@ using SmartStore.Services.Directory; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; -using SmartStore.Services.Media; using SmartStore.Services.Messages; using SmartStore.Services.Security; using SmartStore.Web.Framework; @@ -41,7 +40,6 @@ public class ExportController : AdminControllerBase private readonly ICommonServices _services; private readonly IExportService _exportService; private readonly PluginMediator _pluginMediator; - private readonly IPictureService _pictureService; private readonly ICategoryService _categoryService; private readonly IManufacturerService _manufacturerService; private readonly IProductTagService _productTagService; @@ -57,7 +55,6 @@ public ExportController( ICommonServices services, IExportService exportService, PluginMediator pluginMediator, - IPictureService pictureService, ICategoryService categoryService, IManufacturerService manufacturerService, IProductTagService productTagService, @@ -72,7 +69,6 @@ public ExportController( _services = services; _exportService = exportService; _pluginMediator = pluginMediator; - _pictureService = pictureService; _categoryService = categoryService; _manufacturerService = manufacturerService; _productTagService = productTagService; @@ -89,15 +85,15 @@ public ExportController( private string GetThumbnailUrl(Provider provider) { - if (provider == null) - return ""; + string url = null; - var url = _pluginMediator.GetIconUrl(provider.Metadata); + if (provider != null) + url = _pluginMediator.GetIconUrl(provider.Metadata); if (url.IsEmpty()) - url = _pictureService.GetDefaultPictureUrl(48); - else - url = Url.Content(url); + url = _pluginMediator.GetDefaultIconUrl(null); + + url = Url.Content(url); return url; } @@ -106,6 +102,7 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile { model.Id = profile.Id; model.Name = profile.Name; + model.ProviderSystemName = profile.ProviderSystemName; model.FolderName = profile.FolderName; model.FileNamePattern = profile.FileNamePattern; model.Enabled = profile.Enabled; @@ -114,18 +111,15 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile model.IsTaskRunning = profile.ScheduleTask.IsRunning; model.IsTaskEnabled = profile.ScheduleTask.Enabled; model.LogFileExists = System.IO.File.Exists(profile.GetExportLogFilePath()); + model.HasActiveProvider = (provider != null); - model.Provider = new ExportProfileModel.ProviderModel - { - SystemName = profile.ProviderSystemName - }; + model.Provider = new ExportProfileModel.ProviderModel(); + model.Provider.ThumbnailUrl = GetThumbnailUrl(provider); if (provider != null) { var descriptor = provider.Metadata.PluginDescriptor; - model.Provider.ThumbnailUrl = GetThumbnailUrl(provider); - if (descriptor != null) { model.Provider.Url = descriptor.Url; @@ -172,39 +166,6 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile model.SerializedCompletedEmailAddresses = string.Join(",", profile.CompletedEmailAddresses.SplitSafe(",").Select(x => x.EncodeJsString())); - // get and initialize provider specific configuration data - if (provider != null) - { - model.Provider.Supporting = provider.Metadata.ExportSupport; - - try - { - var configInfo = provider.Value.ConfigurationInfo; - if (configInfo != null) - { - model.Provider.ConfigPartialViewName = configInfo.PartialViewName; - model.Provider.ConfigDataType = configInfo.ModelType; - model.Provider.ConfigData = XmlHelper.Deserialize(profile.ProviderConfigData, configInfo.ModelType); - - if (configInfo.Initialize != null) - { - try - { - configInfo.Initialize(model.Provider.ConfigData); - } - catch (Exception exc) - { - NotifyWarning(exc.ToAllMessages()); - } - } - } - } - catch (Exception exc) - { - NotifyError(exc); - } - } - // projection model.Projection = new ExportProjectionModel { @@ -242,21 +203,6 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) .ToList(); - - if (model.Provider.EntityType == ExportEntityType.Product) - { - model.Projection.AvailableDescriptionMergings = ExportDescriptionMerging.Description.ToSelectList(false); - model.Projection.AvailablePriceTypes = PriceDisplayType.LowestPrice.ToSelectList(false); - model.Projection.AvailableAttributeCombinationValueMerging = ExportAttributeValueMerging.AppendAllValuesToName.ToSelectList(false); - - model.Projection.SerializedAppendDescriptionText = string.Join(",", projection.AppendDescriptionText.SplitSafe(",").Select(x => x.EncodeJsString())); - model.Projection.SerializedCriticalCharacters = string.Join(",", projection.CriticalCharacters.SplitSafe(",").Select(x => x.EncodeJsString())); - } - else if (model.Provider.EntityType == ExportEntityType.Order) - { - model.Projection.AvailableOrderStatusChange = ExportOrderStatusChange.Processing.ToSelectList(false); - } - // filtering model.Filter = new ExportFilterModel { @@ -287,42 +233,6 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile .Select(y => new SelectListItem { Text = y.Name, Value = y.Id.ToString() }) .ToList(); - - if (model.Provider.EntityType == ExportEntityType.Product) - { - var allCategories = _categoryService.GetAllCategories(showHidden: true); - var mappedCategories = allCategories.ToDictionary(x => x.Id); - var allManufacturers = _manufacturerService.GetAllManufacturers(true); - var allProductTags = _productTagService.GetAllProductTags(); - - model.Filter.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); - - model.Filter.AvailableCategories = allCategories - .Select(x => new SelectListItem { Text = x.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = x.Id.ToString() }) - .ToList(); - - model.Filter.AvailableManufacturers = allManufacturers - .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) - .ToList(); - - model.Filter.AvailableProductTags = allProductTags - .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) - .ToList(); - } - else if (model.Provider.EntityType == ExportEntityType.Order) - { - var allCustomerRoles = _customerService.GetAllCustomerRoles(true); - - model.Filter.AvailableOrderStates = OrderStatus.Pending.ToSelectList(false).ToList(); - model.Filter.AvailablePaymentStates = PaymentStatus.Pending.ToSelectList(false).ToList(); - model.Filter.AvailableShippingStates = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); - - model.Filter.AvailableCustomerRoles = allCustomerRoles - .OrderBy(x => x.Name) - .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) - .ToList(); - } - // deployment model.Deployments = profile.Deployments .Select(x => @@ -364,6 +274,84 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile return deploymentModel; }) .ToList(); + + + if (provider != null) + { + model.Provider.Supporting = provider.Metadata.ExportSupport; + + if (model.Provider.EntityType == ExportEntityType.Product) + { + var allCategories = _categoryService.GetAllCategories(showHidden: true); + var mappedCategories = allCategories.ToDictionary(x => x.Id); + var allManufacturers = _manufacturerService.GetAllManufacturers(true); + var allProductTags = _productTagService.GetAllProductTags(); + + model.Projection.AvailableDescriptionMergings = ExportDescriptionMerging.Description.ToSelectList(false); + model.Projection.AvailablePriceTypes = PriceDisplayType.LowestPrice.ToSelectList(false); + model.Projection.AvailableAttributeCombinationValueMerging = ExportAttributeValueMerging.AppendAllValuesToName.ToSelectList(false); + + model.Projection.SerializedAppendDescriptionText = string.Join(",", projection.AppendDescriptionText.SplitSafe(",").Select(x => x.EncodeJsString())); + model.Projection.SerializedCriticalCharacters = string.Join(",", projection.CriticalCharacters.SplitSafe(",").Select(x => x.EncodeJsString())); + + model.Filter.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); + + model.Filter.AvailableCategories = allCategories + .Select(x => new SelectListItem { Text = x.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = x.Id.ToString() }) + .ToList(); + + model.Filter.AvailableManufacturers = allManufacturers + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + model.Filter.AvailableProductTags = allProductTags + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + } + else if (model.Provider.EntityType == ExportEntityType.Order) + { + var allCustomerRoles = _customerService.GetAllCustomerRoles(true); + + model.Projection.AvailableOrderStatusChange = ExportOrderStatusChange.Processing.ToSelectList(false); + + model.Filter.AvailableOrderStates = OrderStatus.Pending.ToSelectList(false).ToList(); + model.Filter.AvailablePaymentStates = PaymentStatus.Pending.ToSelectList(false).ToList(); + model.Filter.AvailableShippingStates = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); + + model.Filter.AvailableCustomerRoles = allCustomerRoles + .OrderBy(x => x.Name) + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + } + + try + { + var configInfo = provider.Value.ConfigurationInfo; + if (configInfo != null) + { + model.Provider.ConfigPartialViewName = configInfo.PartialViewName; + model.Provider.ConfigDataType = configInfo.ModelType; + model.Provider.ConfigData = XmlHelper.Deserialize(profile.ProviderConfigData, configInfo.ModelType); + + if (configInfo.Initialize != null) + { + try + { + configInfo.Initialize(model.Provider.ConfigData); + } + catch (Exception exc) + { + NotifyWarning(exc.ToAllMessages()); + } + } + } + } + catch (Exception exc) + { + NotifyError(exc); + } + } } private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment, Provider provider, bool forEdit) @@ -492,7 +480,7 @@ public ActionResult Create() model.Provider = new ExportProfileModel.ProviderModel(); - model.Provider.AvailableProviders = allProviders + model.AvailableProviders = allProviders .Select(x => { var item = new ExportProfileModel.ProviderSelectItem @@ -531,9 +519,9 @@ public ActionResult Create(ExportProfileModel model) if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); - if (model.Provider.SystemName.HasValue()) + if (model.ProviderSystemName.HasValue()) { - var provider = _exportService.LoadProvider(model.Provider.SystemName); + var provider = _exportService.LoadProvider(model.ProviderSystemName); if (provider != null) { var profile = _exportService.InsertExportProfile(provider, model.CloneProfileId ?? 0); @@ -542,7 +530,7 @@ public ActionResult Create(ExportProfileModel model) } } - NotifyError(T("Admin.DataExchange.Export.ProviderSystemName.Validate", model.Provider.SystemName.NaIfEmpty())); + NotifyError(T("Admin.Common.ProviderNotLoaded", model.ProviderSystemName.NaIfEmpty())); return RedirectToAction("List"); } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 08f7af67b3..80dce133e9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -18,10 +18,15 @@ public partial class ExportProfileModel : EntityModelBase public string AllString { get; set; } public string UnspecifiedString { get; set; } public bool LogFileExists { get; set; } + public bool HasActiveProvider { get; set; } [SmartResourceDisplayName("Admin.DataExchange.Export.Name")] public string Name { get; set; } + [SmartResourceDisplayName("Admin.DataExchange.Export.ProviderSystemName")] + public string ProviderSystemName { get; set; } + public List AvailableProviders { get; set; } + [SmartResourceDisplayName("Admin.DataExchange.Export.FolderName")] public string FolderName { get; set; } @@ -100,10 +105,6 @@ public class ProviderModel [SmartResourceDisplayName("Admin.Configuration.Plugins.Fields.Configure")] public string ConfigurationUrl { get; set; } - [SmartResourceDisplayName("Admin.DataExchange.Export.ProviderSystemName")] - public string SystemName { get; set; } - public List AvailableProviders { get; set; } - [SmartResourceDisplayName("Common.Provider")] public string FriendlyName { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml index 3af31d9510..f1f162fedf 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Create.cshtml @@ -5,17 +5,17 @@ }
    - @if(Model.Provider.AvailableProviders.Count > 0) + @if(Model.AvailableProviders.Count > 0) {
    - @Html.DropDownListFor(model => model.Provider.SystemName, Model.Provider.AvailableExportProviders, new { style = "width: 500px;" }) +
    - @Html.DropDownListFor(model => model.CloneProfileId, Model.AvailableProfiles, Model.UnspecifiedString, new { style = "width: 500px;" }) +
    - @foreach (var item in Model.Provider.ProviderDescriptions) + @foreach (var item in Model.Provider.AvailableProviders) { - @item.Value + @item.Description }
    @Html.SmartLabelFor(x => x.Url) - @Html.EditorFor(x => x.Username) + @Html.TextBoxFor(x => x.Username, new { autocomplete = "off", @class = "text-box single-line" }) @Html.ValidationMessageFor(x => x.Username)
    - @Html.EditorFor(x => x.Password) + @Html.TextBoxFor(x => x.Password, new { autocomplete = "off", type = "password", @class = "text-box single-line password" }) @Html.ValidationMessageFor(x => x.Password)
    - @item.ProductName + @item.ProductName @if (Model.DisplayShortDesc && !String.IsNullOrEmpty(item.ShortDesc)) { @@ -151,7 +151,7 @@
    @if (Model.ShowProductBundleImages) { -
    +
    @if (!item.BundleItem.HideThumbnail) { @item.Picture.AlternateText @@ -162,7 +162,7 @@
    @if (item.VisibleIndividually) { - @item.ProductName + @item.ProductName } else { diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.Mobile.cshtml index f9b932550b..b0fd2d8075 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.Mobile.cshtml @@ -20,7 +20,7 @@
    @if (item.VisibleIndividually) { - @item.ProductName + @item.ProductName } else { @@ -99,7 +99,7 @@
  • - @item.ProductName + @item.ProductName @if (!String.IsNullOrEmpty(item.AttributeInfo)) {
    diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.cshtml index 5f5620e835..9bcd076959 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.cshtml @@ -38,7 +38,7 @@
  • - @item.ProductName + @item.ProductName @if (Model.DisplayShortDesc && !String.IsNullOrEmpty(item.ShortDesc)) { @@ -144,7 +144,7 @@
    @if (item.VisibleIndividually) { - @item.ProductName + @item.ProductName } else { From 4b75aac69bcb1f8581ee7567af490458b58f5cea Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 22 Oct 2015 12:38:05 +0200 Subject: [PATCH 142/874] #791 Preselected attributes or attribute combinations should always be appended as querystring to product page links (part 2) --- .../Catalog/IProductAttributeParser.cs | 9 +++ .../Catalog/ProductAttributeParser.cs | 23 +++++++ .../Controllers/CustomerController.cs | 21 ++++-- .../Controllers/OrderController.cs | 64 ++++++++++++------- .../Controllers/ReturnRequestController.cs | 9 ++- .../Controllers/ShoppingCartController.cs | 24 ++----- .../CustomerDownloadableProductsModel.cs | 3 + .../Customer/CustomerReturnRequestsModel.cs | 3 + .../Models/Order/OrderDetailsModel.cs | 2 + .../Models/Order/ShipmentDetailsModel.cs | 1 + .../Models/Order/SubmitReturnRequestModel.cs | 2 + .../Themes/Alpha/Content/profile.less | 12 ++-- .../DownloadableProducts.Mobile.cshtml | 3 +- .../Customer/DownloadableProducts.cshtml | 3 +- .../Customer/ReturnRequests.Mobile.cshtml | 7 +- .../Views/Customer/ReturnRequests.cshtml | 17 +++-- .../Views/Order/Details.Mobile.cshtml | 4 +- .../SmartStore.Web/Views/Order/Details.cshtml | 5 +- .../Views/Order/ShipmentDetails.Mobile.cshtml | 4 +- .../Views/Order/ShipmentDetails.cshtml | 4 +- .../ReturnRequest/ReturnRequest.Mobile.cshtml | 3 +- .../Views/ReturnRequest/ReturnRequest.cshtml | 2 +- .../ShoppingCart/OrderSummary.Mobile.cshtml | 2 +- .../Views/ShoppingCart/Wishlist.Mobile.cshtml | 3 +- .../Views/ShoppingCart/Wishlist.cshtml | 3 +- 25 files changed, 149 insertions(+), 84 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs index 70a45887dd..bdc8ef7c82 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs @@ -90,6 +90,15 @@ public partial interface IProductAttributeParser /// Json string with attribute data string SerializeQueryData(int productId, string attributesXml, bool urlEncode = true); + /// + /// Gets the URL of the product page including attributes query string + /// + /// Product identifier + /// Product SEO name + /// XML formatted attributes + /// URL of the product page including attributes query string + string GetProductUrlWithAttributes(int productId, string productSeName, string attributesXml); + #endregion #region Gift card attributes diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs index 7b557b1a2c..635893d821 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Text; using System.Web; +using System.Web.Mvc; +using System.Web.Routing; using System.Xml; using System.Xml.Linq; using Newtonsoft.Json; @@ -421,6 +423,27 @@ public virtual string SerializeQueryData(int productId, string attributesXml, bo return ""; } + public virtual string GetProductUrlWithAttributes(int productId, string productSeName, string attributesXml) + { + var url = UrlHelper.GenerateUrl( + "Product", + null, + null, + new RouteValueDictionary(new { SeName = productSeName }), + RouteTable.Routes, + HttpContext.Current.Request.RequestContext, + false); + + var queryString = SerializeQueryData(productId, attributesXml); + + if (queryString.HasValue()) + { + url = string.Concat(url, url.Contains("?") ? "&" : "?", "attributes=", queryString); + } + + return url; + } + #endregion #region Gift card attributes diff --git a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs index 4a13b8c298..62248ddbb4 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs @@ -73,6 +73,7 @@ public partial class CustomerController : PublicControllerBase private readonly IDownloadService _downloadService; private readonly IWebHelper _webHelper; private readonly ICustomerActivityService _customerActivityService; + private readonly IProductAttributeParser _productAttributeParser; private readonly MediaSettings _mediaSettings; private readonly IWorkflowMessageService _workflowMessageService; @@ -105,7 +106,9 @@ public CustomerController(IAuthenticationService authenticationService, IOpenAuthenticationService openAuthenticationService, IBackInStockSubscriptionService backInStockSubscriptionService, IDownloadService downloadService, IWebHelper webHelper, - ICustomerActivityService customerActivityService, MediaSettings mediaSettings, + ICustomerActivityService customerActivityService, + IProductAttributeParser productAttributeParser, + MediaSettings mediaSettings, IWorkflowMessageService workflowMessageService, LocalizationSettings localizationSettings, CaptchaSettings captchaSettings, ExternalAuthenticationSettings externalAuthenticationSettings, PluginMediator pluginMediator) @@ -143,6 +146,7 @@ public CustomerController(IAuthenticationService authenticationService, this._downloadService = downloadService; this._webHelper = webHelper; this._customerActivityService = customerActivityService; + this._productAttributeParser = productAttributeParser; this._mediaSettings = mediaSettings; this._workflowMessageService = workflowMessageService; @@ -1345,7 +1349,7 @@ public ActionResult ReturnRequests() { var product = orderItem.Product; - var itemModel = new CustomerReturnRequestsModel.ReturnRequestModel() + var itemModel = new CustomerReturnRequestsModel.ReturnRequestModel { Id = returnRequest.Id, ReturnRequestStatus = returnRequest.ReturnRequestStatus.GetLocalizedEnum(_localizationService, _workContext), @@ -1356,8 +1360,11 @@ public ActionResult ReturnRequests() ReturnAction = returnRequest.RequestedAction, ReturnReason = returnRequest.ReasonForReturn, Comments = returnRequest.CustomerComments, - CreatedOn = _dateTimeHelper.ConvertToUserTime(returnRequest.CreatedOnUtc, DateTimeKind.Utc), + CreatedOn = _dateTimeHelper.ConvertToUserTime(returnRequest.CreatedOnUtc, DateTimeKind.Utc) }; + + itemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(orderItem.ProductId, itemModel.ProductSeName, orderItem.AttributesXml); + model.Items.Add(itemModel); } } @@ -1380,8 +1387,9 @@ public ActionResult DownloadableProducts() var model = new CustomerDownloadableProductsModel(); model.NavigationModel = GetCustomerNavigationModel(customer); model.NavigationModel.SelectedTab = CustomerNavigationEnum.DownloadableProducts; - var items = _orderService.GetAllOrderItems(null, customer.Id, null, null, - null, null, null, true); + + var items = _orderService.GetAllOrderItems(null, customer.Id, null, null, null, null, null, true); + foreach (var item in items) { var itemModel = new CustomerDownloadableProductsModel.DownloadableProductsModel @@ -1394,6 +1402,9 @@ public ActionResult DownloadableProducts() ProductAttributes = item.AttributeDescription, ProductId = item.ProductId }; + + itemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(item.ProductId, itemModel.ProductSeName, item.AttributesXml); + model.Items.Add(itemModel); if (_downloadService.IsDownloadAllowed(item)) diff --git a/src/Presentation/SmartStore.Web/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Controllers/OrderController.cs index b323a58843..ec9eb8365b 100644 --- a/src/Presentation/SmartStore.Web/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/OrderController.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Web.Mvc; using System.Web.Routing; -using System.Web.SessionState; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; @@ -11,7 +10,6 @@ using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Tax; using SmartStore.Core.Html; -using SmartStore.Core.Localization; using SmartStore.Services; using SmartStore.Services.Catalog; using SmartStore.Services.Directory; @@ -49,6 +47,7 @@ public partial class OrderController : PublicControllerBase private readonly ICountryService _countryService; private readonly IProductService _productService; private readonly IProductAttributeFormatter _productAttributeFormatter; + private readonly IProductAttributeParser _productAttributeParser; private readonly IStoreService _storeService; private readonly ICheckoutAttributeFormatter _checkoutAttributeFormatter; private readonly PluginMediator _pluginMediator; @@ -74,6 +73,7 @@ public OrderController( IStoreService storeService, IProductService productService, IProductAttributeFormatter productAttributeFormatter, + IProductAttributeParser productAttributeParser, Lazy pictureService, PluginMediator pluginMediator, ICommonServices services, @@ -91,6 +91,7 @@ public OrderController( this._countryService = countryService; this._productService = productService; this._productAttributeFormatter = productAttributeFormatter; + this._productAttributeParser = productAttributeParser; this._storeService = storeService; this._checkoutAttributeFormatter = checkoutAttributeFormatter; this._pluginMediator = pluginMediator; @@ -342,17 +343,20 @@ protected ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipment) var catalogSettings = _services.Settings.LoadSetting(store.Id); var shippingSettings = _services.Settings.LoadSetting(store.Id); - var model = new ShipmentDetailsModel(); - - model.Id = shipment.Id; + var model = new ShipmentDetailsModel + { + Id = shipment.Id, + TrackingNumber = shipment.TrackingNumber + }; + if (shipment.ShippedDateUtc.HasValue) model.ShippedDate = _dateTimeHelper.ConvertToUserTime(shipment.ShippedDateUtc.Value, DateTimeKind.Utc); + if (shipment.DeliveryDateUtc.HasValue) model.DeliveryDate = _dateTimeHelper.ConvertToUserTime(shipment.DeliveryDateUtc.Value, DateTimeKind.Utc); - //tracking number and shipment information - model.TrackingNumber = shipment.TrackingNumber; var srcm = _shippingService.LoadShippingRateComputationMethodBySystemName(order.ShippingRateComputationMethodSystemName); + if (srcm != null && srcm.IsShippingRateComputationMethodActive(shippingSettings)) { var shipmentTracker = srcm.Value.ShipmentTracker; @@ -362,19 +366,23 @@ protected ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipment) if (shippingSettings.DisplayShipmentEventsToCustomers) { var shipmentEvents = shipmentTracker.GetShipmentEvents(shipment.TrackingNumber); - if (shipmentEvents != null) - foreach (var shipmentEvent in shipmentEvents) - { - var shipmentStatusEventModel = new ShipmentDetailsModel.ShipmentStatusEventModel(); - var shipmentEventCountry = _countryService.GetCountryByTwoLetterIsoCode(shipmentEvent.CountryCode); - shipmentStatusEventModel.Country = shipmentEventCountry != null - ? shipmentEventCountry.GetLocalized(x => x.Name) - : shipmentEvent.CountryCode; - shipmentStatusEventModel.Date = shipmentEvent.Date; - shipmentStatusEventModel.EventName = shipmentEvent.EventName; - shipmentStatusEventModel.Location = shipmentEvent.Location; - model.ShipmentStatusEvents.Add(shipmentStatusEventModel); - } + if (shipmentEvents != null) + { + foreach (var shipmentEvent in shipmentEvents) + { + var shipmentEventCountry = _countryService.GetCountryByTwoLetterIsoCode(shipmentEvent.CountryCode); + + var shipmentStatusEventModel = new ShipmentDetailsModel.ShipmentStatusEventModel + { + Country = (shipmentEventCountry != null ? shipmentEventCountry.GetLocalized(x => x.Name) : shipmentEvent.CountryCode), + Date = shipmentEvent.Date, + EventName = shipmentEvent.EventName, + Location = shipmentEvent.Location + }; + + model.ShipmentStatusEvents.Add(shipmentStatusEventModel); + } + } } } } @@ -388,7 +396,8 @@ protected ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipment) continue; orderItem.Product.MergeWithCombination(orderItem.AttributesXml); - var shipmentItemModel = new ShipmentDetailsModel.ShipmentItemModel() + + var shipmentItemModel = new ShipmentDetailsModel.ShipmentItemModel { Id = shipmentItem.Id, Sku = orderItem.Product.Sku, @@ -397,8 +406,11 @@ protected ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipment) ProductSeName = orderItem.Product.GetSeName(), AttributeInfo = orderItem.AttributeDescription, QuantityOrdered = orderItem.Quantity, - QuantityShipped = shipmentItem.Quantity, + QuantityShipped = shipmentItem.Quantity }; + + shipmentItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(orderItem.ProductId, shipmentItemModel.ProductSeName, orderItem.AttributesXml); + model.Items.Add(shipmentItemModel); } @@ -412,7 +424,7 @@ private OrderDetailsModel.OrderItemModel PrepareOrderItemModel(Order order, Orde { orderItem.Product.MergeWithCombination(orderItem.AttributesXml); - var model = new OrderDetailsModel.OrderItemModel() + var model = new OrderDetailsModel.OrderItemModel { Id = orderItem.Id, Sku = orderItem.Product.Sku, @@ -424,6 +436,8 @@ private OrderDetailsModel.OrderItemModel PrepareOrderItemModel(Order order, Orde AttributeInfo = orderItem.AttributeDescription }; + model.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(orderItem.ProductId, model.ProductSeName, orderItem.AttributesXml); + var quantityUnit = _quantityUnitService.GetQuantityUnitById(orderItem.Product.QuantityUnitId); model.QuantityUnit = quantityUnit == null ? "" : quantityUnit.GetLocalized(x => x.Name); @@ -436,7 +450,7 @@ private OrderDetailsModel.OrderItemModel PrepareOrderItemModel(Order order, Orde foreach (var bundleItem in bundleData) { - var bundleItemModel = new OrderDetailsModel.BundleItemModel() + var bundleItemModel = new OrderDetailsModel.BundleItemModel { Sku = bundleItem.Sku, ProductName = bundleItem.ProductName, @@ -447,6 +461,8 @@ private OrderDetailsModel.OrderItemModel PrepareOrderItemModel(Order order, Orde AttributeInfo = bundleItem.AttributesInfo }; + bundleItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(bundleItem.ProductId, bundleItemModel.ProductSeName, bundleItem.AttributesXml); + if (model.BundlePerItemShoppingCart) { decimal priceWithDiscount = _currencyService.ConvertCurrency(bundleItem.PriceWithDiscount, order.CurrencyRate); diff --git a/src/Presentation/SmartStore.Web/Controllers/ReturnRequestController.cs b/src/Presentation/SmartStore.Web/Controllers/ReturnRequestController.cs index 95d0aa59af..8fa1e94a3a 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ReturnRequestController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ReturnRequestController.cs @@ -30,6 +30,7 @@ public partial class ReturnRequestController : PublicControllerBase private readonly ILocalizationService _localizationService; private readonly ICustomerService _customerService; private readonly IWorkflowMessageService _workflowMessageService; + private readonly IProductAttributeParser _productAttributeParser; private readonly LocalizationSettings _localizationSettings; private readonly OrderSettings _orderSettings; @@ -46,6 +47,7 @@ public ReturnRequestController( ILocalizationService localizationService, ICustomerService customerService, IWorkflowMessageService workflowMessageService, + IProductAttributeParser productAttributeParser, LocalizationSettings localizationSettings, OrderSettings orderSettings) { @@ -58,6 +60,7 @@ public ReturnRequestController( this._localizationService = localizationService; this._customerService = customerService; this._workflowMessageService = workflowMessageService; + this._productAttributeParser = productAttributeParser; this._localizationSettings = localizationSettings; this._orderSettings = orderSettings; @@ -95,9 +98,10 @@ protected SubmitReturnRequestModel PrepareReturnRequestModel(SubmitReturnRequest //products var orderItems = _orderService.GetAllOrderItems(order.Id, null, null, null, null, null, null); + foreach (var orderItem in orderItems) { - var orderItemModel = new SubmitReturnRequestModel.OrderItemModel() + var orderItemModel = new SubmitReturnRequestModel.OrderItemModel { Id = orderItem.Id, ProductId = orderItem.Product.Id, @@ -106,6 +110,9 @@ protected SubmitReturnRequestModel PrepareReturnRequestModel(SubmitReturnRequest AttributeInfo = orderItem.AttributeDescription, Quantity = orderItem.Quantity }; + + orderItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(orderItem.ProductId, orderItemModel.ProductSeName, orderItem.AttributesXml); + model.Items.Add(orderItemModel); //unit price diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index 5752b73a42..5c22d74b7a 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; using System.Web; using System.Web.Mvc; @@ -239,7 +238,7 @@ private ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartItemModel(Org Weight = product.Weight }; - model.ProductUrl = GetProductUrl(item, model.ProductSeName); + model.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(item.ProductId, model.ProductSeName, item.AttributesXml); if (item.BundleItem != null) { @@ -434,7 +433,7 @@ private WishlistModel.ShoppingCartItemModel PrepareWishlistCartItemModel(Organiz VisibleIndividually = product.VisibleIndividually }; - model.ProductUrl = GetProductUrl(item, model.ProductSeName); + model.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(item.ProductId, model.ProductSeName, item.AttributesXml); if (item.BundleItem != null) { @@ -591,20 +590,6 @@ private void PrepareButtonPaymentMethodModel(ButtonPaymentMethodModel model, ILi } } - private string GetProductUrl(ShoppingCartItem cartItem, string productSeName) - { - var url = Url.RouteUrl("Product", new { SeName = productSeName }); - - var queryString = _productAttributeParser.SerializeQueryData(cartItem.ProductId, cartItem.AttributesXml); - - if (queryString.HasValue()) - { - url = string.Concat(url, url.Contains("?") ? "&" : "?", "attributes=", queryString); - } - - return url; - } - /// /// Prepare shopping cart model /// @@ -995,7 +980,7 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() allowHyperlinks: false) }; - cartItemModel.ProductUrl = GetProductUrl(sci.Item, cartItemModel.ProductSeName); + cartItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(sci.Item.ProductId, cartItemModel.ProductSeName, sci.Item.AttributesXml); if (sci.ChildItems != null && _shoppingCartSettings.ShowProductBundleImagesOnShoppingCart) { @@ -1007,7 +992,8 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() ProductSeName = childItem.Item.Product.GetSeName(), }; - bundleItemModel.ProductUrl = GetProductUrl(childItem.Item, bundleItemModel.ProductSeName); + bundleItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes( + childItem.Item.ProductId, bundleItemModel.ProductSeName, childItem.Item.AttributesXml); var itemPicture = _pictureService.GetPicturesByProductId(childItem.Item.ProductId, 1).FirstOrDefault(); if (itemPicture != null) diff --git a/src/Presentation/SmartStore.Web/Models/Customer/CustomerDownloadableProductsModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/CustomerDownloadableProductsModel.cs index 346e102504..3d5faaeede 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/CustomerDownloadableProductsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/CustomerDownloadableProductsModel.cs @@ -15,6 +15,7 @@ public CustomerDownloadableProductsModel() public CustomerNavigationModel NavigationModel { get; set; } #region Nested classes + public partial class DownloadableProductsModel : ModelBase { public Guid OrderItemGuid { get; set; } @@ -24,6 +25,7 @@ public partial class DownloadableProductsModel : ModelBase public int ProductId { get; set; } public string ProductName { get; set; } public string ProductSeName { get; set; } + public string ProductUrl { get; set; } public string ProductAttributes { get; set; } public int DownloadId { get; set; } @@ -31,6 +33,7 @@ public partial class DownloadableProductsModel : ModelBase public DateTime CreatedOn { get; set; } } + #endregion } diff --git a/src/Presentation/SmartStore.Web/Models/Customer/CustomerReturnRequestsModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/CustomerReturnRequestsModel.cs index d41eb9aefa..227ac02b46 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/CustomerReturnRequestsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/CustomerReturnRequestsModel.cs @@ -15,12 +15,14 @@ public CustomerReturnRequestsModel() public CustomerNavigationModel NavigationModel { get; set; } #region Nested classes + public partial class ReturnRequestModel : EntityModelBase { public string ReturnRequestStatus { get; set; } public int ProductId { get; set; } public string ProductName { get; set; } public string ProductSeName { get; set; } + public string ProductUrl { get; set; } public int Quantity { get; set; } public string ReturnReason { get; set; } @@ -29,6 +31,7 @@ public partial class ReturnRequestModel : EntityModelBase public DateTime CreatedOn { get; set; } } + #endregion } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Order/OrderDetailsModel.cs b/src/Presentation/SmartStore.Web/Models/Order/OrderDetailsModel.cs index 5a79ee2706..ec3b9eed33 100644 --- a/src/Presentation/SmartStore.Web/Models/Order/OrderDetailsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Order/OrderDetailsModel.cs @@ -89,6 +89,7 @@ public OrderItemModel() public int ProductId { get; set; } public string ProductName { get; set; } public string ProductSeName { get; set; } + public string ProductUrl { get; set; } public ProductType ProductType { get; set; } public string UnitPrice { get; set; } public string SubTotal { get; set; } @@ -106,6 +107,7 @@ public partial class BundleItemModel : ModelBase public string Sku { get; set; } public string ProductName { get; set; } public string ProductSeName { get; set; } + public string ProductUrl { get; set; } public bool VisibleIndividually { get; set; } public int Quantity { get; set; } public int DisplayOrder { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/Order/ShipmentDetailsModel.cs b/src/Presentation/SmartStore.Web/Models/Order/ShipmentDetailsModel.cs index 1ffdd4270e..c96764835b 100644 --- a/src/Presentation/SmartStore.Web/Models/Order/ShipmentDetailsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Order/ShipmentDetailsModel.cs @@ -30,6 +30,7 @@ public partial class ShipmentItemModel : EntityModelBase public int ProductId { get; set; } public string ProductName { get; set; } public string ProductSeName { get; set; } + public string ProductUrl { get; set; } public string AttributeInfo { get; set; } public int QuantityOrdered { get; set; } diff --git a/src/Presentation/SmartStore.Web/Models/Order/SubmitReturnRequestModel.cs b/src/Presentation/SmartStore.Web/Models/Order/SubmitReturnRequestModel.cs index 7592a3c785..1c8ff2f007 100644 --- a/src/Presentation/SmartStore.Web/Models/Order/SubmitReturnRequestModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Order/SubmitReturnRequestModel.cs @@ -44,6 +44,8 @@ public partial class OrderItemModel : EntityModelBase public string ProductSeName { get; set; } + public string ProductUrl { get; set; } + public string AttributeInfo { get; set; } public string UnitPrice { get; set; } diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/profile.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/profile.less index 2af25a3deb..19cd3e181b 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/profile.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/profile.less @@ -88,14 +88,18 @@ /* Return Requests ================================================ */ -.return-request-list-page .request-item .title{ - font-size: 13px; +.return-request-list-page .request-item { + margin-bottom: 20px; + + .title { + margin-bottom: 10px; + } } -.reward-points-page .reward-points-overview{ +.reward-points-page .reward-points-overview { padding: 10px 10px 5px 0; width: 100%; } -.return-request-page .why .comment{ +.return-request-page .why .comment { width: 350px; height: 150px; } diff --git a/src/Presentation/SmartStore.Web/Views/Customer/DownloadableProducts.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/DownloadableProducts.Mobile.cshtml index ff876b4672..894ef657cb 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/DownloadableProducts.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/DownloadableProducts.Mobile.cshtml @@ -3,7 +3,6 @@ @{ Layout = "~/Views/Shared/_Root.cshtml"; - //title Html.AddTitleParts(T("PageTitle.Account").Text); }
    - @T("DownloadableProducts.Fields.Product"): @item.ProductName + @T("DownloadableProducts.Fields.Product"): @item.ProductName @if (!String.IsNullOrEmpty(item.ProductAttributes)) {
    diff --git a/src/Presentation/SmartStore.Web/Views/Customer/DownloadableProducts.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/DownloadableProducts.cshtml index db3dfebd6b..71df0afce0 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/DownloadableProducts.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/DownloadableProducts.cshtml @@ -3,7 +3,6 @@ @{ Layout = "~/Views/Shared/_ColumnsTwo.cshtml"; - //title Html.AddTitleParts(T("PageTitle.Account").Text); } @section left @@ -48,7 +47,7 @@ @item.CreatedOn.ToString("d")
    - @item.ProductName + @item.ProductName @if (!String.IsNullOrEmpty(item.ProductAttributes)) {
    diff --git a/src/Presentation/SmartStore.Web/Views/Customer/ReturnRequests.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/ReturnRequests.Mobile.cshtml index a8a56e20ad..7882db1446 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/ReturnRequests.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/ReturnRequests.Mobile.cshtml @@ -3,7 +3,6 @@ @{ Layout = "~/Views/Shared/_Root.cshtml"; - //title Html.AddTitleParts(T("PageTitle.Account").Text); }
    - @T("Account.CustomerReturnRequests.Item") @item.ProductName + @T("Account.CustomerReturnRequests.Item") + @item.ProductName x @item.Quantity
    @@ -35,7 +35,8 @@ @string.Format(T("Account.CustomerReturnRequests.Date").Text, item.CreatedOn.ToString())
    @if (!String.IsNullOrEmpty(item.Comments)) - {
    + { +
    @T("Account.CustomerReturnRequests.Comments")
    diff --git a/src/Presentation/SmartStore.Web/Views/Customer/ReturnRequests.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/ReturnRequests.cshtml index f913a29eef..06e0467f9e 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/ReturnRequests.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/ReturnRequests.cshtml @@ -3,7 +3,6 @@ @{ Layout = "~/Views/Shared/_ColumnsTwo.cshtml"; - //title Html.AddTitleParts(T("PageTitle.Account").Text); } @section left @@ -14,16 +13,20 @@

    @T("Account.MyAccount") - @T("Account.CustomerReturnRequests")

    -
    -
    + +
    +
    @foreach (var item in Model.Items) {
    - @string.Format(T("Account.CustomerReturnRequests.Title").Text, item.Id, item.ReturnRequestStatus) + + @string.Format(T("Account.CustomerReturnRequests.Title").Text, item.Id, item.ReturnRequestStatus) +
    - @T("Account.CustomerReturnRequests.Item") @item.ProductName + @T("Account.CustomerReturnRequests.Item") + @item.ProductName x @item.Quantity
    @@ -46,8 +49,8 @@ }
    -
    -
    + +
    }
    diff --git a/src/Presentation/SmartStore.Web/Views/Order/Details.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Order/Details.Mobile.cshtml index 163646a316..80249d7776 100644 --- a/src/Presentation/SmartStore.Web/Views/Order/Details.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Order/Details.Mobile.cshtml @@ -18,7 +18,7 @@
    @if (item.VisibleIndividually) { - @item.ProductName + @item.ProductName } else { @@ -280,7 +280,7 @@
  • - @item.ProductName + @item.ProductName @if (!String.IsNullOrEmpty(item.AttributeInfo)) {
    diff --git a/src/Presentation/SmartStore.Web/Views/Order/Details.cshtml b/src/Presentation/SmartStore.Web/Views/Order/Details.cshtml index eb356a0372..8e56cfc5a6 100644 --- a/src/Presentation/SmartStore.Web/Views/Order/Details.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Order/Details.cshtml @@ -6,7 +6,6 @@ @{ Layout = "~/Views/Shared/_ColumnsOne.cshtml"; - //title Html.AddTitleParts(T("PageTitle.OrderDetails").Text); } @@ -20,7 +19,7 @@
  • - @item.ProductName + @item.ProductName @if (!String.IsNullOrEmpty(item.AttributeInfo)) {
    @@ -58,7 +57,7 @@
    @if (item.VisibleIndividually) { - @item.ProductName + @item.ProductName } else { diff --git a/src/Presentation/SmartStore.Web/Views/Order/ShipmentDetails.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Order/ShipmentDetails.Mobile.cshtml index 668a97f664..27c24e59e5 100644 --- a/src/Presentation/SmartStore.Web/Views/Order/ShipmentDetails.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Order/ShipmentDetails.Mobile.cshtml @@ -4,7 +4,7 @@ @using SmartStore.Services.Localization; @{ Layout = "~/Views/Shared/_Root.cshtml"; - //title + Html.AddTitleParts(T("PageTitle.ShipmentDetails").Text); }
    @@ -108,7 +108,7 @@
  • - @item.ProductName + @item.ProductName @if (!String.IsNullOrEmpty(item.AttributeInfo)) {
    diff --git a/src/Presentation/SmartStore.Web/Views/Order/ShipmentDetails.cshtml b/src/Presentation/SmartStore.Web/Views/Order/ShipmentDetails.cshtml index fb44c5206b..650e1411c7 100644 --- a/src/Presentation/SmartStore.Web/Views/Order/ShipmentDetails.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Order/ShipmentDetails.cshtml @@ -5,7 +5,7 @@ @{ Layout = "~/Views/Shared/_ColumnsOne.cshtml"; - //title + Html.AddTitleParts(T("PageTitle.ShipmentDetails").Text); }
    @@ -141,7 +141,7 @@
  • - @item.ProductName + @item.ProductName @if (!String.IsNullOrEmpty(item.AttributeInfo)) {
    diff --git a/src/Presentation/SmartStore.Web/Views/ReturnRequest/ReturnRequest.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/ReturnRequest/ReturnRequest.Mobile.cshtml index b4b49a4cc5..2e3b6fb035 100644 --- a/src/Presentation/SmartStore.Web/Views/ReturnRequest/ReturnRequest.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ReturnRequest/ReturnRequest.Mobile.cshtml @@ -3,7 +3,6 @@ @{ Layout = "~/Views/Shared/_Root.cshtml"; - //title Html.AddTitleParts(T("PageTitle.ReturnItems").Text); }
    @@ -31,7 +30,7 @@
  • - @item.ProductName + @item.ProductName @if (!String.IsNullOrEmpty(item.AttributeInfo)) {
    diff --git a/src/Presentation/SmartStore.Web/Views/ReturnRequest/ReturnRequest.cshtml b/src/Presentation/SmartStore.Web/Views/ReturnRequest/ReturnRequest.cshtml index 48be35eaaf..23cbb022b2 100644 --- a/src/Presentation/SmartStore.Web/Views/ReturnRequest/ReturnRequest.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ReturnRequest/ReturnRequest.cshtml @@ -49,7 +49,7 @@ {
  • - @item.ProductName + @item.ProductName @if (!String.IsNullOrEmpty(item.AttributeInfo)) {
    diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.Mobile.cshtml index 8f3dccf235..a543fb732d 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.Mobile.cshtml @@ -81,7 +81,7 @@
  • - @item.ProductName + @item.ProductName @if (!String.IsNullOrEmpty(item.AttributeInfo)) {
    diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.Mobile.cshtml index b0fd2d8075..8132df2d46 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.Mobile.cshtml @@ -6,7 +6,6 @@ @{ Layout = "~/Views/Shared/_Root.cshtml"; - //title Html.AddTitleParts(T("PageTitle.Wishlist").Text); } @@ -99,7 +98,7 @@
  • - @item.ProductName + @item.ProductName @if (!String.IsNullOrEmpty(item.AttributeInfo)) {
    diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.cshtml index 9bcd076959..57e67c51ad 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/Wishlist.cshtml @@ -6,7 +6,6 @@ @{ Layout = "~/Views/Shared/_ColumnsOne.cshtml"; - //title Html.AddTitleParts(T("PageTitle.Wishlist").Text); } @@ -38,7 +37,7 @@
  • - @item.ProductName + @item.ProductName @if (Model.DisplayShortDesc && !String.IsNullOrEmpty(item.ShortDesc)) { From b1d8bdd8b6feb79e3dde81662b2d3df561b2ac10 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 23 Oct 2015 12:49:03 +0200 Subject: [PATCH 143/874] #791 Preselected attributes or attribute combinations should always be appended as querystring to product page links (part 3) --- .../Catalog/IProductAttributeParser.cs | 33 +++++++-- .../Catalog/ProductAttributeParser.cs | 70 ++++++++++++------- .../NameValueCollectionExtensions.cs | 13 ++-- .../Controllers/ProductController.cs | 5 +- .../Controllers/CatalogHelper.cs | 9 +-- .../Controllers/CustomerController.cs | 18 ++++- .../Controllers/OrderController.cs | 37 ++++++++-- .../Controllers/ProductController.cs | 11 ++- .../Controllers/ReturnRequestController.cs | 23 ++++-- .../Controllers/ShoppingCartController.cs | 62 +++++++++++----- 10 files changed, 205 insertions(+), 76 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs index bdc8ef7c82..64a412d339 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs @@ -82,22 +82,47 @@ public partial interface IProductAttributeParser List> DeserializeQueryData(string jsonData); /// - /// Serializes attribute data + /// Deserializes attribute data /// + /// List with deserialized data + /// XML formatted attributes /// Product identifier + /// Bundle item identifier + void DeserializeQueryData(List> queryData, string attributesXml, int productId, int bundleItemId = 0); + + /// + /// Serializes attribute data + /// /// XML formatted attributes + /// Product identifier /// Whether to URL encode /// Json string with attribute data - string SerializeQueryData(int productId, string attributesXml, bool urlEncode = true); + string SerializeQueryData(string attributesXml, int productId, bool urlEncode = true); + + /// + /// Serializes attribute data + /// + /// List with deserialized data + /// Whether to URL encode + /// Json string with attribute data + string SerializeQueryData(List> queryData, bool urlEncode = true); /// /// Gets the URL of the product page including attributes query string /// + /// XML formatted attributes /// Product identifier /// Product SEO name - /// XML formatted attributes /// URL of the product page including attributes query string - string GetProductUrlWithAttributes(int productId, string productSeName, string attributesXml); + string GetProductUrlWithAttributes(string attributesXml, int productId, string productSeName); + + /// + /// Gets the URL of the product page including attributes query string + /// + /// Attribute query data + /// Product SEO name + /// URL of the product page including attributes query string + string GetProductUrlWithAttributes(List> queryData, string productSeName); #endregion diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs index 635893d821..23ee794f0b 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs @@ -372,58 +372,68 @@ public virtual ProductVariantAttributeCombination FindProductVariantAttributeCom return result; } - /// - /// Deserializes attribute data from an URL query string - /// - /// Json data query string - /// List items with following structure: Product.Id, ProductAttribute.Id, Product_ProductAttribute_Mapping.Id, ProductVariantAttributeValue.Id public virtual List> DeserializeQueryData(string jsonData) { if (jsonData.HasValue()) { if (jsonData.StartsWith("[")) + { return JsonConvert.DeserializeObject>>(jsonData); + } - return new List>() { JsonConvert.DeserializeObject>(jsonData) }; + return new List> { JsonConvert.DeserializeObject>(jsonData) }; } return new List>(); } - - /// - /// Serializes attribute data - /// - /// Product identifier - /// Attribute XML string - /// Whether to URL encode - /// Json string with attribute data - public virtual string SerializeQueryData(int productId, string attributesXml, bool urlEncode = true) + + public virtual void DeserializeQueryData(List> queryData, string attributesXml, int productId, int bundleItemId = 0) { + Guard.ArgumentNotNull(() => queryData); + if (attributesXml.HasValue() && productId != 0) { - var data = new List>(); var attributeValues = ParseProductVariantAttributeValues(attributesXml).ToList(); foreach (var value in attributeValues) { - data.Add(new List + var lst = new List { productId, value.ProductVariantAttribute.ProductAttributeId, value.ProductVariantAttributeId, value.Id - }); - } + }; - if (data.Count > 0) - { - string result = JsonConvert.SerializeObject(data); - return (urlEncode ? HttpUtility.UrlEncode(result) : result); + if (bundleItemId != 0) + lst.Add(bundleItemId); + + queryData.Add(lst); } } + } + + public virtual string SerializeQueryData(string attributesXml, int productId, bool urlEncode = true) + { + var data = new List>(); + + DeserializeQueryData(data, attributesXml, productId); + + return SerializeQueryData(data, urlEncode); + } + + public virtual string SerializeQueryData(List> queryData, bool urlEncode = true) + { + if (queryData.Count > 0) + { + var result = JsonConvert.SerializeObject(queryData); + + return (urlEncode ? HttpUtility.UrlEncode(result) : result); + } + return ""; } - public virtual string GetProductUrlWithAttributes(int productId, string productSeName, string attributesXml) + private string CreateProductUrl(string queryString, string productSeName) { var url = UrlHelper.GenerateUrl( "Product", @@ -434,8 +444,6 @@ public virtual string GetProductUrlWithAttributes(int productId, string productS HttpContext.Current.Request.RequestContext, false); - var queryString = SerializeQueryData(productId, attributesXml); - if (queryString.HasValue()) { url = string.Concat(url, url.Contains("?") ? "&" : "?", "attributes=", queryString); @@ -444,6 +452,16 @@ public virtual string GetProductUrlWithAttributes(int productId, string productS return url; } + public virtual string GetProductUrlWithAttributes(string attributesXml, int productId, string productSeName) + { + return CreateProductUrl(SerializeQueryData(attributesXml, productId), productSeName); + } + + public virtual string GetProductUrlWithAttributes(List> queryData, string productSeName) + { + return CreateProductUrl(SerializeQueryData(queryData), productSeName); + } + #endregion #region Gift card attributes diff --git a/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs b/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs index 68d8f8dda6..c7fa1aa9dd 100644 --- a/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs +++ b/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs @@ -35,23 +35,24 @@ public static void AddProductAttribute(this NameValueCollection collection, int /// Converts attribute query data /// /// Name value collection - /// Attribute query data items with following structure: Product.Id, ProductAttribute.Id, Product_ProductAttribute_Mapping.Id, ProductVariantAttributeValue.Id + /// 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) { if (collection == null || queryData == null || queryData.Count <= 0) return; - var enm = queryData.Where(i => i.Count > 3); + var items = queryData.Where(i => i.Count > 3); if (productId != 0) - enm = enm.Where(i => i[0] == productId); + items = items.Where(i => i[0] == productId); - foreach (var itm in enm) + foreach (var item in items) { - string name = AttributeFormatedName(itm[1], itm[2], itm[0]); + var name = AttributeFormatedName(item[1], item[2], item[0], item.Count > 4 ? item[4] : 0); - collection.Add(name, itm[3].ToString()); + collection.Add(name, item[3].ToString()); } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 3f2466d5e6..3f9fa79477 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -4104,15 +4104,14 @@ public ActionResult ProductVariantAttributeCombinationList(GridCommand command, var allCombinations = _productAttributeService.GetAllProductVariantAttributeCombinations(product.Id, command.Page - 1, command.PageSize); var productUrlTitle = _localizationService.GetResource("Common.OpenInShop"); - var productUrl = Url.RouteUrl("Product", new { SeName = product.GetSeName() }); - productUrl = "{0}{1}attributes=".FormatInvariant(productUrl, productUrl.Contains("?") ? "&" : "?"); + var productSeName = product.GetSeName(); var productVariantAttributesModel = allCombinations.Select(x => { var pvacModel = x.ToModel(); PrepareProductAttributeCombinationModel(pvacModel, x, product, true); - pvacModel.ProductUrl = productUrl + _productAttributeParser.SerializeQueryData(product.Id, x.AttributesXml); + pvacModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(x.AttributesXml, product.Id, productSeName); pvacModel.ProductUrlTitle = productUrlTitle; try diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index 83d17f1efa..ad36b89187 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -31,6 +31,7 @@ using SmartStore.Web.Models.Catalog; using SmartStore.Web.Models.Media; using SmartStore.Core.Data; +using System.Collections.Specialized; namespace SmartStore.Web.Controllers { @@ -147,7 +148,7 @@ public ProductDetailsModel PrepareProductDetailsPageModel( bool isAssociatedProduct = false, ProductBundleItemData productBundleItem = null, IList productBundleItems = null, - FormCollection selectedAttributes = null) + NameValueCollection selectedAttributes = null) { if (product == null) throw new ArgumentNullException("product"); @@ -229,7 +230,7 @@ public ProductDetailsModel PrepareProductDetailsPageModel( foreach (var itemData in bundleItems.Where(x => x.Item.Product.CanBeBundleItem())) { var item = itemData.Item; - var bundledProductModel = PrepareProductDetailsPageModel(item.Product, false, itemData); + var bundledProductModel = PrepareProductDetailsPageModel(item.Product, false, itemData, null, selectedAttributes); bundledProductModel.BundleItem.Id = item.Id; bundledProductModel.BundleItem.Quantity = item.Quantity; @@ -418,7 +419,7 @@ public ProductDetailsModel PrepareProductDetailModel( bool isAssociatedProduct = false, ProductBundleItemData productBundleItem = null, IList productBundleItems = null, - FormCollection selectedAttributes = null, + NameValueCollection selectedAttributes = null, int selectedQuantity = 1) { if (product == null) @@ -428,7 +429,7 @@ public ProductDetailsModel PrepareProductDetailModel( throw new ArgumentNullException("model"); if (selectedAttributes == null) - selectedAttributes = new FormCollection(); + selectedAttributes = new NameValueCollection(); decimal preSelectedPriceAdjustmentBase = decimal.Zero; decimal preSelectedWeightAdjustment = decimal.Zero; diff --git a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs index 62248ddbb4..a643b42759 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs @@ -1,8 +1,10 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using SmartStore.Core; +using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; @@ -1348,6 +1350,7 @@ public ActionResult ReturnRequests() if (orderItem != null) { var product = orderItem.Product; + var attributeQueryData = new List>(); var itemModel = new CustomerReturnRequestsModel.ReturnRequestModel { @@ -1363,7 +1366,18 @@ public ActionResult ReturnRequests() CreatedOn = _dateTimeHelper.ConvertToUserTime(returnRequest.CreatedOnUtc, DateTimeKind.Utc) }; - itemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(orderItem.ProductId, itemModel.ProductSeName, orderItem.AttributesXml); + if (orderItem.Product.ProductType != ProductType.BundledProduct) + { + _productAttributeParser.DeserializeQueryData(attributeQueryData, orderItem.AttributesXml, orderItem.ProductId); + } + else if (orderItem.Product.BundlePerItemPricing && orderItem.BundleData.HasValue()) + { + var bundleData = orderItem.GetBundleData(); + + bundleData.ForEach(x => _productAttributeParser.DeserializeQueryData(attributeQueryData, x.AttributesXml, x.ProductId, x.BundleItemId)); + } + + itemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(attributeQueryData, itemModel.ProductSeName); model.Items.Add(itemModel); } @@ -1403,7 +1417,7 @@ public ActionResult DownloadableProducts() ProductId = item.ProductId }; - itemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(item.ProductId, itemModel.ProductSeName, item.AttributesXml); + itemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(item.AttributesXml, item.ProductId, itemModel.ProductSeName); model.Items.Add(itemModel); diff --git a/src/Presentation/SmartStore.Web/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Controllers/OrderController.cs index ec9eb8365b..b4b1976c25 100644 --- a/src/Presentation/SmartStore.Web/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/OrderController.cs @@ -389,6 +389,7 @@ protected ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipment) //products in this shipment model.ShowSku = catalogSettings.ShowProductSku; + foreach (var shipmentItem in shipment.ShipmentItems) { var orderItem = _orderService.GetOrderItemById(shipmentItem.OrderItemId); @@ -397,6 +398,8 @@ protected ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipment) orderItem.Product.MergeWithCombination(orderItem.AttributesXml); + var attributeQueryData = new List>(); + var shipmentItemModel = new ShipmentDetailsModel.ShipmentItemModel { Id = shipmentItem.Id, @@ -409,12 +412,22 @@ protected ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipment) QuantityShipped = shipmentItem.Quantity }; - shipmentItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(orderItem.ProductId, shipmentItemModel.ProductSeName, orderItem.AttributesXml); + if (orderItem.Product.ProductType != ProductType.BundledProduct) + { + _productAttributeParser.DeserializeQueryData(attributeQueryData, orderItem.AttributesXml, orderItem.ProductId); + } + else if (orderItem.Product.BundlePerItemPricing && orderItem.BundleData.HasValue()) + { + var bundleData = orderItem.GetBundleData(); + + bundleData.ForEach(x => _productAttributeParser.DeserializeQueryData(attributeQueryData, x.AttributesXml, x.ProductId, x.BundleItemId)); + } + + shipmentItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(attributeQueryData, shipmentItemModel.ProductSeName); model.Items.Add(shipmentItemModel); } - //order details model model.Order = PrepareOrderDetailsModel(order); return model; @@ -422,6 +435,8 @@ protected ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipment) private OrderDetailsModel.OrderItemModel PrepareOrderItemModel(Order order, OrderItem orderItem) { + var attributeQueryData = new List>(); + orderItem.Product.MergeWithCombination(orderItem.AttributesXml); var model = new OrderDetailsModel.OrderItemModel @@ -436,10 +451,13 @@ private OrderDetailsModel.OrderItemModel PrepareOrderItemModel(Order order, Orde AttributeInfo = orderItem.AttributeDescription }; - model.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(orderItem.ProductId, model.ProductSeName, orderItem.AttributesXml); + if (orderItem.Product.ProductType != ProductType.BundledProduct) + { + _productAttributeParser.DeserializeQueryData(attributeQueryData, orderItem.AttributesXml, orderItem.ProductId); + } var quantityUnit = _quantityUnitService.GetQuantityUnitById(orderItem.Product.QuantityUnitId); - model.QuantityUnit = quantityUnit == null ? "" : quantityUnit.GetLocalized(x => x.Name); + model.QuantityUnit = (quantityUnit == null ? "" : quantityUnit.GetLocalized(x => x.Name)); if (orderItem.Product.ProductType == ProductType.BundledProduct && orderItem.BundleData.HasValue()) { @@ -461,7 +479,12 @@ private OrderDetailsModel.OrderItemModel PrepareOrderItemModel(Order order, Orde AttributeInfo = bundleItem.AttributesInfo }; - bundleItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(bundleItem.ProductId, bundleItemModel.ProductSeName, bundleItem.AttributesXml); + bundleItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(bundleItem.AttributesXml, bundleItem.ProductId, bundleItemModel.ProductSeName); + + if (orderItem.Product.BundlePerItemPricing) + { + _productAttributeParser.DeserializeQueryData(attributeQueryData, bundleItem.AttributesXml, bundleItem.ProductId, bundleItem.BundleItemId); + } if (model.BundlePerItemShoppingCart) { @@ -485,6 +508,7 @@ private OrderDetailsModel.OrderItemModel PrepareOrderItemModel(Order order, Orde model.SubTotal = _priceFormatter.FormatPrice(priceExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, false, false); } break; + case TaxDisplayType.IncludingTax: { var unitPriceInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(orderItem.UnitPriceInclTax, order.CurrencyRate); @@ -495,6 +519,9 @@ private OrderDetailsModel.OrderItemModel PrepareOrderItemModel(Order order, Orde } break; } + + model.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(attributeQueryData, model.ProductSeName); + return model; } diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index 048274fc77..3b94189942 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using System.Web.Mvc; using SmartStore.Core.Domain.Catalog; @@ -167,8 +168,11 @@ public ActionResult ProductDetails(int productId, string attributes) } //prepare the model - var selectedAttributes = new FormCollection(); - selectedAttributes.ConvertAttributeQueryData(_productAttributeParser.DeserializeQueryData(attributes), product.Id); + var selectedAttributes = new NameValueCollection(); + + selectedAttributes.ConvertAttributeQueryData( + _productAttributeParser.DeserializeQueryData(attributes), + product.ProductType == ProductType.BundledProduct ? 0 : product.Id); var model = _helper.PrepareProductDetailsPageModel(product, selectedAttributes: selectedAttributes); @@ -624,9 +628,10 @@ public ActionResult UpdateProductDetails(int productId, string itemType, int bun bundleItems = _productService.GetBundleItems(product.Id); if (form.Count > 0) { + // may add elements to form if they are preselected by bundle item filter foreach (var itemData in bundleItems) { - var tempModel = _helper.PrepareProductDetailsPageModel(itemData.Item.Product, false, itemData, null, form); + var unused = _helper.PrepareProductDetailsPageModel(itemData.Item.Product, false, itemData, null, form); } } } diff --git a/src/Presentation/SmartStore.Web/Controllers/ReturnRequestController.cs b/src/Presentation/SmartStore.Web/Controllers/ReturnRequestController.cs index 8fa1e94a3a..d7b5e454d6 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ReturnRequestController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ReturnRequestController.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Core; +using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Tax; @@ -11,9 +13,9 @@ using SmartStore.Services.Messages; using SmartStore.Services.Orders; using SmartStore.Services.Seo; +using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Security; using SmartStore.Web.Models.Order; -using SmartStore.Web.Framework.Controllers; namespace SmartStore.Web.Controllers { @@ -101,19 +103,30 @@ protected SubmitReturnRequestModel PrepareReturnRequestModel(SubmitReturnRequest foreach (var orderItem in orderItems) { + var attributeQueryData = new List>(); + var orderItemModel = new SubmitReturnRequestModel.OrderItemModel { Id = orderItem.Id, ProductId = orderItem.Product.Id, ProductName = orderItem.Product.GetLocalized(x => x.Name), - ProductSeName = orderItem.Product.GetSeName(), + ProductSeName = orderItem.Product.GetSeName(), AttributeInfo = orderItem.AttributeDescription, Quantity = orderItem.Quantity }; - orderItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(orderItem.ProductId, orderItemModel.ProductSeName, orderItem.AttributesXml); + if (orderItem.Product.ProductType != ProductType.BundledProduct) + { + _productAttributeParser.DeserializeQueryData(attributeQueryData, orderItem.AttributesXml, orderItem.ProductId); + } + else if (orderItem.Product.BundlePerItemPricing && orderItem.BundleData.HasValue()) + { + var bundleData = orderItem.GetBundleData(); - model.Items.Add(orderItemModel); + bundleData.ForEach(x => _productAttributeParser.DeserializeQueryData(attributeQueryData, x.AttributesXml, x.ProductId, x.BundleItemId)); + } + + orderItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(attributeQueryData, orderItemModel.ProductSeName); //unit price switch (order.CustomerTaxDisplayType) @@ -131,6 +144,8 @@ protected SubmitReturnRequestModel PrepareReturnRequestModel(SubmitReturnRequest } break; } + + model.Items.Add(orderItemModel); } return model; diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index 5c22d74b7a..98ea0b9fcc 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -238,7 +238,7 @@ private ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartItemModel(Org Weight = product.Weight }; - model.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(item.ProductId, model.ProductSeName, item.AttributesXml); + model.ProductUrl = GetProductUrlWithAttributes(sci, model.ProductSeName); if (item.BundleItem != null) { @@ -413,6 +413,7 @@ private ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartItemModel(Org return model; } + private WishlistModel.ShoppingCartItemModel PrepareWishlistCartItemModel(OrganizedShoppingCartItem sci) { var item = sci.Item; @@ -433,7 +434,7 @@ private WishlistModel.ShoppingCartItemModel PrepareWishlistCartItemModel(Organiz VisibleIndividually = product.VisibleIndividually }; - model.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(item.ProductId, model.ProductSeName, item.AttributesXml); + model.ProductUrl = GetProductUrlWithAttributes(sci, model.ProductSeName); if (item.BundleItem != null) { @@ -472,7 +473,7 @@ private WishlistModel.ShoppingCartItemModel PrepareWishlistCartItemModel(Organiz var allowedQuantities = product.ParseAllowedQuatities(); foreach (var qty in allowedQuantities) { - model.AllowedQuantities.Add(new SelectListItem() + model.AllowedQuantities.Add(new SelectListItem { Text = qty.ToString(), Value = qty.ToString(), @@ -913,7 +914,7 @@ protected void PrepareWishlistModel(WishlistModel model, IList x.Name), - ProductSeName = sci.Item.Product.GetSeName(), - Quantity = sci.Item.Quantity, + Id = item.Id, + ProductId = product.Id, + ProductName = product.GetLocalized(x => x.Name), + ProductSeName = product.GetSeName(), + Quantity = item.Quantity, AttributeInfo = _productAttributeFormatter.FormatAttributes( - sci.Item.Product, - sci.Item.AttributesXml, + product, + item.AttributesXml, null, serapator: ", ", renderPrices: false, @@ -980,11 +984,11 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() allowHyperlinks: false) }; - cartItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(sci.Item.ProductId, cartItemModel.ProductSeName, sci.Item.AttributesXml); + cartItemModel.ProductUrl = GetProductUrlWithAttributes(sci, cartItemModel.ProductSeName); if (sci.ChildItems != null && _shoppingCartSettings.ShowProductBundleImagesOnShoppingCart) { - foreach (var childItem in sci.ChildItems.Where(x => x.Item.Id != sci.Item.Id && x.Item.BundleItem != null && !x.Item.BundleItem.HideThumbnail)) + foreach (var childItem in sci.ChildItems.Where(x => x.Item.Id != item.Id && x.Item.BundleItem != null && !x.Item.BundleItem.HideThumbnail)) { var bundleItemModel = new MiniShoppingCartModel.ShoppingCartItemBundleItem { @@ -993,7 +997,7 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() }; bundleItemModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes( - childItem.Item.ProductId, bundleItemModel.ProductSeName, childItem.Item.AttributesXml); + childItem.Item.AttributesXml, childItem.Item.ProductId, bundleItemModel.ProductSeName); var itemPicture = _pictureService.GetPicturesByProductId(childItem.Item.ProductId, 1).FirstOrDefault(); if (itemPicture != null) @@ -1004,16 +1008,16 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() } //unit prices - if (sci.Item.Product.CallForPrice) + if (product.CallForPrice) { cartItemModel.UnitPrice = _localizationService.GetResource("Products.CallForPrice"); } else { - sci.Item.Product.MergeWithCombination(sci.Item.AttributesXml); + product.MergeWithCombination(item.AttributesXml); decimal taxRate = decimal.Zero; - decimal shoppingCartUnitPriceWithDiscountBase = _taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetUnitPrice(sci, true), out taxRate); + decimal shoppingCartUnitPriceWithDiscountBase = _taxService.GetProductPrice(product, _priceCalculationService.GetUnitPrice(sci, true), out taxRate); decimal shoppingCartUnitPriceWithDiscount = _currencyService.ConvertFromPrimaryStoreCurrency(shoppingCartUnitPriceWithDiscountBase, _workContext.WorkingCurrency); cartItemModel.UnitPrice = _priceFormatter.FormatPrice(shoppingCartUnitPriceWithDiscount); @@ -1022,8 +1026,7 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() //picture if (_shoppingCartSettings.ShowProductImagesInMiniShoppingCart) { - cartItemModel.Picture = PrepareCartItemPictureModel(sci.Item.Product, - _mediaSettings.MiniCartThumbPictureSize, true, cartItemModel.ProductName, sci.Item.AttributesXml); + cartItemModel.Picture = PrepareCartItemPictureModel(product, _mediaSettings.MiniCartThumbPictureSize, true, cartItemModel.ProductName, item.AttributesXml); } model.Items.Add(cartItemModel); @@ -1148,6 +1151,27 @@ protected void ParseAndSaveCheckoutAttributes(List ca _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.CheckoutAttributes, selectedAttributes); } + private string GetProductUrlWithAttributes(OrganizedShoppingCartItem cartItem, string productSeName) + { + var attributeQueryData = new List>(); + var product = cartItem.Item.Product; + + if (product.ProductType != ProductType.BundledProduct) + { + _productAttributeParser.DeserializeQueryData(attributeQueryData, cartItem.Item.AttributesXml, product.Id); + } + else if (cartItem.ChildItems != null && product.BundlePerItemPricing) + { + foreach (var childItem in cartItem.ChildItems.Where(x => x.Item.Id != cartItem.Item.Id)) + { + _productAttributeParser.DeserializeQueryData(attributeQueryData, childItem.Item.AttributesXml, childItem.Item.ProductId, childItem.BundleItemData.Item.Id); + } + } + + var url = _productAttributeParser.GetProductUrlWithAttributes(attributeQueryData, productSeName); + return url; + } + #endregion #region Shopping cart From 592ccbad6cd3a3c879a6cdb6ab3d1eebe1e7011a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 23 Oct 2015 15:05:13 +0200 Subject: [PATCH 144/874] Resolves #796 Selected specification in product filter mask is displayed with default language (not localized) --- changelog.md | 1 + .../Filter/FilterCriteria.cs | 13 +++++--- .../Filter/FilterExtensions.cs | 32 ++++++++++--------- .../Filter/FilterService.cs | 26 +++++++++++---- .../Controllers/FilterController.cs | 21 ++++++------ 5 files changed, 58 insertions(+), 35 deletions(-) diff --git a/changelog.md b/changelog.md index f72b36b86e..e8364c5e2c 100644 --- a/changelog.md +++ b/changelog.md @@ -66,6 +66,7 @@ * #784 Biz-Importer: Name of delivery time must not be imported empty * #776 Preview: Manufacturer and Product in Multi Store * #755 Some methods still loading all products in one go +* #796 Selected specification in product filter mask is displayed with default language (not localized) ## SmartStore.NET 2.2.2 diff --git a/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs b/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs index 59de0fa9c1..5470367d49 100644 --- a/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs +++ b/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs @@ -33,10 +33,12 @@ public class FilterCriteria : IComparable [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public int? ID { get; set; } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public int? PId { get; set; } + // Metadata public int MatchCount { get; set; } public bool IsInactive { get; set; } - public int ParentId { get; set; } public string NameLocalized { get; set; } public string ValueLocalized { get; set; } @@ -45,10 +47,12 @@ public string SqlName get { if (Entity == "Manufacturer" && !Name.Contains('.')) - return "{0}.{1}".FormatWith(Entity, Name); + return "{0}.{1}".FormatInvariant(Entity, Name); + return Name; } } + public bool IsRange { get @@ -59,9 +63,9 @@ public bool IsRange int IComparable.CompareTo(object obj) { - FilterCriteria filter = (FilterCriteria)obj; + var filter = (FilterCriteria)obj; - int compare = string.Compare(this.Entity, filter.Entity, true); + var compare = string.Compare(this.Entity, filter.Entity, true); if (compare == 0) { @@ -86,6 +90,7 @@ int IComparable.CompareTo(object obj) //return string.Compare(this.Value, filter.Value, true); } + public override string ToString() { try diff --git a/src/Libraries/SmartStore.Services/Filter/FilterExtensions.cs b/src/Libraries/SmartStore.Services/Filter/FilterExtensions.cs index a466c8adeb..cd9996d010 100644 --- a/src/Libraries/SmartStore.Services/Filter/FilterExtensions.cs +++ b/src/Libraries/SmartStore.Services/Filter/FilterExtensions.cs @@ -17,6 +17,7 @@ private static string FormatPrice(string value) if (value.HasValue()) { decimal d = 0; + if (StringToPrice(value, out d)) return EngineContext.Current.Resolve().FormatPrice(d, true, false); } @@ -36,32 +37,32 @@ public static string ToDescription(this FilterCriteria criteria) criteria.Value.SplitToPair(out valueLeft, out valueRight, "~"); if (criteria.Entity == FilterService.ShortcutPrice) - return "{0} - {1}".FormatWith(FormatPrice(valueLeft), FormatPrice(valueRight)); + return "{0} - {1}".FormatInvariant(FormatPrice(valueLeft), FormatPrice(valueRight)); - return "{0} - {1}".FormatWith(valueLeft, valueRight); + return "{0} - {1}".FormatInvariant(valueLeft, valueRight); } - string value = (criteria.ValueLocalized.HasValue() ? criteria.ValueLocalized : criteria.Value); + var value = (criteria.ValueLocalized.HasValue() ? criteria.ValueLocalized : criteria.Value); if (criteria.Entity == FilterService.ShortcutPrice) value = FormatPrice(criteria.Value); if (criteria.Operator == FilterOperator.Unequal) - return "≠ {0}".FormatWith(value); + return "≠ {0}".FormatInvariant(value); if (criteria.Operator == FilterOperator.Greater) - return "> {0}".FormatWith(value); + return "> {0}".FormatInvariant(value); if (criteria.Operator == FilterOperator.GreaterEqual) - return "≥ {0}".FormatWith(value); + return "≥ {0}".FormatInvariant(value); if (criteria.Operator == FilterOperator.Less) - return "< {0}".FormatWith(value); + return "< {0}".FormatInvariant(value); if (criteria.Operator == FilterOperator.LessEqual) - return "≤ {0}".FormatWith(value); + return "≤ {0}".FormatInvariant(value); if (criteria.Operator == FilterOperator.Contains) - return "{0} {1}".FormatWith(localize.GetResource("Products.Filter.Contains"), value); + return "{0} {1}".FormatInvariant(localize.GetResource("Products.Filter.Contains"), value); if (criteria.Operator == FilterOperator.StartsWith) - return "{0} {1}".FormatWith(localize.GetResource("Products.Filter.StartsWith"), value); + return "{0} {1}".FormatInvariant(localize.GetResource("Products.Filter.StartsWith"), value); if (criteria.Operator == FilterOperator.EndsWith) - return "{0} {1}".FormatWith(localize.GetResource("Products.Filter.EndsWith"), value); + return "{0} {1}".FormatInvariant(localize.GetResource("Products.Filter.EndsWith"), value); return value; } @@ -132,7 +133,8 @@ public static bool StringToPrice(this string[] range, int index, out decimal res result = 0; if (range != null && index < range.Length) { - string value = range[index].Trim(); + var value = range[index].Trim(); + return StringToPrice(value, out result); } return false; @@ -140,11 +142,11 @@ public static bool StringToPrice(this string[] range, int index, out decimal res public static string GetUrl(this FilterProductContext context, FilterCriteria criteriaAdd = null, FilterCriteria criteriaRemove = null) { - string url = "{0}?pagesize={1}&viewmode={2}".FormatWith(context.Path, context.PageSize, context.ViewMode); + var url = "{0}?pagesize={1}&viewmode={2}".FormatInvariant(context.Path, context.PageSize, context.ViewMode); if (context.OrderBy.HasValue) { - url = "{0}&orderby={1}".FormatWith(url, context.OrderBy.Value); + url = "{0}&orderby={1}".FormatInvariant(url, context.OrderBy.Value); } try @@ -162,7 +164,7 @@ public static string GetUrl(this FilterProductContext context, FilterCriteria cr criterias.RemoveAll(c => c.Entity == criteriaRemove.Entity && c.Name == criteriaRemove.Name && c.Value == criteriaRemove.Value); if (criterias.Count > 0) - url = "{0}&filter={1}".FormatWith(url, HttpUtility.UrlEncode(JsonConvert.SerializeObject(criterias))); + url = "{0}&filter={1}".FormatInvariant(url, HttpUtility.UrlEncode(JsonConvert.SerializeObject(criterias))); } } catch (Exception exc) diff --git a/src/Libraries/SmartStore.Services/Filter/FilterService.cs b/src/Libraries/SmartStore.Services/Filter/FilterService.cs index fad58fc5c8..93673743aa 100644 --- a/src/Libraries/SmartStore.Services/Filter/FilterService.cs +++ b/src/Libraries/SmartStore.Services/Filter/FilterService.cs @@ -53,6 +53,7 @@ private string ValidateValue(string value, string alternativeValue) { if (value.HasValue() && !value.IsCaseInsensitiveEqual("null")) return value; + return alternativeValue; } @@ -163,7 +164,7 @@ private IQueryable AllProducts(List categoryIds) { if (_products == null) { - var searchContext = new ProductSearchContext() + var searchContext = new ProductSearchContext { Query = _productRepository.TableUntracked, FeaturedProducts = (_catalogSettings.IncludeFeaturedProductsInNormalLists ? null : (bool?)false), @@ -300,7 +301,7 @@ from a in attributes Name = g.FirstOrDefault().SpecificationAttribute.Name, Value = g.FirstOrDefault().Name, ID = g.Key.Id, - ParentId = g.FirstOrDefault().SpecificationAttribute.Id, + PId = g.FirstOrDefault().SpecificationAttribute.Id, MatchCount = g.Count() }; @@ -313,8 +314,8 @@ from a in attributes c.Entity = ShortcutSpecAttribute; c.IsInactive = true; - if (c.ParentId != 0) - c.NameLocalized = _localizedEntityService.GetLocalizedValue(languageId, c.ParentId, "SpecificationAttribute", "Name"); + if (c.PId.HasValue) + c.NameLocalized = _localizedEntityService.GetLocalizedValue(languageId, c.PId.Value, "SpecificationAttribute", "Name"); if (c.ID.HasValue) c.ValueLocalized = _localizedEntityService.GetLocalizedValue(languageId, c.ID.Value, "SpecificationAttributeOption", "Name"); @@ -342,12 +343,13 @@ public virtual string Serialize(List criteria) //criteria.FindAll(c => c.Type.IsNullOrEmpty()).ForEach(c => c.Type = _defaultType); if (criteria != null && criteria.Count > 0) return JsonConvert.SerializeObject(criteria); + return ""; } public virtual FilterProductContext CreateFilterProductContext(string filter, int categoryID, string path, int? pagesize, int? orderby, string viewmode) { - var context = new FilterProductContext() + var context = new FilterProductContext { Filter = filter, ParentCategoryID = categoryID, @@ -366,6 +368,17 @@ public virtual FilterProductContext CreateFilterProductContext(string filter, in ); } + int languageId = _services.WorkContext.WorkingLanguage.Id; + + foreach (var criteria in context.Criteria.Where(x => x.Entity == ShortcutSpecAttribute)) + { + if (criteria.PId.HasValue) + criteria.NameLocalized = _localizedEntityService.GetLocalizedValue(languageId, criteria.PId.Value, "SpecificationAttribute", "Name"); + + if (criteria.ID.HasValue) + criteria.ValueLocalized = _localizedEntityService.GetLocalizedValue(languageId, criteria.ID.Value, "SpecificationAttributeOption", "Name"); + } + return context; } @@ -448,6 +461,7 @@ public virtual bool ToWhereClause(FilterSql context, List findIn context.Criteria.Clear(); // ! context.Criteria = findIn.FindAll(match); + return ToWhereClause(context); } @@ -569,7 +583,7 @@ public virtual void ProductFilterableMultiSelect(FilterProductContext context, s } // filters WITHOUT the multiple selectable filters - string excludedFilter = Serialize(context.Criteria); + var excludedFilter = Serialize(context.Criteria); // filters WITH the multiple selectable filters (required for highlighting selected values) context.Criteria = Deserialize(context.Filter); diff --git a/src/Presentation/SmartStore.Web/Controllers/FilterController.cs b/src/Presentation/SmartStore.Web/Controllers/FilterController.cs index 01a4528ad5..4b069a06f3 100644 --- a/src/Presentation/SmartStore.Web/Controllers/FilterController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/FilterController.cs @@ -18,6 +18,14 @@ public FilterController(IFilterService filterService, CatalogSettings catalogSet _catalogSettings = catalogSettings; } + private bool IsShowAllText(ICollection criteriaGroup) + { + if (criteriaGroup.Any(c => c.Entity == FilterService.ShortcutPrice)) + return false; + + return (criteriaGroup.Count >= _catalogSettings.MaxFilterItemsToDisplay || criteriaGroup.Any(c => !c.IsInactive)); + } + public ActionResult Products(string filter, int categoryID, string path, int? pagesize, int? orderby, string viewmode) { var context = _filterService.CreateFilterProductContext(filter, categoryID, path, pagesize, orderby, viewmode); @@ -39,7 +47,7 @@ public ActionResult Products(string active, string inactive, int categoryID, int // TODO: needed later for ajax based filtering... see example below //System.Threading.Thread.Sleep(3000); - var context = new FilterProductContext() + var context = new FilterProductContext { ParentCategoryID = categoryID, CategoryIds = new List { categoryID }, @@ -74,20 +82,13 @@ public ActionResult ProductsMultiSelect(string filter, int categoryID, string pa _filterService.ProductFilterableMultiSelect(context, filterMultiSelect); - return PartialView(new ProductFilterModel { + return PartialView(new ProductFilterModel + { Context = context, IsShowAllText = IsShowAllText(context.Criteria), MaxFilterItemsToDisplay = _catalogSettings.MaxFilterItemsToDisplay, ExpandAllFilterGroups = _catalogSettings.ExpandAllFilterCriteria }); } - - private bool IsShowAllText(ICollection criteriaGroup) - { - if (criteriaGroup.Any(c => c.Entity == FilterService.ShortcutPrice)) - return false; - - return (criteriaGroup.Count >= _catalogSettings.MaxFilterItemsToDisplay || criteriaGroup.Any(c => !c.IsInactive)); - } } } From 9ca7b8dc4751890d72b9cbfd1dbb87bc45330603 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 25 Oct 2015 13:59:55 +0100 Subject: [PATCH 145/874] Minor changes --- .../Migrations/201508091512101_ExportFramework.cs | 4 ++-- .../Migrations/201509021536425_ExportFramework1.cs | 2 +- .../SmartStore.Web/Administration/Views/Export/Edit.cshtml | 4 ++-- .../Views/Export/_CreateOrUpdate.Deployment.cshtml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs index 795d74921a..65999838ab 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs @@ -493,13 +493,13 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.EmailSubject", "Email subject", "E-Mail Betreff", - "Specifies the subject of the data should be sent.", + "Specifies the subject of the email.", "Legt den Betreff der verschickten Daten fest."); builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.EmailAccountId", "Email account", "E-Mail Konto", - "Specifies the email account through which the data should be sent.", + "Specifies the email account used to sent the data.", "Legt das E-Mail Konto fest, ber welches die Daten verschickt werden sollen."); } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs index 4f7df13d97..d8e9b8ab9d 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs @@ -96,7 +96,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.DataExchange.Export.FolderName", "Folder name", "Ordnername", - "Specifies the name of the folder where the data will be exported.", + "Specifies the name of the folder where to export the data.", "Legt den Namen des Ordners fest, in den die Daten exportiert werden."); builder.AddOrUpdate("Admin.DataExchange.Export.FolderAndFileName.Validate", diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 05d16db1cc..529b85f5d4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -98,12 +98,12 @@ { @Model.Provider.FriendlyName.NaIfEmpty() - (@(Model.Provider.SystemName.NaIfEmpty())) } else { - @Model.Provider.FriendlyName.NaIfEmpty() (@(Model.Provider.SystemName.NaIfEmpty())) +
    @Model.Provider.FriendlyName.NaIfEmpty()
    } +
    @(Model.Provider.SystemName.NaIfEmpty())
    +
    - @Html.SmartLabelFor(model => model.Provider.SystemName) + @Html.SmartLabelFor(model => model.ProviderSystemName) - + @foreach (var item in Model.AvailableProviders) { } @@ -45,7 +45,7 @@   - @foreach (var item in Model.Provider.AvailableProviders) + @foreach (var item in Model.AvailableProviders) { @item.Description } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 529b85f5d4..f9174a7b29 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -13,13 +13,16 @@
    + +

     

    -
     
    - @Html.SmartLabelFor(x => x.Provider.FriendlyName) - - @if (Model.Provider.ConfigurationUrl.HasValue()) - { - - @Model.Provider.FriendlyName.NaIfEmpty() - } - else - { -
    @Model.Provider.FriendlyName.NaIfEmpty()
    - } -
    @(Model.Provider.SystemName.NaIfEmpty())
    -
    - @Html.SmartLabelFor(x => x.Provider.Description) - - @Html.Raw(Model.Provider.Description) -
    - } - @if (Model.Provider.Author.HasValue()) - { + @if (Model.Provider.Description.HasValue()) + { + + + + + } + @if (Model.Provider.Url.HasValue()) + { + + + + + } + @if (Model.Provider.Author.HasValue()) + { + + + + + } + @if (Model.Provider.Version.HasValue()) + { + + + + + } - } - @if (Model.Provider.Version.HasValue()) - { - } - - - - - - - - -
    - @Html.SmartLabelFor(x => x.Provider.Url) + @Html.SmartLabelFor(x => x.Provider.FriendlyName) - @Model.Provider.Url + @if (Model.Provider.ConfigurationUrl.HasValue()) + { + + @Model.Provider.FriendlyName.NaIfEmpty() + + } + else + { +
    @Model.Provider.FriendlyName.NaIfEmpty()
    + } +
    @(Model.ProviderSystemName.NaIfEmpty())
    + @Html.SmartLabelFor(x => x.Provider.Description) + + @Html.Raw(Model.Provider.Description) +
    + @Html.SmartLabelFor(x => x.Provider.Url) + + @Model.Provider.Url +
    + @Html.SmartLabelFor(x => x.Provider.Author) + + @Html.DisplayFor(x => x.Provider.Author) +
    + @Html.SmartLabelFor(x => x.Provider.Version) + + @Html.DisplayFor(x => x.Provider.Version) +
    - @Html.SmartLabelFor(x => x.Provider.Author) + @Html.SmartLabelFor(x => x.Provider.EntityTypeName) - @Html.DisplayFor(x => x.Provider.Author) + @Html.DisplayFor(x => x.Provider.EntityTypeName)
    - @Html.SmartLabelFor(x => x.Provider.Version) + @Html.SmartLabelFor(x => x.Provider.FileExtension) - @Html.DisplayFor(x => x.Provider.Version) + @Html.IconForFileExtension(Model.Provider.FileExtension, true)
    - @Html.SmartLabelFor(x => x.Provider.EntityTypeName) - - @Html.DisplayFor(x => x.Provider.EntityTypeName) -
    - @Html.SmartLabelFor(x => x.Provider.FileExtension) - - @Html.IconForFileExtension(Model.Provider.FileExtension, true) -
    + + } + else + { +
    + @T("Admin.Common.ProviderNotLoaded", Model.ProviderSystemName) +
    + } } @helper TabPartition() @@ -233,7 +251,7 @@ } else { -
    @T("Admin.DataExchange.Export.NoProjection")
    +
    @T("Admin.DataExchange.Export.NoProjection")
    } } @@ -257,7 +275,7 @@ else {
    - @Html.Raw(T("Admin.DataExchange.Export.Configuration.NotRequired", Model.Provider.FriendlyName.NaIfEmpty())) + @Html.Raw(T("Admin.DataExchange.Export.Configuration.NotRequired", Model.Provider.FriendlyName ?? Model.ProviderSystemName))
    } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 0704662b78..689da7f9a4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -48,14 +48,17 @@ @profile.Name - @profile.Provider.FriendlyName.NaIfEmpty() -
    @profile.Provider.SystemName
    + @if (profile.Provider.ConfigurationUrl.HasValue()) + { + @profile.Provider.FriendlyName.NaIfEmpty() + } +
    @profile.ProviderSystemName
    @Html.SymbolForBool(profile.Enabled) - @profile.Provider.EntityTypeName + @profile.Provider.EntityTypeName.NaIfEmpty() @Html.IconForFileExtension(profile.Provider.FileExtension, true) @@ -144,7 +147,7 @@ else callbackSuccess: function (resp) { dialog.find('.modal-body').html(resp); - $('select[name="Provider.SystemName"]').select2({ + $('select[name="ProviderSystemName"]').select2({ minimumResultsForSearch: 16, formatResult: providerSelectItemFormatting, formatSelection: providerSelectItemFormatting @@ -181,7 +184,7 @@ else }); // selected provider changed - $(document).on('change', 'select[name="Provider.SystemName"]', toggleProviderDescription); + $(document).on('change', 'select[name="ProviderSystemName"]', toggleProviderDescription); function refreshGrid() { setTimeout(function () { @@ -196,7 +199,7 @@ else function toggleProviderDescription() { dialog.find('.export-provider-description').hide(); - var option = dialog.find('select[name="Provider.SystemName"]').find(':selected'); + var option = dialog.find('select[name="ProviderSystemName"]').find(':selected'); $('#ExportProviderDescription' + option.attr('data-id')).fadeIn('fast'); } @@ -206,7 +209,7 @@ else // item.element is undefined for selection formatting if (option.length === 0) { - option = $('select[name="Provider.SystemName"]').find('option[value="' + item.id + '"]'); + option = $('select[name="ProviderSystemName"]').find('option[value="' + item.id + '"]'); } var html = '
    ', From 3100528a4d9868d149282bbbc594bc23bf705347 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Mon, 26 Oct 2015 19:09:45 +0100 Subject: [PATCH 149/874] Minor fixes --- .../SmartStore.Core/Extensions/MiscExtensions.cs | 12 ++++-------- src/Libraries/SmartStore.Data/SmartObjectContext.cs | 2 +- .../Configuration/SettingService.cs | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs index 3e871038fe..865d5f8ed4 100644 --- a/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs @@ -117,17 +117,13 @@ public static string ToHexString(this byte[] bytes, int length = 0) public static T GetMergedDataValue(this IMergedData mergedData, string key, T defaultValue) { - try + if (mergedData.MergedDataValues != null && !mergedData.MergedDataIgnore) { - if (mergedData.MergedDataValues != null && !mergedData.MergedDataIgnore) - { - object value; + object value; - if (mergedData.MergedDataValues.TryGetValue(key, out value)) - return (T)value; - } + if (mergedData.MergedDataValues.TryGetValue(key, out value)) + return (T)value; } - catch (Exception) { } return defaultValue; } diff --git a/src/Libraries/SmartStore.Data/SmartObjectContext.cs b/src/Libraries/SmartStore.Data/SmartObjectContext.cs index 97a8982b71..0d5356688c 100644 --- a/src/Libraries/SmartStore.Data/SmartObjectContext.cs +++ b/src/Libraries/SmartStore.Data/SmartObjectContext.cs @@ -53,7 +53,7 @@ protected override void OnModelCreating(DbModelBuilder modelBuilder) // && type.BaseType != null // && type.BaseType.IsGenericType // && type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>)); - + var typesToRegister = from t in Assembly.GetExecutingAssembly().GetTypes() where t.Namespace.HasValue() && t.BaseType != null && diff --git a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs index d6efe2222c..9bdcecbfcb 100644 --- a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs +++ b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs @@ -81,7 +81,7 @@ protected virtual IDictionary> GetAllSettingsCa orderby s.Name, s.StoreId select s; var settings = query.ToList(); - var dictionary = new Dictionary>(); + var dictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); foreach (var s in settings) { var settingName = s.Name.ToLowerInvariant(); From e1615370eb4d5e1eed5f4869f21fdc4a33adc7ba Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 26 Oct 2015 22:06:27 +0100 Subject: [PATCH 150/874] Fixed issue with selected attributes at bundles with per item pricing --- .../Catalog/PriceCalculationService.cs | 2 +- .../Extensions/NameValueCollectionExtensions.cs | 4 ++-- .../SmartStore.Web/Controllers/CatalogHelper.cs | 14 +++++++++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs index 28eebe4491..1ad7adb761 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs @@ -241,7 +241,7 @@ protected virtual decimal GetPreselectedPrice(Product product, PriceCalculationC // 2. find attribute combination for selected attributes and merge it if (!isBundle && selectedAttributes.Count > 0) { - string attributeXml = selectedAttributes.CreateSelectedAttributesXml(product.Id, attributes, _productAttributeParser, _services.Localization, + var attributeXml = selectedAttributes.CreateSelectedAttributesXml(product.Id, attributes, _productAttributeParser, _services.Localization, _downloadService, _catalogSettings, _httpRequestBase, new List(), true, bundleItemId); var combinations = context.AttributeCombinations.Load(product.Id); diff --git a/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs b/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs index c7fa1aa9dd..a2e53aa8da 100644 --- a/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs +++ b/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs @@ -16,9 +16,9 @@ public static class NameValueCollectionExtensions private static string AttributeFormatedName(int productAttributeId, int attributeId, int productId = 0, int bundleItemId = 0) { if (productId == 0) - return "product_attribute_{0}_{1}".FormatWith(productAttributeId, attributeId); + return "product_attribute_{0}_{1}".FormatInvariant(productAttributeId, attributeId); else - return "product_attribute_{0}_{1}_{2}_{3}".FormatWith(productId, bundleItemId, productAttributeId, attributeId); + return "product_attribute_{0}_{1}_{2}_{3}".FormatInvariant(productId, bundleItemId, productAttributeId, attributeId); } public static void AddProductAttribute(this NameValueCollection collection, int productAttributeId, int attributeId, int valueId, int productId = 0, int bundleItemId = 0) diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index ad36b89187..4f20eefd3c 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -230,7 +230,15 @@ public ProductDetailsModel PrepareProductDetailsPageModel( foreach (var itemData in bundleItems.Where(x => x.Item.Product.CanBeBundleItem())) { var item = itemData.Item; - var bundledProductModel = PrepareProductDetailsPageModel(item.Product, false, itemData, null, selectedAttributes); + var bundleItemAttributes = new NameValueCollection(); + var keyPrefix = "product_attribute_{0}_".FormatInvariant(itemData.Item.ProductId); + + foreach (var key in selectedAttributes.AllKeys.Where(x => x.HasValue() && x.StartsWith(keyPrefix))) + { + bundleItemAttributes.Add(key, selectedAttributes[key]); + } + + var bundledProductModel = PrepareProductDetailsPageModel(item.Product, false, itemData, null, bundleItemAttributes); bundledProductModel.BundleItem.Id = item.Id; bundledProductModel.BundleItem.Quantity = item.Quantity; @@ -238,11 +246,11 @@ public ProductDetailsModel PrepareProductDetailsPageModel( bundledProductModel.BundleItem.Visible = item.Visible; bundledProductModel.BundleItem.IsBundleItemPricing = item.BundleProduct.BundlePerItemPricing; - string bundleItemName = item.GetLocalized(x => x.Name); + var bundleItemName = item.GetLocalized(x => x.Name); if (bundleItemName.HasValue()) bundledProductModel.Name = bundleItemName; - string bundleItemShortDescription = item.GetLocalized(x => x.ShortDescription); + var bundleItemShortDescription = item.GetLocalized(x => x.ShortDescription); if (bundleItemShortDescription.HasValue()) bundledProductModel.ShortDescription = bundleItemShortDescription; From 7567fb4c77d300fbe8ae07fe26d655bf6f899e5d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 27 Oct 2015 12:02:52 +0100 Subject: [PATCH 151/874] Some changes to last commit --- src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs | 2 +- .../SmartStore.Web/Controllers/ProductController.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index 4f20eefd3c..beb33343b0 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -231,7 +231,7 @@ public ProductDetailsModel PrepareProductDetailsPageModel( { var item = itemData.Item; var bundleItemAttributes = new NameValueCollection(); - var keyPrefix = "product_attribute_{0}_".FormatInvariant(itemData.Item.ProductId); + var keyPrefix = "product_attribute_{0}_{1}".FormatInvariant(item.ProductId, item.Id); foreach (var key in selectedAttributes.AllKeys.Where(x => x.HasValue() && x.StartsWith(keyPrefix))) { diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index 3b94189942..74c3d06c58 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -172,7 +172,7 @@ public ActionResult ProductDetails(int productId, string attributes) selectedAttributes.ConvertAttributeQueryData( _productAttributeParser.DeserializeQueryData(attributes), - product.ProductType == ProductType.BundledProduct ? 0 : product.Id); + product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing ? 0 : product.Id); var model = _helper.PrepareProductDetailsPageModel(product, selectedAttributes: selectedAttributes); From 9e1791f2042b44b831d9e3003e42a5e4c29ceafb Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 27 Oct 2015 14:34:30 +0100 Subject: [PATCH 152/874] Amazon Payment: Message tokens for billing address are not resolved in order placed email because the billing address is unavailable at this time (shipping address is used instead). Updated registration link. --- .../Messages/IMessageTokenProvider.cs | 2 +- .../Messages/MessageTokenProvider.cs | 26 +++++++++---------- .../Messages/WorkflowMessageService.cs | 5 +++- .../Localization/resources.de-de.xml | 3 +-- .../Localization/resources.en-us.xml | 3 +-- 5 files changed, 20 insertions(+), 19 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Messages/IMessageTokenProvider.cs b/src/Libraries/SmartStore.Services/Messages/IMessageTokenProvider.cs index add0f4e57a..d5456194c7 100644 --- a/src/Libraries/SmartStore.Services/Messages/IMessageTokenProvider.cs +++ b/src/Libraries/SmartStore.Services/Messages/IMessageTokenProvider.cs @@ -15,7 +15,7 @@ public partial interface IMessageTokenProvider { void AddStoreTokens(IList tokens, Store store); - void AddOrderTokens(IList tokens, Order order, int languageId); + void AddOrderTokens(IList tokens, Order order, int languageId, bool skipBillingAddress = false); void AddShipmentTokens(IList tokens, Shipment shipment, int languageId); diff --git a/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs b/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs index 0d294a4c54..7591020c72 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs @@ -722,7 +722,7 @@ public virtual void AddContactDataTokens(IList tokens) tokens.Add(new Token("Contact.ContactEmailAddress", _contactDataSettings.ContactEmailAddress)); } - public virtual void AddOrderTokens(IList tokens, Order order, int languageId) + public virtual void AddOrderTokens(IList tokens, Order order, int languageId, bool skipBillingAddress = false) { tokens.Add(new Token("Order.ID", order.Id.ToString())); tokens.Add(new Token("Order.OrderNumber", order.GetOrderNumber())); @@ -730,18 +730,18 @@ public virtual void AddOrderTokens(IList tokens, Order order, int languag tokens.Add(new Token("Order.CustomerFullName", string.Format("{0} {1}", order.BillingAddress.FirstName, order.BillingAddress.LastName))); tokens.Add(new Token("Order.CustomerEmail", order.BillingAddress.Email)); - tokens.Add(new Token("Order.BillingFirstName", order.BillingAddress.FirstName)); - tokens.Add(new Token("Order.BillingLastName", order.BillingAddress.LastName)); - tokens.Add(new Token("Order.BillingPhoneNumber", order.BillingAddress.PhoneNumber)); - tokens.Add(new Token("Order.BillingEmail", order.BillingAddress.Email)); - tokens.Add(new Token("Order.BillingFaxNumber", order.BillingAddress.FaxNumber)); - tokens.Add(new Token("Order.BillingCompany", order.BillingAddress.Company)); - tokens.Add(new Token("Order.BillingAddress1", order.BillingAddress.Address1)); - tokens.Add(new Token("Order.BillingAddress2", order.BillingAddress.Address2)); - tokens.Add(new Token("Order.BillingCity", order.BillingAddress.City)); - tokens.Add(new Token("Order.BillingStateProvince", order.BillingAddress.StateProvince != null ? order.BillingAddress.StateProvince.GetLocalized(x => x.Name) : "")); - tokens.Add(new Token("Order.BillingZipPostalCode", order.BillingAddress.ZipPostalCode)); - tokens.Add(new Token("Order.BillingCountry", order.BillingAddress.Country != null ? order.BillingAddress.Country.GetLocalized(x => x.Name) : "")); + tokens.Add(new Token("Order.BillingFirstName", skipBillingAddress ? "".NaIfEmpty() : order.BillingAddress.FirstName)); + tokens.Add(new Token("Order.BillingLastName", skipBillingAddress ? "" : order.BillingAddress.LastName)); + tokens.Add(new Token("Order.BillingPhoneNumber", skipBillingAddress ? "" : order.BillingAddress.PhoneNumber)); + tokens.Add(new Token("Order.BillingEmail", skipBillingAddress ? "" : order.BillingAddress.Email)); + tokens.Add(new Token("Order.BillingFaxNumber", skipBillingAddress ? "" : order.BillingAddress.FaxNumber)); + tokens.Add(new Token("Order.BillingCompany", skipBillingAddress ? "" : order.BillingAddress.Company)); + tokens.Add(new Token("Order.BillingAddress1", skipBillingAddress ? "" : order.BillingAddress.Address1)); + tokens.Add(new Token("Order.BillingAddress2", order.BillingAddress.Address2)); + tokens.Add(new Token("Order.BillingCity", skipBillingAddress ? "" : order.BillingAddress.City)); + tokens.Add(new Token("Order.BillingStateProvince", !skipBillingAddress && order.BillingAddress.StateProvince != null ? order.BillingAddress.StateProvince.GetLocalized(x => x.Name) : "")); + tokens.Add(new Token("Order.BillingZipPostalCode", skipBillingAddress ? "" : order.BillingAddress.ZipPostalCode)); + tokens.Add(new Token("Order.BillingCountry", !skipBillingAddress && order.BillingAddress.Country != null ? order.BillingAddress.Country.GetLocalized(x => x.Name) : "")); tokens.Add(new Token("Order.ShippingMethod", order.ShippingMethod)); tokens.Add(new Token("Order.ShippingFirstName", order.ShippingAddress != null ? order.ShippingAddress.FirstName : "")); diff --git a/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs b/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs index 64adb4aa26..075b2f653b 100644 --- a/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs +++ b/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs @@ -467,10 +467,13 @@ public virtual int SendOrderPlacedCustomerNotification(Order order, int language if (messageTemplate == null) return 0; + // unavailable at this time. shipping address is used as billing address here, so do not resolve tokens. + var skipBillingAddress = order.PaymentMethodSystemName.IsCaseInsensitiveEqual("SmartStore.AmazonPay"); + //tokens var tokens = new List(); _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddOrderTokens(tokens, order, languageId); + _messageTokenProvider.AddOrderTokens(tokens, order, languageId, skipBillingAddress); _messageTokenProvider.AddCustomerTokens(tokens, order.Customer); _messageTokenProvider.AddCompanyTokens(tokens); diff --git a/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml b/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml index da4712b22d..3bdbb45a9b 100644 --- a/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml @@ -8,14 +8,13 @@ - Registrieren Sie sich bei Amazon Payments.

    + Registrieren Sie sich zunächst bei Amazon Payments.

    So richten Sie "Bezahlen mit Amazon" ein:

    • Tragen Sie Ihre Amazon-Zugangsdaten unten in die dafür vorgesehenen Felder ein. Sie finden diese Daten in Ihrem Amazon Seller Central Konto.
    • Ihre Händlernummer finden Sie dort rechts oben unter Einstellungen - Integrationseinstellungen.
    • Die beiden Zugangsschlüssel finden Sie dort links oben unter Integration - MWS Access Key. In diesem Dokument finden Sie Bilder, wie Sie diese erstellen.
    • Falls Sie Sofortbenachrichtigungen (IPN) erhalten möchten (SSL zwingend erforderlich!), so tragen Sie die unten aufgeführte IPN URL unter Einstellungen - Integrationseinstellungen - Sofortbenachrichtigungs-Einstellungen - Händler-URL ein.
    -

    Hinweis! Die Rechnungsadresse des Kunden erhalten Sie erst nach Bestätigung der Zahlungsautorisierung von Amazon, also i.d.R. erst kurz nach Versand der Bestellbestätigungs-Email. Um Missverständnisse zu vermeiden ist es ratsam, den Platzhalter für die Rechnungsadresse aus der Nachrichtenvorlage für die Bestellbestätigung zu entfernen.

    Bitte fügen Sie Informationen zu "Bezahlen mit Amazon" auf Ihrer Seite der Zahlungsarten ein (siehe CMS - Seiten). Bildmaterial finden Sie hier. Textvorschläge:

    • Option 1: Bezahlen mit Amazon: Zahlen Sie jetzt mit den Zahl- und Lieferinformationen aus Ihrem Amazon-Konto.
    • diff --git a/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml b/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml index 99d505ab8e..49fa863b0f 100644 --- a/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml @@ -8,14 +8,13 @@ - Register now at Amazon Payments.

      + Register now at Amazon Payments.

      How to set up "Pay with Amazon":

      • Enter your Amazon credentials in the fields provided below. You can find these credentials in your Amazon Seller Central account.
      • You can find the Merchant ID in Seller Central at Settings - Integration Settings.
      • You can find both access keys in Seller Central at Integration - MWS Access Key.
      • If you would like to receive instant payment notifications (SSL required!) enter the IPN URL listed bewlow under Settings - Integration Settings - Instant Notification Settings - Merchant URL.
      -

      Note! You get the billing address of the customer after the payment has been authorised by Amazon, so usually shortly after the order confirmation E-mail has been sent to the customer. To avoid misunderstandings, it is therefore advisable to remove the placeholder for the billing address from the message template for order confirmations.

      Please add information about "Pay with Amazon" on your payment page (see CMS - Topics). You will find picture material here. Text suggestions:

      • Option 1: Pay with Amazon: Pay now with the payment and shipping information from your Amazon account.
      • From 5b92f50323c00ec6836e8aa921d9671eec31a49b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 27 Oct 2015 18:22:20 +0100 Subject: [PATCH 153/874] Reverted last commit and added message token for billing address note in order placed customer notification --- .../Messages/IMessageTokenProvider.cs | 2 +- .../Messages/MessageTokenProvider.cs | 24 +++++----- .../Messages/WorkflowMessageService.cs | 5 +- .../SmartStore.AmazonPay/Description.txt | 2 +- .../Events/OrderEventConsumer.cs | 47 +++++++++++++++++++ .../Localization/resources.de-de.xml | 3 ++ .../Localization/resources.en-us.xml | 3 ++ .../SmartStore.AmazonPay.csproj | 1 + src/Plugins/SmartStore.AmazonPay/changelog.md | 44 +++++++++-------- 9 files changed, 93 insertions(+), 38 deletions(-) create mode 100644 src/Plugins/SmartStore.AmazonPay/Events/OrderEventConsumer.cs diff --git a/src/Libraries/SmartStore.Services/Messages/IMessageTokenProvider.cs b/src/Libraries/SmartStore.Services/Messages/IMessageTokenProvider.cs index d5456194c7..7265b4faa4 100644 --- a/src/Libraries/SmartStore.Services/Messages/IMessageTokenProvider.cs +++ b/src/Libraries/SmartStore.Services/Messages/IMessageTokenProvider.cs @@ -15,7 +15,7 @@ public partial interface IMessageTokenProvider { void AddStoreTokens(IList tokens, Store store); - void AddOrderTokens(IList tokens, Order order, int languageId, bool skipBillingAddress = false); + void AddOrderTokens(IList tokens, Order order, int languageId); void AddShipmentTokens(IList tokens, Shipment shipment, int languageId); diff --git a/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs b/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs index 7591020c72..b1e50412da 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs @@ -722,7 +722,7 @@ public virtual void AddContactDataTokens(IList tokens) tokens.Add(new Token("Contact.ContactEmailAddress", _contactDataSettings.ContactEmailAddress)); } - public virtual void AddOrderTokens(IList tokens, Order order, int languageId, bool skipBillingAddress = false) + public virtual void AddOrderTokens(IList tokens, Order order, int languageId) { tokens.Add(new Token("Order.ID", order.Id.ToString())); tokens.Add(new Token("Order.OrderNumber", order.GetOrderNumber())); @@ -730,18 +730,18 @@ public virtual void AddOrderTokens(IList tokens, Order order, int languag tokens.Add(new Token("Order.CustomerFullName", string.Format("{0} {1}", order.BillingAddress.FirstName, order.BillingAddress.LastName))); tokens.Add(new Token("Order.CustomerEmail", order.BillingAddress.Email)); - tokens.Add(new Token("Order.BillingFirstName", skipBillingAddress ? "".NaIfEmpty() : order.BillingAddress.FirstName)); - tokens.Add(new Token("Order.BillingLastName", skipBillingAddress ? "" : order.BillingAddress.LastName)); - tokens.Add(new Token("Order.BillingPhoneNumber", skipBillingAddress ? "" : order.BillingAddress.PhoneNumber)); - tokens.Add(new Token("Order.BillingEmail", skipBillingAddress ? "" : order.BillingAddress.Email)); - tokens.Add(new Token("Order.BillingFaxNumber", skipBillingAddress ? "" : order.BillingAddress.FaxNumber)); - tokens.Add(new Token("Order.BillingCompany", skipBillingAddress ? "" : order.BillingAddress.Company)); - tokens.Add(new Token("Order.BillingAddress1", skipBillingAddress ? "" : order.BillingAddress.Address1)); + tokens.Add(new Token("Order.BillingFirstName", order.BillingAddress.FirstName)); + tokens.Add(new Token("Order.BillingLastName", order.BillingAddress.LastName)); + tokens.Add(new Token("Order.BillingPhoneNumber", order.BillingAddress.PhoneNumber)); + tokens.Add(new Token("Order.BillingEmail", order.BillingAddress.Email)); + tokens.Add(new Token("Order.BillingFaxNumber", order.BillingAddress.FaxNumber)); + tokens.Add(new Token("Order.BillingCompany", order.BillingAddress.Company)); + tokens.Add(new Token("Order.BillingAddress1", order.BillingAddress.Address1)); tokens.Add(new Token("Order.BillingAddress2", order.BillingAddress.Address2)); - tokens.Add(new Token("Order.BillingCity", skipBillingAddress ? "" : order.BillingAddress.City)); - tokens.Add(new Token("Order.BillingStateProvince", !skipBillingAddress && order.BillingAddress.StateProvince != null ? order.BillingAddress.StateProvince.GetLocalized(x => x.Name) : "")); - tokens.Add(new Token("Order.BillingZipPostalCode", skipBillingAddress ? "" : order.BillingAddress.ZipPostalCode)); - tokens.Add(new Token("Order.BillingCountry", !skipBillingAddress && order.BillingAddress.Country != null ? order.BillingAddress.Country.GetLocalized(x => x.Name) : "")); + tokens.Add(new Token("Order.BillingCity", order.BillingAddress.City)); + tokens.Add(new Token("Order.BillingStateProvince", order.BillingAddress.StateProvince != null ? order.BillingAddress.StateProvince.GetLocalized(x => x.Name) : "")); + tokens.Add(new Token("Order.BillingZipPostalCode", order.BillingAddress.ZipPostalCode)); + tokens.Add(new Token("Order.BillingCountry", order.BillingAddress.Country != null ? order.BillingAddress.Country.GetLocalized(x => x.Name) : "")); tokens.Add(new Token("Order.ShippingMethod", order.ShippingMethod)); tokens.Add(new Token("Order.ShippingFirstName", order.ShippingAddress != null ? order.ShippingAddress.FirstName : "")); diff --git a/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs b/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs index 075b2f653b..64adb4aa26 100644 --- a/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs +++ b/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs @@ -467,13 +467,10 @@ public virtual int SendOrderPlacedCustomerNotification(Order order, int language if (messageTemplate == null) return 0; - // unavailable at this time. shipping address is used as billing address here, so do not resolve tokens. - var skipBillingAddress = order.PaymentMethodSystemName.IsCaseInsensitiveEqual("SmartStore.AmazonPay"); - //tokens var tokens = new List(); _messageTokenProvider.AddStoreTokens(tokens, store); - _messageTokenProvider.AddOrderTokens(tokens, order, languageId, skipBillingAddress); + _messageTokenProvider.AddOrderTokens(tokens, order, languageId); _messageTokenProvider.AddCustomerTokens(tokens, order.Customer); _messageTokenProvider.AddCompanyTokens(tokens); diff --git a/src/Plugins/SmartStore.AmazonPay/Description.txt b/src/Plugins/SmartStore.AmazonPay/Description.txt index 92acfd1fab..383de5f866 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.2.0.2 +Version: 2.2.0.3 Group: Payment MinAppVersion: 2.2.0 Author: SmartStore AG diff --git a/src/Plugins/SmartStore.AmazonPay/Events/OrderEventConsumer.cs b/src/Plugins/SmartStore.AmazonPay/Events/OrderEventConsumer.cs new file mode 100644 index 0000000000..16ff49247a --- /dev/null +++ b/src/Plugins/SmartStore.AmazonPay/Events/OrderEventConsumer.cs @@ -0,0 +1,47 @@ +using System.Linq; +using SmartStore.AmazonPay.Services; +using SmartStore.Core.Domain.Messages; +using SmartStore.Core.Events; +using SmartStore.Core.Plugins; +using SmartStore.Services; +using SmartStore.Services.Messages; +using SmartStore.Services.Orders; +using SmartStore.Web.Framework; + +namespace SmartStore.AmazonPay.Events +{ + public class OrderEventConsumer : IConsumer> + { + private readonly IPluginFinder _pluginFinder; + private readonly ICommonServices _services; + private readonly IOrderService _orderService; + + public OrderEventConsumer( + IPluginFinder pluginFinder, + ICommonServices services, + IOrderService orderService) + { + _pluginFinder = pluginFinder; + _services = services; + _orderService = orderService; + } + + public void HandleEvent(MessageTokensAddedEvent messageTokenEvent) + { + if (!messageTokenEvent.Message.Name.IsCaseInsensitiveEqual("OrderPlaced.CustomerNotification")) + return; + + var storeId = _services.StoreContext.CurrentStore.Id; + + if (!_pluginFinder.IsPluginReady(_services.Settings, AmazonPayCore.SystemName, storeId)) + return; + + var order = _orderService.SearchOrders(storeId, _services.WorkContext.CurrentCustomer.Id, null, null, null, null, null, null, null, null, 0, 1).FirstOrDefault(); + + var isAmazonPayment = (order != null && order.PaymentMethodSystemName.IsCaseInsensitiveEqual(AmazonPayCore.SystemName)); + var tokenValue = (isAmazonPayment ? _services.Localization.GetResource("Plugins.Payments.AmazonPay.BillingAddressMessageNote") : ""); + + messageTokenEvent.Tokens.Add(new Token("SmartStore.AmazonPay.BillingAddressMessageNote", tokenValue)); + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml b/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml index 3bdbb45a9b..63b8f6b811 100644 --- a/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.AmazonPay/Localization/resources.de-de.xml @@ -23,6 +23,9 @@ Textvorschläge:

        ]]> + + Bitte beachten Sie, dass es sich bei dieser Rechnungsadresse unter Umständen nicht um die für diese Bestellung gültige handelt! + Es wurde keine Auftrags-Referenz-ID durch Amazon übermittelt! diff --git a/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml b/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml index 49fa863b0f..d21b5bc3cb 100644 --- a/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.AmazonPay/Localization/resources.en-us.xml @@ -23,6 +23,9 @@ Text suggestions:

        ]]> + + Please note that this billing address is possibly not the valid billing address for this order! + There was no order reference ID transmitted by Amazon! diff --git a/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj b/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj index ab7c222b62..399f6faa95 100644 --- a/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj +++ b/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj @@ -139,6 +139,7 @@ + diff --git a/src/Plugins/SmartStore.AmazonPay/changelog.md b/src/Plugins/SmartStore.AmazonPay/changelog.md index 416414c398..c22eca67c8 100644 --- a/src/Plugins/SmartStore.AmazonPay/changelog.md +++ b/src/Plugins/SmartStore.AmazonPay/changelog.md @@ -1,48 +1,52 @@ -#Release Notes# +#Release Notes + +##Pay with Amazon 2.2.0.3 +###Improvements +* Added message token %SmartStore.AmazonPay.BillingAddressMessageNote% for billing address note in order placed customer notification ##Pay with Amazon 2.2.0.2 -###Bugfixes### +###Bugfixes * Send currency code of primary store currency (not of working currency) to payment gateway ##Pay with Amazon 2.2.0.1 ### New Features * Supports order list label for new incoming IPNs -##Pay with Amazon 1.20## -###Bugfixes### +##Pay with Amazon 1.20 +###Bugfixes * PlatformID must be .NET ID not merchant ID. PlatformID needs to be identical for all orders -##Pay with Amazon 1.19## -###Bugfixes### +##Pay with Amazon 1.19 +###Bugfixes * Declined authorization IPN did not void the payment status -##Pay with Amazon 1.18## -###Bugfixes### +##Pay with Amazon 1.18 +###Bugfixes * Order wasn't found if the capturing\refunding took place at Amazon Seller Central and the notification came through IPN -##Pay with Amazon 1.17## -###Improvements### +##Pay with Amazon 1.17 +###Improvements * Amazon payments review -##Pay with Amazon 1.16## -###Bugfixes### +##Pay with Amazon 1.16 +###Bugfixes * Reflect refunds made at amazon seller central when using data polling -##Pay with Amazon 1.15## -###Bugfixes### +##Pay with Amazon 1.15 +###Bugfixes * Multistore configuration might be lost if "All stores" are left empty -##Pay with Amazon 1.14## -###Bugfixes### +##Pay with Amazon 1.14 +###Bugfixes * Data polling did not reflect the transaction status correctly if the action took place at amazon seller central -##Pay with Amazon 1.13## -###Bugfixes### +##Pay with Amazon 1.13 +###Bugfixes * Sometimes shipping method restrictions were not applied * Shipping tab in checkout may show "Shipping address is not set" even if set -##Pay with Amazon 1.12## -###Improvements### +##Pay with Amazon 1.12 +###Improvements * Created this changelog * Changed string „Bezahlen über Amazon“ into „Bezahlen mit Amazon“ From ff715fbba3042be3090adc736a2d3fee5649a668 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 27 Oct 2015 20:27:41 +0100 Subject: [PATCH 154/874] Minor change --- .../{OrderEventConsumer.cs => MessageTokenEventConsumer.cs} | 4 ++-- src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/Plugins/SmartStore.AmazonPay/Events/{OrderEventConsumer.cs => MessageTokenEventConsumer.cs} (92%) diff --git a/src/Plugins/SmartStore.AmazonPay/Events/OrderEventConsumer.cs b/src/Plugins/SmartStore.AmazonPay/Events/MessageTokenEventConsumer.cs similarity index 92% rename from src/Plugins/SmartStore.AmazonPay/Events/OrderEventConsumer.cs rename to src/Plugins/SmartStore.AmazonPay/Events/MessageTokenEventConsumer.cs index 16ff49247a..78f10c09e2 100644 --- a/src/Plugins/SmartStore.AmazonPay/Events/OrderEventConsumer.cs +++ b/src/Plugins/SmartStore.AmazonPay/Events/MessageTokenEventConsumer.cs @@ -10,13 +10,13 @@ namespace SmartStore.AmazonPay.Events { - public class OrderEventConsumer : IConsumer> + public class MessageTokenEventConsumer : IConsumer> { private readonly IPluginFinder _pluginFinder; private readonly ICommonServices _services; private readonly IOrderService _orderService; - public OrderEventConsumer( + public MessageTokenEventConsumer( IPluginFinder pluginFinder, ICommonServices services, IOrderService orderService) diff --git a/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj b/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj index 399f6faa95..f436a67f06 100644 --- a/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj +++ b/src/Plugins/SmartStore.AmazonPay/SmartStore.AmazonPay.csproj @@ -139,7 +139,7 @@ - + From 7806ca91b91d43ba8e28124755e0c34616bab6dd Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 28 Oct 2015 12:12:40 +0100 Subject: [PATCH 155/874] PayPal Standard: Closes #758. Option to add order note when order total validation fails. --- .../Controllers/PayPalStandardController.cs | 70 +++++++++++++------ src/Plugins/SmartStore.PayPal/Description.txt | 2 +- .../Localization/resources.de-de.xml | 30 ++++---- .../Localization/resources.en-us.xml | 30 ++++---- .../PayPalStandardConfigurationModel.cs | 6 +- .../Settings/PayPalSettings.cs | 2 +- .../Views/PayPalStandard/Configure.cshtml | 19 ++++- src/Plugins/SmartStore.PayPal/changelog.md | 4 ++ 8 files changed, 112 insertions(+), 51 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs index 10aa50046e..0256173046 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs @@ -119,8 +119,12 @@ public override ProcessPaymentRequest GetPaymentInfo(FormCollection form) [ValidateInput(false)] public ActionResult PDTHandler(FormCollection form) { - string tx = _webHelper.QueryString("tx"); Dictionary values; + var tx = _webHelper.QueryString("tx"); + var utcNow = DateTime.UtcNow; + var orderNumberGuid = Guid.Empty; + var orderNumber = string.Empty; + var total = decimal.Zero; string response; var provider = _paymentService.LoadPaymentMethodBySystemName("Payments.PayPalStandard", true); @@ -132,18 +136,18 @@ public ActionResult PDTHandler(FormCollection form) if (processor.GetPDTDetails(tx, settings, out values, out response)) { - string orderNumber = string.Empty; values.TryGetValue("custom", out orderNumber); - Guid orderNumberGuid = Guid.Empty; + try { orderNumberGuid = new Guid(orderNumber); } catch { } - Order order = _orderService.GetOrderByGuid(orderNumberGuid); + + var order = _orderService.GetOrderByGuid(orderNumberGuid); + if (order != null) { - decimal total = decimal.Zero; try { total = decimal.Parse(values["mc_gross"], new CultureInfo("en-US")); @@ -174,26 +178,48 @@ public ActionResult PDTHandler(FormCollection form) string payment_fee = string.Empty; values.TryGetValue("payment_fee", out payment_fee); - string paymentNote = _localizationService.GetResource("Plugins.Payments.PayPalStandard.PaymentNote").FormatWith( + var paymentNote = _localizationService.GetResource("Plugins.Payments.PayPalStandard.PaymentNote").FormatInvariant( total, mc_currency, payer_status, payment_status, pending_reason, txn_id, payment_type, payer_id, receiver_id, invoice, payment_fee); - //order note - order.OrderNotes.Add(new OrderNote() + order.OrderNotes.Add(new OrderNote { Note = paymentNote, DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow + CreatedOnUtc = utcNow }); - _orderService.UpdateOrder(order); - //validate order total - if (settings.PdtValidateOrderTotal && !Math.Round(total, 2).Equals(Math.Round(order.OrderTotal, 2))) + //validate order total... you may get differences if settings.PassProductNamesAndTotals is true + if (settings.PdtValidateOrderTotal) { - Logger.Error(_localizationService.GetResource("Plugins.Payments.PayPalStandard.UnequalTotalOrder").FormatWith(total, order.OrderTotal)); + var roundedTotal = Math.Round(total, 2); + var roundedOrderTotal = Math.Round(order.OrderTotal, 2); + var roundedDifference = Math.Abs(roundedTotal - roundedOrderTotal); + + if (!roundedTotal.Equals(roundedOrderTotal)) + { + var message = _localizationService.GetResource("Plugins.Payments.PayPalStandard.UnequalTotalOrder").FormatInvariant( + total, roundedOrderTotal.FormatInvariant(), order.OrderTotal, roundedDifference.FormatInvariant()); + + if (settings.PdtValidateOnlyWarn) + { + order.OrderNotes.Add(new OrderNote + { + Note = message, + DisplayToCustomer = false, + CreatedOnUtc = utcNow + }); + } + else + { + Logger.Error(message); - return RedirectToAction("Index", "Home", new { area = "" }); + return RedirectToAction("Index", "Home", new { area = "" }); + } + } } + _orderService.UpdateOrder(order); + //mark order as paid if (_orderProcessingService.CanMarkOrderAsPaid(order)) { @@ -208,26 +234,27 @@ public ActionResult PDTHandler(FormCollection form) } else { - string orderNumber = string.Empty; values.TryGetValue("custom", out orderNumber); - Guid orderNumberGuid = Guid.Empty; + try { orderNumberGuid = new Guid(orderNumber); } catch { } - Order order = _orderService.GetOrderByGuid(orderNumberGuid); + + var order = _orderService.GetOrderByGuid(orderNumberGuid); + if (order != null) { - //order note - order.OrderNotes.Add(new OrderNote() + order.OrderNotes.Add(new OrderNote { Note = "{0} {1}".FormatWith(_localizationService.GetResource("Plugins.Payments.PayPalStandard.PdtFailed"), response), DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow + CreatedOnUtc = utcNow }); _orderService.UpdateOrder(order); } + return RedirectToAction("Index", "Home", new { area = "" }); } } @@ -235,7 +262,7 @@ public ActionResult PDTHandler(FormCollection form) [ValidateInput(false)] public ActionResult IPNHandler() { - Debug.WriteLine("PayPal Standard IPN: {0}".FormatWith(Request.ContentLength)); + Debug.WriteLine("PayPal Standard IPN: {0}".FormatInvariant(Request.ContentLength)); byte[] param = Request.BinaryRead(Request.ContentLength); string strRequest = Encoding.ASCII.GetString(param); @@ -249,6 +276,7 @@ public ActionResult IPNHandler() if (processor.VerifyIPN(strRequest, out values)) { #region values + decimal total = decimal.Zero; try { diff --git a/src/Plugins/SmartStore.PayPal/Description.txt b/src/Plugins/SmartStore.PayPal/Description.txt index ecf91d9d38..854a27fa9f 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 Express, PayPal Standard and PayPal Direct. SystemName: SmartStore.PayPal Group: Payment -Version: 2.2.0.2 +Version: 2.2.0.3 MinAppVersion: 2.2.0 DisplayOrder: 1 FileName: SmartStore.PayPal.dll diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index d9f8e940a1..ba2d95cd8d 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -271,6 +271,12 @@ Aktivieren Sie diese Option, falls der durch PayPal übermittelte Gesamtbetrag überprüft werden soll. + + Nur warnen + + + Legt fest, ob im Falle einer Nichtübereinstimmung des Gesamtbetrags lediglich eine Auftragsnotiz angelegt werden soll. Ansonsten wird ein Fehler erzeugt und der Zahlungsstatus wird nicht aktualisiert. + Zusätzliche Gebühren @@ -311,7 +317,7 @@ PayPal Standard PDT. Fehler beim Abruf von mc_gross. - PayPal Standard PDT. Der übermittelte Gesamtbetrag {0} entspricht nicht dem des Shops {1}. + PayPal Standard PDT. Der übermittelte Gesamtbetrag {0} entspricht nicht dem des Shops {1} (ungerundet {2}). Die Differenz beträgt {3}. PayPal Standard PDT ist fehlgeschlagen. @@ -343,17 +349,17 @@ +Total: {0} +Währung: {1} +Zahlenderstatus: {2} +Zahlungsstatus: {3} +Ausstehend (Grund): {4} +Transaktions-ID: {5} +Zahlungstyp: {6} +Zahlender-ID: {7} +Empfänger-ID: {8} +Rechnung: {9} +Zahlungsgebühr: {10}]]> diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index 3aebd55408..a0143309c7 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -310,6 +310,12 @@ Check the box if the PDT handler should validate PayPal's order totals. + + Only warn + + + Specifies whether to add an order note if the order total is unequal. Otherwise an error will be logged and the payment status is not updated. + Additional fee @@ -350,7 +356,7 @@ PayPal Standard PDT. Error getting mc_gross. - PayPal Standard PDT. Returned order total {0} doesn't equal order total {1}. + PayPal Standard PDT. Returned order total {0} doesn't equal order total {1} (not rounded {2}). The difference is {3}. PayPal Standard PDT failed. @@ -382,17 +388,17 @@ +Total: {0} +Currency: {1} +Payer status: {2} +Payment status: {3} +Pending reason: {4} +Transaction ID: {5} +Payment type: {6} +Payer ID: {7} +Receiver ID: {8} +Invoice: {9} +Payment fee: {10}]]> diff --git a/src/Plugins/SmartStore.PayPal/Models/PayPalStandardConfigurationModel.cs b/src/Plugins/SmartStore.PayPal/Models/PayPalStandardConfigurationModel.cs index 76f329c55c..d928e57927 100644 --- a/src/Plugins/SmartStore.PayPal/Models/PayPalStandardConfigurationModel.cs +++ b/src/Plugins/SmartStore.PayPal/Models/PayPalStandardConfigurationModel.cs @@ -18,6 +18,9 @@ public class PayPalStandardConfigurationModel : ModelBase [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.PDTValidateOrderTotal")] public bool PdtValidateOrderTotal { get; set; } + [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.PdtValidateOnlyWarn")] + public bool PdtValidateOnlyWarn { get; set; } + [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.AdditionalFee")] public decimal AdditionalFee { get; set; } @@ -41,6 +44,7 @@ public void Copy(PayPalStandardPaymentSettings settings, bool fromSettings) BusinessEmail = settings.BusinessEmail; PdtToken = settings.PdtToken; PdtValidateOrderTotal = settings.PdtValidateOrderTotal; + PdtValidateOnlyWarn = settings.PdtValidateOnlyWarn; AdditionalFee = settings.AdditionalFee; AdditionalFeePercentage = settings.AdditionalFeePercentage; PassProductNamesAndTotals = settings.PassProductNamesAndTotals; @@ -53,13 +57,13 @@ public void Copy(PayPalStandardPaymentSettings settings, bool fromSettings) settings.BusinessEmail = BusinessEmail; settings.PdtToken = PdtToken; settings.PdtValidateOrderTotal = PdtValidateOrderTotal; + settings.PdtValidateOnlyWarn = PdtValidateOnlyWarn; settings.AdditionalFee = AdditionalFee; settings.AdditionalFeePercentage = AdditionalFeePercentage; settings.PassProductNamesAndTotals = PassProductNamesAndTotals; settings.EnableIpn = EnableIpn; settings.IpnUrl = IpnUrl; } - } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs index 9e3adea8da..863197acc0 100644 --- a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs +++ b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs @@ -72,7 +72,6 @@ public class PayPalStandardPaymentSettings : PayPalSettingsBase, ISettings public PayPalStandardPaymentSettings() { UseSandbox = true; - PdtValidateOrderTotal = true; EnableIpn = true; } @@ -80,6 +79,7 @@ public PayPalStandardPaymentSettings() public string PdtToken { get; set; } public bool PassProductNamesAndTotals { get; set; } public bool PdtValidateOrderTotal { get; set; } + public bool PdtValidateOnlyWarn { get; set; } public bool EnableIpn { get; set; } public string IpnUrl { get; set; } } diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml index c4b1c4b0eb..70868da9e5 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml @@ -61,6 +61,15 @@ @Html.ValidationMessageFor(model => model.PdtValidateOrderTotal) + + + @Html.SmartLabelFor(model => model.PdtValidateOnlyWarn) + + + @Html.SettingEditorFor(model => model.PdtValidateOnlyWarn) + @Html.ValidationMessageFor(model => model.PdtValidateOnlyWarn) + + @Html.SmartLabelFor(model => model.AdditionalFee) @@ -97,7 +106,7 @@ @Html.ValidationMessageFor(model => model.EnableIpn) - + @Html.SmartLabelFor(model => model.IpnUrl) @@ -106,7 +115,7 @@ @Html.ValidationMessageFor(model => model.IpnUrl) - +
        @T("Plugins.Payments.PayPalStandard.Fields.EnableIpn.Hint2") @@ -130,7 +139,11 @@ $(document).ready(function () { $("#@Html.FieldIdFor(model => model.EnableIpn)").change(function () { - $('.ipn-url').toggle($(this).is(':checked')); + $('.ipn-handling').toggle($(this).is(':checked')); + }).trigger('change'); + + $("#@Html.FieldIdFor(model => model.PdtValidateOrderTotal)").change(function () { + $('#PdtValidateOnlyWarnContainer').toggle($(this).is(':checked')); }).trigger('change'); }); diff --git a/src/Plugins/SmartStore.PayPal/changelog.md b/src/Plugins/SmartStore.PayPal/changelog.md index 62451fa42c..80afe49fdd 100644 --- a/src/Plugins/SmartStore.PayPal/changelog.md +++ b/src/Plugins/SmartStore.PayPal/changelog.md @@ -1,5 +1,9 @@ #Release Notes# +##Paypal 2.2.0.3 +### New Features +* Option to add order note when order total validation fails + ##PayPal 2.2.0.2 ###Improvements * Redirecting to payment provider performed by core instead of plugin From 1c98019fe07ed0d69695adb57e5f9c980f86dc15 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 29 Oct 2015 19:21:35 +0100 Subject: [PATCH 156/874] New interface to filter out payment methods --- ...06211043073_PaymentShippingRestrictions.cs | 8 ++-- .../201509150931528_ExportFramework2.cs | 4 ++ .../Payments/IPaymentFilter.cs | 48 +++++++++++++++++++ .../Payments/IPaymentService.cs | 5 ++ .../Payments/PaymentService.cs | 32 ++++++++++++- .../SmartStore.Services.csproj | 1 + .../Controllers/PaymentController.cs | 21 +++++++- .../Models/Payments/PaymentMethodEditModel.cs | 4 +- .../Administration/Views/Payment/Edit.cshtml | 22 ++++++++- .../Payments/PaymentServiceTests.cs | 5 +- 10 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 src/Libraries/SmartStore.Services/Payments/IPaymentFilter.cs diff --git a/src/Libraries/SmartStore.Data/Migrations/201506211043073_PaymentShippingRestrictions.cs b/src/Libraries/SmartStore.Data/Migrations/201506211043073_PaymentShippingRestrictions.cs index 1f5f6223e8..5cd8878827 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201506211043073_PaymentShippingRestrictions.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201506211043073_PaymentShippingRestrictions.cs @@ -80,12 +80,12 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Payment.Methods.RestrictionNote", - "Select customer roles, shipping methods, countries and order amounts for which you do not want to offer this payment method.", - "Whlen Sie Kundengruppen, Versandarten, Lnder und Bestellwerte, bei denen Sie diese Zahlungsmethode nicht anbieten mchten."); + "Select features for which you do not want to offer this payment method.", + "Whlen Sie Merkmale, bei denen Sie diese Zahlungsmethode nicht anbieten mchten."); builder.AddOrUpdate("Admin.Configuration.Shipping.Methods.RestrictionNote", - "Select customer roles and countries for which you do not want to offer this shipping method.", - "Whlen Sie Kundengruppen und Lnder, bei denen Sie diese Versandart nicht anbieten mchten."); + "Select features for which you do not want to offer this shipping method.", + "Whlen Sie Merkmale, bei denen Sie diese Versandart nicht anbieten mchten."); builder.AddOrUpdate("Admin.Configuration.Payment.Methods.ExcludedCustomerRole", diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index 195519dbc5..afbc9f0d63 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -52,6 +52,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Cannot load the provider {0}.", "Der Provider {0} konnte nicht geladen werden."); + builder.AddOrUpdate("ActivityLog.EditPaymentMethod", + "Edited payment method '{0}' ({1})", + "Zahlungsart '{0}' ({1}) bearbeitet"); + builder.AddOrUpdate("Admin.DataExchange.Export.NotPreviewCompatible", "This option is not taken into account in the preview.", "Diese Option wird in der Vorschau nicht bercksichtigt."); diff --git a/src/Libraries/SmartStore.Services/Payments/IPaymentFilter.cs b/src/Libraries/SmartStore.Services/Payments/IPaymentFilter.cs new file mode 100644 index 0000000000..53962b2a8a --- /dev/null +++ b/src/Libraries/SmartStore.Services/Payments/IPaymentFilter.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Plugins; + +namespace SmartStore.Services.Payments +{ + public partial interface IPaymentFilter + { + /// + /// Gets a value indicating whether a payment method should be filtered out + /// + /// Payment filter request + /// true filter out method, false do not filter out method + bool FilterOutPaymentMethod(PaymentFilterRequest request); + + /// + /// Get URL for filter configuration + /// + /// Payment provider system name + /// URL for filter configuration + string GetConfigurationUrl(string systemName); + } + + + public partial class PaymentFilterRequest + { + /// + /// The payment method to be checked + /// + public Provider PaymentMethod { get; set; } + + /// + /// The context shopping cart + /// + public IList Cart { get; set; } + + /// + /// The context store identifier + /// + public int StoreId { get; set; } + + /// + /// The context customer + /// + public Customer Customer { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Payments/IPaymentService.cs b/src/Libraries/SmartStore.Services/Payments/IPaymentService.cs index 6e45d3d0e2..a03fddee54 100644 --- a/src/Libraries/SmartStore.Services/Payments/IPaymentService.cs +++ b/src/Libraries/SmartStore.Services/Payments/IPaymentService.cs @@ -209,5 +209,10 @@ IEnumerable> LoadActivePaymentMethods( /// Masked credit card number string GetMaskedCreditCardNumber(string creditCardNumber); + /// + /// Gets all payment filters + /// + /// List of payment filters + IList GetAllPaymentFilters(); } } diff --git a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs index c029fc051b..9d78091d1c 100644 --- a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs +++ b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs @@ -9,6 +9,7 @@ using SmartStore.Core.Domain.Payments; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Events; +using SmartStore.Core.Infrastructure; using SmartStore.Core.Plugins; using SmartStore.Services.Common; using SmartStore.Services.Directory; @@ -37,6 +38,7 @@ public partial class PaymentService : IPaymentService private readonly ICurrencyService _currencyService; private readonly ICommonServices _services; private readonly IOrderTotalCalculationService _orderTotalCalculationService; + private readonly ITypeFinder _typeFinder; #endregion @@ -57,7 +59,8 @@ public PaymentService( IProviderManager providerManager, ICurrencyService currencyService, ICommonServices services, - IOrderTotalCalculationService orderTotalCalculationService) + IOrderTotalCalculationService orderTotalCalculationService, + ITypeFinder typeFinder) { this._paymentMethodRepository = paymentMethodRepository; this._paymentSettings = paymentSettings; @@ -67,6 +70,7 @@ public PaymentService( this._currencyService = currencyService; this._services = services; this._orderTotalCalculationService = orderTotalCalculationService; + this._typeFinder = typeFinder; } #endregion @@ -94,8 +98,16 @@ public virtual IEnumerable> LoadActivePaymentMethods( decimal? orderSubTotal = null; decimal? orderTotal = null; IList allMethods = null; + IList allFilters = null; IEnumerable> allProviders = null; + var filterRequest = new PaymentFilterRequest + { + Cart = cart, + StoreId = storeId, + Customer = customer + }; + if (types != null && types.Any()) allProviders = LoadAllPaymentMethods(storeId).Where(x => types.Contains(x.Value.PaymentMethodType)); else @@ -107,6 +119,16 @@ public virtual IEnumerable> LoadActivePaymentMethods( if (!p.Value.IsActive || !_paymentSettings.ActivePaymentMethodSystemNames.Contains(p.Metadata.SystemName, StringComparer.InvariantCultureIgnoreCase)) return false; + // payment method filtering + if (allFilters == null) + allFilters = GetAllPaymentFilters(); + + filterRequest.PaymentMethod = p; + + if (allFilters.Any(x => x.FilterOutPaymentMethod(filterRequest))) + return false; + + // payment method core restrictions if (customer != null) { if (allMethods == null) @@ -196,6 +218,7 @@ public virtual IEnumerable> LoadActivePaymentMethods( } } } + return true; }); @@ -697,6 +720,13 @@ public virtual string GetMaskedCreditCardNumber(string creditCardNumber) return maskedChars + last4; } + public virtual IList GetAllPaymentFilters() + { + return _typeFinder.FindClassesOfType(ignoreInactivePlugins: true) + .Select(x => EngineContext.Current.ContainerManager.ResolveUnregistered(x) as IPaymentFilter) + .ToList(); + } + #endregion } } diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 708211c879..0ae2169db8 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -207,6 +207,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PaymentController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PaymentController.cs index e0abb761b8..53a020c313 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PaymentController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PaymentController.cs @@ -4,6 +4,7 @@ using SmartStore.Admin.Models.Payments; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services; using SmartStore.Services.Customers; @@ -34,6 +35,7 @@ public partial class PaymentController : AdminControllerBase private readonly IShippingService _shippingService; private readonly ICountryService _countryService; private readonly ILocalizedEntityService _localizedEntityService; + private readonly ICustomerActivityService _customerActivityService; #endregion @@ -49,7 +51,8 @@ public PaymentController( ICustomerService customerService, IShippingService shippingService, ICountryService countryService, - ILocalizedEntityService localizedEntityService) + ILocalizedEntityService localizedEntityService, + ICustomerActivityService customerActivityService) { this._services = services; this._paymentService = paymentService; @@ -61,6 +64,7 @@ public PaymentController( this._shippingService = shippingService; this._countryService = countryService; this._localizedEntityService = localizedEntityService; + this._customerActivityService = customerActivityService; } #endregion @@ -72,6 +76,7 @@ private void PreparePaymentMethodEditModel(PaymentMethodEditModel model, Payment var customerRoles = _customerService.GetAllCustomerRoles(true); var shippingMethods = _shippingService.GetAllShippingMethods(); var countries = _countryService.GetAllCountries(true); + var allFilters = _paymentService.GetAllPaymentFilters(); model.AvailableCustomerRoles = new List(); model.AvailableShippingMethods = new List(); @@ -95,8 +100,15 @@ private void PreparePaymentMethodEditModel(PaymentMethodEditModel model, Payment model.AvailableCountries.Add(new SelectListItem { Text = country.GetLocalized(x => x.Name), Value = country.Id.ToString() }); } + + model.FilterConfigurationUrls = allFilters + .Select(x => "'" + x.GetConfigurationUrl(model.SystemName) + "'") + .OrderBy(x => x) + .ToList(); + if (paymentMethod != null) { + model.Id = paymentMethod.Id; model.ExcludedCustomerRoleIds = paymentMethod.ExcludedCustomerRoleIds.SplitSafe(","); model.ExcludedShippingMethodIds = paymentMethod.ExcludedShippingMethodIds.SplitSafe(","); model.ExcludedCountryIds = paymentMethod.ExcludedCountryIds.SplitSafe(","); @@ -196,7 +208,7 @@ public ActionResult Edit(string systemName) } [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] - public ActionResult Edit(string systemName, bool continueEditing, PaymentMethodEditModel model) + public ActionResult Edit(string systemName, bool continueEditing, PaymentMethodEditModel model, FormCollection form) { if (!_services.Permissions.Authorize(StandardPermissionProvider.ManagePaymentMethods)) return AccessDeniedView(); @@ -238,6 +250,11 @@ public ActionResult Edit(string systemName, bool continueEditing, PaymentMethodE _localizedEntityService.SaveLocalizedValue(paymentMethod, x => x.FullDescription, localized.FullDescription, localized.LanguageId); } + _services.EventPublisher.Publish(new ModelBoundEvent(model, paymentMethod, form)); + + _customerActivityService.InsertActivity("EditPaymentMethod", _services.Localization.GetResource("ActivityLog.EditPaymentMethod"), + model.FriendlyName.NaIfEmpty(), model.SystemName); + NotifySuccess(_services.Localization.GetResource("Admin.Common.DataEditSuccess")); return (continueEditing ? diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Payments/PaymentMethodEditModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Payments/PaymentMethodEditModel.cs index 1a84067969..28e7cfabe5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Payments/PaymentMethodEditModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Payments/PaymentMethodEditModel.cs @@ -7,15 +7,17 @@ namespace SmartStore.Admin.Models.Payments { - public class PaymentMethodEditModel : EntityModelBase, ILocalizedModel + public class PaymentMethodEditModel : TabbableModel, ILocalizedModel { public PaymentMethodEditModel() { Locales = new List(); + FilterConfigurationUrls = new List(); } public IList Locales { get; set; } public string IconUrl { get; set; } + public IList FilterConfigurationUrls { get; set; } [SmartResourceDisplayName("Common.SystemName")] public string SystemName { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Payment/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Payment/Edit.cshtml index 6151ad6e0d..6a0b4582a0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Payment/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Payment/Edit.cshtml @@ -23,7 +23,6 @@ @Html.ValidationSummary(false) @Html.HiddenFor(model => model.SystemName) - @Html.SmartStore().TabStrip().Name("payment-method-edit").Items(x => { x.Add().Text(T("Admin.Common.General").Text).Content(TabGeneral()).Selected(true); @@ -181,5 +180,26 @@ + +
        } + + \ No newline at end of file diff --git a/src/Tests/SmartStore.Services.Tests/Payments/PaymentServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Payments/PaymentServiceTests.cs index 2a4a4499af..b7148173fb 100644 --- a/src/Tests/SmartStore.Services.Tests/Payments/PaymentServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Payments/PaymentServiceTests.cs @@ -5,6 +5,7 @@ using SmartStore.Core.Data; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Infrastructure; using SmartStore.Core.Plugins; using SmartStore.Services.Directory; using SmartStore.Services.Localization; @@ -24,6 +25,7 @@ public class PaymentServiceTests : ServiceTest ICurrencyService _currencyService; ICommonServices _services; IOrderTotalCalculationService _orderTotalCalculationService; + ITypeFinder _typeFinder; [SetUp] public new void SetUp() @@ -39,12 +41,13 @@ public class PaymentServiceTests : ServiceTest _currencyService = MockRepository.GenerateMock(); _services = MockRepository.GenerateMock(); _orderTotalCalculationService = MockRepository.GenerateMock(); + _typeFinder = MockRepository.GenerateMock(); var localizationService = MockRepository.GenerateMock(); localizationService.Expect(ls => ls.GetResource(null)).IgnoreArguments().Return("NotSupported").Repeat.Any(); _paymentService = new PaymentService(_paymentMethodRepository, _paymentSettings, pluginFinder, _shoppingCartSettings, - this.ProviderManager, _currencyService, _services, _orderTotalCalculationService); + this.ProviderManager, _currencyService, _services, _orderTotalCalculationService, _typeFinder); } [Test] From 1e4a1b0fa3c7ba593fa3ca0e9c5d240b17d7ac36 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 2 Nov 2015 13:11:15 +0100 Subject: [PATCH 157/874] New interface to filter out shipping methods --- .../Orders/OrderTotalCalculationService.cs | 2 +- ...ymentFilter.cs => IPaymentMethodFilter.cs} | 2 +- .../Payments/IPaymentService.cs | 2 +- .../Payments/PaymentService.cs | 10 +- .../Shipping/GetShippingOptionRequest.cs | 5 + .../Shipping/IShippingMethodFilter.cs | 35 +++++++ .../Shipping/IShippingService.cs | 13 ++- .../Shipping/ShippingService.cs | 91 ++++++++++++------- .../SmartStore.Services.csproj | 3 +- .../Providers/ByTotalProvider.cs | 2 +- .../Providers/FixedRateProvider.cs | 4 +- .../ByWeightShippingComputationMethod.cs | 2 +- .../Controllers/PaymentController.cs | 2 +- .../Controllers/ShippingController.cs | 6 ++ .../Infrastructure/AutoMapperStartupTask.cs | 3 +- .../Models/Shipping/ShippingMethodModel.cs | 3 + .../Shipping/_CreateOrUpdateMethod.cshtml | 21 +++++ .../Orders/OrderProcessingServiceTests.cs | 9 +- .../OrderTotalCalculationServiceTests.cs | 7 +- .../Shipping/ShippingServiceTests.cs | 6 +- 20 files changed, 173 insertions(+), 55 deletions(-) rename src/Libraries/SmartStore.Services/Payments/{IPaymentFilter.cs => IPaymentMethodFilter.cs} (96%) create mode 100644 src/Libraries/SmartStore.Services/Shipping/IShippingMethodFilter.cs diff --git a/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs b/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs index 4aef87d01f..3e48061380 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs @@ -617,7 +617,7 @@ public virtual decimal AdjustShippingRate(decimal shippingRate, IList /// Gets a value indicating whether a payment method should be filtered out diff --git a/src/Libraries/SmartStore.Services/Payments/IPaymentService.cs b/src/Libraries/SmartStore.Services/Payments/IPaymentService.cs index a03fddee54..ced73b9b10 100644 --- a/src/Libraries/SmartStore.Services/Payments/IPaymentService.cs +++ b/src/Libraries/SmartStore.Services/Payments/IPaymentService.cs @@ -213,6 +213,6 @@ IEnumerable> LoadActivePaymentMethods( /// Gets all payment filters ///
    /// List of payment filters - IList GetAllPaymentFilters(); + IList GetAllPaymentMethodFilters(); } } diff --git a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs index 9d78091d1c..d7bd0f6176 100644 --- a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs +++ b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs @@ -98,7 +98,7 @@ public virtual IEnumerable> LoadActivePaymentMethods( decimal? orderSubTotal = null; decimal? orderTotal = null; IList allMethods = null; - IList allFilters = null; + IList allFilters = null; IEnumerable> allProviders = null; var filterRequest = new PaymentFilterRequest @@ -121,7 +121,7 @@ public virtual IEnumerable> LoadActivePaymentMethods( // payment method filtering if (allFilters == null) - allFilters = GetAllPaymentFilters(); + allFilters = GetAllPaymentMethodFilters(); filterRequest.PaymentMethod = p; @@ -720,10 +720,10 @@ public virtual string GetMaskedCreditCardNumber(string creditCardNumber) return maskedChars + last4; } - public virtual IList GetAllPaymentFilters() + public virtual IList GetAllPaymentMethodFilters() { - return _typeFinder.FindClassesOfType(ignoreInactivePlugins: true) - .Select(x => EngineContext.Current.ContainerManager.ResolveUnregistered(x) as IPaymentFilter) + return _typeFinder.FindClassesOfType(ignoreInactivePlugins: true) + .Select(x => EngineContext.Current.ContainerManager.ResolveUnregistered(x) as IPaymentMethodFilter) .ToList(); } diff --git a/src/Libraries/SmartStore.Services/Shipping/GetShippingOptionRequest.cs b/src/Libraries/SmartStore.Services/Shipping/GetShippingOptionRequest.cs index 1962f97177..3439b6fd4f 100644 --- a/src/Libraries/SmartStore.Services/Shipping/GetShippingOptionRequest.cs +++ b/src/Libraries/SmartStore.Services/Shipping/GetShippingOptionRequest.cs @@ -16,6 +16,11 @@ public GetShippingOptionRequest() this.Items = new List(); } + /// + /// The context store identifier + /// + public int StoreId { get; set; } + /// /// Gets or sets a customer /// diff --git a/src/Libraries/SmartStore.Services/Shipping/IShippingMethodFilter.cs b/src/Libraries/SmartStore.Services/Shipping/IShippingMethodFilter.cs new file mode 100644 index 0000000000..5718dc56ff --- /dev/null +++ b/src/Libraries/SmartStore.Services/Shipping/IShippingMethodFilter.cs @@ -0,0 +1,35 @@ +using SmartStore.Core.Domain.Shipping; + +namespace SmartStore.Services.Shipping +{ + public partial interface IShippingMethodFilter + { + /// + /// Gets a value indicating whether a shipping method should be filtered out + /// + /// Shipping filter request + /// true filter out method, false do not filter out method + bool FilterOutShippingMethod(ShippingFilterRequest request); + + /// + /// Get URL for filter configuration + /// + /// Shipping method identifier + /// URL for filter configuration + string GetConfigurationUrl(int shippingMethodId); + } + + + public partial class ShippingFilterRequest + { + /// + /// The shipping method to be checked + /// + public ShippingMethod ShippingMethod { get; set; } + + /// + /// Shipping method request + /// + public GetShippingOptionRequest Request { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/Shipping/IShippingService.cs b/src/Libraries/SmartStore.Services/Shipping/IShippingService.cs index 8ec9c19c40..9018fab4b6 100644 --- a/src/Libraries/SmartStore.Services/Shipping/IShippingService.cs +++ b/src/Libraries/SmartStore.Services/Shipping/IShippingService.cs @@ -53,9 +53,9 @@ public partial interface IShippingService /// /// Gets all shipping methods /// - /// Filter shipping methods by customer and apply payment method restrictions; null to load all records + /// Shipping option request to filter out shipping methods. null to load all shipping methods. /// Shipping method collection - IList GetAllShippingMethods(Customer customer = null); + IList GetAllShippingMethods(GetShippingOptionRequest request = null); /// /// Inserts a shipping method @@ -97,8 +97,9 @@ public partial interface IShippingService /// /// Shopping cart /// Shipping address + /// Store identifier /// Shipment package - GetShippingOptionRequest CreateShippingOptionRequest(IList cart, Address shippingAddress); + GetShippingOptionRequest CreateShippingOptionRequest(IList cart, Address shippingAddress, int storeId); /// /// Gets available shipping options @@ -110,5 +111,11 @@ public partial interface IShippingService /// Shipping options GetShippingOptionResponse GetShippingOptions(IList cart, Address shippingAddress, string allowedShippingRateComputationMethodSystemName = "", int storeId = 0); + + /// + /// Gets all shipping method filters + /// + /// List of shipping method filters + IList GetAllShippingMethodFilters(); } } diff --git a/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs b/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs index 1959d39d27..ec8bd410ad 100644 --- a/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs +++ b/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs @@ -9,6 +9,7 @@ using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Events; +using SmartStore.Core.Infrastructure; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Catalog; @@ -37,6 +38,7 @@ public partial class ShippingService : IShippingService private readonly ShoppingCartSettings _shoppingCartSettings; private readonly ISettingService _settingService; private readonly IProviderManager _providerManager; + private readonly ITypeFinder _typeFinder; #endregion @@ -71,7 +73,8 @@ public ShippingService(ICacheManager cacheManager, IEventPublisher eventPublisher, ShoppingCartSettings shoppingCartSettings, ISettingService settingService, - IProviderManager providerManager) + IProviderManager providerManager, + ITypeFinder typeFinder) { this._cacheManager = cacheManager; this._shippingMethodRepository = shippingMethodRepository; @@ -87,6 +90,7 @@ public ShippingService(ICacheManager cacheManager, this._shoppingCartSettings = shoppingCartSettings; this._settingService = settingService; this._providerManager = providerManager; + this._typeFinder = typeFinder; } #endregion @@ -184,13 +188,8 @@ public virtual ShippingMethod GetShippingMethodById(int shippingMethodId) return _shippingMethodRepository.GetById(shippingMethodId); } - - /// - /// Gets all shipping methods - /// - /// Filter shipping methods by customer and apply payment method restrictions; null to load all records - /// Shipping method collection - public virtual IList GetAllShippingMethods(Customer customer = null) + + public virtual IList GetAllShippingMethods(GetShippingOptionRequest request = null) { List customerRoleIds = null; @@ -201,31 +200,44 @@ orderby sm.DisplayOrder var allMethods = query.ToList(); - if (customer == null) + if (request == null) return allMethods; - var activeShippingMethods = allMethods.Where(x => + var allFilters = GetAllShippingMethodFilters(); + var filterRequest = new ShippingFilterRequest { Request = request }; + + var activeShippingMethods = allMethods.Where(s => { - // method restricted by customer role id? - var excludedRoleIds = x.ExcludedCustomerRoleIds.ToIntArray(); - if (excludedRoleIds.Any()) + // shipping method filtering + filterRequest.ShippingMethod = s; + + if (allFilters.Any(x => x.FilterOutShippingMethod(filterRequest))) + return false; + + // shipping method core restrictions + if (request.Customer != null) { - if (customerRoleIds == null) - customerRoleIds = customer.CustomerRoles.Where(r => r.Active).Select(r => r.Id).ToList(); + // method restricted by customer role id? + var excludedRoleIds = s.ExcludedCustomerRoleIds.ToIntArray(); + if (excludedRoleIds.Any()) + { + if (customerRoleIds == null) + customerRoleIds = request.Customer.CustomerRoles.Where(r => r.Active).Select(r => r.Id).ToList(); - if (customerRoleIds != null && !customerRoleIds.Except(excludedRoleIds).Any()) - return false; - } + if (customerRoleIds != null && !customerRoleIds.Except(excludedRoleIds).Any()) + return false; + } - // method restricted by country of selected billing or shipping address? - int countryId = 0; - if (x.CountryExclusionContext == CountryRestrictionContextType.ShippingAddress) - countryId = (customer.ShippingAddress != null ? (customer.ShippingAddress.CountryId ?? 0) : 0); - else - countryId = (customer.BillingAddress != null ? (customer.BillingAddress.CountryId ?? 0) : 0); + // method restricted by country of selected billing or shipping address? + int countryId = 0; + if (s.CountryExclusionContext == CountryRestrictionContextType.ShippingAddress) + countryId = (request.Customer.ShippingAddress != null ? (request.Customer.ShippingAddress.CountryId ?? 0) : 0); + else + countryId = (request.Customer.BillingAddress != null ? (request.Customer.BillingAddress.CountryId ?? 0) : 0); - if (countryId != 0 && x.CountryRestrictionExists(countryId)) - return false; + if (countryId != 0 && s.CountryRestrictionExists(countryId)) + return false; + } return true; }); @@ -354,20 +366,26 @@ public virtual decimal GetShoppingCartTotalWeight(IList /// Shopping cart /// Shipping address + /// Store identifier /// Shipment package - public virtual GetShippingOptionRequest CreateShippingOptionRequest(IList cart, - Address shippingAddress) + public virtual GetShippingOptionRequest CreateShippingOptionRequest(IList cart, Address shippingAddress, int storeId) { var request = new GetShippingOptionRequest(); + request.StoreId = storeId; request.Customer = cart.GetCustomer(); - request.Items = new List(); - foreach (var sc in cart) - if (sc.Item.IsShipEnabled) - request.Items.Add(sc); request.ShippingAddress = shippingAddress; request.CountryFrom = null; request.StateProvinceFrom = null; request.ZipPostalCodeFrom = string.Empty; + + request.Items = new List(); + + foreach (var sc in cart) + { + if (sc.Item.IsShipEnabled) + request.Items.Add(sc); + } + return request; } @@ -389,7 +407,7 @@ public virtual GetShippingOptionResponse GetShippingOptions(IList @@ -441,6 +459,13 @@ public virtual GetShippingOptionResponse GetShippingOptions(IList GetAllShippingMethodFilters() + { + return _typeFinder.FindClassesOfType(ignoreInactivePlugins: true) + .Select(x => EngineContext.Current.ContainerManager.ResolveUnregistered(x) as IShippingMethodFilter) + .ToList(); + } + #endregion #endregion diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 0ae2169db8..2de19dd74f 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -207,7 +207,7 @@ - + @@ -462,6 +462,7 @@ + diff --git a/src/Plugins/SmartStore.Shipping/Providers/ByTotalProvider.cs b/src/Plugins/SmartStore.Shipping/Providers/ByTotalProvider.cs index 9ca91fdca8..882a576f15 100644 --- a/src/Plugins/SmartStore.Shipping/Providers/ByTotalProvider.cs +++ b/src/Plugins/SmartStore.Shipping/Providers/ByTotalProvider.cs @@ -187,7 +187,7 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest get decimal sqThreshold = _shippingByTotalSettings.SmallQuantityThreshold; decimal sqSurcharge = _shippingByTotalSettings.SmallQuantitySurcharge; - var shippingMethods = _shippingService.GetAllShippingMethods(getShippingOptionRequest.Customer); + var shippingMethods = _shippingService.GetAllShippingMethods(getShippingOptionRequest); foreach (var shippingMethod in shippingMethods) { decimal? rate = GetRate(subTotal, shippingMethod.Id, storeId, countryId, stateProvinceId, zip); diff --git a/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs b/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs index 3f51635f18..a4ae31c94e 100644 --- a/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs +++ b/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs @@ -55,7 +55,7 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest get return response; } - var shippingMethods = this._shippingService.GetAllShippingMethods(getShippingOptionRequest.Customer); + var shippingMethods = this._shippingService.GetAllShippingMethods(getShippingOptionRequest); foreach (var shippingMethod in shippingMethods) { var shippingOption = new ShippingOption(); @@ -79,7 +79,7 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest get if (getShippingOptionRequest == null) throw new ArgumentNullException("getShippingOptionRequest"); - var shippingMethods = this._shippingService.GetAllShippingMethods(getShippingOptionRequest.Customer); + var shippingMethods = this._shippingService.GetAllShippingMethods(getShippingOptionRequest); var rates = new List(); foreach (var shippingMethod in shippingMethods) diff --git a/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs b/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs index 01042c2d5d..0c59828d2a 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs @@ -143,7 +143,7 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest get } decimal weight = _shippingService.GetShoppingCartTotalWeight(getShippingOptionRequest.Items); - var shippingMethods = _shippingService.GetAllShippingMethods(getShippingOptionRequest.Customer); + var shippingMethods = _shippingService.GetAllShippingMethods(getShippingOptionRequest); foreach (var shippingMethod in shippingMethods) { var record = _shippingByWeightService.FindRecord(shippingMethod.Id, storeId, countryId, weight, zip); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PaymentController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PaymentController.cs index 53a020c313..e2708847b0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PaymentController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PaymentController.cs @@ -76,7 +76,7 @@ private void PreparePaymentMethodEditModel(PaymentMethodEditModel model, Payment var customerRoles = _customerService.GetAllCustomerRoles(true); var shippingMethods = _shippingService.GetAllShippingMethods(); var countries = _countryService.GetAllCountries(true); - var allFilters = _paymentService.GetAllPaymentFilters(); + var allFilters = _paymentService.GetAllPaymentMethodFilters(); model.AvailableCustomerRoles = new List(); model.AvailableShippingMethods = new List(); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ShippingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ShippingController.cs index 05ee6e911d..e3a001ce12 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ShippingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ShippingController.cs @@ -79,6 +79,7 @@ private void PrepareShippingMethodModel(ShippingMethodModel model, ShippingMetho { var customerRoles = _customerService.GetAllCustomerRoles(true); var countries = _countryService.GetAllCountries(true); + var allFilters = _shippingService.GetAllShippingMethodFilters(); model.AvailableCustomerRoles = new List(); model.AvailableCountries = new List(); @@ -95,6 +96,11 @@ private void PrepareShippingMethodModel(ShippingMethodModel model, ShippingMetho model.AvailableCountries.Add(new SelectListItem { Text = country.GetLocalized(x => x.Name), Value = country.Id.ToString() }); } + model.FilterConfigurationUrls = allFilters + .Select(x => "'" + x.GetConfigurationUrl(shippingMethod.Id) + "'") + .OrderBy(x => x) + .ToList(); + if (shippingMethod != null) { model.ExcludedCustomerRoleIds = shippingMethod.ExcludedCustomerRoleIds.SplitSafe(","); diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs index b8b44b146e..42a7fcb454 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs @@ -396,7 +396,8 @@ public void Execute() .ForMember(dest => dest.ExcludedCountryIds, mo => mo.Ignore()) .ForMember(dest => dest.AvailableCustomerRoles, mo => mo.Ignore()) .ForMember(dest => dest.AvailableCountries, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableCountryExclusionContextTypes, mo => mo.Ignore()); + .ForMember(dest => dest.AvailableCountryExclusionContextTypes, mo => mo.Ignore()) + .ForMember(dest => dest.FilterConfigurationUrls, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.RestrictedCountries, mo => mo.Ignore()) .ForMember(dest => dest.ExcludedCustomerRoleIds, mo => mo.Ignore()) diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Shipping/ShippingMethodModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Shipping/ShippingMethodModel.cs index 4a8e380e73..d51b1aef24 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Shipping/ShippingMethodModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Shipping/ShippingMethodModel.cs @@ -15,8 +15,11 @@ public class ShippingMethodModel : EntityModelBase, ILocalizedModel(); + FilterConfigurationUrls = new List(); } + public IList FilterConfigurationUrls { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Shipping.Methods.Fields.Name")] [AllowHtml] public string Name { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shipping/_CreateOrUpdateMethod.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shipping/_CreateOrUpdateMethod.cshtml index 3639eef59c..fa6e0fa7e8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shipping/_CreateOrUpdateMethod.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shipping/_CreateOrUpdateMethod.cshtml @@ -125,5 +125,26 @@ + +
    } + + \ No newline at end of file diff --git a/src/Tests/SmartStore.Services.Tests/Orders/OrderProcessingServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Orders/OrderProcessingServiceTests.cs index ba2ee83ac7..afab03b6fc 100644 --- a/src/Tests/SmartStore.Services.Tests/Orders/OrderProcessingServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Orders/OrderProcessingServiceTests.cs @@ -17,6 +17,7 @@ using SmartStore.Core.Domain.Stores; using SmartStore.Core.Domain.Tax; using SmartStore.Core.Events; +using SmartStore.Core.Infrastructure; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Affiliates; @@ -91,8 +92,8 @@ public class OrderProcessingServiceTests : ServiceTest ICommonServices _services; HttpRequestBase _httpRequestBase; IGeoCountryLookup _geoCountryLookup; - Store _store; + ITypeFinder _typeFinder; [SetUp] public new void SetUp() @@ -120,6 +121,7 @@ public class OrderProcessingServiceTests : ServiceTest _localizationService = MockRepository.GenerateMock(); _settingService = MockRepository.GenerateMock(); + _typeFinder = MockRepository.GenerateMock(); //shipping _shippingSettings = new ShippingSettings(); @@ -127,6 +129,7 @@ public class OrderProcessingServiceTests : ServiceTest _shippingSettings.ActiveShippingRateComputationMethodSystemNames.Add("FixedRateTestShippingRateComputationMethod"); _shippingMethodRepository = MockRepository.GenerateMock>(); _logger = new NullLogger(); + _shippingService = new ShippingService(cacheManager, _shippingMethodRepository, _logger, @@ -138,7 +141,9 @@ public class OrderProcessingServiceTests : ServiceTest _shippingSettings, pluginFinder, _eventPublisher, _shoppingCartSettings, _settingService, - this.ProviderManager); + this.ProviderManager, + _typeFinder); + _shipmentService = MockRepository.GenerateMock(); _paymentService = MockRepository.GenerateMock(); diff --git a/src/Tests/SmartStore.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs index ffab0f5e54..77eb79f431 100644 --- a/src/Tests/SmartStore.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs @@ -15,6 +15,7 @@ using SmartStore.Core.Domain.Stores; using SmartStore.Core.Domain.Tax; using SmartStore.Core.Events; +using SmartStore.Core.Infrastructure; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Catalog; @@ -66,6 +67,7 @@ public class OrderTotalCalculationServiceTests : ServiceTest HttpRequestBase _httpRequestBase; IGeoCountryLookup _geoCountryLookup; Store _store; + ITypeFinder _typeFinder; [SetUp] public new void SetUp() @@ -94,6 +96,7 @@ public class OrderTotalCalculationServiceTests : ServiceTest _localizationService = MockRepository.GenerateMock(); _settingService = MockRepository.GenerateMock(); + _typeFinder = MockRepository.GenerateMock(); //shipping _shippingSettings = new ShippingSettings(); @@ -101,6 +104,7 @@ public class OrderTotalCalculationServiceTests : ServiceTest _shippingSettings.ActiveShippingRateComputationMethodSystemNames.Add("FixedRateTestShippingRateComputationMethod"); _shippingMethodRepository = MockRepository.GenerateMock>(); _logger = new NullLogger(); + _shippingService = new ShippingService(cacheManager, _shippingMethodRepository, _logger, @@ -112,7 +116,8 @@ public class OrderTotalCalculationServiceTests : ServiceTest _shippingSettings, pluginFinder, _eventPublisher, _shoppingCartSettings, _settingService, - this.ProviderManager); + this.ProviderManager, + _typeFinder); _providerManager = MockRepository.GenerateMock(); _checkoutAttributeParser = MockRepository.GenerateMock(); diff --git a/src/Tests/SmartStore.Services.Tests/Shipping/ShippingServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Shipping/ShippingServiceTests.cs index 7ffc605216..6f817238ec 100644 --- a/src/Tests/SmartStore.Services.Tests/Shipping/ShippingServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Shipping/ShippingServiceTests.cs @@ -18,6 +18,7 @@ using Rhino.Mocks; using SmartStore.Services.Common; using SmartStore.Services.Configuration; +using SmartStore.Core.Infrastructure; namespace SmartStore.Services.Tests.Shipping { @@ -36,6 +37,7 @@ public class ShippingServiceTests : ServiceTest IShippingService _shippingService; ShoppingCartSettings _shoppingCartSettings; ISettingService _settingService; + ITypeFinder _typeFinder; [SetUp] public new void SetUp() @@ -60,6 +62,7 @@ public class ShippingServiceTests : ServiceTest _localizationService = MockRepository.GenerateMock(); _genericAttributeService = MockRepository.GenerateMock(); _settingService = MockRepository.GenerateMock(); + _typeFinder = MockRepository.GenerateMock(); _shoppingCartSettings = new ShoppingCartSettings(); _shippingService = new ShippingService(cacheManager, @@ -73,7 +76,8 @@ public class ShippingServiceTests : ServiceTest _shippingSettings, pluginFinder, _eventPublisher, _shoppingCartSettings, _settingService, - this.ProviderManager); + this.ProviderManager, + _typeFinder); } [Test] From 9306e66eef12976d5386ef503a0018f7c935cde6 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 2 Nov 2015 13:16:04 +0100 Subject: [PATCH 158/874] Updated changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index e8364c5e2c..0244d80d1c 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,7 @@ * #141 Payment and shipping methods by customer roles * #67 Restrict payment methods to countries * #94 Restrict payment methods to shipping methods +* #747 Restrict payment methods by old versus new customer (plugin) * #584 Email attachment support for message templates * Attach order invoice PDF automatically to order notification emails * #526 Min/Max amount option for which the payment method should be offered during checkout From 94fab62cffeb62a6c7fe7841318280bb04d42e13 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 2 Nov 2015 15:33:36 +0100 Subject: [PATCH 159/874] Resolves #802 Saving customer looses Customer Roles when validation fails --- .../Controllers/CustomerController.cs | 124 +++++++++++------- .../Views/Customer/_CreateOrUpdate.cshtml | 2 +- 2 files changed, 80 insertions(+), 46 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs index a609ee76e8..96de99a9d5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs @@ -250,11 +250,10 @@ private void PrepareCustomerModelForCreate(CustomerModel model) foreach (var tzi in _dateTimeHelper.GetSystemTimeZones()) { - model.AvailableTimeZones.Add(new SelectListItem() { Text = tzi.DisplayName, Value = tzi.Id, Selected = (tzi.Id == timeZoneId) }); + model.AvailableTimeZones.Add(new SelectListItem { Text = tzi.DisplayName, Value = tzi.Id, Selected = (tzi.Id == timeZoneId) }); } model.DisplayVatNumber = false; - //customer roles model.AvailableCustomerRoles = _customerService .GetAllCustomerRoles(true) .Select(cr => cr.ToModel()) @@ -282,11 +281,11 @@ private void PrepareCustomerModelForCreate(CustomerModel model) if (_customerSettings.CountryEnabled) { - model.AvailableCountries.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); + model.AvailableCountries.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); foreach (var c in _countryService.GetAllCountries()) { - model.AvailableCountries.Add(new SelectListItem() { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == model.CountryId) }); + model.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), Selected = (c.Id == model.CountryId) }); } if (_customerSettings.StateProvinceEnabled) @@ -297,12 +296,12 @@ private void PrepareCustomerModelForCreate(CustomerModel model) { foreach (var s in states) { - model.AvailableStates.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.StateProvinceId) }); + model.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.StateProvinceId) }); } } else { - model.AvailableStates.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); + model.AvailableStates.Add(new SelectListItem { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); } } } @@ -585,7 +584,6 @@ public ActionResult Edit(int id) var customer = _customerService.GetCustomerById(id); if (customer == null || customer.Deleted) - //No customer found with the specified id return RedirectToAction("List"); var model = new CustomerModel(); @@ -599,12 +597,18 @@ public ActionResult Edit(int id) model.UsernamesEnabled = _customerSettings.UsernamesEnabled; model.AllowUsersToChangeUsernames = _customerSettings.AllowUsersToChangeUsernames; model.AllowCustomersToSetTimeZone = _dateTimeSettings.AllowCustomersToSetTimeZone; - foreach (var tzi in _dateTimeHelper.GetSystemTimeZones()) - model.AvailableTimeZones.Add(new SelectListItem() { Text = tzi.DisplayName, Value = tzi.Id, Selected = (tzi.Id == model.TimeZoneId) }); + + foreach (var tzi in _dateTimeHelper.GetSystemTimeZones()) + { + model.AvailableTimeZones.Add(new SelectListItem { Text = tzi.DisplayName, Value = tzi.Id, Selected = (tzi.Id == model.TimeZoneId) }); + } + model.DisplayVatNumber = _taxSettings.EuVatEnabled; model.VatNumber = customer.GetAttribute(SystemCustomerAttributeNames.VatNumber); + model.VatNumberStatusNote = ((VatNumberStatus)customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId)) .GetLocalizedEnum(_localizationService, _workContext); + model.CreatedOn = _dateTimeHelper.ConvertToUserTime(customer.CreatedOnUtc, DateTimeKind.Utc); model.LastActivityDate = _dateTimeHelper.ConvertToUserTime(customer.LastActivityDateUtc, DateTimeKind.Utc); model.LastIpAddress = customer.LastIpAddress; @@ -650,10 +654,10 @@ public ActionResult Edit(int id) //countries and states if (_customerSettings.CountryEnabled) { - model.AvailableCountries.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); + model.AvailableCountries.Add(new SelectListItem { Text = T("Admin.Address.SelectCountry"), Value = "0" }); foreach (var c in _countryService.GetAllCountries()) { - model.AvailableCountries.Add(new SelectListItem() + model.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), @@ -665,14 +669,17 @@ public ActionResult Edit(int id) { //states var states = _stateProvinceService.GetStateProvincesByCountryId(model.CountryId).ToList(); - if (states.Count > 0) - { - foreach (var s in states) - model.AvailableStates.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.StateProvinceId) }); - } - else - model.AvailableStates.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); - + if (states.Count > 0) + { + foreach (var s in states) + { + model.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.StateProvinceId) }); + } + } + else + { + model.AvailableStates.Add(new SelectListItem { Text = T("Admin.Address.OtherNonUS"), Value = "0" }); + } } } @@ -681,12 +688,14 @@ public ActionResult Edit(int id) .GetAllCustomerRoles(true) .Select(cr => cr.ToModel()) .ToList(); + model.SelectedCustomerRoleIds = customer.CustomerRoles.Select(cr => cr.Id).ToArray(); model.AllowManagingCustomerRoles = _permissionService.Authorize(StandardPermissionProvider.ManageCustomerRoles); + //reward points gistory model.DisplayRewardPointsHistory = _rewardPointsSettings.Enabled; model.AddRewardPointsValue = 0; - model.AddRewardPointsMessage = "Some comment here..."; + //external authentication records model.AssociatedExternalAuthRecords = GetAssociatedExternalAuthRecords(customer); @@ -703,19 +712,26 @@ public ActionResult Edit(CustomerModel model, bool continueEditing, FormCollecti var customer = _customerService.GetCustomerById(model.Id); if (customer == null || customer.Deleted) - //No customer found with the specified id return RedirectToAction("List"); //validate customer roles - var allCustomerRoles = _customerService.GetAllCustomerRoles(true); - var newCustomerRoles = new List(); - foreach (var customerRole in allCustomerRoles) - if (model.SelectedCustomerRoleIds != null && model.SelectedCustomerRoleIds.Contains(customerRole.Id)) - newCustomerRoles.Add(customerRole); - var customerRolesError = ValidateCustomerRoles(newCustomerRoles); - if (!String.IsNullOrEmpty(customerRolesError)) - ModelState.AddModelError("", customerRolesError); - bool allowManagingCustomerRoles = _permissionService.Authorize(StandardPermissionProvider.ManageCustomerRoles); + var allCustomerRoles = _customerService.GetAllCustomerRoles(true); + var allowManagingCustomerRoles = _permissionService.Authorize(StandardPermissionProvider.ManageCustomerRoles); + + if (allowManagingCustomerRoles) + { + var newCustomerRoles = new List(); + + foreach (var customerRole in allCustomerRoles) + { + if (model.SelectedCustomerRoleIds != null && model.SelectedCustomerRoleIds.Contains(customerRole.Id)) + newCustomerRoles.Add(customerRole); + } + + var customerRolesError = ValidateCustomerRoles(newCustomerRoles); + if (customerRolesError.HasValue()) + ModelState.AddModelError("", customerRolesError); + } if (ModelState.IsValid) { @@ -724,6 +740,7 @@ public ActionResult Edit(CustomerModel model, bool continueEditing, FormCollecti customer.AdminComment = model.AdminComment; customer.IsTaxExempt = model.IsTaxExempt; customer.Active = model.Active; + //email if (!String.IsNullOrWhiteSpace(model.Email)) { @@ -778,8 +795,10 @@ public ActionResult Edit(CustomerModel model, bool continueEditing, FormCollecti _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.TimeZoneId, model.TimeZoneId); if (_customerSettings.GenderEnabled) _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Gender, model.Gender); + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.FirstName, model.FirstName); _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.LastName, model.LastName); + if (_customerSettings.DateOfBirthEnabled) _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.DateOfBirth, model.DateOfBirth); if (_customerSettings.CompanyEnabled) @@ -844,6 +863,7 @@ public ActionResult Edit(CustomerModel model, bool continueEditing, FormCollecti _customerActivityService.InsertActivity("EditCustomer", _localizationService.GetResource("ActivityLog.EditCustomer"), customer.Id); NotifySuccess(_localizationService.GetResource("Admin.Customers.Customers.Updated")); + return continueEditing ? RedirectToAction("Edit", customer.Id) : RedirectToAction("List"); } catch (Exception exc) @@ -857,15 +877,21 @@ public ActionResult Edit(CustomerModel model, bool continueEditing, FormCollecti model.UsernamesEnabled = _customerSettings.UsernamesEnabled; model.AllowUsersToChangeUsernames = _customerSettings.AllowUsersToChangeUsernames; model.AllowCustomersToSetTimeZone = _dateTimeSettings.AllowCustomersToSetTimeZone; - foreach (var tzi in _dateTimeHelper.GetSystemTimeZones()) - model.AvailableTimeZones.Add(new SelectListItem() { Text = tzi.DisplayName, Value = tzi.Id, Selected = (tzi.Id == model.TimeZoneId) }); + + foreach (var tzi in _dateTimeHelper.GetSystemTimeZones()) + { + model.AvailableTimeZones.Add(new SelectListItem { Text = tzi.DisplayName, Value = tzi.Id, Selected = (tzi.Id == model.TimeZoneId) }); + } + model.DisplayVatNumber = _taxSettings.EuVatEnabled; model.VatNumberStatusNote = ((VatNumberStatus)customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId)) .GetLocalizedEnum(_localizationService, _workContext); + model.CreatedOn = _dateTimeHelper.ConvertToUserTime(customer.CreatedOnUtc, DateTimeKind.Utc); model.LastActivityDate = _dateTimeHelper.ConvertToUserTime(customer.LastActivityDateUtc, DateTimeKind.Utc); model.LastIpAddress = model.LastIpAddress; model.LastVisitedPage = customer.GetAttribute(SystemCustomerAttributeNames.LastVisitedPage); + //form fields model.GenderEnabled = _customerSettings.GenderEnabled; model.DateOfBirthEnabled = _customerSettings.DateOfBirthEnabled; @@ -879,13 +905,14 @@ public ActionResult Edit(CustomerModel model, bool continueEditing, FormCollecti model.StateProvinceEnabled = _customerSettings.StateProvinceEnabled; model.PhoneEnabled = _customerSettings.PhoneEnabled; model.FaxEnabled = _customerSettings.FaxEnabled; + //countries and states if (_customerSettings.CountryEnabled) { - model.AvailableCountries.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.SelectCountry"), Value = "0" }); + model.AvailableCountries.Add(new SelectListItem { Text = T("Admin.Address.SelectCountry"), Value = "0" }); foreach (var c in _countryService.GetAllCountries()) { - model.AvailableCountries.Add(new SelectListItem() + model.AvailableCountries.Add(new SelectListItem { Text = c.Name, Value = c.Id.ToString(), @@ -897,28 +924,35 @@ public ActionResult Edit(CustomerModel model, bool continueEditing, FormCollecti { //states var states = _stateProvinceService.GetStateProvincesByCountryId(model.CountryId).ToList(); - if (states.Count > 0) - { - foreach (var s in states) - model.AvailableStates.Add(new SelectListItem() { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.StateProvinceId) }); - } - else - model.AvailableStates.Add(new SelectListItem() { Text = _localizationService.GetResource("Admin.Address.OtherNonUS"), Value = "0" }); - + if (states.Count > 0) + { + foreach (var s in states) + { + model.AvailableStates.Add(new SelectListItem { Text = s.Name, Value = s.Id.ToString(), Selected = (s.Id == model.StateProvinceId) }); + } + } + else + { + model.AvailableStates.Add(new SelectListItem { Text = T("Admin.Address.OtherNonUS"), Value = "0" }); + } } } + //customer roles model.AvailableCustomerRoles = _customerService .GetAllCustomerRoles(true) .Select(cr => cr.ToModel()) .ToList(); + model.AllowManagingCustomerRoles = allowManagingCustomerRoles; - //reward points gistory + + //reward points gistory model.DisplayRewardPointsHistory = _rewardPointsSettings.Enabled; model.AddRewardPointsValue = 0; - model.AddRewardPointsMessage = "Some comment here..."; - //external authentication records + + //external authentication records model.AssociatedExternalAuthRecords = GetAssociatedExternalAuthRecords(customer); + return View(model); } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml index ab629aa3a4..b77a71517c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml @@ -516,7 +516,7 @@ @Html.SmartLabelFor(model => model.AddRewardPointsMessage) - @Html.EditorFor(model => model.AddRewardPointsMessage) + @Html.TextAreaFor(model => model.AddRewardPointsMessage) @Html.ValidationMessageFor(model => model.AddRewardPointsMessage) From 2833c586bd199c3a11f9781b1cea355bddb97242 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 2 Nov 2015 17:08:19 +0100 Subject: [PATCH 160/874] Minor change --- .../Controllers/DevToolsController.cs | 50 ++++++------------- 1 file changed, 15 insertions(+), 35 deletions(-) diff --git a/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs b/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs index 20e212472d..d83af505bb 100644 --- a/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs +++ b/src/Plugins/SmartStore.DevTools/Controllers/DevToolsController.cs @@ -1,13 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.Mvc; -using SmartStore.Core; +using System.Web.Mvc; using SmartStore.DevTools.Models; using SmartStore.Services; -using SmartStore.Services.Configuration; -using SmartStore.Services.Stores; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Settings; @@ -16,40 +9,27 @@ namespace SmartStore.DevTools.Controllers public class DevToolsController : SmartController { - private readonly IWorkContext _workContext; - private readonly IStoreContext _storeContext; - private readonly IStoreService _storeService; - private readonly ISettingService _settingService; - - public DevToolsController( - IWorkContext workContext, - IStoreContext storeContext, - IStoreService storeService, - ISettingService settingService) + private readonly ICommonServices _services; + + public DevToolsController(ICommonServices services) { - _workContext = workContext; - _storeContext = storeContext; - _storeService = storeService; - _settingService = settingService; + _services = services; } - [AdminAuthorize] - [ChildActionOnly] + [AdminAuthorize, ChildActionOnly] public ActionResult Configure() { // load settings for a chosen store scope - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var settings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var settings = _services.Settings.LoadSetting(storeScope); var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - storeDependingSettingHelper.GetOverrideKeys(settings, settings, storeScope, _settingService); + storeDependingSettingHelper.GetOverrideKeys(settings, settings, storeScope, _services.Settings); return View(settings); } - [AdminAuthorize] - [HttpPost] - [ChildActionOnly] + [HttpPost, AdminAuthorize, ChildActionOnly] public ActionResult Configure(ProfilerSettings model, FormCollection form) { if (!ModelState.IsValid) @@ -59,10 +39,10 @@ public ActionResult Configure(ProfilerSettings model, FormCollection form) // load settings for a chosen store scope var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); - storeDependingSettingHelper.UpdateSettings(model /*settings*/, form, storeScope, _settingService); - _settingService.ClearCache(); + storeDependingSettingHelper.UpdateSettings(model /*settings*/, form, storeScope, _services.Settings); + _services.Settings.ClearCache(); return Configure(); } @@ -74,8 +54,8 @@ public ActionResult MiniProfiler() public ActionResult WidgetZone(string widgetZone) { - var storeScope = this.GetActiveStoreScopeConfiguration(_storeService, _workContext); - var settings = _settingService.LoadSetting(storeScope); + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var settings = _services.Settings.LoadSetting(storeScope); if (settings.DisplayWidgetZones) { From 652661481a51d22ed83f49a3ef491b9a51dbd919 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 3 Nov 2015 11:31:23 +0100 Subject: [PATCH 161/874] Minor changes --- .../Catalog/PriceCalculationService.cs | 2 +- .../Views/Common/SeNames.cshtml | 25 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs index 1ad7adb761..dcb4b35d5f 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs @@ -744,7 +744,7 @@ public virtual decimal GetUnitPrice(OrganizedShoppingCartItem shoppingCartItem, { foreach (var bundleItem in shoppingCartItem.ChildItems) { - bundleItem.Item.Product.MergeWithCombination(bundleItem.Item.AttributesXml); + bundleItem.Item.Product.MergeWithCombination(bundleItem.Item.AttributesXml, _productAttributeParser); } var bundleItems = shoppingCartItem.ChildItems.Where(x => x.BundleItemData.IsValid()).Select(x => x.BundleItemData).ToList(); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Common/SeNames.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Common/SeNames.cshtml index d703ee96ac..9d4219f6dd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Common/SeNames.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Common/SeNames.cshtml @@ -3,7 +3,6 @@ @{ var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - //page title ViewBag.Title = T("Admin.System.SeNames").Text; } @using (Html.BeginForm()) @@ -17,6 +16,7 @@ +
    @@ -37,6 +37,7 @@
    + @foreach (var cr in Model.AvailableCustomerRoles) @@ -66,5 +73,4 @@
    @@ -51,16 +52,22 @@ .Width(50) .HtmlAttributes(new { style = "text-align:center" }) .HeaderHtmlAttributes(new { style = "text-align:center" }); - columns.Bound(x => x.Id).Width(50); + columns.Bound(x => x.Id) + .Width(100) + .Centered(); columns.Bound(x => x.Name); - columns.Bound(x => x.EntityId).Width(50); - columns.Bound(x => x.EntityName).Width(200); + columns.Bound(x => x.EntityId) + .Width(140) + .Centered(); + columns.Bound(x => x.EntityName) + .Width(200); columns.Bound(x => x.IsActive) - .Template(item => @Html.SymbolForBool(item.IsActive)) - .ClientTemplate(@Html.SymbolForBool("IsActive")) - .Centered() - .Width(100); - columns.Bound(x => x.Language).Width(200); + .Template(item => @Html.SymbolForBool(item.IsActive)) + .ClientTemplate(@Html.SymbolForBool("IsActive")) + .Width(100) + .Centered(); + columns.Bound(x => x.Language) + .Width(200); }) .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("SeNames", "Common")) From 436460c32ffd307d42976283d24d26aa7ed75936 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 3 Nov 2015 14:50:45 +0100 Subject: [PATCH 162/874] Resolves #722 System > SEO Names: Implement editing of an UrlRecord --- changelog.md | 1 + .../201509150931528_ExportFramework2.cs | 37 +++ .../Seo/IUrlRecordService.cs | 13 +- .../Seo/UrlRecordService.cs | 32 ++- .../Controllers/CommonController.cs | 79 ------- .../Controllers/UrlRecordController.cs | 207 +++++++++++++++++ .../UrlRecordListModel.cs | 10 +- .../{Common => UrlRecord}/UrlRecordModel.cs | 11 +- .../Administration/SmartStore.Admin.csproj | 11 +- .../Views/Common/SeNames.cshtml | 185 --------------- .../Views/UrlRecord/Edit.cshtml | 21 ++ .../Views/UrlRecord/List.cshtml | 216 ++++++++++++++++++ .../Views/UrlRecord/_CreateOrUpdate.cshtml | 61 +++++ .../Administration/sitemap.config | 2 +- 14 files changed, 604 insertions(+), 282 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs rename src/Presentation/SmartStore.Web/Administration/Models/{Common => UrlRecord}/UrlRecordListModel.cs (52%) rename src/Presentation/SmartStore.Web/Administration/Models/{Common => UrlRecord}/UrlRecordModel.cs (68%) delete mode 100644 src/Presentation/SmartStore.Web/Administration/Views/Common/SeNames.cshtml create mode 100644 src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/Edit.cshtml create mode 100644 src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml create mode 100644 src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml diff --git a/changelog.md b/changelog.md index 0244d80d1c..fb272219ce 100644 --- a/changelog.md +++ b/changelog.md @@ -25,6 +25,7 @@ * New payment provider for Offline Payment Plugin: Purchase Order Number * #202 Implement option for product list 'default sort order' * #360 Import & export product variant combinations +* #722 System > SEO Names: Implement editing of an UrlRecord ### Improvements * (Perf) Implemented static caches for URL aliases and localized properties. Increases app startup and request speed by up to 30%. diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index afbc9f0d63..311a491a0e 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -39,6 +39,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) { builder.AddOrUpdate("Common.Example", "Example", "Beispiel"); builder.AddOrUpdate("Admin.Common.Selected", "Selected", "Ausgewhlte"); + builder.AddOrUpdate("Admin.Common.Entity", "Entity", "Entitt"); + builder.AddOrUpdate("Admin.Common.FilesDeleted", "{0} files were deleted", @@ -56,6 +58,41 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Edited payment method '{0}' ({1})", "Zahlungsart '{0}' ({1}) bearbeitet"); + + builder.AddOrUpdate("Admin.System.SeNames", "SEO Names", "SEO Namen"); + builder.Delete("Admin.System.SeNames.DeleteSelected"); + + builder.AddOrUpdate("Admin.System.SeNames.Name", + "SEO Name", + "SEO Name", + "Specifies the SEO name.", + "Legt den SEO Namen fest."); + + builder.AddOrUpdate("Admin.System.SeNames.EntityId", + "Entity ID", + "ID der Entitt", + "Specifies the ID of the associated entity.", + "Legt die ID der zugehrigen Entitt fest."); + + builder.AddOrUpdate("Admin.System.SeNames.EntityName", + "Entity name", + "Name der Entitt", + "Specifies the name of the associated entity.", + "Legt den Namen der zugehrigen Entitt fest."); + + builder.AddOrUpdate("Admin.System.SeNames.IsActive", + "Is active", + "Ist aktiv", + "Specifies whether the SEO name is active or inactive.", + "Legt fest, ob der SEO Name aktiv oder inaktiv ist."); + + builder.AddOrUpdate("Admin.System.SeNames.Language", + "Language", + "Sprache", + "Specifies the language of the SEO name.", + "Legt die Sprache des SEO Namens fest."); + + builder.AddOrUpdate("Admin.DataExchange.Export.NotPreviewCompatible", "This option is not taken into account in the preview.", "Diese Option wird in der Vorschau nicht bercksichtigt."); diff --git a/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs index 3f3451c12d..0981e33384 100644 --- a/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs @@ -24,6 +24,13 @@ public partial interface IUrlRecordService /// URL record UrlRecord GetUrlRecordById(int urlRecordId); + /// + /// Gets URL records by identifiers + /// + /// + /// List of URL records + IList GetUrlRecordsByIds(int[] urlRecordIds); + /// /// Inserts an URL record /// @@ -46,11 +53,13 @@ public partial interface IUrlRecordService /// /// Gets all URL records /// - /// Slug /// Page index /// Page size + /// Slug + /// Entity name + /// Whether to load only active records /// Customer collection - IPagedList GetAllUrlRecords(string slug, int pageIndex, int pageSize); + IPagedList GetAllUrlRecords(int pageIndex, int pageSize, string slug, string entityName, bool? isActive); /// /// Gets all URL records for the specified entity diff --git a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs index dc5950affa..9c360bb699 100644 --- a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs @@ -63,6 +63,18 @@ public virtual UrlRecord GetUrlRecordById(int urlRecordId) return urlRecord; } + public virtual IList GetUrlRecordsByIds(int[] urlRecordIds) + { + if (urlRecordIds == null || urlRecordIds.Length == 0) + return new List(); + + var urlRecords = _urlRecordRepository + .Where(x => urlRecordIds.Contains(x.Id)) + .ToList(); + + return urlRecords; + } + public virtual void InsertUrlRecord(UrlRecord urlRecord) { if (urlRecord == null) @@ -85,15 +97,23 @@ public virtual void UpdateUrlRecord(UrlRecord urlRecord) _cacheManager.RemoveByPattern(URLRECORD_PATTERN_KEY); } - public virtual IPagedList GetAllUrlRecords(string slug, int pageIndex, int pageSize) + public virtual IPagedList GetAllUrlRecords(int pageIndex, int pageSize, string slug, string entityName, bool? isActive) { var query = _urlRecordRepository.Table; - if (!String.IsNullOrWhiteSpace(slug)) - query = query.Where(ur => ur.Slug.Contains(slug)); - query = query.OrderBy(ur => ur.Slug); - var urlRecords = new PagedList(query, pageIndex, pageSize); - return urlRecords; + if (slug.HasValue()) + query = query.Where(x => x.Slug.Contains(slug)); + + if (entityName.HasValue()) + query = query.Where(x => x.EntityName == entityName); + + if (isActive.HasValue) + query = query.Where(x => x.IsActive == isActive.Value); + + query = query.OrderBy(x => x.Slug); + + var urlRecords = new PagedList(query, pageIndex, pageSize); + return urlRecords; } public virtual IList GetUrlRecordsFor(string entityName, int entityId, bool activeOnly = false) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs index 9c4c7b4424..5c9c8e233b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs @@ -21,7 +21,6 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Security; -using SmartStore.Core.Domain.Seo; using SmartStore.Core.Infrastructure; using SmartStore.Core.Logging; using SmartStore.Core.Packaging; @@ -35,7 +34,6 @@ using SmartStore.Services.Media; using SmartStore.Services.Payments; using SmartStore.Services.Security; -using SmartStore.Services.Seo; using SmartStore.Services.Shipping; using SmartStore.Utilities; using SmartStore.Web.Framework; @@ -56,7 +54,6 @@ public class CommonController : AdminControllerBase private readonly Lazy _currencyService; private readonly Lazy _measureService; private readonly ICustomerService _customerService; - private readonly IUrlRecordService _urlRecordService; private readonly Lazy _commonSettings; private readonly Lazy _currencySettings; private readonly Lazy _measureSettings; @@ -83,7 +80,6 @@ public CommonController( Lazy currencyService, Lazy measureService, ICustomerService customerService, - IUrlRecordService urlRecordService, Lazy commonSettings, Lazy currencySettings, Lazy measureSettings, @@ -103,7 +99,6 @@ public CommonController( this._currencyService = currencyService; this._measureService = measureService; this._customerService = customerService; - this._urlRecordService = urlRecordService; this._commonSettings = commonSettings; this._currencySettings = currencySettings; this._measureSettings = measureSettings; @@ -981,80 +976,6 @@ public ActionResult RestartApplication(string previousUrl) #endregion - #region Search engine friendly names - - public ActionResult SeNames() - { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) - return AccessDeniedView(); - - var model = new UrlRecordListModel(); - return View(model); - } - - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult SeNames(GridCommand command, UrlRecordListModel model) - { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) - return AccessDeniedView(); - - var urlRecords = _urlRecordService.GetAllUrlRecords(model.SeName, command.Page - 1, command.PageSize); - var gridModel = new GridModel - { - Data = urlRecords.Select(x => - { - string languageName; - if (x.LanguageId == 0) - { - languageName = _localizationService.GetResource("Admin.System.SeNames.Language.Standard"); - } - else - { - var language = _languageService.GetLanguageById(x.LanguageId); - languageName = language != null ? language.Name : "Unknown"; - } - return new UrlRecordModel() - { - Id = x.Id, - Name = x.Slug, - EntityId = x.EntityId, - EntityName = x.EntityName, - IsActive = x.IsActive, - Language = languageName, - }; - }), - Total = urlRecords.TotalCount - }; - return new JsonResult - { - Data = gridModel - }; - } - - [HttpPost] - public ActionResult DeleteSelectedSeNames(ICollection selectedIds) - { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) - return AccessDeniedView(); - - if (selectedIds != null) - { - var urlRecords = new List(); - foreach (var id in selectedIds) - { - var urlRecord = _urlRecordService.GetUrlRecordById(id); - if (urlRecord != null) - urlRecords.Add(urlRecord); - } - foreach (var urlRecord in urlRecords) - _urlRecordService.DeleteUrlRecord(urlRecord); - } - - return Json(new { Result = true }); - } - - #endregion - #region Generic Attributes [ChildActionOnly] diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs new file mode 100644 index 0000000000..de577163fd --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using SmartStore.Admin.Models.UrlRecord; +using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Seo; +using SmartStore.Services; +using SmartStore.Services.Localization; +using SmartStore.Services.Security; +using SmartStore.Services.Seo; +using SmartStore.Web.Framework.Controllers; +using Telerik.Web.Mvc; + +namespace SmartStore.Admin.Controllers +{ + // TODO: add new permisssion record "ManageUrlRecords" + [AdminAuthorize] + public class UrlRecordController : AdminControllerBase + { + private readonly IUrlRecordService _urlRecordService; + private readonly AdminAreaSettings _adminAreaSettings; + private readonly ICommonServices _services; + private readonly ILanguageService _languageService; + + public UrlRecordController( + IUrlRecordService urlRecordService, + AdminAreaSettings adminAreaSettings, + ICommonServices services, + ILanguageService languageService) + { + _urlRecordService = urlRecordService; + _adminAreaSettings = adminAreaSettings; + _services = services; + _languageService = languageService; + } + + private void PrepareUrlRecordModel(UrlRecordModel model, UrlRecord urlRecord) + { + var allLanguages = _languageService.GetAllLanguages(true); + + model.AvailableLanguages = allLanguages + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + model.AvailableLanguages.Insert(0, new SelectListItem { Text = T("Admin.System.SeNames.Language.Standard"), Value = "0" }); + + if (urlRecord != null) + { + model.Id = urlRecord.Id; + model.Slug = urlRecord.Slug; + model.EntityId = urlRecord.EntityId; + model.EntityName = urlRecord.EntityName; + model.IsActive = urlRecord.IsActive; + model.LanguageId = urlRecord.LanguageId; + } + } + + public ActionResult List() + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) + return AccessDeniedView(); + + var model = new UrlRecordListModel + { + GridPageSize = _adminAreaSettings.GridPageSize + }; + + return View(model); + } + + [HttpPost, GridAction(EnableCustomBinding = true)] + public ActionResult List(GridCommand command, UrlRecordListModel model) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) + return AccessDeniedView(); + + var allLanguages = _languageService.GetAllLanguages(true); + var defaultLanguageName = T("Admin.System.SeNames.Language.Standard"); + + var urlRecords = _urlRecordService.GetAllUrlRecords(command.Page - 1, command.PageSize, model.SeName, model.EntityName, model.IsActive); + + var gridModel = new GridModel + { + Data = urlRecords.Select(x => + { + string languageName; + + if (x.LanguageId == 0) + { + languageName = defaultLanguageName; + } + else + { + var language = allLanguages.FirstOrDefault(y => y.Id == x.LanguageId); + languageName = (language != null ? language.Name : "".NaIfEmpty()); + } + + return new UrlRecordModel + { + Id = x.Id, + Slug = x.Slug, + EntityId = x.EntityId, + EntityName = x.EntityName, + IsActive = x.IsActive, + Language = languageName, + }; + }), + Total = urlRecords.TotalCount + }; + + return new JsonResult + { + Data = gridModel + }; + } + + public ActionResult Edit(int id) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) + return AccessDeniedView(); + + var urlRecord = _urlRecordService.GetUrlRecordById(id); + if (urlRecord == null) + return RedirectToAction("List"); + + var model = new UrlRecordModel(); + PrepareUrlRecordModel(model, urlRecord); + + return View(model); + } + + [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + public ActionResult Edit(UrlRecordModel model, bool continueEditing) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) + return AccessDeniedView(); + + var urlRecord = _urlRecordService.GetUrlRecordById(model.Id); + if (urlRecord == null) + return RedirectToAction("List"); + + if (ModelState.IsValid) + { + urlRecord.Slug = model.Slug; + urlRecord.EntityId = model.EntityId; + urlRecord.EntityName = model.EntityName; + urlRecord.IsActive = model.IsActive; + urlRecord.LanguageId = model.LanguageId; + + _urlRecordService.UpdateUrlRecord(urlRecord); + + NotifySuccess(T("Admin.Common.DataEditSuccess")); + + return continueEditing ? RedirectToAction("Edit", new { id = urlRecord.Id }) : RedirectToAction("List"); + } + + PrepareUrlRecordModel(model, null); + + return View(model); + } + + [HttpPost, ActionName("Delete")] + public ActionResult DeleteConfirmed(int id) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) + return AccessDeniedView(); + + var urlRecord = _urlRecordService.GetUrlRecordById(id); + if (urlRecord == null) + return RedirectToAction("List"); + + try + { + _urlRecordService.DeleteUrlRecord(urlRecord); + + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + + return RedirectToAction("List"); + } + catch (Exception exc) + { + NotifyError(exc); + return RedirectToAction("Edit", new { id = urlRecord.Id }); + } + } + + [HttpPost] + public ActionResult DeleteSelected(ICollection selectedIds) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) + return AccessDeniedView(); + + if (selectedIds != null) + { + var urlRecords = _urlRecordService.GetUrlRecordsByIds(selectedIds.ToArray()); + + foreach (var urlRecord in urlRecords) + { + _urlRecordService.DeleteUrlRecord(urlRecord); + } + } + + return Json(new { Result = true }); + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Common/UrlRecordListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordListModel.cs similarity index 52% rename from src/Presentation/SmartStore.Web/Administration/Models/Common/UrlRecordListModel.cs rename to src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordListModel.cs index 0a26749535..3b07866768 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Common/UrlRecordListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordListModel.cs @@ -2,12 +2,20 @@ using SmartStore.Web.Framework; using SmartStore.Web.Framework.Mvc; -namespace SmartStore.Admin.Models.Common +namespace SmartStore.Admin.Models.UrlRecord { public partial class UrlRecordListModel : ModelBase { + public int GridPageSize { get; set; } + [SmartResourceDisplayName("Admin.System.SeNames.Name")] [AllowHtml] public string SeName { get; set; } + + [SmartResourceDisplayName("Admin.Common.Entity")] + public string EntityName { get; set; } + + [SmartResourceDisplayName("Common.IsActive")] + public bool? IsActive { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Common/UrlRecordModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs similarity index 68% rename from src/Presentation/SmartStore.Web/Administration/Models/Common/UrlRecordModel.cs rename to src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs index 6a5a5fb235..b3db7e2089 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Common/UrlRecordModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs @@ -1,14 +1,15 @@ -using System.Web.Mvc; +using System.Collections.Generic; +using System.Web.Mvc; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Mvc; -namespace SmartStore.Admin.Models.Common +namespace SmartStore.Admin.Models.UrlRecord { public partial class UrlRecordModel : EntityModelBase { [SmartResourceDisplayName("Admin.System.SeNames.Name")] [AllowHtml] - public string Name { get; set; } + public string Slug { get; set; } [SmartResourceDisplayName("Admin.System.SeNames.EntityId")] public int EntityId { get; set; } @@ -19,6 +20,10 @@ public partial class UrlRecordModel : EntityModelBase [SmartResourceDisplayName("Admin.System.SeNames.IsActive")] public bool IsActive { get; set; } + [SmartResourceDisplayName("Admin.System.SeNames.Language")] + public int LanguageId { get; set; } + public List AvailableLanguages { get; set; } + [SmartResourceDisplayName("Admin.System.SeNames.Language")] public string Language { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index 0c37de1799..564a1aa36f 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -218,6 +218,7 @@ + @@ -254,8 +255,6 @@ - - @@ -271,6 +270,8 @@ + + @@ -534,6 +535,9 @@ + + + @@ -1161,9 +1165,6 @@ - - - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Common/SeNames.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Common/SeNames.cshtml deleted file mode 100644 index 9d4219f6dd..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Common/SeNames.cshtml +++ /dev/null @@ -1,185 +0,0 @@ -@model UrlRecordListModel -@using Telerik.Web.Mvc.UI -@{ - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - - ViewBag.Title = T("Admin.System.SeNames").Text; -} -@using (Html.BeginForm()) -{ -
    -
    - - @T("Admin.System.SeNames") -
    -
    - -
    -
    - - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SeName) - - @Html.EditorFor(model => Model.SeName) -
    -   - - -
    - - - - - -
    - @(Html.Telerik().Grid() - .Name("sename-grid") - .ClientEvents(events => events.OnDataBinding("onDataBinding").OnDataBound("onDataBound")) - .Columns(columns => - { - columns.Bound(x => x.Id) - .ClientTemplate("") - .Title("") - .Width(50) - .HtmlAttributes(new { style = "text-align:center" }) - .HeaderHtmlAttributes(new { style = "text-align:center" }); - columns.Bound(x => x.Id) - .Width(100) - .Centered(); - columns.Bound(x => x.Name); - columns.Bound(x => x.EntityId) - .Width(140) - .Centered(); - columns.Bound(x => x.EntityName) - .Width(200); - columns.Bound(x => x.IsActive) - .Template(item => @Html.SymbolForBool(item.IsActive)) - .ClientTemplate(@Html.SymbolForBool("IsActive")) - .Width(100) - .Centered(); - columns.Bound(x => x.Language) - .Width(200); - }) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("SeNames", "Common")) - .EnableCustomBinding(true)) -
    - - -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/Edit.cshtml new file mode 100644 index 0000000000..ea6dfc9cdf --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/Edit.cshtml @@ -0,0 +1,21 @@ +@model UrlRecordModel +@using SmartStore.Admin.Models.UrlRecord; +@{ + var title = string.Concat(T("Admin.Common.Edit"), " ", T("Admin.System.SeNames.Name")); + ViewBag.Title = title; +} +@using (Html.BeginForm()) +{ +
    +
    + @title - @Model.Slug @Html.ActionLink("(" + T("Admin.Common.BackToList") + ")", "List") +
    +
    + + + +
    +
    + @Html.Partial("_CreateOrUpdate", Model) +} +@Html.DeleteConfirmation("urlrecord-delete") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml new file mode 100644 index 0000000000..8df5062147 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml @@ -0,0 +1,216 @@ +@model UrlRecordListModel +@using SmartStore.Admin.Models.UrlRecord; +@using Telerik.Web.Mvc.UI +@{ + ViewBag.Title = T("Admin.System.SeNames").Text; +} + +@using (Html.BeginForm()) +{ +
    +
    + + @T("Admin.System.SeNames") +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.SeName) + + @Html.EditorFor(model => Model.SeName) +
    + @Html.SmartLabelFor(model => model.EntityName) + + @Html.EditorFor(model => Model.EntityName) +
    + @Html.SmartLabelFor(model => model.IsActive) + + @Html.EditorFor(model => Model.IsActive) +
    +   + + +
    + +

    + + + + + +
    + @(Html.Telerik().Grid() + .Name("urlrecord-grid") + .Columns(columns => + { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .Width(50) + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); + columns.Bound(x => x.Id) + .Width(100) + .Centered(); + columns.Bound(x => x.Slug) + .ClientTemplate("<#= Slug #>"); + columns.Bound(x => x.EntityId) + .Width(140) + .Centered(); + columns.Bound(x => x.EntityName) + .Width(200); + columns.Bound(x => x.IsActive) + .Template(item => @Html.SymbolForBool(item.IsActive)) + .ClientTemplate(@Html.SymbolForBool("IsActive")) + .Width(100) + .Centered(); + columns.Bound(x => x.Language) + .Width(200); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "UrlRecord")) + .ClientEvents(events => events.OnDataBinding("onDataBinding").OnDataBound("onDataBound").OnRowDataBound("onRowDataBound")) + .EnableCustomBinding(true)) +
    +} + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml new file mode 100644 index 0000000000..58798845b4 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml @@ -0,0 +1,61 @@ +@model UrlRecordModel +@using SmartStore.Admin.Models.UrlRecord; +@using Telerik.Web.Mvc.UI; + +@Html.HiddenFor(model => model.Id) + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.Id) + + @Html.DisplayFor(model => model.Id) +
    + @Html.SmartLabelFor(model => model.IsActive) + + @Html.EditorFor(model => model.IsActive) + @Html.ValidationMessageFor(model => model.IsActive) +
    + @Html.SmartLabelFor(model => model.Slug) + + @Html.EditorFor(model => model.Slug) + @Html.ValidationMessageFor(model => model.Slug) +
    + @Html.SmartLabelFor(model => model.EntityId) + + @Html.EditorFor(model => model.EntityId) + @Html.ValidationMessageFor(model => model.EntityId) +
    + @Html.SmartLabelFor(model => model.EntityName) + + @Html.EditorFor(model => model.EntityName) + @Html.ValidationMessageFor(model => model.EntityName) +
    + @Html.SmartLabelFor(x => x.LanguageId) + + @Html.DropDownListFor(x => x.LanguageId, Model.AvailableLanguages) + @Html.ValidationMessageFor(x => x.LanguageId) +
    diff --git a/src/Presentation/SmartStore.Web/Administration/sitemap.config b/src/Presentation/SmartStore.Web/Administration/sitemap.config index 6a43d810d8..573d1227b0 100644 --- a/src/Presentation/SmartStore.Web/Administration/sitemap.config +++ b/src/Presentation/SmartStore.Web/Administration/sitemap.config @@ -138,7 +138,7 @@ - + From ecdf5ee3cb4c1b00b4b40dc93ee5c0efdd8ddece Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 3 Nov 2015 17:57:44 +0100 Subject: [PATCH 163/874] Minor changes --- .../Views/Order/AddShipment.cshtml | 124 ++++++++---------- .../Administration/Views/Order/Edit.cshtml | 14 +- .../Views/Order/ShipmentDetails.cshtml | 95 +++++++------- 3 files changed, 109 insertions(+), 124 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/AddShipment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/AddShipment.cshtml index 9003cade34..0deedb6ebd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/AddShipment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/AddShipment.cshtml @@ -2,7 +2,6 @@ @using SmartStore.Core.Domain.Tax; @using SmartStore.Core.Domain.Catalog; @{ - //page title ViewBag.Title = string.Format(T("Admin.Orders.Shipments.AddNew.Title").Text, Model.OrderId); } @using (Html.BeginForm()) @@ -15,8 +14,9 @@ - + @Html.ValidationSummary(false) +
    @@ -27,78 +27,70 @@
    + - +
    - @T("Admin.Orders.Shipments.Products") +

    @T("Admin.Orders.Shipments.Products")

    + -
    + + + + + + + + + + + + @foreach (var item in Model.Items) + { + + + + + + + + } + +
    + @T("Admin.Orders.Shipments.Products.ProductName") + + @T("Admin.Orders.Shipments.Products.SKU") + + @T("Admin.Orders.Shipments.Products.QtyOrdered") + + @T("Admin.Orders.Shipments.Products.QtyShipped") + + @T("Admin.Orders.Shipments.Products.QtyToShip") +
    +
    + @Html.LabeledProductName(item.ProductId, item.ProductName, item.ProductTypeName, item.ProductTypeLabelHint) + @if (!String.IsNullOrEmpty(item.AttributeInfo)) + { +
    + @Html.Raw(item.AttributeInfo) + } +
    +
    +
    + @item.Sku +
    +
    + @item.QuantityOrdered + + @item.QuantityInAllShipments + + +
    - - - - - - - - - - - - - - - - - - @foreach (var item in Model.Items) - { - - - - - - - - } - -
    - @T("Admin.Orders.Shipments.Products.ProductName") - - @T("Admin.Orders.Shipments.Products.SKU") - - @T("Admin.Orders.Shipments.Products.QtyOrdered") - - @T("Admin.Orders.Shipments.Products.QtyShipped") - - @T("Admin.Orders.Shipments.Products.QtyToShip") -
    -
    - @Html.LabeledProductName(item.ProductId, item.ProductName, item.ProductTypeName, item.ProductTypeLabelHint) - @if (!String.IsNullOrEmpty(item.AttributeInfo)) - { -
    - @Html.Raw(item.AttributeInfo) - } -
    -
    -
    - @item.Sku -
    -
    - @item.QuantityOrdered - - @item.QuantityInAllShipments - - -
    - -
    -
    } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml index 020e9eaccc..305c73e02f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml @@ -1221,17 +1221,13 @@ }) .Columns(columns => { - columns.Bound(x => x.Id).Width(50); + columns.Bound(x => x.Id) + .Template(x => Html.ActionLink(x.Id.ToString(), "ShipmentDetails", "Order", new { id = x.Id }, new { })) + .ClientTemplate("\"><#= Id #>"); columns.Bound(x => x.TrackingNumber); columns.Bound(x => x.TotalWeight); columns.Bound(x => x.ShippedDate); columns.Bound(x => x.DeliveryDate); - columns.Bound(x => x.Id) - .Template(x => Html.ActionLink(T("Admin.Common.View").Text, "ShipmentDetails", "Order", new { id = x.Id }, new { })) - .ClientTemplate("\">" + T("Admin.Common.View").Text + "") - .Width(50) - .Centered() - .HeaderTemplate("{0}".FormatWith(T("Admin.Common.View").Text)); }) .EnableCustomBinding(true)) @@ -1375,7 +1371,7 @@ - - - + + - + - - + @@ -176,10 +175,10 @@ - - + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Tax.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Tax.cshtml index 150886779f..7f2e872994 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Tax.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Tax.cshtml @@ -1,7 +1,6 @@ @model TaxSettingsModel @using Telerik.Web.Mvc.UI; @{ - //page title ViewBag.Title = T("Admin.Configuration.Settings.Tax").Text; } @using (Html.BeginForm()) @@ -150,11 +149,6 @@ @Html.ValidationMessageFor(model => model.HideTaxInOrderSummary) - - - - - + @@ -197,21 +191,21 @@ @Html.ValidationMessageFor(model => model.TaxBasedOn) - + - - - + @@ -243,8 +237,8 @@ @Html.ValidationMessageFor(model => model.ShippingTaxClassId) - - + @@ -276,8 +270,8 @@ @Html.ValidationMessageFor(model => model.PaymentMethodAdditionalFeeTaxClassId) - - + @@ -336,6 +330,5 @@ @Html.ValidationMessageFor(model => model.VatRequired) -
    + @@ -1410,7 +1406,7 @@ @foreach (var item in Model.Items) { - From cd8fc023bc7596c1c44523607fa249dc101832f4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 30 Nov 2015 11:28:46 +0100 Subject: [PATCH 272/874] FastProperty fails for getting plugin descriptor localized value --- .../SmartStore.Services/Localization/LocalizationExtentions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizationExtentions.cs b/src/Libraries/SmartStore.Services/Localization/LocalizationExtentions.cs index 3fb8b75d82..72f1524895 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizationExtentions.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizationExtentions.cs @@ -321,7 +321,7 @@ public static string GetLocalizedValue(this PluginDescriptor descriptor, ILocali if (String.IsNullOrEmpty(result) && returnDefaultValue) { - var fastProp = FastProperty.GetProperty(descriptor.GetType(), propertyName); + var fastProp = FastProperty.GetProperty(descriptor.GetType(), propertyName, PropertyCachingStrategy.EagerCached); if (fastProp != null) { result = fastProp.GetValue(descriptor) as string; From f2c05ea4fd88b9a1ec41032bae71045a6263c864 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 30 Nov 2015 11:35:48 +0100 Subject: [PATCH 273/874] FastProperty fails for getting unmapped properties through Web API --- .../SmartStore.Web.Framework/WebApi/WebApiEntityController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs index 3f48b374d2..fa41b10058 100644 --- a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs +++ b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs @@ -89,7 +89,7 @@ protected virtual internal HttpResponseMessage UnmappedGetProperty(ODataPath oda propertyName = propertySegment.PropertyName; if (propertyName.HasValue()) - prop = FastProperty.GetProperty(entity.GetType(), propertyName); + prop = FastProperty.GetProperty(entity.GetType(), propertyName, PropertyCachingStrategy.EagerCached); if (prop == null) return UnmappedGetProperty(entity, propertyName ?? ""); From 41dd9648560ff87db683363c91f78eeb98f9dddc Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 30 Nov 2015 12:19:13 +0100 Subject: [PATCH 274/874] FastProperty fails for property fulfillment through Web API Also fixes System.InvalidOperationException "The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects." --- .../DependencyRegistrar.cs | 32 ++++++++++++++++++- .../WebApi/WebApiEntityController.cs | 15 ++++++--- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index 6b287477a3..e8cd2fdfc7 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -657,7 +657,13 @@ protected override void AttachToComponentRegistration(IComponentRegistry compone var repoProperty = FindRepositoryProperty(type, implementingType.GetGenericArguments()[0]); var serviceProperty = FindServiceProperty(type, implementingType.GetGenericArguments()[1]); - if (repoProperty != null || serviceProperty != null) + var countryServiceProperty = type.GetProperty("CountryService", typeof(ICountryService)); + var stateProvinceServiceProperty = type.GetProperty("StateProvinceService", typeof(IStateProvinceService)); + var languageServiceProperty = type.GetProperty("LanguageService", typeof(ILanguageService)); + var currencyServiceProperty = type.GetProperty("CurrencyService", typeof(ICurrencyService)); + + if (repoProperty != null || serviceProperty != null || + countryServiceProperty != null || stateProvinceServiceProperty != null || languageServiceProperty != null || currencyServiceProperty != null) { registration.Activated += (sender, e) => { @@ -672,6 +678,30 @@ protected override void AttachToComponentRegistration(IComponentRegistry compone var service = e.Context.Resolve(serviceProperty.PropertyType); serviceProperty.SetValue(e.Instance, service, null); } + + if (countryServiceProperty != null) + { + var service = e.Context.Resolve(typeof(ICountryService)); + countryServiceProperty.SetValue(e.Instance, service, null); + } + + if (stateProvinceServiceProperty != null) + { + var service = e.Context.Resolve(typeof(IStateProvinceService)); + stateProvinceServiceProperty.SetValue(e.Instance, service, null); + } + + if (languageServiceProperty != null) + { + var service = e.Context.Resolve(typeof(ILanguageService)); + languageServiceProperty.SetValue(e.Instance, service, null); + } + + if (currencyServiceProperty != null) + { + var service = e.Context.Resolve(typeof(ICurrencyService)); + currencyServiceProperty.SetValue(e.Instance, service, null); + } }; } } diff --git a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs index fa41b10058..b789c1785c 100644 --- a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs +++ b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs @@ -165,6 +165,11 @@ public virtual TService Service set; } + public virtual ICountryService CountryService { get; set; } + public virtual IStateProvinceService StateProvinceService { get; set; } + public virtual ILanguageService LanguageService { get; set; } + public virtual ICurrencyService CurrencyService { get; set; } + public override IQueryable Get() { if (!ModelState.IsValid) @@ -430,19 +435,19 @@ protected internal virtual object FulfillPropertyOn(TEntity entity, string prope { if (propertyName.IsCaseInsensitiveEqual("Country")) { - return EngineContext.Current.Resolve().GetCountryByTwoOrThreeLetterIsoCode(queryValue); + return CountryService.GetCountryByTwoOrThreeLetterIsoCode(queryValue); } else if (propertyName.IsCaseInsensitiveEqual("StateProvince")) { - return EngineContext.Current.Resolve().GetStateProvinceByAbbreviation(queryValue); + return StateProvinceService.GetStateProvinceByAbbreviation(queryValue); } else if (propertyName.IsCaseInsensitiveEqual("Language")) { - return EngineContext.Current.Resolve().GetLanguageByCulture(queryValue); + return LanguageService.GetLanguageByCulture(queryValue); } else if (propertyName.IsCaseInsensitiveEqual("Currency")) { - return EngineContext.Current.Resolve().GetCurrencyByCode(queryValue); + return CurrencyService.GetCurrencyByCode(queryValue); } return null; } @@ -466,7 +471,7 @@ protected internal virtual TEntity FulfillPropertiesOn(TEntity entity) if (propertyName.HasValue() && queryValue.HasValue()) { - var prop = FastProperty.GetProperty(entity.GetType(), propertyName); + var prop = FastProperty.GetProperty(entity.GetType(), propertyName, PropertyCachingStrategy.EagerCached); if (prop != null) { var propertyValue = prop.GetValue(entity); From e3ea5d735b6db16ddd87f0c60f34bb08fe01ddc5 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 30 Nov 2015 16:24:03 +0100 Subject: [PATCH 275/874] Hide link to a topic page if it is limited to stores (part 1) --- changelog.md | 1 + .../201509150931528_ExportFramework2.cs | 5 + .../Topics/TopicService.cs | 112 +++++++++--------- .../Models/Topics/TopicModel.cs | 7 +- .../Administration/Views/Topic/List.cshtml | 69 ++++++----- .../Controllers/CommonController.cs | 83 +++++++------ .../Models/Common/FooterModel.cs | 10 +- .../SmartStore.Web/Models/Common/MenuModel.cs | 4 +- .../SmartStore.Web/Views/Common/Footer.cshtml | 49 +++++--- .../SmartStore.Web/Views/Common/Menu.cshtml | 10 +- 10 files changed, 189 insertions(+), 161 deletions(-) diff --git a/changelog.md b/changelog.md index 30d578f594..0d463568f9 100644 --- a/changelog.md +++ b/changelog.md @@ -74,6 +74,7 @@ * #755 Some methods still loading all products in one go * #796 Selected specification in product filter mask is displayed with default language (not localized) * #805 Product filter is reset if 'product sorting' or 'view mode' or 'amount of displayed products per page' is changed +* Hide link to a topic page if it is limited to stores ## SmartStore.NET 2.2.2 diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index d3fe755327..8c5d34109f 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -253,6 +253,11 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Themes.NoConfigurationRequired", "Theme requires no configuration", "Theme bentigt keine Konfiguration"); + + + builder.AddOrUpdate("Tax.LegalInfoFooter2", + "* All prices {0}, plus shipping", + "* Alle Preise {0}, zzgl. Versandkosten"); } } } diff --git a/src/Libraries/SmartStore.Services/Topics/TopicService.cs b/src/Libraries/SmartStore.Services/Topics/TopicService.cs index daaa424a3c..d9ffb1a6af 100644 --- a/src/Libraries/SmartStore.Services/Topics/TopicService.cs +++ b/src/Libraries/SmartStore.Services/Topics/TopicService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Domain.Topics; @@ -13,23 +14,29 @@ namespace SmartStore.Services.Topics /// public partial class TopicService : ITopicService { - #region Fields + private const string TOPICS_ALL_KEY = "SmartStore.topics.all-{0}"; + private const string TOPICS_PATTERN_KEY = "SmartStore.topics."; - private readonly IRepository _topicRepository; + #region Fields + + private readonly IRepository _topicRepository; private readonly IRepository _storeMappingRepository; private readonly IEventPublisher _eventPublisher; + private readonly ICacheManager _cacheManager; - #endregion + #endregion - #region Ctor + #region Ctor - public TopicService(IRepository topicRepository, + public TopicService(IRepository topicRepository, IRepository storeMappingRepository, - IEventPublisher eventPublisher) + IEventPublisher eventPublisher, + ICacheManager cacheManager) { _topicRepository = topicRepository; _storeMappingRepository = storeMappingRepository; _eventPublisher = eventPublisher; + _cacheManager = cacheManager; this.QuerySettings = DbQuerySettings.Default; } @@ -51,8 +58,10 @@ public virtual void DeleteTopic(Topic topic) _topicRepository.Delete(topic); - //event notification - _eventPublisher.EntityDeleted(topic); + _cacheManager.RemoveByPattern(TOPICS_PATTERN_KEY); + + //event notification + _eventPublisher.EntityDeleted(topic); } /// @@ -79,29 +88,13 @@ public virtual Topic GetTopicBySystemName(string systemName, int storeId) if (String.IsNullOrEmpty(systemName)) return null; - var query = _topicRepository.Table; - query = query.Where(t => t.SystemName == systemName); - query = query.OrderBy(t => t.Id); + var allTopics = GetAllTopics(storeId); - //Store mapping - if (storeId > 0 && !QuerySettings.IgnoreMultiStore) - { - query = from t in query - join sm in _storeMappingRepository.Table - on new { c1 = t.Id, c2 = "Topic" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into t_sm - from sm in t_sm.DefaultIfEmpty() - where !t.LimitedToStores || storeId == sm.StoreId - select t; - - //only distinct items (group by ID) - query = from t in query - group t by t.Id into tGroup - orderby tGroup.Key - select tGroup.FirstOrDefault(); - query = query.OrderBy(t => t.Id); - } - - return query.FirstOrDefault(); + var topic = allTopics + .OrderBy(x => x.Id) + .FirstOrDefault(x => x.SystemName.IsCaseInsensitiveEqual(systemName)); + + return topic; } /// @@ -111,28 +104,33 @@ orderby tGroup.Key /// Topics public virtual IList GetAllTopics(int storeId) { - var query = _topicRepository.Table; - query = query.OrderBy(t => t.Priority).ThenBy(t => t.SystemName); - - //Store mapping - if (storeId > 0 && !QuerySettings.IgnoreMultiStore) + var result = _cacheManager.Get(TOPICS_ALL_KEY.FormatInvariant(storeId), () => { - query = from t in query - join sm in _storeMappingRepository.Table - on new { c1 = t.Id, c2 = "Topic" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into t_sm - from sm in t_sm.DefaultIfEmpty() - where !t.LimitedToStores || storeId == sm.StoreId - select t; - - //only distinct items (group by ID) - query = from t in query - group t by t.Id into tGroup - orderby tGroup.Key - select tGroup.FirstOrDefault(); - query = query.OrderBy(t => t.SystemName); - } - - return query.ToList(); + var query = _topicRepository.Table; + + //Store mapping + if (storeId > 0 && !QuerySettings.IgnoreMultiStore) + { + query = from t in query + join sm in _storeMappingRepository.Table + on new { c1 = t.Id, c2 = "Topic" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into t_sm + from sm in t_sm.DefaultIfEmpty() + where !t.LimitedToStores || storeId == sm.StoreId + select t; + + //only distinct items (group by ID) + query = from t in query + group t by t.Id into tGroup + orderby tGroup.Key + select tGroup.FirstOrDefault(); + } + + query = query.OrderBy(t => t.Priority).ThenBy(t => t.SystemName); + + return query.ToList(); + }); + + return result; } /// @@ -146,8 +144,10 @@ public virtual void InsertTopic(Topic topic) _topicRepository.Insert(topic); - //event notification - _eventPublisher.EntityInserted(topic); + _cacheManager.RemoveByPattern(TOPICS_PATTERN_KEY); + + //event notification + _eventPublisher.EntityInserted(topic); } /// @@ -161,8 +161,10 @@ public virtual void UpdateTopic(Topic topic) _topicRepository.Update(topic); - //event notification - _eventPublisher.EntityUpdated(topic); + _cacheManager.RemoveByPattern(TOPICS_PATTERN_KEY); + + //event notification + _eventPublisher.EntityUpdated(topic); } #endregion diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Topics/TopicModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Topics/TopicModel.cs index 43de02db29..516f7d402f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Topics/TopicModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Topics/TopicModel.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using FluentValidation.Attributes; @@ -11,7 +10,7 @@ namespace SmartStore.Admin.Models.Topics { - [Validator(typeof(TopicValidator))] + [Validator(typeof(TopicValidator))] public class TopicModel : TabbableModel, ILocalizedModel { #region widget zone names @@ -47,9 +46,9 @@ public TopicModel() AvailableTitleTags.Add(new SelectListItem { Text = "span", Value = "span" }); } - //Store mapping [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; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml index 00f7b28cee..08de2f50ab 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml @@ -1,7 +1,6 @@ @model TopicListModel @using Telerik.Web.Mvc.UI @{ - //page title ViewBag.Title = T("Admin.ContentManagement.Topics").Text; }
    @@ -16,7 +15,8 @@
    -
    +
    @Html.LabeledProductName(item.ProductId, item.ProductName, item.ProductTypeName, item.ProductTypeLabelHint) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.cshtml index e8a3fea84a..800e47eee5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/ShipmentDetails.cshtml @@ -3,12 +3,10 @@ @using SmartStore.Core.Domain.Tax; @using SmartStore.Core.Domain.Catalog; @{ - //page title ViewBag.Title = T("Admin.Orders.Shipments.ViewDetails").Text; } @using (Html.BeginForm()) { -
    @T("Admin.Orders.Shipments.ViewDetails") - @Model.Id @Html.ActionLink("(" + T("Admin.Orders.Shipments.BackToOrder") + ")", "Edit", new { id = Model.OrderId }) @@ -23,8 +21,10 @@ }
    -
    +
    + @Html.ValidationSummary(false) + + + + + + + + + + + + + + - + @@ -40,7 +41,7 @@ @foreach (var profile in Model) { - + @@ -51,6 +52,9 @@
    @profile.SystemName
    } + -
    @@ -82,54 +82,51 @@ -
    + -
    - - - - - - - - - - - @foreach (var item in Model.Items) - { - - + + @if (!String.IsNullOrEmpty(item.AttributeInfo)) + { +
    + @Html.Raw(item.AttributeInfo) + } + + + + + + } + +
    - @T("Admin.Orders.Shipments.Products.ProductName") - - @T("Admin.Orders.Shipments.Products.SKU") - - @T("Admin.Orders.Shipments.Products.QtyShipped") -
    -
    + + + + + + + + + + @foreach (var item in Model.Items) + { + + - - - - } - -
    + @T("Admin.Orders.Shipments.Products.ProductName") + + @T("Admin.Orders.Shipments.Products.SKU") + + @T("Admin.Orders.Shipments.Products.QtyShipped") +
    +
    @Html.LabeledProductName(item.ProductId, item.ProductName, item.ProductTypeName, item.ProductTypeLabelHint) - - @if (!String.IsNullOrEmpty(item.AttributeInfo)) - { -
    - @Html.Raw(item.AttributeInfo) - } -
    -
    -
    - @item.Sku -
    -
    - @item.QuantityInThisShipment -
    - -
    -
    +
    + @item.Sku +
    +
    + @item.QuantityInThisShipment +
    + +
    } From 00cc912960badb9db6ac7be9ff5a5aaa9079eb92 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 3 Nov 2015 21:12:52 +0100 Subject: [PATCH 164/874] SEO Names: New column with the number of slugs per entity (click on the value filters them) --- .../201509150931528_ExportFramework2.cs | 10 +++++-- .../Seo/IUrlRecordService.cs | 10 ++++++- .../Seo/UrlRecordService.cs | 26 +++++++++++++++- .../Controllers/UrlRecordController.cs | 30 +++++++++++++++---- .../Models/UrlRecord/UrlRecordListModel.cs | 7 +++-- .../Models/UrlRecord/UrlRecordModel.cs | 9 ++++-- .../Views/UrlRecord/List.cshtml | 29 +++++++++++++++--- .../Views/UrlRecord/_CreateOrUpdate.cshtml | 8 +++++ 8 files changed, 110 insertions(+), 19 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index 311a491a0e..6de44115cc 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -75,8 +75,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Legt die ID der zugehrigen Entitt fest."); builder.AddOrUpdate("Admin.System.SeNames.EntityName", - "Entity name", - "Name der Entitt", + "Entity", + "Entitt", "Specifies the name of the associated entity.", "Legt den Namen der zugehrigen Entitt fest."); @@ -92,6 +92,12 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Specifies the language of the SEO name.", "Legt die Sprache des SEO Namens fest."); + builder.AddOrUpdate("Admin.System.SeNames.SlugsPerEntity", + "Names per entity", + "Namen pro Entitt", + "The number of SEO names per entity.", + "Die Anzahl der SEO Namen pro Entitt."); + builder.AddOrUpdate("Admin.DataExchange.Export.NotPreviewCompatible", "This option is not taken into account in the preview.", diff --git a/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs index 0981e33384..c2e14366ff 100644 --- a/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs @@ -57,9 +57,10 @@ public partial interface IUrlRecordService /// Page size /// Slug /// Entity name + /// Entity identifier /// Whether to load only active records /// Customer collection - IPagedList GetAllUrlRecords(int pageIndex, int pageSize, string slug, string entityName, bool? isActive); + IPagedList GetAllUrlRecords(int pageIndex, int pageSize, string slug, string entityName, int? entityId, bool? isActive); /// /// Gets all URL records for the specified entity @@ -99,5 +100,12 @@ public partial interface IUrlRecordService /// Name of a property /// Url record UrlRecord SaveSlug(T entity, Expression> nameProperty) where T : BaseEntity, ISlugSupported; + + /// + /// Get number of slugs per entity + /// + /// URL record identifier + /// Dictionary of slugs per entity count + Dictionary CountSlugsPerEntity(int[] urlRecordIds); } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs index 9c360bb699..78ec3ed5c4 100644 --- a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs @@ -97,7 +97,7 @@ public virtual void UpdateUrlRecord(UrlRecord urlRecord) _cacheManager.RemoveByPattern(URLRECORD_PATTERN_KEY); } - public virtual IPagedList GetAllUrlRecords(int pageIndex, int pageSize, string slug, string entityName, bool? isActive) + public virtual IPagedList GetAllUrlRecords(int pageIndex, int pageSize, string slug, string entityName, int? entityId, bool? isActive) { var query = _urlRecordRepository.Table; @@ -107,6 +107,9 @@ public virtual IPagedList GetAllUrlRecords(int pageIndex, int pageSiz if (entityName.HasValue()) query = query.Where(x => x.EntityName == entityName); + if (entityId.HasValue) + query = query.Where(x => x.EntityId == entityId.Value); + if (isActive.HasValue) query = query.Where(x => x.IsActive == isActive.Value); @@ -324,6 +327,27 @@ private string GenerateKey(int entityId, string entityName, int languageId) return "{0}.{1}.{2}".FormatInvariant(entityId, entityName, languageId); } + public virtual Dictionary CountSlugsPerEntity(int[] urlRecordIds) + { + if (urlRecordIds == null || urlRecordIds.Length == 0) + return new Dictionary(); + + var query = + from x in _urlRecordRepository.TableUntracked + where urlRecordIds.Contains(x.Id) + select new + { + Id = x.Id, + Count = _urlRecordRepository.TableUntracked.Where(y => y.EntityName == x.EntityName && y.EntityId == x.EntityId).Count() + }; + + var result = query + .ToList() + .ToDictionary(x => x.Id, x => x.Count); + + return result; + } + #endregion } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs index de577163fd..309f812304 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs @@ -49,21 +49,34 @@ private void PrepareUrlRecordModel(UrlRecordModel model, UrlRecord urlRecord) { model.Id = urlRecord.Id; model.Slug = urlRecord.Slug; - model.EntityId = urlRecord.EntityId; model.EntityName = urlRecord.EntityName; + model.EntityId = urlRecord.EntityId; model.IsActive = urlRecord.IsActive; model.LanguageId = urlRecord.LanguageId; + + try + { + var slugCount = _urlRecordService.CountSlugsPerEntity(new int[] { urlRecord.Id }); + + model.SlugsPerEntity = (slugCount.ContainsKey(urlRecord.Id) ? slugCount[urlRecord.Id] : 0); + } + catch (Exception exc) + { + NotifyError(exc); + } } } - public ActionResult List() + public ActionResult List(string entityName, int? entityId) { if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) return AccessDeniedView(); var model = new UrlRecordListModel { - GridPageSize = _adminAreaSettings.GridPageSize + GridPageSize = _adminAreaSettings.GridPageSize, + EntityName = entityName, + EntityId = entityId }; return View(model); @@ -78,7 +91,9 @@ public ActionResult List(GridCommand command, UrlRecordListModel model) var allLanguages = _languageService.GetAllLanguages(true); var defaultLanguageName = T("Admin.System.SeNames.Language.Standard"); - var urlRecords = _urlRecordService.GetAllUrlRecords(command.Page - 1, command.PageSize, model.SeName, model.EntityName, model.IsActive); + var urlRecords = _urlRecordService.GetAllUrlRecords(command.Page - 1, command.PageSize, model.SeName, model.EntityName, model.EntityId, model.IsActive); + + var slugsPerEntity = _urlRecordService.CountSlugsPerEntity(urlRecords.Select(x => x.Id).Distinct().ToArray()); var gridModel = new GridModel { @@ -96,15 +111,18 @@ public ActionResult List(GridCommand command, UrlRecordListModel model) languageName = (language != null ? language.Name : "".NaIfEmpty()); } - return new UrlRecordModel + var urlRecordModel = new UrlRecordModel { Id = x.Id, Slug = x.Slug, - EntityId = x.EntityId, EntityName = x.EntityName, + EntityId = x.EntityId, IsActive = x.IsActive, Language = languageName, + SlugsPerEntity = (slugsPerEntity.ContainsKey(x.Id) ? slugsPerEntity[x.Id] : 0) }; + + return urlRecordModel; }), Total = urlRecords.TotalCount }; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordListModel.cs index 3b07866768..6fd819787b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordListModel.cs @@ -12,10 +12,13 @@ public partial class UrlRecordListModel : ModelBase [AllowHtml] public string SeName { get; set; } - [SmartResourceDisplayName("Admin.Common.Entity")] + [SmartResourceDisplayName("Admin.System.SeNames.EntityName")] public string EntityName { get; set; } - [SmartResourceDisplayName("Common.IsActive")] + [SmartResourceDisplayName("Admin.System.SeNames.EntityId")] + public int? EntityId { get; set; } + + [SmartResourceDisplayName("Admin.System.SeNames.IsActive")] public bool? IsActive { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs index b3db7e2089..50569db7d0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs @@ -11,12 +11,12 @@ public partial class UrlRecordModel : EntityModelBase [AllowHtml] public string Slug { get; set; } - [SmartResourceDisplayName("Admin.System.SeNames.EntityId")] - public int EntityId { get; set; } - [SmartResourceDisplayName("Admin.System.SeNames.EntityName")] public string EntityName { get; set; } + [SmartResourceDisplayName("Admin.System.SeNames.EntityId")] + public int EntityId { get; set; } + [SmartResourceDisplayName("Admin.System.SeNames.IsActive")] public bool IsActive { get; set; } @@ -26,5 +26,8 @@ public partial class UrlRecordModel : EntityModelBase [SmartResourceDisplayName("Admin.System.SeNames.Language")] public string Language { get; set; } + + [SmartResourceDisplayName("Admin.System.SeNames.SlugsPerEntity")] + public int SlugsPerEntity { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml index 8df5062147..648f7bfef5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml @@ -36,6 +36,14 @@ @Html.EditorFor(model => Model.EntityName)
    + @Html.SmartLabelFor(model => model.EntityId) + + @Html.EditorFor(model => Model.EntityId) +
    @Html.SmartLabelFor(model => model.IsActive) @@ -76,11 +84,15 @@ .Centered(); columns.Bound(x => x.Slug) .ClientTemplate("<#= Slug #>"); - columns.Bound(x => x.EntityId) - .Width(140) - .Centered(); + columns.Bound(x => x.SlugsPerEntity) + .Centered() + .Width(180) + .ClientTemplate("<#= SlugsPerEntity #>"); columns.Bound(x => x.EntityName) .Width(200); + columns.Bound(x => x.EntityId) + .Width(140) + .Centered(); columns.Bound(x => x.IsActive) .Template(item => @Html.SymbolForBool(item.IsActive)) .ClientTemplate(@Html.SymbolForBool("IsActive")) @@ -191,13 +203,13 @@ var searchModel = { SeName: $('#@Html.FieldIdFor(model => model.SeName)').val(), EntityName: $('#@Html.FieldIdFor(model => model.EntityName)').val(), + EntityId: $('#@Html.FieldIdFor(model => model.EntityId)').val(), IsActive: $('#@Html.FieldIdFor(model => model.IsActive)').val() }; e.data = searchModel; } function onDataBound(e) { - $('#urlrecord-grid input[type=checkbox][id!=mastercheckbox]').each(function () { var currentId = $(this).val(); var checked = jQuery.inArray(currentId, selectedIds); @@ -213,4 +225,13 @@ var numChkBoxesChecked = $('#urlrecord-grid input[type=checkbox][checked][id!=mastercheckbox]').length; $('#mastercheckbox').attr('checked', numChkBoxes == numChkBoxesChecked && numChkBoxes > 0); } + + function filterPerEntity(entityName, entityId) { + $('#@Html.FieldIdFor(model => model.EntityName)').val(entityName); + $('#@Html.FieldIdFor(model => model.EntityId)').val(entityId).blur(); + + var grid = $('#urlrecord-grid').data('tGrid'); + grid.currentPage = 1; + grid.ajaxRequest(); + } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml index 58798845b4..3d147687f9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml @@ -13,6 +13,14 @@ @Html.DisplayFor(model => model.Id)
    + @Html.SmartLabelFor(model => model.SlugsPerEntity) + + @Model.SlugsPerEntity +
    @Html.SmartLabelFor(model => model.IsActive) From f0672cdfe53e938200d6d9e786e7316246571aa9 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 4 Nov 2015 10:59:19 +0100 Subject: [PATCH 165/874] SEO Names: Do not allow more than one active slug per entity and language --- .../201509150931528_ExportFramework2.cs | 4 ++++ .../Seo/IUrlRecordService.cs | 3 ++- .../Seo/UrlRecordService.cs | 5 ++++- .../Controllers/PaymentController.cs | 2 +- .../Controllers/UrlRecordController.cs | 20 ++++++++++++++++++- .../Models/UrlRecord/UrlRecordListModel.cs | 7 ++++++- .../Views/UrlRecord/List.cshtml | 11 +++++++++- .../Views/UrlRecord/_CreateOrUpdate.cshtml | 1 + 8 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index 6de44115cc..cad4436211 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -98,6 +98,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "The number of SEO names per entity.", "Die Anzahl der SEO Namen pro Entitt."); + builder.AddOrUpdate("Admin.System.SeNames.ActiveSlugAlreadyExist", + "Only one active SEO name should be set per language.", + "Pro Sprache darf nur ein aktiver SEO Name festgelegt werden."); + builder.AddOrUpdate("Admin.DataExchange.Export.NotPreviewCompatible", "This option is not taken into account in the preview.", diff --git a/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs index c2e14366ff..16eefa19f8 100644 --- a/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs @@ -59,8 +59,9 @@ public partial interface IUrlRecordService /// Entity name /// Entity identifier /// Whether to load only active records + /// Language identifier /// Customer collection - IPagedList GetAllUrlRecords(int pageIndex, int pageSize, string slug, string entityName, int? entityId, bool? isActive); + IPagedList GetAllUrlRecords(int pageIndex, int pageSize, string slug, string entityName, int? entityId, int? languageId, bool? isActive); /// /// Gets all URL records for the specified entity diff --git a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs index 78ec3ed5c4..72c3bdd3fe 100644 --- a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs @@ -97,7 +97,7 @@ public virtual void UpdateUrlRecord(UrlRecord urlRecord) _cacheManager.RemoveByPattern(URLRECORD_PATTERN_KEY); } - public virtual IPagedList GetAllUrlRecords(int pageIndex, int pageSize, string slug, string entityName, int? entityId, bool? isActive) + public virtual IPagedList GetAllUrlRecords(int pageIndex, int pageSize, string slug, string entityName, int? entityId, int? languageId, bool? isActive) { var query = _urlRecordRepository.Table; @@ -113,6 +113,9 @@ public virtual IPagedList GetAllUrlRecords(int pageIndex, int pageSiz if (isActive.HasValue) query = query.Where(x => x.IsActive == isActive.Value); + if (languageId.HasValue) + query = query.Where(x => x.LanguageId == languageId); + query = query.OrderBy(x => x.Slug); var urlRecords = new PagedList(query, pageIndex, pageSize); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PaymentController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PaymentController.cs index e2708847b0..4fba64859a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PaymentController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PaymentController.cs @@ -207,7 +207,7 @@ public ActionResult Edit(string systemName) return View(model); } - [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] + [HttpPost, ValidateInput(false), ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] public ActionResult Edit(string systemName, bool continueEditing, PaymentMethodEditModel model, FormCollection form) { if (!_services.Permissions.Authorize(StandardPermissionProvider.ManagePaymentMethods)) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs index 309f812304..10bdf9a841 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs @@ -79,6 +79,14 @@ public ActionResult List(string entityName, int? entityId) EntityId = entityId }; + var allLanguages = _languageService.GetAllLanguages(true); + + model.AvailableLanguages = allLanguages + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + model.AvailableLanguages.Insert(0, new SelectListItem { Text = T("Admin.System.SeNames.Language.Standard"), Value = "0" }); + return View(model); } @@ -91,7 +99,8 @@ public ActionResult List(GridCommand command, UrlRecordListModel model) var allLanguages = _languageService.GetAllLanguages(true); var defaultLanguageName = T("Admin.System.SeNames.Language.Standard"); - var urlRecords = _urlRecordService.GetAllUrlRecords(command.Page - 1, command.PageSize, model.SeName, model.EntityName, model.EntityId, model.IsActive); + var urlRecords = _urlRecordService.GetAllUrlRecords(command.Page - 1, command.PageSize, + model.SeName, model.EntityName, model.EntityId, model.LanguageId, model.IsActive); var slugsPerEntity = _urlRecordService.CountSlugsPerEntity(urlRecords.Select(x => x.Id).Distinct().ToArray()); @@ -158,6 +167,15 @@ public ActionResult Edit(UrlRecordModel model, bool continueEditing) if (urlRecord == null) return RedirectToAction("List"); + if (!urlRecord.IsActive && model.IsActive) + { + var urlRecords = _urlRecordService.GetAllUrlRecords(0, int.MaxValue, null, model.EntityName, model.EntityId, model.LanguageId, true); + if (urlRecords.Count > 0) + { + ModelState.AddModelError("", T("Admin.System.SeNames.ActiveSlugAlreadyExist")); + } + } + if (ModelState.IsValid) { urlRecord.Slug = model.Slug; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordListModel.cs index 6fd819787b..414f8f32eb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordListModel.cs @@ -1,4 +1,5 @@ -using System.Web.Mvc; +using System.Collections.Generic; +using System.Web.Mvc; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Mvc; @@ -20,5 +21,9 @@ public partial class UrlRecordListModel : ModelBase [SmartResourceDisplayName("Admin.System.SeNames.IsActive")] public bool? IsActive { get; set; } + + [SmartResourceDisplayName("Admin.System.SeNames.Language")] + public int? LanguageId { get; set; } + public List AvailableLanguages { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml index 648f7bfef5..196aedd999 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml @@ -52,6 +52,14 @@ @Html.EditorFor(model => Model.IsActive)
    + @Html.SmartLabelFor(x => x.LanguageId) + + @Html.DropDownListFor(x => x.LanguageId, Model.AvailableLanguages, T("Common.Unspecified")) +
      @@ -204,7 +212,8 @@ SeName: $('#@Html.FieldIdFor(model => model.SeName)').val(), EntityName: $('#@Html.FieldIdFor(model => model.EntityName)').val(), EntityId: $('#@Html.FieldIdFor(model => model.EntityId)').val(), - IsActive: $('#@Html.FieldIdFor(model => model.IsActive)').val() + IsActive: $('#@Html.FieldIdFor(model => model.IsActive)').val(), + LanguageId: $('#@Html.FieldIdFor(model => model.LanguageId)').val() }; e.data = searchModel; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml index 3d147687f9..24bf8ab910 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml @@ -2,6 +2,7 @@ @using SmartStore.Admin.Models.UrlRecord; @using Telerik.Web.Mvc.UI; +@Html.ValidationSummary(true) @Html.HiddenFor(model => model.Id) From 339bd3221512e0b7d7f1921cf68e5d7fe676991e Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 4 Nov 2015 12:14:54 +0100 Subject: [PATCH 166/874] SEO Names: Link to filter all slugs per entity on entity's edit page --- .../Migrations/201509150931528_ExportFramework2.cs | 1 + .../SmartStore.Services/Seo/IUrlRecordService.cs | 8 ++++++++ .../SmartStore.Services/Seo/UrlRecordService.cs | 9 +++++++++ .../Controllers/UrlRecordController.cs | 14 ++++++++++++++ .../Administration/SmartStore.Admin.csproj | 1 + .../Views/Blog/_CreateOrUpdate.cshtml | 4 ++++ .../Views/Category/_CreateOrUpdate.cshtml | 4 ++++ .../Views/Forum/_CreateOrUpdateForum.cshtml | 4 ++++ .../Views/Forum/_CreateOrUpdateForumGroup.cshtml | 4 ++++ .../Views/Manufacturer/_CreateOrUpdate.cshtml | 4 ++++ .../Views/News/_CreateOrUpdate.cshtml | 4 ++++ .../Views/Product/_CreateOrUpdate.SEO.cshtml | 4 ++++ .../Views/UrlRecord/NamesPerEntity.cshtml | 6 ++++++ 13 files changed, 67 insertions(+) create mode 100644 src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/NamesPerEntity.cshtml diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index cad4436211..374cf79ba1 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -38,6 +38,7 @@ public void Seed(SmartObjectContext context) public void MigrateLocaleResources(LocaleResourcesBuilder builder) { builder.AddOrUpdate("Common.Example", "Example", "Beispiel"); + builder.AddOrUpdate("Common.ShowAll", "Show all", "Alle anzeigen"); builder.AddOrUpdate("Admin.Common.Selected", "Selected", "Ausgewhlte"); builder.AddOrUpdate("Admin.Common.Entity", "Entity", "Entitt"); diff --git a/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs index 16eefa19f8..d3bfa5a04d 100644 --- a/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/IUrlRecordService.cs @@ -108,5 +108,13 @@ public partial interface IUrlRecordService /// URL record identifier /// Dictionary of slugs per entity count Dictionary CountSlugsPerEntity(int[] urlRecordIds); + + /// + /// Get number of slugs per entity + /// + /// Entity name + /// Entity identifier + /// Number of slugs per entity + int CountSlugsPerEntity(string entityName, int entityId); } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs index 72c3bdd3fe..337cf5bd98 100644 --- a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs @@ -351,6 +351,15 @@ where urlRecordIds.Contains(x.Id) return result; } + public virtual int CountSlugsPerEntity(string entityName, int entityId) + { + var count = _urlRecordRepository.Table + .Where(x => x.EntityName == entityName && x.EntityId == entityId) + .Count(); + + return count; + } + #endregion } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs index 10bdf9a841..2bf6480ff0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs @@ -239,5 +239,19 @@ public ActionResult DeleteSelected(ICollection selectedIds) return Json(new { Result = true }); } + + [ChildActionOnly] + public ActionResult NamesPerEntity(string entityName, int entityId) + { + if (entityName.IsEmpty() || entityId == 0) + return new EmptyResult(); + + var count = _urlRecordService.CountSlugsPerEntity(entityName, entityId); + + ViewBag.CountSlugsPerEntity = count; + ViewBag.UrlRecordListUrl = Url.Action("List", "UrlRecord", new { entityName = entityName, entityId = entityId }); + + return View(); + } } } \ 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 564a1aa36f..9b3e27db4d 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -538,6 +538,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Blog/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Blog/_CreateOrUpdate.cshtml index 71d80f25b4..effce8793b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Blog/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Blog/_CreateOrUpdate.cshtml @@ -126,6 +126,10 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml index 6899d373c1..9f98f0d5a0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml @@ -294,6 +294,10 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForum.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForum.cshtml index 2d27a88242..08b4cea0c7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForum.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForum.cshtml @@ -56,6 +56,10 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForumGroup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForumGroup.cshtml index 611d9cef72..8aaf21cf5e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForumGroup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Forum/_CreateOrUpdateForumGroup.cshtml @@ -68,6 +68,10 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/_CreateOrUpdate.cshtml index 25db45dfe1..44385835cb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/_CreateOrUpdate.cshtml @@ -215,6 +215,10 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/News/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/News/_CreateOrUpdate.cshtml index ad61050588..84562a94b1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/News/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/News/_CreateOrUpdate.cshtml @@ -128,6 +128,10 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SEO.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SEO.cshtml index 329d12360b..985ff1f3bb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SEO.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SEO.cshtml @@ -79,6 +79,10 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/NamesPerEntity.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/NamesPerEntity.cshtml new file mode 100644 index 0000000000..14e9654c01 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/NamesPerEntity.cshtml @@ -0,0 +1,6 @@ +@{ + Layout = null; +} + +  @T("Common.ShowAll") (@ViewBag.CountSlugsPerEntity) + \ No newline at end of file From 9278a0cefb33bc6ac264424d2e6db0291bce7b51 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 4 Nov 2015 14:05:17 +0100 Subject: [PATCH 167/874] SEO Names: Link from URL record edit page to entity's edit page --- changelog.md | 1 + .../Controllers/UrlRecordController.cs | 53 +++++++++++-------- .../Models/UrlRecord/UrlRecordModel.cs | 3 ++ .../Views/UrlRecord/Edit.cshtml | 3 ++ .../Views/UrlRecord/List.cshtml | 3 +- .../Views/UrlRecord/_CreateOrUpdate.cshtml | 21 +++----- 6 files changed, 46 insertions(+), 38 deletions(-) diff --git a/changelog.md b/changelog.md index fb272219ce..7a5df6a93e 100644 --- a/changelog.md +++ b/changelog.md @@ -55,6 +55,7 @@ * #767 Remove assignments to a grouped product if the grouped product is deleted * #773 Reduce number of guest records created by search engine requests * #791 Preselected attributes or attribute combinations should always be appended as querystring to product page links +* Simplified managing of SEO names ### Bugfixes * #523 Redirecting to payment provider performed by core instead of plugin diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs index 2bf6480ff0..3d82fa456d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs @@ -35,15 +35,18 @@ public UrlRecordController( _languageService = languageService; } - private void PrepareUrlRecordModel(UrlRecordModel model, UrlRecord urlRecord) + private void PrepareUrlRecordModel(UrlRecordModel model, UrlRecord urlRecord, bool forList = false) { - var allLanguages = _languageService.GetAllLanguages(true); + if (!forList) + { + var allLanguages = _languageService.GetAllLanguages(true); - model.AvailableLanguages = allLanguages - .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) - .ToList(); + model.AvailableLanguages = allLanguages + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); - model.AvailableLanguages.Insert(0, new SelectListItem { Text = T("Admin.System.SeNames.Language.Standard"), Value = "0" }); + model.AvailableLanguages.Insert(0, new SelectListItem { Text = T("Admin.System.SeNames.Language.Standard"), Value = "0" }); + } if (urlRecord != null) { @@ -54,15 +57,25 @@ private void PrepareUrlRecordModel(UrlRecordModel model, UrlRecord urlRecord) model.IsActive = urlRecord.IsActive; model.LanguageId = urlRecord.LanguageId; - try + if (urlRecord.EntityName.IsCaseInsensitiveEqual("BlogPost")) { - var slugCount = _urlRecordService.CountSlugsPerEntity(new int[] { urlRecord.Id }); - - model.SlugsPerEntity = (slugCount.ContainsKey(urlRecord.Id) ? slugCount[urlRecord.Id] : 0); + model.EntityUrl = Url.Action("Edit", "Blog", new { id = urlRecord.EntityId }); } - catch (Exception exc) + else if (urlRecord.EntityName.IsCaseInsensitiveEqual("Forum")) { - NotifyError(exc); + model.EntityUrl = Url.Action("EditForum", "Forum", new { id = urlRecord.EntityId }); + } + else if (urlRecord.EntityName.IsCaseInsensitiveEqual("ForumGroup")) + { + model.EntityUrl = Url.Action("EditForumGroup", "Forum", new { id = urlRecord.EntityId }); + } + else if (urlRecord.EntityName.IsCaseInsensitiveEqual("NewsItem")) + { + model.EntityUrl = Url.Action("Edit", "News", new { id = urlRecord.EntityId }); + } + else + { + model.EntityUrl = Url.Action("Edit", urlRecord.EntityName, new { id = urlRecord.EntityId }); } } } @@ -120,16 +133,11 @@ public ActionResult List(GridCommand command, UrlRecordListModel model) languageName = (language != null ? language.Name : "".NaIfEmpty()); } - var urlRecordModel = new UrlRecordModel - { - Id = x.Id, - Slug = x.Slug, - EntityName = x.EntityName, - EntityId = x.EntityId, - IsActive = x.IsActive, - Language = languageName, - SlugsPerEntity = (slugsPerEntity.ContainsKey(x.Id) ? slugsPerEntity[x.Id] : 0) - }; + var urlRecordModel = new UrlRecordModel(); + PrepareUrlRecordModel(urlRecordModel, x, true); + + urlRecordModel.Language = languageName; + urlRecordModel.SlugsPerEntity = (slugsPerEntity.ContainsKey(x.Id) ? slugsPerEntity[x.Id] : 0); return urlRecordModel; }), @@ -179,7 +187,6 @@ public ActionResult Edit(UrlRecordModel model, bool continueEditing) if (ModelState.IsValid) { urlRecord.Slug = model.Slug; - urlRecord.EntityId = model.EntityId; urlRecord.EntityName = model.EntityName; urlRecord.IsActive = model.IsActive; urlRecord.LanguageId = model.LanguageId; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs index 50569db7d0..a5996c6bbc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs @@ -17,6 +17,9 @@ public partial class UrlRecordModel : EntityModelBase [SmartResourceDisplayName("Admin.System.SeNames.EntityId")] public int EntityId { get; set; } + [SmartResourceDisplayName("Admin.Common.Entity")] + public string EntityUrl { get; set; } + [SmartResourceDisplayName("Admin.System.SeNames.IsActive")] public bool IsActive { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/Edit.cshtml index ea6dfc9cdf..4914d5c1ea 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/Edit.cshtml @@ -11,6 +11,9 @@ @title - @Model.Slug @Html.ActionLink("(" + T("Admin.Common.BackToList") + ")", "List")
    + +  @T("Admin.Common.Entity") + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml index 196aedd999..fa2a560a7d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/List.cshtml @@ -100,7 +100,8 @@ .Width(200); columns.Bound(x => x.EntityId) .Width(140) - .Centered(); + .Centered() + .ClientTemplate("<#= EntityId #>"); columns.Bound(x => x.IsActive) .Template(item => @Html.SymbolForBool(item.IsActive)) .ClientTemplate(@Html.SymbolForBool("IsActive")) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml index 24bf8ab910..95ad8832ce 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml @@ -14,14 +14,6 @@ @Html.DisplayFor(model => model.Id) -
    - - - From 4df73100cf842f4f52206eabe49f250809742087 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 4 Nov 2015 17:12:17 +0100 Subject: [PATCH 168/874] Export profiles: Minor UI fixes (further changes & fixes follow in a separate branch) --- .../Administration/Content/admin.less | 9 +++++--- .../Administration/Views/Export/Edit.cshtml | 21 ++++++++++--------- .../Views/Export/Preview.cshtml | 4 ++-- src/Presentation/SmartStore.Web/Web.config | 2 +- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index 947ca2b377..620ce8206f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -430,12 +430,12 @@ td.adminData > input[type="checkbox"] { -------------------------------------------------------------- */ .large-select2 { .select2-choice { - height: 52px; - min-height: 52px; + height: 42px; + min-height: 42px; padding-top: 4px; > div > b { - padding-top: 14px; + padding-top: 10px; } } } @@ -444,10 +444,13 @@ td.adminData > input[type="checkbox"] { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + height: 38px; img { margin-right: 12px; vertical-align: middle; + text-align: center; + max-height: 100%; } > div { margin-right: 0px !important; diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index f9174a7b29..55bac26fec 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -82,16 +82,17 @@ @Html.ValidationMessageFor(x => x.Enabled) - - - - + @if (Model.ScheduleTaskId > 0) + { + + + + + }
    @Html.EditorFor(model => model.SeName) + @if (Model.Id != 0) + { + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = "BlogPost", entityId = @Model.Id }) + } @Html.ValidationMessageFor(model => model.SeName)
    @Html.TextAreaFor(x => x.SeName, new { @class = "input-large" }) + @if (Model.Id != 0) + { + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = "Category", entityId = @Model.Id }) + } @Html.ValidationMessageFor(model => model.SeName)
    @Html.EditorFor(x => x.SeName) + @if (Model.Id != 0) + { + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = "Forum", entityId = @Model.Id }) + } @Html.ValidationMessageFor(model => model.SeName)
    @Html.EditorFor(x => x.SeName) + @if (Model.Id != 0) + { + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = "ForumGroup", entityId = @Model.Id }) + } @Html.ValidationMessageFor(model => model.SeName)
    @Html.TextAreaFor(x => x.SeName) + @if (Model.Id != 0) + { + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = "Manufacturer", entityId = @Model.Id }) + } @Html.ValidationMessageFor(model => model.SeName)
    @Html.EditorFor(model => model.SeName) + @if (Model.Id != 0) + { + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = "NewsItem", entityId = @Model.Id }) + } @Html.ValidationMessageFor(model => model.SeName)
    @Html.TextAreaFor(x => x.SeName, new { @class = "input-large" }) + @if (Model.Id != 0) + { + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = "Product", entityId = @Model.Id }) + } @Html.ValidationMessageFor(model => model.SeName)
    - @Html.SmartLabelFor(model => model.SlugsPerEntity) - - @Model.SlugsPerEntity -
    @Html.SmartLabelFor(model => model.IsActive) @@ -37,25 +29,26 @@ @Html.EditorFor(model => model.Slug) + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = Model.EntityName, entityId = @Model.EntityId }) @Html.ValidationMessageFor(model => model.Slug)
    - @Html.SmartLabelFor(model => model.EntityId) + @Html.SmartLabelFor(model => model.EntityName) - @Html.EditorFor(model => model.EntityId) - @Html.ValidationMessageFor(model => model.EntityId) + @Html.EditorFor(model => model.EntityName) + @Html.ValidationMessageFor(model => model.EntityName)
    - @Html.SmartLabelFor(model => model.EntityName) + @Html.SmartLabelFor(model => model.EntityId) - @Html.EditorFor(model => model.EntityName) - @Html.ValidationMessageFor(model => model.EntityName) + @Html.EditorFor(model => model.EntityId) + @Html.ValidationMessageFor(model => model.EntityId)
    - @Html.SmartLabelFor(x => x.ScheduleTaskId) - - @(Model.IsTaskEnabled ? T("Common.Scheduled") : T("Common.Unscheduled")) - - @Model.ScheduleTaskName -
    + @Html.SmartLabelFor(x => x.ScheduleTaskId) + + @Html.Action("MinimalTask", "ScheduleTask", new { taskId = Model.ScheduleTaskId, returnUrl = Request.RawUrl, cancellable = true, area = "admin" }) +

     

    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml index 1c0af5cf0a..b5e527976d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml @@ -22,10 +22,10 @@ {  @T("Admin.Configuration.ActivityLog") } - - diff --git a/src/Presentation/SmartStore.Web/Web.config b/src/Presentation/SmartStore.Web/Web.config index f45ffa195f..4a073d85ad 100644 --- a/src/Presentation/SmartStore.Web/Web.config +++ b/src/Presentation/SmartStore.Web/Web.config @@ -92,7 +92,7 @@ - + From 6b81ecadafd972363bc1d3b200968c9c86eb5520 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 4 Nov 2015 18:03:03 +0100 Subject: [PATCH 169/874] SEO Names: Input validation --- .../Controllers/UrlRecordController.cs | 2 +- .../Models/UrlRecord/UrlRecordModel.cs | 8 ++-- .../Views/UrlRecord/_CreateOrUpdate.cshtml | 4 +- .../MvcLocalization.Designer.cs | 41 +++++++++++-------- .../MvcLocalization.de.resx | 9 ++-- .../App_GlobalResources/MvcLocalization.resx | 9 ++-- 6 files changed, 44 insertions(+), 29 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs index 3d82fa456d..1eb07d10a5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs @@ -180,7 +180,7 @@ public ActionResult Edit(UrlRecordModel model, bool continueEditing) var urlRecords = _urlRecordService.GetAllUrlRecords(0, int.MaxValue, null, model.EntityName, model.EntityId, model.LanguageId, true); if (urlRecords.Count > 0) { - ModelState.AddModelError("", T("Admin.System.SeNames.ActiveSlugAlreadyExist")); + ModelState.AddModelError("IsActive", T("Admin.System.SeNames.ActiveSlugAlreadyExist")); } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs index a5996c6bbc..589c616eac 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/UrlRecord/UrlRecordModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Mvc; @@ -7,14 +8,13 @@ namespace SmartStore.Admin.Models.UrlRecord { public partial class UrlRecordModel : EntityModelBase { - [SmartResourceDisplayName("Admin.System.SeNames.Name")] - [AllowHtml] + [Required, AllowHtml, SmartResourceDisplayName("Admin.System.SeNames.Name")] public string Slug { get; set; } - [SmartResourceDisplayName("Admin.System.SeNames.EntityName")] + [Required, SmartResourceDisplayName("Admin.System.SeNames.EntityName")] public string EntityName { get; set; } - [SmartResourceDisplayName("Admin.System.SeNames.EntityId")] + [Required, SmartResourceDisplayName("Admin.System.SeNames.EntityId")] public int EntityId { get; set; } [SmartResourceDisplayName("Admin.Common.Entity")] diff --git a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml index 95ad8832ce..ba361be39e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/UrlRecord/_CreateOrUpdate.cshtml @@ -2,7 +2,7 @@ @using SmartStore.Admin.Models.UrlRecord; @using Telerik.Web.Mvc.UI; -@Html.ValidationSummary(true) +@Html.ValidationSummary() @Html.HiddenFor(model => model.Id) @@ -29,8 +29,8 @@ diff --git a/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.Designer.cs b/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.Designer.cs index 07cb03a819..1eda45cf26 100644 --- a/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.Designer.cs +++ b/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.Designer.cs @@ -1,10 +1,10 @@ //------------------------------------------------------------------------------ // -// Dieser Code wurde von einem Tool generiert. -// Laufzeitversion:4.0.30319.34209 +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 // -// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn -// der Code erneut generiert wird. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. // //------------------------------------------------------------------------------ @@ -13,12 +13,12 @@ namespace Resources { /// - /// Eine stark typisierte Ressourcenklasse zum Suchen von lokalisierten Zeichenfolgen usw. + /// A strongly-typed resource class, for looking up localized strings, etc. /// - // Diese Klasse wurde von der StronglyTypedResourceBuilder-Klasse - // über ein Tool wie ResGen oder Visual Studio automatisch generiert. - // Um einen Member hinzuzufügen oder zu entfernen, bearbeiten Sie die .ResX-Datei und führen dann ResGen - // mit der /str-Option erneut aus, oder Sie erstellen das Visual Studio-Projekt neu. + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option or rebuild the Visual Studio project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Web.Application.StronglyTypedResourceProxyBuilder", "12.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] @@ -33,7 +33,7 @@ internal MvcLocalization() { } /// - /// Gibt die zwischengespeicherte ResourceManager-Instanz zurück, die von dieser Klasse verwendet wird. + /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { @@ -47,8 +47,8 @@ internal MvcLocalization() { } /// - /// Überschreibt die CurrentUICulture-Eigenschaft des aktuellen Threads für alle - /// Ressourcenlookups, die diese stark typisierte Ressourcenklasse verwenden. + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { @@ -61,7 +61,7 @@ internal MvcLocalization() { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The field {0} must be a date. ähnelt. + /// Looks up a localized string similar to The field '{0}' must be a date.. /// internal static string FieldMustBeDate { get { @@ -70,7 +70,7 @@ internal static string FieldMustBeDate { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The field {0} must be a number. ähnelt. + /// Looks up a localized string similar to The field '{0}' must be a number.. /// internal static string FieldMustBeNumeric { get { @@ -79,7 +79,7 @@ internal static string FieldMustBeNumeric { } /// - /// Sucht eine lokalisierte Zeichenfolge, die The value '{0}' is not valid for {1}. ähnelt. + /// Looks up a localized string similar to The value '{0}' is not valid for '{1}'.. /// internal static string PropertyValueInvalid { get { @@ -88,12 +88,21 @@ internal static string PropertyValueInvalid { } /// - /// Sucht eine lokalisierte Zeichenfolge, die A value is required. ähnelt. + /// Looks up a localized string similar to A value is required.. /// internal static string PropertyValueRequired { get { return ResourceManager.GetString("PropertyValueRequired", resourceCulture); } } + + /// + /// Looks up a localized string similar to The field '{0}' is required.. + /// + internal static string Required { + get { + return ResourceManager.GetString("Required", resourceCulture); + } + } } } diff --git a/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.de.resx b/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.de.resx index e1d0fa764f..fee5893b3b 100644 --- a/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.de.resx +++ b/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.de.resx @@ -118,15 +118,18 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - Das Feld {0} muss ein Datum sein. + Das Feld '{0}' muss ein Datum sein. - Das Feld {0} muss numerisch sein. + Das Feld '{0}' muss numerisch sein. - Der Wert '{0}' für {1} ist ungültig. + Der Wert '{0}' für '{1}' ist ungültig. Ein Wert ist zwingend erforderlich. + + Das Feld '{0}' ist erforderlich. + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.resx b/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.resx index da4a85fa32..0e272fccd2 100644 --- a/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.resx +++ b/src/Presentation/SmartStore.Web/App_GlobalResources/MvcLocalization.resx @@ -118,15 +118,18 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - The field {0} must be a date. + The field '{0}' must be a date. - The field {0} must be a number. + The field '{0}' must be a number. - The value '{0}' is not valid for {1}. + The value '{0}' is not valid for '{1}'. A value is required. + + The field '{0}' is required. + \ No newline at end of file From 4f5247fe220897b012ab5e180ba5e944deceeebe Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 4 Nov 2015 20:50:40 +0100 Subject: [PATCH 170/874] Web-API: Added OData endpoint for shipment items --- .../Domain/Shipping/Shipment.cs | 1 + .../Domain/Shipping/ShipmentItem.cs | 6 ++++ .../Shipping/ShipmentService.cs | 31 ++++++++++++++++-- .../OData/ShipmentItemsController.cs | 32 +++++++++++++++++++ src/Plugins/SmartStore.WebApi/Description.txt | 2 +- .../SmartStore.WebApi.csproj | 1 + .../WebApiConfigurationProvider.cs | 2 ++ src/Plugins/SmartStore.WebApi/changelog.md | 4 +++ 8 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 src/Plugins/SmartStore.WebApi/Controllers/OData/ShipmentItemsController.cs diff --git a/src/Libraries/SmartStore.Core/Domain/Shipping/Shipment.cs b/src/Libraries/SmartStore.Core/Domain/Shipping/Shipment.cs index d0841d1dd6..e189e22b12 100644 --- a/src/Libraries/SmartStore.Core/Domain/Shipping/Shipment.cs +++ b/src/Libraries/SmartStore.Core/Domain/Shipping/Shipment.cs @@ -59,6 +59,7 @@ public partial class Shipment : BaseEntity /// /// Gets or sets the shipment items /// + [DataMember] public virtual ICollection ShipmentItems { get { return _shipmentItems ?? (_shipmentItems = new HashSet()); } diff --git a/src/Libraries/SmartStore.Core/Domain/Shipping/ShipmentItem.cs b/src/Libraries/SmartStore.Core/Domain/Shipping/ShipmentItem.cs index 48db08b9e1..fc77fb18b2 100644 --- a/src/Libraries/SmartStore.Core/Domain/Shipping/ShipmentItem.cs +++ b/src/Libraries/SmartStore.Core/Domain/Shipping/ShipmentItem.cs @@ -1,29 +1,35 @@ +using System.Runtime.Serialization; namespace SmartStore.Core.Domain.Shipping { /// /// Represents a shipment order product variant /// + [DataContract] public partial class ShipmentItem : BaseEntity { /// /// Gets or sets the shipment identifier /// + [DataMember] public int ShipmentId { get; set; } /// /// Gets or sets the order item identifier /// + [DataMember] public int OrderItemId { get; set; } /// /// Gets or sets the quantity /// + [DataMember] public int Quantity { get; set; } /// /// Gets the shipment /// + [DataMember] public virtual Shipment Shipment { get; set; } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Shipping/ShipmentService.cs b/src/Libraries/SmartStore.Services/Shipping/ShipmentService.cs index 301122a7fc..3a8c261164 100644 --- a/src/Libraries/SmartStore.Services/Shipping/ShipmentService.cs +++ b/src/Libraries/SmartStore.Services/Shipping/ShipmentService.cs @@ -7,7 +7,6 @@ using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Events; -using SmartStore.Core.Plugins; using SmartStore.Services.Orders; namespace SmartStore.Services.Shipping @@ -240,7 +239,20 @@ public virtual void InsertShipmentItem(ShipmentItem shipmentItem) //event notifications _eventPublisher.EntityInserted(shipmentItem); - _eventPublisher.PublishOrderUpdated(shipmentItem.Shipment.Order); + + if (shipmentItem.Shipment != null && shipmentItem.Shipment.Order != null) + { + _eventPublisher.PublishOrderUpdated(shipmentItem.Shipment.Order); + } + else + { + var shipment = _shipmentRepository.Table + .Expand(x => x.Order) + .FirstOrDefault(x => x.Id == shipmentItem.ShipmentId); + + if (shipment != null) + _eventPublisher.PublishOrderUpdated(shipment.Order); + } } /// @@ -256,7 +268,20 @@ public virtual void UpdateShipmentItem(ShipmentItem shipmentItem) //event notifications _eventPublisher.EntityUpdated(shipmentItem); - _eventPublisher.PublishOrderUpdated(shipmentItem.Shipment.Order); + + if (shipmentItem.Shipment != null && shipmentItem.Shipment.Order != null) + { + _eventPublisher.PublishOrderUpdated(shipmentItem.Shipment.Order); + } + else + { + var shipment = _shipmentRepository.Table + .Expand(x => x.Order) + .FirstOrDefault(x => x.Id == shipmentItem.ShipmentId); + + if (shipment != null) + _eventPublisher.PublishOrderUpdated(shipment.Order); + } } #endregion diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/ShipmentItemsController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/ShipmentItemsController.cs new file mode 100644 index 0000000000..47fdf9982a --- /dev/null +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/ShipmentItemsController.cs @@ -0,0 +1,32 @@ +using System.Web.Http; +using SmartStore.Core.Domain.Shipping; +using SmartStore.Services.Shipping; +using SmartStore.Web.Framework.WebApi; +using SmartStore.Web.Framework.WebApi.OData; +using SmartStore.Web.Framework.WebApi.Security; + +namespace SmartStore.WebApi.Controllers.OData +{ + [WebApiAuthenticate(Permission = "ManageOrders")] + public class ShipmentItemsController : WebApiEntityController + { + protected override void Insert(ShipmentItem entity) + { + Service.InsertShipmentItem(entity); + } + protected override void Update(ShipmentItem entity) + { + Service.UpdateShipmentItem(entity); + } + protected override void Delete(ShipmentItem entity) + { + Service.DeleteShipmentItem(entity); + } + + [WebApiQueryable] + public SingleResult GetShipmentItem(int key) + { + return GetSingleResult(key); + } + } +} diff --git a/src/Plugins/SmartStore.WebApi/Description.txt b/src/Plugins/SmartStore.WebApi/Description.txt index cbe8fe9af5..085a7c5a09 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.2.0.3 +Version: 2.2.0.4 Group: Api MinAppVersion: 2.2.0 Author: SmartStore AG diff --git a/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj b/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj index feaadf8fcc..20e3fc318c 100644 --- a/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj +++ b/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj @@ -215,6 +215,7 @@ + diff --git a/src/Plugins/SmartStore.WebApi/WebApiConfigurationProvider.cs b/src/Plugins/SmartStore.WebApi/WebApiConfigurationProvider.cs index 40c3a8b75c..db3d1f3377 100644 --- a/src/Plugins/SmartStore.WebApi/WebApiConfigurationProvider.cs +++ b/src/Plugins/SmartStore.WebApi/WebApiConfigurationProvider.cs @@ -92,6 +92,7 @@ public void Configure(WebApiConfigurationBroadcaster configData) m.EntitySet(WebApiOdataEntitySet.ReturnRequests); m.EntitySet(WebApiOdataEntitySet.Settings); m.EntitySet(WebApiOdataEntitySet.Shipments); + m.EntitySet(WebApiOdataEntitySet.ShipmentItems); m.EntitySet(WebApiOdataEntitySet.ShippingMethods); m.EntitySet(WebApiOdataEntitySet.SpecificationAttributeOptions); m.EntitySet(WebApiOdataEntitySet.SpecificationAttributes); @@ -145,6 +146,7 @@ public static class WebApiOdataEntitySet public static string ReturnRequests { get { return "ReturnRequests"; } } public static string Settings { get { return "Settings"; } } public static string Shipments { get { return "Shipments"; } } + public static string ShipmentItems { get { return "ShipmentItems"; } } public static string ShippingMethods { get { return "ShippingMethods"; } } public static string SpecificationAttributeOptions { get { return "SpecificationAttributeOptions"; } } public static string SpecificationAttributes { get { return "SpecificationAttributes"; } } diff --git a/src/Plugins/SmartStore.WebApi/changelog.md b/src/Plugins/SmartStore.WebApi/changelog.md index 6c5bd87808..2191a7d055 100644 --- a/src/Plugins/SmartStore.WebApi/changelog.md +++ b/src/Plugins/SmartStore.WebApi/changelog.md @@ -1,5 +1,9 @@ #Release Notes +##Web Api 2.2.0.4 +###New Features +* Added OData endpoint for shipment items + ##Web Api 2.2.0.3 ###New Features * Added OData endpoint for payment method From fdf1c6349c2b8c8d660fafc51742a841d600d310 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 4 Nov 2015 22:44:13 +0100 Subject: [PATCH 171/874] Export Framework: Refactored enums --- .../{ExportCore.cs => ExportEnums.cs} | 26 ++++----- .../Domain/DataExchange/ExportProfile.cs | 2 +- .../Domain/DataExchange/ExportProjection.cs | 4 +- .../Plugins/Providers/ProviderMetadata.cs | 2 +- .../SmartStore.Core/SmartStore.Core.csproj | 2 +- src/Libraries/SmartStore.Core/XmlHelper.cs | 56 +++++++------------ .../DataExchange/ExportExtensions.cs | 9 +-- ...tributes.cs => ExportFeaturesAttribute.cs} | 8 +-- .../DataExchange/ExportService.cs | 4 +- .../ExportTask/ExportProfileTask.cs | 21 +++---- .../ExportTask/ExportProfileTaskContext.cs | 10 ++-- .../SmartStore.Services.csproj | 2 +- .../Providers/ProductExportXmlProvider.cs | 18 +++--- .../DependencyRegistrar.cs | 12 ++-- .../Models/DataExchange/ExportProfileModel.cs | 2 +- .../custom/telerik/telerik.smartstore.less | 2 +- 16 files changed, 83 insertions(+), 97 deletions(-) rename src/Libraries/SmartStore.Core/Domain/DataExchange/{ExportCore.cs => ExportEnums.cs} (87%) rename src/Libraries/SmartStore.Services/DataExchange/{ExportAttributes.cs => ExportFeaturesAttribute.cs} (68%) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs similarity index 87% rename from src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs rename to src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs index f9a5dcc18e..b7f041fed5 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportCore.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs @@ -61,62 +61,62 @@ public enum ExportAttributeValueMerging /// /// Controls data processing and projection items supported by export provider /// - public enum ExportSupport + public enum ExportFeatures { /// /// Whether to automatically create a file based public deployment when an export profile is created /// - CreateInitialPublicDeployment = 0, + CreatesInitialPublicDeployment = 0, /// /// Whether to offer option to include\exclude grouped products /// - ProjectionNoGroupedProducts, + CanOmitGroupedProducts, /// /// Whether to offer option to export attribute combinations as products /// - ProjectionAttributeCombinationAsProduct, + CanProjectAttributeCombinations, /// /// Whether to offer further options to manipulate the product description /// - ProjectionDescription, + CanProjectDescription, /// /// Whether to offer option to enter a brand fallback /// - ProjectionBrand, + OffersBrandFallback, /// /// Whether to offer option to set a picture size and to get the URL of the main image /// - ProjectionMainPictureUrl, + CanIncludeMainPicture, /// /// Whether to use SKU as manufacturer part number if MPN is empty /// - ProjectionUseOwnProductNo, + UsesSkuAsMpnFallback, /// /// Whether to offer option to enter a shipping time fallback /// - ProjectionShippingTime, + OffersShippingTimeFallback, /// /// Whether to offer option to enter a shipping costs fallback and a free shipping threshold /// - ProjectionShippingCosts, + OffersShippingCostsFallback, /// /// Whether to get the calculated old product price /// - ProjectionOldPrice, + UsesOldPrice, /// - /// Whether to get the calculated special and regular (ignoring special offers) price + /// Whether to get the calculated special and regular price (ignoring special offers) /// - ProjectionSpecialPrice + UsesSpecialPrice } /// diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs index 75331d4a2c..ea98e0838b 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs @@ -88,7 +88,7 @@ public ExportProfile() public bool PerStore { get; set; } /// - /// Identifier of an email account used to send a notification message of the completion of the export + /// Email Account identifier used to send a notification message an export completes /// public int EmailAccountId { get; set; } diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs index abe979107d..472c94e114 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProjection.cs @@ -7,7 +7,9 @@ namespace SmartStore.Core.Domain.DataExchange /// /// Settings projected onto an export /// - /// Note possible projection controlling: a) developer controls, b) merchant controls, c) developer controls what the merchant can control + /// + /// Note possible projection controlling: a) developer controls, b) merchant controls, c) developer controls what the merchant can control + /// [Serializable] public class ExportProjection { diff --git a/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs b/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs index b1da4a1304..31d98942d4 100644 --- a/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs +++ b/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs @@ -76,7 +76,7 @@ public class ProviderMetadata /// /// Gets or sets an array of values that reflects what export data processing is supported by a provider /// - public ExportSupport[] ExportSupport { get; set; } + public ExportFeatures[] ExportSupport { get; set; } /// /// Gets or sets an array of widget system names, which depend on the current provider diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index ab5fd94130..afe41e0061 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -166,7 +166,7 @@ - + diff --git a/src/Libraries/SmartStore.Core/XmlHelper.cs b/src/Libraries/SmartStore.Core/XmlHelper.cs index b73c914f03..6d9f60def4 100644 --- a/src/Libraries/SmartStore.Core/XmlHelper.cs +++ b/src/Libraries/SmartStore.Core/XmlHelper.cs @@ -123,17 +123,7 @@ public static DateTime DeserializeDateTime(string dateTime) /// XML string public static string Serialize(T instance) { - if (instance != null) - { - using (var writer = new StringWriter()) - { - var xmlSerializer = new XmlSerializer(typeof(T)); - - xmlSerializer.Serialize(writer, instance); - return writer.ToString(); - } - } - return null; + return Serialize(instance, typeof(T)); } /// @@ -144,17 +134,18 @@ public static string Serialize(T instance) /// XML string public static string Serialize(object instance, Type type) { - if (instance != null) + if (instance == null) + return null; + + Guard.NotNull(() => type); + + using (var writer = new StringWriter()) { - using (var writer = new StringWriter()) - { - var xmlSerializer = new XmlSerializer(type); + var xmlSerializer = new XmlSerializer(type); - xmlSerializer.Serialize(writer, instance); - return writer.ToString(); - } + xmlSerializer.Serialize(writer, instance); + return writer.ToString(); } - return null; } /// @@ -165,16 +156,7 @@ public static string Serialize(object instance, Type type) /// Object instance public static T Deserialize(string xml) { - if (xml.HasValue()) - { - using (var reader = new StringReader(xml)) - { - var serializer = new XmlSerializer(typeof(T)); - - return (T)serializer.Deserialize(reader); - } - } - return (T)Activator.CreateInstance(typeof(T)); + return (T)Deserialize(xml, typeof(T)); } /// @@ -185,16 +167,16 @@ public static T Deserialize(string xml) /// Object instance public static object Deserialize(string xml, Type type) { - if (xml.HasValue()) - { - using (var reader = new StringReader(xml)) - { - var serializer = new XmlSerializer(type); + Guard.NotNull(() => type); - return serializer.Deserialize(reader); - } + if (xml.IsEmpty()) + return Activator.CreateInstance(type); + + using (var reader = new StringReader(xml)) + { + var serializer = new XmlSerializer(type); + return serializer.Deserialize(reader); } - return Activator.CreateInstance(type); } #endregion diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs index c7f6827bb0..ae5b8ff6ce 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs @@ -21,19 +21,20 @@ public static class ExportExtensions /// true provider is valid, false provider is invalid. public static bool IsValid(this Provider provider) { - return (provider != null); + return provider != null; } /// /// Returns a value indicating whether the export provider supports a projection type /// /// Export provider - /// The type to check + /// The feature to check /// true provider supports type, false provider does not support type. - public static bool Supports(this Provider provider, ExportSupport type) + public static bool Supports(this Provider provider, ExportFeatures feature) { if (provider != null) - return provider.Metadata.ExportSupport.Contains(type); + return provider.Metadata.ExportSupport.Contains(feature); + return false; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportAttributes.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportFeaturesAttribute.cs similarity index 68% rename from src/Libraries/SmartStore.Services/DataExchange/ExportAttributes.cs rename to src/Libraries/SmartStore.Services/DataExchange/ExportFeaturesAttribute.cs index ece4d81d38..d29f5641d3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportAttributes.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportFeaturesAttribute.cs @@ -8,13 +8,13 @@ namespace SmartStore.Services.DataExchange /// Projection type controls whether to display corresponding projection fields while editing an export profile. /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] - public sealed class ExportSupportingAttribute : Attribute + public sealed class ExportFeaturesAttribute : Attribute { - public ExportSupportingAttribute(params ExportSupport[] types) + public ExportFeaturesAttribute(params ExportFeatures[] features) { - Types = types; + Features = features; } - public ExportSupport[] Types { get; set; } + public ExportFeatures[] Features { get; set; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs index 250e0eecff..aea03c6fa9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs @@ -116,7 +116,7 @@ public virtual ExportProfile InsertExportProfile(Provider provi RemoveCriticalCharacters = true, CriticalCharacters = "¼,½,¾", PriceType = PriceDisplayType.PreSelectedPrice, - NoGroupedProducts = (provider.Supports(ExportSupport.ProjectionNoGroupedProducts) ? true : false) + NoGroupedProducts = (provider.Supports(ExportFeatures.CanOmitGroupedProducts) ? true : false) }; var filter = new ExportFilter @@ -150,7 +150,7 @@ public virtual ExportProfile InsertExportProfile(Provider provi { if (cloneProfile == null) { - if (provider.Supports(ExportSupport.CreateInitialPublicDeployment)) + if (provider.Supports(ExportFeatures.CreatesInitialPublicDeployment)) { profile.Deployments.Add(new ExportDeployment { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 66698ab1a3..d9a6a17a36 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -1556,7 +1556,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro var productTemplate = ctx.ProductTemplates.FirstOrDefault(x => x.Key == product.ProductTemplateId); var pictureSize = _mediaSettings.Value.ProductDetailsPictureSize; - if (ctx.Supporting[ExportSupport.ProjectionMainPictureUrl] && ctx.Projection.PictureSize > 0) + if (ctx.SupportedFeatures[ExportFeatures.CanIncludeMainPicture] && ctx.Projection.PictureSize > 0) pictureSize = ctx.Projection.PictureSize; var perfLoadId = (ctx.IsPreview ? 0 : product.Id); // perf preview (it's a compromise) @@ -1762,12 +1762,12 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro #region more attribute controlled data - if (ctx.Supports(ExportSupport.ProjectionDescription)) + if (ctx.Supports(ExportFeatures.CanProjectDescription)) { PrepareProductDescription(ctx, expando, product); } - if (ctx.Supports(ExportSupport.ProjectionBrand)) + if (ctx.Supports(ExportFeatures.OffersBrandFallback)) { string brand = null; var productManus = ctx.DataContextProduct.ProductManufacturers.Load(perfLoadId); @@ -1781,7 +1781,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro expando._Brand = brand; } - if (ctx.Supports(ExportSupport.ProjectionMainPictureUrl)) + if (ctx.Supports(ExportFeatures.CanIncludeMainPicture)) { if (productPictures != null && productPictures.Any()) expando._MainPictureUrl = _pictureService.Value.GetPictureUrl(productPictures.First().Picture, ctx.Projection.PictureSize, storeLocation: ctx.Store.Url); @@ -1823,18 +1823,18 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro // navigation properties GetDeliveryTimeAndQuantityUnit(ctx, exp, product.DeliveryTimeId, product.QuantityUnitId); - if (ctx.Supports(ExportSupport.ProjectionUseOwnProductNo) && product.ManufacturerPartNumber.IsEmpty()) + if (ctx.Supports(ExportFeatures.UsesSkuAsMpnFallback) && product.ManufacturerPartNumber.IsEmpty()) { exp.ManufacturerPartNumber = product.Sku; } - if (ctx.Supports(ExportSupport.ProjectionShippingTime)) + if (ctx.Supports(ExportFeatures.OffersShippingTimeFallback)) { dynamic deliveryTime = exp.DeliveryTime; exp._ShippingTime = (deliveryTime == null ? ctx.Projection.ShippingTime : deliveryTime.Name); } - if (ctx.Supports(ExportSupport.ProjectionShippingCosts)) + if (ctx.Supports(ExportFeatures.OffersShippingCostsFallback)) { exp._FreeShippingThreshold = ctx.Projection.FreeShippingThreshold; @@ -1844,7 +1844,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro exp._ShippingCosts = ctx.Projection.ShippingCosts; } - if (ctx.Supports(ExportSupport.ProjectionOldPrice)) + if (ctx.Supports(ExportFeatures.UsesOldPrice)) { if (product.OldPrice != decimal.Zero && product.OldPrice != (decimal)exp.Price && !(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)) { @@ -1864,7 +1864,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro } } - if (ctx.Supports(ExportSupport.ProjectionSpecialPrice)) + if (ctx.Supports(ExportFeatures.UsesSpecialPrice)) { exp._SpecialPrice = null; exp._RegularPrice = null; // price if a special price would not exist @@ -2907,7 +2907,8 @@ public void Execute(TaskExecutionContext taskContext) /// Any data passed on IExportExecuteContext.CustomProperties /// Product query that supersede profile filtering /// Export execute result - public ExportExecuteResult Execute(string providerSystemName, + public ExportExecuteResult Execute( + string providerSystemName, IComponentContext context, CancellationToken cancellationToken, ExportProfile profile = null, diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs index 2beab3db1b..5fee680634 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs @@ -40,8 +40,8 @@ public ExportProfileTaskContext( EntityIdsSelected = selectedIds.SplitSafe(",").Select(x => x.ToInt()).ToList(); PreviewData = previewData; - Supporting = Enum.GetValues(typeof(ExportSupport)) - .Cast() + SupportedFeatures = Enum.GetValues(typeof(ExportFeatures)) + .Cast() .ToDictionary(x => x, x => Provider.Supports(x)); FolderContent = FileSystemHelper.TempDir(@"Profile\Export\{0}\Content".FormatInvariant(profile.FolderName)); @@ -83,10 +83,10 @@ public bool IsPreview public ExportProfile Profile { get; private set; } public Provider Provider { get; private set; } - public Dictionary Supporting { get; private set; } - public bool Supports(ExportSupport type) + public Dictionary SupportedFeatures { get; private set; } + public bool Supports(ExportFeatures feature) { - return (!IsPreview && Supporting[type]); + return (!IsPreview && SupportedFeatures[feature]); } public ExportFilter Filter { get; private set; } diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 2de19dd74f..3c3f321c24 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -192,7 +192,7 @@ - + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs index ed7eb62390..03ed6a00ff 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs @@ -19,15 +19,15 @@ namespace SmartStore.GoogleMerchantCenter.Providers [SystemName("Feeds.GoogleMerchantCenterProductXml")] [FriendlyName("Google Merchant Center XML product feed")] [DisplayOrder(1)] - [ExportSupporting( - ExportSupport.CreateInitialPublicDeployment, - ExportSupport.ProjectionNoGroupedProducts, - ExportSupport.ProjectionAttributeCombinationAsProduct, - ExportSupport.ProjectionDescription, - ExportSupport.ProjectionUseOwnProductNo, - ExportSupport.ProjectionBrand, - ExportSupport.ProjectionMainPictureUrl, - ExportSupport.ProjectionSpecialPrice)] + [ExportFeatures( + ExportFeatures.CreatesInitialPublicDeployment, + ExportFeatures.CanOmitGroupedProducts, + ExportFeatures.CanProjectAttributeCombinations, + ExportFeatures.CanProjectDescription, + ExportFeatures.UsesSkuAsMpnFallback, + ExportFeatures.OffersBrandFallback, + ExportFeatures.CanIncludeMainPicture, + ExportFeatures.UsesSpecialPrice)] public class ProductExportXmlProvider : IExportProvider { private const string _googleNamespace = "http://base.google.com/ns/1.0"; diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index d9b7ca9b43..ee70cae011 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -768,7 +768,7 @@ protected override void Load(ContainerBuilder builder) var isConfigurable = typeof(IConfigurable).IsAssignableFrom(type); var isEditable = typeof(IUserEditable).IsAssignableFrom(type); var isHidden = GetIsHidden(type); - var exportSupport = GetExportSupport(type); + var exportSupport = GetExportFeatures(type); var registration = builder.RegisterType(type).Named(systemName).InstancePerRequest().PropertiesAutowired(PropertyWiringOptions.None); registration.WithMetadata(m => @@ -863,16 +863,16 @@ private bool GetIsHidden(Type type) return false; } - private ExportSupport[] GetExportSupport(Type type) + private ExportFeatures[] GetExportFeatures(Type type) { - var attr = type.GetAttribute(false); + var attr = type.GetAttribute(false); - if (attr != null && attr.Types != null) + if (attr != null && attr.Features != null) { - return attr.Types; + return attr.Features; } - return new ExportSupport[0]; + return new ExportFeatures[0]; } private Tuple GetFriendlyName(Type type, PluginDescriptor descriptor) diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 80dce133e9..3ebf4350d9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -94,7 +94,7 @@ public class ProviderModel public Type ConfigDataType { get; set; } public object ConfigData { get; set; } - public ExportSupport[] Supporting { get; set; } + public ExportFeatures[] Supporting { get; set; } [SmartResourceDisplayName("Common.Image")] public string ThumbnailUrl { get; set; } diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/telerik/telerik.smartstore.less b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/telerik/telerik.smartstore.less index 6b333e4237..ab27daa31b 100644 --- a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/telerik/telerik.smartstore.less +++ b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/telerik/telerik.smartstore.less @@ -153,7 +153,7 @@ } .t-numerictextbox .t-input { - width: 252px; + width: 301px; padding: 6px 8px 6px 3px; } .t-numerictextbox.small .t-input { From 17fcdd3af7b2a1d25971fe2a082b3cfb22ec7a90 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 4 Nov 2015 23:11:09 +0100 Subject: [PATCH 172/874] Export framework: minor refactoring --- .../ExportCategoryXmlProvider.cs | 2 +- .../ExportCustomerXlsxProvider.cs | 2 +- .../ExportCustomerXmlProvider.cs | 2 +- .../ExportManufacturerXmlProvider.cs | 2 +- .../ExportNewsSubscriptionCsvProvider.cs | 2 +- .../ExportProvider/ExportOrderXlsxProvider.cs | 2 +- .../ExportProvider/ExportOrderXmlProvider.cs | 2 +- .../ExportProductXlsxProvider.cs | 2 +- .../ExportProductXmlProvider.cs | 2 +- .../ExportTask/ExportDataContextCategory.cs | 3 +- .../ExportTask/ExportDataContextCustomer.cs | 3 +- .../ExportDataContextManufacturer.cs | 3 +- .../ExportTask/ExportDataContextProduct.cs | 3 +- .../ExportTask/ExportProfileTask.cs | 35 ++++++++++--------- .../DataExchange/IExportProvider.cs | 2 +- .../DataExchange/IExportService.cs | 2 +- .../Providers/ProductExportXmlProvider.cs | 2 +- .../Views/Export/_Tab.Projection.cshtml | 14 ++++---- 18 files changed, 46 insertions(+), 39 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs index 7e4e6f9fba..6bd4aa3d2f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs @@ -105,7 +105,7 @@ public void Execute(IExportExecuteContext context) } } - public void ExecuteEnded(IExportExecuteContext context) + public void OnExecuted(IExportExecuteContext context) { // nothing to do } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs index c2f4722f1a..2ab8dcb6b0 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs @@ -215,7 +215,7 @@ public void Execute(IExportExecuteContext context) } } - public void ExecuteEnded(IExportExecuteContext context) + public void OnExecuted(IExportExecuteContext context) { // nothing to do } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs index cbf1c80719..4573e6c69c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs @@ -88,7 +88,7 @@ public void Execute(IExportExecuteContext context) } } - public void ExecuteEnded(IExportExecuteContext context) + public void OnExecuted(IExportExecuteContext context) { // nothing to do } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs index ebe52eb5f9..5341411be7 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs @@ -105,7 +105,7 @@ public void Execute(IExportExecuteContext context) } } - public void ExecuteEnded(IExportExecuteContext context) + public void OnExecuted(IExportExecuteContext context) { // nothing to do } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs index 00400d64e9..36479f946f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs @@ -74,7 +74,7 @@ public void Execute(IExportExecuteContext context) } } - public void ExecuteEnded(IExportExecuteContext context) + public void OnExecuted(IExportExecuteContext context) { // nothing to do } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs index 4cd8a5c5e2..ba85cc3b0d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs @@ -251,7 +251,7 @@ public void Execute(IExportExecuteContext context) } } - public void ExecuteEnded(IExportExecuteContext context) + public void OnExecuted(IExportExecuteContext context) { // nothing to do } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs index e04e6829bf..185181c013 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs @@ -261,7 +261,7 @@ public void Execute(IExportExecuteContext context) } } - public void ExecuteEnded(IExportExecuteContext context) + public void OnExecuted(IExportExecuteContext context) { // nothing to do } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs index 516d05488a..e9ec1bbc59 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs @@ -415,7 +415,7 @@ public void Execute(IExportExecuteContext context) GC.Collect(); } - public void ExecuteEnded(IExportExecuteContext context) + public void OnExecuted(IExportExecuteContext context) { // nothing to do } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs index a6894b4242..6352c98b03 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs @@ -88,7 +88,7 @@ public void Execute(IExportExecuteContext context) } } - public void ExecuteEnded(IExportExecuteContext context) + public void OnExecuted(IExportExecuteContext context) { // nothing to do } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCategory.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCategory.cs index f19fb56f47..8e2efa0f93 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCategory.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCategory.cs @@ -18,7 +18,8 @@ internal class ExportDataContextCategory private LazyMultimap _productCategories; private LazyMultimap _pictures; - public ExportDataContextCategory(IEnumerable categories, + public ExportDataContextCategory( + IEnumerable categories, Func> productCategories, Func> pictures) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCustomer.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCustomer.cs index ac85860cc9..f7c027a27d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCustomer.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCustomer.cs @@ -15,7 +15,8 @@ public class ExportDataContextCustomer private LazyMultimap _genericAttributes; - public ExportDataContextCustomer(IEnumerable customers, + public ExportDataContextCustomer( + IEnumerable customers, Func> genericAttributes) { if (customers == null) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextManufacturer.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextManufacturer.cs index 79bd1558ca..9d2002be35 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextManufacturer.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextManufacturer.cs @@ -18,7 +18,8 @@ internal class ExportDataContextManufacturer private LazyMultimap _productManufacturers; private LazyMultimap _pictures; - public ExportDataContextManufacturer(IEnumerable manufacturers, + public ExportDataContextManufacturer( + IEnumerable manufacturers, Func> productManufacturers, Func> pictures) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextProduct.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextProduct.cs index 14f9168be4..bb0d6ea6c1 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextProduct.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextProduct.cs @@ -24,7 +24,8 @@ internal class ExportDataContextProduct : PriceCalculationContext private LazyMultimap _productSpecificationAttributes; private LazyMultimap _productBundleItems; - public ExportDataContextProduct(IEnumerable products, + public ExportDataContextProduct( + IEnumerable products, Func> attributes, Func> attributeCombinations, Func> tierPrices, diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index d9a6a17a36..e6016546ef 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -51,6 +51,7 @@ using SmartStore.Services.Tax; using SmartStore.Utilities; using SmartStore.Utilities.Threading; +using SmartStore.Core.Localization; // note: namespace persisted in ScheduleTask.Type namespace SmartStore.Services.DataExchange.ExportTask @@ -132,6 +133,8 @@ private void InitDependencies(TaskExecutionContext context) _subscriptionRepository = context.Resolve>>(); } + public Localizer T { get; set; } + #endregion #region Utilities @@ -2598,7 +2601,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) if (ctx.Export.Abort != ExportAbortion.Hard) { - ctx.Provider.Value.ExecuteEnded(ctx.Export); + ctx.Provider.Value.OnExecuted(ctx.Export); } } } @@ -2624,7 +2627,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) ctx.Log = logger; ctx.Export.Log = logger; - ctx.ProgressInfo = _services.Localization.GetResource("Admin.DataExchange.Export.ProgressInfo"); + ctx.ProgressInfo = T("Admin.DataExchange.Export.ProgressInfo"); if (ctx.Profile.ProviderConfigData.HasValue()) { @@ -2637,9 +2640,9 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) using (var scope = new DbContextScope(_services.DbContext, autoDetectChanges: false, proxyCreation: true, validateOnSave: false, forceNoTracking: true)) { - ctx.DeliveryTimes = _deliveryTimeService.Value.GetAllDeliveryTimes().ToDictionary(x => x.Id, x => x); - ctx.QuantityUnits = _quantityUnitService.Value.GetAllQuantityUnits().ToDictionary(x => x.Id, x => x); - ctx.ProductTemplates = _productTemplateService.Value.GetAllProductTemplates().ToDictionary(x => x.Id, x => x); + 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); if (ctx.Provider.Value.EntityType == ExportEntityType.Product) { @@ -2681,7 +2684,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) ZipFile.CreateFromDirectory(ctx.FolderContent, ctx.ZipPath, CompressionLevel.Fastest, true); } - SetProgress(ctx, _services.Localization.GetResource("Common.Deployment")); + SetProgress(ctx, T("Common.Deployment")); foreach (var deployment in ctx.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) { @@ -2885,7 +2888,7 @@ public void Execute(TaskExecutionContext taskContext) var provider = _exportService.Value.LoadProvider(profile.ProviderSystemName); if (provider == null) - throw new SmartException(_services.Localization.GetResource("Admin.Common.ProviderNotLoaded").FormatInvariant(profile.ProviderSystemName.NaIfEmpty())); + throw new SmartException(T("Admin.Common.ProviderNotLoaded", profile.ProviderSystemName.NaIfEmpty())); var ctx = new ExportProfileTaskContext(taskContext, profile, provider, selectedEntityIds); @@ -2927,7 +2930,7 @@ public ExportExecuteResult Execute( var provider = _exportService.Value.LoadProvider(providerSystemName); if (provider == null) - throw new SmartException(_services.Localization.GetResource("Admin.Common.ProviderNotLoaded").FormatInvariant(providerSystemName.NaIfEmpty())); + throw new SmartException(T("Admin.Common.ProviderNotLoaded", providerSystemName.NaIfEmpty())); if (profile == null) profile = _exportService.Value.CreateVolatileProfile(provider); @@ -2950,24 +2953,24 @@ public ExportExecuteResult Execute( var extension = Path.GetExtension(ctx.Result.Files.First().FileName); if (provider.Value.EntityType == ExportEntityType.Product) - prefix = _services.Localization.GetResource("Admin.Catalog.Products"); + prefix = T("Admin.Catalog.Products"); else if (provider.Value.EntityType == ExportEntityType.Order) - prefix = _services.Localization.GetResource("Admin.Orders"); + prefix = T("Admin.Orders"); else if (provider.Value.EntityType == ExportEntityType.Category) - prefix = _services.Localization.GetResource("Admin.Catalog.Categories"); + prefix = T("Admin.Catalog.Categories"); else if (provider.Value.EntityType == ExportEntityType.Manufacturer) - prefix = _services.Localization.GetResource("Admin.Catalog.Manufacturers"); + prefix = T("Admin.Catalog.Manufacturers"); else if (provider.Value.EntityType == ExportEntityType.Customer) - prefix = _services.Localization.GetResource("Admin.Customers"); + prefix = T("Admin.Customers"); else if (provider.Value.EntityType == ExportEntityType.NewsLetterSubscription) - prefix = _services.Localization.GetResource("Admin.Promotions.NewsLetterSubscriptions"); + prefix = T("Admin.Promotions.NewsLetterSubscriptions"); else prefix = provider.Value.EntityType.ToString(); if (selectedEntityIds.HasValue()) - suffix = (selectedEntityIds.Contains(",") ? _services.Localization.GetResource("Admin.Common.Selected") : selectedEntityIds); + suffix = (selectedEntityIds.Contains(",") ? T("Admin.Common.Selected").Text : selectedEntityIds); else - suffix = _services.Localization.GetResource("Common.All"); + suffix = T("Common.All"); ctx.Result.DownloadFileName = string.Concat(prefix, "-", suffix).ToLower().ToValidFileName() + extension; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs index c0e683a0e8..b0872ef1eb 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs @@ -30,6 +30,6 @@ public partial interface IExportProvider : IProvider, IUserEditable /// Called once per store when export execution ended /// /// Export execution context - void ExecuteEnded(IExportExecuteContext context); + void OnExecuted(IExportExecuteContext context); } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs index 734777325f..e3823a7c4e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs @@ -8,7 +8,7 @@ namespace SmartStore.Services.DataExchange public interface IExportService { /// - /// Creates a volatile export project + /// Creates a volatile export profile /// /// Export provider /// New export profile diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs index 03ed6a00ff..bbcb1bcbda 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs @@ -401,7 +401,7 @@ public void Execute(IExportExecuteContext context) } } - public void ExecuteEnded(IExportExecuteContext context) + public void OnExecuted(IExportExecuteContext context) { // nothing to do } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml index 73113cc2a5..4815fba951 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml @@ -42,7 +42,7 @@ @if (Model.Provider.EntityType == ExportEntityType.Product) { - if (Model.Provider.Supporting.Contains(ExportSupport.ProjectionAttributeCombinationAsProduct)) + if (Model.Provider.Supporting.Contains(ExportFeatures.CanProjectAttributeCombinations)) { } - if (Model.Provider.Supporting.Contains(ExportSupport.ProjectionNoGroupedProducts)) + if (Model.Provider.Supporting.Contains(ExportFeatures.CanOmitGroupedProducts)) { - if (Model.Provider.Supporting.Contains(ExportSupport.ProjectionShippingTime)) + if (Model.Provider.Supporting.Contains(ExportFeatures.OffersShippingTimeFallback)) { } - if (Model.Provider.Supporting.Contains(ExportSupport.ProjectionShippingCosts)) + if (Model.Provider.Supporting.Contains(ExportFeatures.OffersShippingCostsFallback)) { } - if (Model.Provider.Supporting.Contains(ExportSupport.ProjectionMainPictureUrl)) + if (Model.Provider.Supporting.Contains(ExportFeatures.CanIncludeMainPicture)) { } - if (Model.Provider.Supporting.Contains(ExportSupport.ProjectionBrand)) + if (Model.Provider.Supporting.Contains(ExportFeatures.OffersBrandFallback)) { } - if (Model.Provider.Supporting.Contains(ExportSupport.ProjectionDescription)) + if (Model.Provider.Supporting.Contains(ExportFeatures.CanProjectDescription)) { } - if (Model.Provider.Supporting.Contains(ExportFeatures.CanOmitGroupedProducts)) + if (Model.Provider.Feature.HasFlag(ExportFeature.CanOmitGroupedProducts)) { - if (Model.Provider.Supporting.Contains(ExportFeatures.OffersShippingTimeFallback)) + if (Model.Provider.Feature.HasFlag(ExportFeature.OffersShippingTimeFallback)) { } - if (Model.Provider.Supporting.Contains(ExportFeatures.OffersShippingCostsFallback)) + if (Model.Provider.Feature.HasFlag(ExportFeature.OffersShippingCostsFallback)) { } - if (Model.Provider.Supporting.Contains(ExportFeatures.CanIncludeMainPicture)) + if (Model.Provider.Feature.HasFlag(ExportFeature.CanIncludeMainPicture)) { } - if (Model.Provider.Supporting.Contains(ExportFeatures.OffersBrandFallback)) + if (Model.Provider.Feature.HasFlag(ExportFeature.OffersBrandFallback)) { } - if (Model.Provider.Supporting.Contains(ExportFeatures.CanProjectDescription)) + if (Model.Provider.Feature.HasFlag(ExportFeature.CanProjectDescription)) { } - if (Model.Provider.Feature.HasFlag(ExportFeature.CanOmitGroupedProducts)) + if (Model.Provider.Feature.HasFlag(ExportFeatures.CanOmitGroupedProducts)) { - if (Model.Provider.Feature.HasFlag(ExportFeature.OffersShippingTimeFallback)) + if (Model.Provider.Feature.HasFlag(ExportFeatures.OffersShippingTimeFallback)) { } - if (Model.Provider.Feature.HasFlag(ExportFeature.OffersShippingCostsFallback)) + if (Model.Provider.Feature.HasFlag(ExportFeatures.OffersShippingCostsFallback)) { } - if (Model.Provider.Feature.HasFlag(ExportFeature.CanIncludeMainPicture)) + if (Model.Provider.Feature.HasFlag(ExportFeatures.CanIncludeMainPicture)) { } - if (Model.Provider.Feature.HasFlag(ExportFeature.OffersBrandFallback)) + if (Model.Provider.Feature.HasFlag(ExportFeatures.OffersBrandFallback)) { } - if (Model.Provider.Feature.HasFlag(ExportFeature.CanProjectDescription)) + if (Model.Provider.Feature.HasFlag(ExportFeatures.CanProjectDescription)) { + + + + + + + + + @@ -45,12 +46,20 @@ +
    @Html.EditorFor(model => model.Slug) - @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = Model.EntityName, entityId = @Model.EntityId }) @Html.ValidationMessageFor(model => model.Slug) + @Html.Action("NamesPerEntity", "UrlRecord", new { entityName = Model.EntityName, entityId = @Model.EntityId })
    @@ -71,7 +71,7 @@
    @@ -110,7 +110,7 @@
    @@ -123,7 +123,7 @@
    @@ -145,7 +145,7 @@
    @@ -158,7 +158,7 @@
    @@ -171,7 +171,7 @@
    From 3f19750ae6e7c5f9e085e4b756e2b95dde55749e Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 5 Nov 2015 18:38:36 +0100 Subject: [PATCH 173/874] Fix #804: ShopBar conflicts with 'close store' feature --- .../SmartStore.Core/ComponentModel/Expando.cs | 5 + .../201509150931528_ExportFramework2.cs | 9 + .../ExportTask/ExportProfileTask.cs | 2 + .../DataExchange/Internal/ExpandoEntity.cs | 25 ++ .../DataExchange/Internal/ExpandoHelpers.cs | 240 ++++++++++++++++ .../SmartStore.Services.csproj | 3 + .../Controllers/StoreClosedAttribute.cs | 22 +- .../Controllers/CommonController.cs | 40 ++- .../Themes/Alpha/Content/header.less | 4 + .../Themes/Alpha/Content/layout.less | 1 + .../Views/Home/StoreClosed.cshtml | 3 +- src/SmartStoreNET.Minimal.sln | 270 ++++++++++++++++++ 12 files changed, 608 insertions(+), 16 deletions(-) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoEntity.cs create mode 100644 src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoHelpers.cs create mode 100644 src/SmartStoreNET.Minimal.sln diff --git a/src/Libraries/SmartStore.Core/ComponentModel/Expando.cs b/src/Libraries/SmartStore.Core/ComponentModel/Expando.cs index 81c2ecb4f7..c696b77e66 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/Expando.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/Expando.cs @@ -112,6 +112,11 @@ protected virtual void Initialize(object instance) _instanceType = instance.GetType(); } + protected object WrappedObject + { + get { return _instance; } + } + IList InstancePropertyInfo { get diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index 374cf79ba1..4cf5af7438 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -227,6 +227,15 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.Delete("Plugins.Widgets.OpenTrans.IsLexwareCompatibe"); builder.Delete("Admin.System.Maintenance.DeleteExportedFolders.TotalDeleted"); + + // Common + builder.AddOrUpdate("StoreClosed", + "We'll be back.", + "Wir sind bald wieder da."); + + builder.AddOrUpdate("StoreClosed.Hint", + "We're busy updating our online store for you and will be back soon.", + "Wir aktualisieren gerade das Angebot in unserem Online-Shop. Die Seite ist demnchst wieder verfgbar."); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index e6016546ef..d8bd7d7d4a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -52,6 +52,7 @@ using SmartStore.Utilities; using SmartStore.Utilities.Threading; using SmartStore.Core.Localization; +using SmartStore.Services.DataExchange.Internal; // note: namespace persisted in ScheduleTask.Type namespace SmartStore.Services.DataExchange.ExportTask @@ -62,6 +63,7 @@ public class ExportProfileTask : ITask #region Dependencies + private ExpandoHelpers _expandoHelpers; private ICommonServices _services; private Lazy _exportService; private Lazy _dataExchangeSettings; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoEntity.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoEntity.cs new file mode 100644 index 0000000000..565f9e00e5 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoEntity.cs @@ -0,0 +1,25 @@ +using SmartStore.ComponentModel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Dynamic; + +namespace SmartStore.Services.DataExchange.Internal +{ + internal class ExpandoEntity : Expando + { + private readonly object _entity; + + public ExpandoEntity(object entity) + : base(entity) + { + } + + public object Entity + { + get { return base.WrappedObject; } + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoHelpers.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoHelpers.cs new file mode 100644 index 0000000000..d8da9e6f8c --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoHelpers.cs @@ -0,0 +1,240 @@ +using SmartStore.ComponentModel; +using SmartStore.Core; +using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Media; +using SmartStore.Core.Domain.Seo; +using SmartStore.Core.Domain.Stores; +using SmartStore.Services.DataExchange.ExportTask; +using SmartStore.Services.Localization; +using SmartStore.Services.Seo; +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.Services.DataExchange.Internal +{ + internal class ExpandoHelpers + { + private readonly ExportProfileTaskContext _ctx; + private readonly IUrlRecordService _urlRecordService; + private readonly ILocalizedEntityService _localizedEntityService; + + public ExpandoHelpers( + ExportProfileTaskContext context, + IUrlRecordService urlRecordService, + ILocalizedEntityService localizedEntityService) + { + _ctx = context; + _urlRecordService = urlRecordService; + _localizedEntityService = localizedEntityService; + } + + public dynamic ToExpando(Currency currency) + { + if (currency == null) + return null; + + dynamic expando = new ExpandoEntity(currency); + expando.Name = currency.GetLocalized(x => x.Name, _ctx.Projection.LanguageId ?? 0, true, false); + expando._Localized = GetLocalized(currency, x => x.Name); + + return expando; + } + + private dynamic ToExpando(Language language) + { + if (language == null) + return null; + + dynamic expando = new ExpandoEntity(language); + return expando; + } + + private dynamic ToExpando(Country country) + { + if (country == null) + return null; + + dynamic expando = new ExpandoEntity(country); + expando.Name = country.GetLocalized(x => x.Name, _ctx.Projection.LanguageId ?? 0, true, false); + expando._Localized = GetLocalized(country, x => x.Name); + + return expando; + } + + private dynamic ToExpando(Address address) + { + if (address == null) + return null; + + dynamic expando = new ExpandoEntity(address); + expando.Country = ToExpando(address.Country); + + if (address.StateProvinceId.GetValueOrDefault() > 0) + { + dynamic sp = new ExpandoEntity(address.StateProvince); + sp.Name = address.StateProvince.GetLocalized(x => x.Name, _ctx.Projection.LanguageId ?? 0, true, false); + sp._Localized = GetLocalized(address.StateProvince, x => x.Name); + expando.StateProvince = sp; + } + else + { + expando.StateProvince = null; + } + + return expando; + } + + private dynamic ToExpando(RewardPointsHistory points) + { + if (points == null) + return null; + + dynamic expando = new ExpandoEntity(points); + return expando; + } + + private dynamic ToExpando( Customer customer) + { + if (customer == null) + return null; + + dynamic expando = new ExpandoEntity(customer); + + expando.BillingAddress = null; + expando.ShippingAddress = null; + expando.Addresses = null; + expando.CustomerRoles = null; + + expando.RewardPointsHistory = null; + expando._RewardPointsBalance = 0; + + expando._GenericAttributes = null; + expando._HasNewsletterSubscription = false; + + return expando; + } + + private dynamic ToExpando(Store store) + { + if (store == null) + return null; + + dynamic expando = new ExpandoEntity(store); + expando.PrimaryStoreCurrency = ToExpando(store.PrimaryStoreCurrency); + expando.PrimaryExchangeRateCurrency = ToExpando(store.PrimaryExchangeRateCurrency); + + return expando; + } + + private dynamic ToExpando(DeliveryTime deliveryTime) + { + if (deliveryTime == null) + return null; + + dynamic expando = new ExpandoEntity(deliveryTime); + expando.Name = deliveryTime.GetLocalized(x => x.Name, _ctx.Projection.LanguageId ?? 0, true, false); + expando._Localized = GetLocalized(deliveryTime, x => x.Name); + + return expando; + } + + private dynamic ToExpando(QuantityUnit quantityUnit) + { + if (quantityUnit == null) + return null; + + dynamic expando = new ExpandoEntity(quantityUnit); + expando.Name = quantityUnit.GetLocalized(x => x.Name, _ctx.Projection.LanguageId ?? 0, true, false); + expando.Description = quantityUnit.GetLocalized(x => x.Description, _ctx.Projection.LanguageId ?? 0, true, false); + expando._Localized = GetLocalized(quantityUnit, + x => x.Name, + x => x.Description); + + return expando; + } + + private dynamic ToExpando(Picture picture, int thumbPictureSize, int detailsPictureSize) + { + if (picture == null) + return null; + + dynamic expando = new ExpandoEntity(picture); + + //// TODO!!!!! + //expando._ThumbImageUrl = _pictureService.Value.GetPictureUrl(picture, thumbPictureSize, false, _ctx.Store.Url); + //expando._ImageUrl = _pictureService.Value.GetPictureUrl(picture, detailsPictureSize, false, _ctx.Store.Url); + //expando._FullSizeImageUrl = _pictureService.Value.GetPictureUrl(picture, 0, false, _ctx.Store.Url); + + //var relativeUrl = _pictureService.Value.GetPictureUrl(picture); + //expando._FileName = relativeUrl.Substring(relativeUrl.LastIndexOf("/") + 1); + + //expando._ThumbLocalPath = _pictureService.Value.GetThumbLocalPath(picture); + + return expando; + } + + // TODO: weitermachen [...] + + private List GetLocalized(T entity, params Expression>[] keySelectors) + where T : BaseEntity, ILocalizedEntity + { + if (_ctx.Languages.Count <= 1) + return null; + + var localized = new List(); + + var localeKeyGroup = typeof(T).Name; + var isSlugSupported = typeof(ISlugSupported).IsAssignableFrom(typeof(T)); + + foreach (var language in _ctx.Languages) + { + var languageCulture = language.Value.LanguageCulture.EmptyNull().ToLower(); + + // add SeName + if (isSlugSupported) + { + var value = _urlRecordService.GetActiveSlug(entity.Id, localeKeyGroup, language.Value.Id); + if (value.HasValue()) + { + dynamic exp = new ExpandoObject(); + exp.Culture = languageCulture; + exp.LocaleKey = "SeName"; + exp.LocaleValue = value; + + localized.Add(exp); + } + } + + foreach (var keySelector in keySelectors) + { + var member = keySelector.Body as MemberExpression; + var propInfo = member.Member as PropertyInfo; + string localeKey = propInfo.Name; + var value = _localizedEntityService.GetLocalizedValue(language.Value.Id, entity.Id, localeKeyGroup, localeKey); + + // we better not export empty values. The risk is too high that they get reimported and unnecessarily pollute databases. + if (value.HasValue()) + { + dynamic exp = new ExpandoObject(); + exp.Culture = languageCulture; + exp.LocaleKey = localeKey; + exp.LocaleValue = value; + + localized.Add(exp); + } + } + } + + return (localized.Count == 0 ? null : localized); + } + } +} diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 3c3f321c24..b5d926c04e 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -185,6 +185,8 @@ + + @@ -572,6 +574,7 @@ Nop_Services_EuropaCheckVatService_checkVatService + diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/StoreClosedAttribute.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/StoreClosedAttribute.cs index b4cfa9c518..67cce55e92 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/StoreClosedAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/StoreClosedAttribute.cs @@ -7,6 +7,8 @@ using SmartStore.Core.Domain; using SmartStore.Core.Infrastructure; using SmartStore.Services.Customers; +using SmartStore.Services.Localization; +using SmartStore.Core.Logging; namespace SmartStore.Web.Framework.Controllers { @@ -20,7 +22,8 @@ public class StoreClosedAttribute : ActionFilterAttribute new Tuple("SmartStore.Web.Controllers.CommonController", "SetLanguage") }; - + public ILocalizationService Localizer { get; set; } + public Lazy Notifier { get; set; } public Lazy WorkContext { get; set; } public Lazy StoreInformationSettings { get; set; } @@ -60,8 +63,21 @@ public override void OnActionExecuting(ActionExecutingContext filterContext) } else { - var storeClosedUrl = new UrlHelper(filterContext.RequestContext).RouteUrl("StoreClosed"); - filterContext.Result = new RedirectResult(storeClosedUrl); + if (request.IsAjaxRequest()) + { + var storeClosedMessage = "{0} {1}".FormatCurrentUI( + Localizer.GetResource("StoreClosed", 0, false), + Localizer.GetResource("StoreClosed.Hint", 0, false)); + Notifier.Value.Error(storeClosedMessage); + + //filterContext.Result = new ContentResult { Content = "", ContentType = "text/html" }; + } + else + { + var storeClosedUrl = new UrlHelper(filterContext.RequestContext).RouteUrl("StoreClosed"); + filterContext.Result = new RedirectResult(storeClosedUrl); + } + } } } diff --git a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs index 4c411f9238..36d72e8a38 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs @@ -42,6 +42,7 @@ using SmartStore.Web.Framework.UI; using SmartStore.Web.Infrastructure.Cache; using SmartStore.Web.Models.Common; +using SmartStore.Core.Domain; namespace SmartStore.Web.Controllers { @@ -57,10 +58,12 @@ public partial class CommonController : PublicControllerBase private readonly Lazy _forumservice; private readonly Lazy _genericAttributeService; private readonly Lazy _mobileDeviceHelper; + private readonly Lazy _compareProductsService; private readonly static string[] s_hints = new string[] { "Shopsystem", "Onlineshop Software", "Shopsoftware", "E-Commerce Solution" }; - private readonly CustomerSettings _customerSettings; + private readonly StoreInformationSettings _storeInfoSettings; + private readonly CustomerSettings _customerSettings; private readonly TaxSettings _taxSettings; private readonly CatalogSettings _catalogSettings; private readonly ThemeSettings _themeSettings; @@ -70,8 +73,8 @@ public partial class CommonController : PublicControllerBase private readonly ForumSettings _forumSettings; private readonly LocalizationSettings _localizationSettings; private readonly Lazy _securitySettings; - - private readonly IOrderTotalCalculationService _orderTotalCalculationService; + private readonly IOrderTotalCalculationService _orderTotalCalculationService; + private readonly IPriceFormatter _priceFormatter; private readonly IPageAssetsBuilder _pageAssetsBuilder; private readonly Lazy _pictureService; @@ -90,7 +93,9 @@ public CommonController( Lazy forumService, Lazy genericAttributeService, Lazy mobileDeviceHelper, - CustomerSettings customerSettings, + Lazy compareProductsService, + StoreInformationSettings storeInfoSettings, + CustomerSettings customerSettings, TaxSettings taxSettings, CatalogSettings catalogSettings, EmailAccountSettings emailAccountSettings, @@ -115,8 +120,10 @@ public CommonController( this._forumservice = forumService; this._genericAttributeService = genericAttributeService; this._mobileDeviceHelper = mobileDeviceHelper; - - this._customerSettings = customerSettings; + this._compareProductsService = compareProductsService; + + this._storeInfoSettings = storeInfoSettings; + this._customerSettings = customerSettings; this._taxSettings = taxSettings; this._catalogSettings = catalogSettings; this._commonSettings = commonSettings; @@ -506,7 +513,18 @@ public ActionResult ShopBar() { var customer = _services.WorkContext.CurrentCustomer; - var unreadMessageCount = GetUnreadPrivateMessages(); + var isAdmin = customer.IsAdmin(); + var isRegistered = isAdmin || customer.IsRegistered(); + + if (_storeInfoSettings.StoreClosed) + { + if (!isAdmin || !_storeInfoSettings.StoreClosedAllowForAdmins) + { + return Content(""); + } + } + + var unreadMessageCount = GetUnreadPrivateMessages(); var unreadMessage = string.Empty; var alertMessage = string.Empty; if (unreadMessageCount > 0) @@ -542,8 +560,8 @@ public ActionResult ShopBar() } var model = new ShopBarModel { - IsAuthenticated = customer.IsRegistered(), - CustomerEmailUsername = customer.IsRegistered() ? (_customerSettings.UsernamesEnabled ? customer.Username : customer.Email) : "", + IsAuthenticated = isRegistered, + CustomerEmailUsername = isRegistered ? (_customerSettings.UsernamesEnabled ? customer.Username : customer.Email) : "", IsCustomerImpersonated = _services.WorkContext.OriginalCustomerIfImpersonated != null, DisplayAdminLink = _services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel), ShoppingCartEnabled = _services.Permissions.Authorize(StandardPermissionProvider.EnableShoppingCart), @@ -555,7 +573,7 @@ public ActionResult ShopBar() CompareProductsEnabled = _catalogSettings.CompareProductsEnabled }; - if (model.ShoppingCartEnabled || model.WishlistEnabled) + if (model.ShoppingCartEnabled || model.WishlistEnabled) { if (model.ShoppingCartEnabled) model.ShoppingCartItems = cart.GetTotalProducts(); @@ -566,7 +584,7 @@ public ActionResult ShopBar() if (_catalogSettings.CompareProductsEnabled) { - model.CompareItems = EngineContext.Current.Resolve().GetComparedProductsCount(); + model.CompareItems = _compareProductsService.Value.GetComparedProductsCount(); } return PartialView(model); diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/header.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/header.less index c783048936..012a75ee71 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/header.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/header.less @@ -7,6 +7,10 @@ padding: 46px 0 0 0; } +.store-closed #header { + padding-top: 0; +} + #logobar { position: relative; padding: 10px 0; diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/layout.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/layout.less index ae6bc7a7fd..b6cb2e661c 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/layout.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/layout.less @@ -11,6 +11,7 @@ #content-body { margin-top: 10px; padding-bottom: 10px; + min-height: 400px; } /* one column */ diff --git a/src/Presentation/SmartStore.Web/Views/Home/StoreClosed.cshtml b/src/Presentation/SmartStore.Web/Views/Home/StoreClosed.cshtml index e848f13ad2..f174ee3abe 100644 --- a/src/Presentation/SmartStore.Web/Views/Home/StoreClosed.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Home/StoreClosed.cshtml @@ -3,13 +3,12 @@ //title Html.AddTitleParts(T("PageTitle.StoreClosed").Text); + Html.AddBodyCssClass("store-closed"); }

    @T("StoreClosed")

    -
    -
    @T("StoreClosed.Hint")
    diff --git a/src/SmartStoreNET.Minimal.sln b/src/SmartStoreNET.Minimal.sln new file mode 100644 index 0000000000..feeb737168 --- /dev/null +++ b/src/SmartStoreNET.Minimal.sln @@ -0,0 +1,270 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.23107.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{7881B112-7843-4542-B1F7-F99553FB9BB7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartStore.Services", "Libraries\SmartStore.Services\SmartStore.Services.csproj", "{210541AD-F659-47DA-8763-16F36C5CD2F4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartStore.Data", "Libraries\SmartStore.Data\SmartStore.Data.csproj", "{CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartStore.Core", "Libraries\SmartStore.Core\SmartStore.Core.csproj", "{6BDA8332-939F-45B7-A25E-7A797260AE59}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartStore.Web", "Presentation\SmartStore.Web\SmartStore.Web.csproj", "{4F1F649C-1020-45BE-A487-F416D9297FF3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartStore.Admin", "Presentation\SmartStore.Web\Administration\SmartStore.Admin.csproj", "{152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartStore.Web.Framework", "Presentation\SmartStore.Web.Framework\SmartStore.Web.Framework.csproj", "{75FD4163-333C-4DD5-854D-2EF294E45D94}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E11A41CA-FA9F-4BB7-A35E-F80EE1084232}" + ProjectSection(SolutionItems) = preProject + AssemblySharedInfo.cs = AssemblySharedInfo.cs + AssemblyVersionInfo.cs = AssemblyVersionInfo.cs + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{C0A5A9AE-1509-4795-82CD-0BE7D6A17C69}" + ProjectSection(SolutionItems) = preProject + .nuget\NuGet.Config = .nuget\NuGet.Config + .nuget\NuGet.exe = .nuget\NuGet.exe + .nuget\NuGet.targets = .nuget\NuGet.targets + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartStore.OfflinePayment", "Plugins\SmartStore.OfflinePayment\SmartStore.OfflinePayment.csproj", "{692E9D31-1393-47BF-B372-63F671052F89}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartStore.Tax", "Plugins\SmartStore.Tax\SmartStore.Tax.csproj", "{94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartStore.Shipping", "Plugins\SmartStore.Shipping\SmartStore.Shipping.csproj", "{ACC1E122-B2C8-4441-BDED-D4A77763331A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartStore.DevTools", "Plugins\SmartStore.DevTools\SmartStore.DevTools.csproj", "{542B9C12-E2A1-49BB-9134-0E3484F9D669}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + EFMigrations|Any CPU = EFMigrations|Any CPU + EFMigrations|Mixed Platforms = EFMigrations|Mixed Platforms + EFMigrations|x86 = EFMigrations|x86 + PluginDev|Any CPU = PluginDev|Any CPU + PluginDev|Mixed Platforms = PluginDev|Mixed Platforms + PluginDev|x86 = PluginDev|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {210541AD-F659-47DA-8763-16F36C5CD2F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.Debug|x86.ActiveCfg = Debug|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.EFMigrations|Any CPU.ActiveCfg = EFMigrations|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.EFMigrations|Any CPU.Build.0 = EFMigrations|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.EFMigrations|Mixed Platforms.ActiveCfg = EFMigrations|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.EFMigrations|Mixed Platforms.Build.0 = EFMigrations|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.EFMigrations|x86.ActiveCfg = EFMigrations|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.PluginDev|Any CPU.ActiveCfg = PluginDev|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.PluginDev|Any CPU.Build.0 = PluginDev|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.PluginDev|Mixed Platforms.ActiveCfg = PluginDev|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.PluginDev|Mixed Platforms.Build.0 = PluginDev|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.PluginDev|x86.ActiveCfg = PluginDev|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.Release|Any CPU.Build.0 = Release|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {210541AD-F659-47DA-8763-16F36C5CD2F4}.Release|x86.ActiveCfg = Release|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.Debug|x86.ActiveCfg = Debug|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.EFMigrations|Any CPU.ActiveCfg = EFMigrations|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.EFMigrations|Any CPU.Build.0 = EFMigrations|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.EFMigrations|Mixed Platforms.ActiveCfg = EFMigrations|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.EFMigrations|Mixed Platforms.Build.0 = EFMigrations|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.EFMigrations|x86.ActiveCfg = EFMigrations|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.PluginDev|Any CPU.ActiveCfg = PluginDev|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.PluginDev|Any CPU.Build.0 = PluginDev|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.PluginDev|Mixed Platforms.ActiveCfg = PluginDev|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.PluginDev|Mixed Platforms.Build.0 = PluginDev|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.PluginDev|x86.ActiveCfg = PluginDev|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.Release|Any CPU.Build.0 = Release|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {CCD7F2C9-6A2C-4CF0-8E89-076B8FC0F144}.Release|x86.ActiveCfg = Release|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.Debug|x86.ActiveCfg = Debug|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.EFMigrations|Any CPU.ActiveCfg = EFMigrations|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.EFMigrations|Any CPU.Build.0 = EFMigrations|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.EFMigrations|Mixed Platforms.ActiveCfg = EFMigrations|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.EFMigrations|Mixed Platforms.Build.0 = EFMigrations|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.EFMigrations|x86.ActiveCfg = EFMigrations|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.PluginDev|Any CPU.ActiveCfg = PluginDev|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.PluginDev|Any CPU.Build.0 = PluginDev|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.PluginDev|Mixed Platforms.ActiveCfg = PluginDev|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.PluginDev|Mixed Platforms.Build.0 = PluginDev|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.PluginDev|x86.ActiveCfg = PluginDev|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.Release|Any CPU.Build.0 = Release|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {6BDA8332-939F-45B7-A25E-7A797260AE59}.Release|x86.ActiveCfg = Release|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.Debug|x86.ActiveCfg = Debug|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.EFMigrations|Any CPU.ActiveCfg = EFMigrations|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.EFMigrations|Any CPU.Build.0 = EFMigrations|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.EFMigrations|Mixed Platforms.ActiveCfg = EFMigrations|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.EFMigrations|Mixed Platforms.Build.0 = EFMigrations|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.EFMigrations|x86.ActiveCfg = EFMigrations|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.PluginDev|Any CPU.ActiveCfg = PluginDev|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.PluginDev|Any CPU.Build.0 = PluginDev|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.PluginDev|Mixed Platforms.ActiveCfg = PluginDev|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.PluginDev|Mixed Platforms.Build.0 = PluginDev|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.PluginDev|x86.ActiveCfg = PluginDev|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.Release|Any CPU.Build.0 = Release|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {4F1F649C-1020-45BE-A487-F416D9297FF3}.Release|x86.ActiveCfg = Release|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.Debug|x86.ActiveCfg = Debug|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.EFMigrations|Any CPU.ActiveCfg = EFMigrations|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.EFMigrations|Any CPU.Build.0 = EFMigrations|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.EFMigrations|Mixed Platforms.ActiveCfg = EFMigrations|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.EFMigrations|Mixed Platforms.Build.0 = EFMigrations|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.EFMigrations|x86.ActiveCfg = EFMigrations|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.PluginDev|Any CPU.ActiveCfg = PluginDev|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.PluginDev|Any CPU.Build.0 = PluginDev|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.PluginDev|Mixed Platforms.ActiveCfg = PluginDev|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.PluginDev|Mixed Platforms.Build.0 = PluginDev|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.PluginDev|x86.ActiveCfg = PluginDev|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.Release|Any CPU.Build.0 = Release|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {152C761A-DD2E-4C1F-AF89-DFB2547A3BCA}.Release|x86.ActiveCfg = Release|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.Debug|x86.ActiveCfg = Debug|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.EFMigrations|Any CPU.ActiveCfg = EFMigrations|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.EFMigrations|Any CPU.Build.0 = EFMigrations|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.EFMigrations|Mixed Platforms.ActiveCfg = EFMigrations|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.EFMigrations|Mixed Platforms.Build.0 = EFMigrations|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.EFMigrations|x86.ActiveCfg = EFMigrations|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.PluginDev|Any CPU.ActiveCfg = PluginDev|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.PluginDev|Any CPU.Build.0 = PluginDev|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.PluginDev|Mixed Platforms.ActiveCfg = PluginDev|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.PluginDev|Mixed Platforms.Build.0 = PluginDev|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.PluginDev|x86.ActiveCfg = PluginDev|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.Release|Any CPU.Build.0 = Release|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {75FD4163-333C-4DD5-854D-2EF294E45D94}.Release|x86.ActiveCfg = Release|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.Debug|x86.ActiveCfg = Debug|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.EFMigrations|Any CPU.ActiveCfg = EFMigrations|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.EFMigrations|Any CPU.Build.0 = EFMigrations|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.EFMigrations|Mixed Platforms.ActiveCfg = EFMigrations|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.EFMigrations|Mixed Platforms.Build.0 = EFMigrations|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.EFMigrations|x86.ActiveCfg = EFMigrations|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.PluginDev|Any CPU.ActiveCfg = PluginDev|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.PluginDev|Any CPU.Build.0 = PluginDev|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.PluginDev|Mixed Platforms.ActiveCfg = PluginDev|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.PluginDev|Mixed Platforms.Build.0 = PluginDev|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.PluginDev|x86.ActiveCfg = PluginDev|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.Release|Any CPU.Build.0 = Release|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {692E9D31-1393-47BF-B372-63F671052F89}.Release|x86.ActiveCfg = Release|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.Debug|x86.ActiveCfg = Debug|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.EFMigrations|Any CPU.ActiveCfg = EFMigrations|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.EFMigrations|Any CPU.Build.0 = EFMigrations|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.EFMigrations|Mixed Platforms.ActiveCfg = EFMigrations|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.EFMigrations|Mixed Platforms.Build.0 = EFMigrations|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.EFMigrations|x86.ActiveCfg = EFMigrations|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.PluginDev|Any CPU.ActiveCfg = PluginDev|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.PluginDev|Any CPU.Build.0 = PluginDev|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.PluginDev|Mixed Platforms.ActiveCfg = PluginDev|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.PluginDev|Mixed Platforms.Build.0 = PluginDev|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.PluginDev|x86.ActiveCfg = PluginDev|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.Release|Any CPU.Build.0 = Release|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2}.Release|x86.ActiveCfg = Release|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.Debug|x86.ActiveCfg = Debug|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.EFMigrations|Any CPU.ActiveCfg = EFMigrations|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.EFMigrations|Any CPU.Build.0 = EFMigrations|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.EFMigrations|Mixed Platforms.ActiveCfg = EFMigrations|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.EFMigrations|Mixed Platforms.Build.0 = EFMigrations|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.EFMigrations|x86.ActiveCfg = EFMigrations|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.PluginDev|Any CPU.ActiveCfg = PluginDev|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.PluginDev|Any CPU.Build.0 = PluginDev|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.PluginDev|Mixed Platforms.ActiveCfg = PluginDev|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.PluginDev|Mixed Platforms.Build.0 = PluginDev|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.PluginDev|x86.ActiveCfg = PluginDev|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.Release|Any CPU.Build.0 = Release|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {ACC1E122-B2C8-4441-BDED-D4A77763331A}.Release|x86.ActiveCfg = Release|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.Debug|Any CPU.Build.0 = Debug|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.Debug|x86.ActiveCfg = Debug|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.EFMigrations|Any CPU.ActiveCfg = EFMigrations|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.EFMigrations|Any CPU.Build.0 = EFMigrations|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.EFMigrations|Mixed Platforms.ActiveCfg = EFMigrations|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.EFMigrations|Mixed Platforms.Build.0 = EFMigrations|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.EFMigrations|x86.ActiveCfg = EFMigrations|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.PluginDev|Any CPU.ActiveCfg = PluginDev|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.PluginDev|Any CPU.Build.0 = PluginDev|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.PluginDev|Mixed Platforms.ActiveCfg = PluginDev|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.PluginDev|Mixed Platforms.Build.0 = PluginDev|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.PluginDev|x86.ActiveCfg = PluginDev|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.Release|Any CPU.ActiveCfg = Release|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.Release|Any CPU.Build.0 = Release|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {542B9C12-E2A1-49BB-9134-0E3484F9D669}.Release|x86.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {692E9D31-1393-47BF-B372-63F671052F89} = {7881B112-7843-4542-B1F7-F99553FB9BB7} + {94D1BEEB-64A3-4EB6-9017-D66AFAF4F2B2} = {7881B112-7843-4542-B1F7-F99553FB9BB7} + {ACC1E122-B2C8-4441-BDED-D4A77763331A} = {7881B112-7843-4542-B1F7-F99553FB9BB7} + {542B9C12-E2A1-49BB-9134-0E3484F9D669} = {7881B112-7843-4542-B1F7-F99553FB9BB7} + EndGlobalSection + GlobalSection(NDepend) = preSolution + Project = ".\SmartStoreNET.ndproj" + EndGlobalSection +EndGlobal From 9de2317cb37303ace68b9b357acaf98722423dcd Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 5 Nov 2015 18:41:41 +0100 Subject: [PATCH 174/874] Web-API: Added OData action to add a shipment to an order and to set it as shipped --- .../Orders/IOrderProcessingService.cs | 9 ++ .../Orders/OrderProcessingService.cs | 72 ++++++++++++++ .../Controllers/OData/OrdersController.cs | 25 +++++ .../WebApiConfigurationProvider.cs | 6 ++ src/Plugins/SmartStore.WebApi/changelog.md | 1 + .../WebApi/OData/WebApiQueryableAttribute.cs | 13 ++- .../WebApi/WebApiEntityController.cs | 17 ++++ .../WebApi/WebApiExtension.cs | 2 + .../Controllers/OrderController.cs | 95 +++++-------------- 9 files changed, 166 insertions(+), 74 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Orders/IOrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/IOrderProcessingService.cs index 9452f42b7b..9c3f825b03 100644 --- a/src/Libraries/SmartStore.Services/Orders/IOrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/IOrderProcessingService.cs @@ -266,5 +266,14 @@ public partial interface IOrderProcessingService /// Shopping cart /// true - OK; false - minimum order total amount is not reached bool ValidateMinOrderTotalAmount(IList cart); + + /// + /// Adds a shipment to an order + /// + /// Order + /// Tracking number + /// Quantities by order item identifiers. null to use the remaining total number of products for each order item. + /// New shipment, null if no shipment was added + Shipment AddShipment(Order order, string trackingNumber, Dictionary quantities); } } diff --git a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs index 4995d1bd38..7c0e0f4bf6 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs @@ -2736,6 +2736,78 @@ public virtual bool ValidateMinOrderTotalAmount(IList return true; } + public virtual Shipment AddShipment(Order order, string trackingNumber, Dictionary quantities) + { + Guard.ArgumentNotNull(() => order); + + Shipment shipment = null; + decimal? totalWeight = null; + + foreach (var orderItem in order.OrderItems) + { + if (!orderItem.Product.IsShipEnabled) + continue; + + //ensure that this product can be shipped (have at least one item to ship) + var maxQtyToAdd = orderItem.GetTotalNumberOfItemsCanBeAddedToShipment(); + if (maxQtyToAdd <= 0) + continue; + + var qtyToAdd = 0; + + if (quantities != null && quantities.ContainsKey(orderItem.Id)) + qtyToAdd = quantities[orderItem.Id]; + else if (quantities == null) + qtyToAdd = maxQtyToAdd; + + if (qtyToAdd <= 0) + continue; + + if (qtyToAdd > maxQtyToAdd) + qtyToAdd = maxQtyToAdd; + + var orderItemTotalWeight = orderItem.ItemWeight.HasValue ? orderItem.ItemWeight * qtyToAdd : null; + if (orderItemTotalWeight.HasValue) + { + if (!totalWeight.HasValue) + totalWeight = 0; + totalWeight += orderItemTotalWeight.Value; + } + + if (shipment == null) + { + shipment = new Shipment + { + OrderId = order.Id, + Order = order, // otherwise order updated event would not be fired during InsertShipment + TrackingNumber = trackingNumber, + TotalWeight = null, + ShippedDateUtc = null, + DeliveryDateUtc = null, + CreatedOnUtc = DateTime.UtcNow, + }; + } + + var shipmentItem = new ShipmentItem + { + OrderItemId = orderItem.Id, + Quantity = qtyToAdd + }; + + shipment.ShipmentItems.Add(shipmentItem); + } + + if (shipment != null && shipment.ShipmentItems.Count > 0) + { + shipment.TotalWeight = totalWeight; + _shipmentService.InsertShipment(shipment); + + return shipment; + } + + return null; + } + #endregion } } diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs index d633274612..2cf72507b8 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs @@ -168,5 +168,30 @@ public Order Cancel(int key) }); return entity; } + + [HttpPost] + public SingleResult AddShipment(int key, ODataActionParameters parameters) + { + var result = GetSingleResult(key); + var order = GetExpandedEntity(key, result, "OrderItems, OrderItems.Product, Shipments, Shipments.ShipmentItems"); + + this.ProcessEntity(() => + { + if (order.HasItemsToAddToShipment()) + { + var trackingNumber = parameters.GetValue("TrackingNumber"); + + var shipment = _orderProcessingService.Value.AddShipment(order, trackingNumber, null); + + if (shipment != null) + { + if (parameters.ContainsKey("SetAsShipped") && parameters.GetValue("SetAsShipped")) + _orderProcessingService.Value.Ship(shipment, true); + } + } + return null; + }); + return result; + } } } diff --git a/src/Plugins/SmartStore.WebApi/WebApiConfigurationProvider.cs b/src/Plugins/SmartStore.WebApi/WebApiConfigurationProvider.cs index db3d1f3377..7440806c04 100644 --- a/src/Plugins/SmartStore.WebApi/WebApiConfigurationProvider.cs +++ b/src/Plugins/SmartStore.WebApi/WebApiConfigurationProvider.cs @@ -35,6 +35,12 @@ private void AddActionsToOrder(EntityTypeConfiguration config) config.Action("Cancel") .ReturnsFromEntitySet(WebApiOdataEntitySet.Orders); + + var addShipment = config.Action("AddShipment") + .ReturnsFromEntitySet(WebApiOdataEntitySet.Orders); + + addShipment.Parameter("TrackingNumber"); + addShipment.Parameter("SetAsShipped"); } private void AddActionsToProduct(EntityTypeConfiguration config) diff --git a/src/Plugins/SmartStore.WebApi/changelog.md b/src/Plugins/SmartStore.WebApi/changelog.md index 2191a7d055..c844fe4dbb 100644 --- a/src/Plugins/SmartStore.WebApi/changelog.md +++ b/src/Plugins/SmartStore.WebApi/changelog.md @@ -3,6 +3,7 @@ ##Web Api 2.2.0.4 ###New Features * Added OData endpoint for shipment items +* Added OData action to add a shipment to an order and to set it as shipped ##Web Api 2.2.0.3 ###New Features diff --git a/src/Presentation/SmartStore.Web.Framework/WebApi/OData/WebApiQueryableAttribute.cs b/src/Presentation/SmartStore.Web.Framework/WebApi/OData/WebApiQueryableAttribute.cs index f3487d624a..b71ce1700a 100644 --- a/src/Presentation/SmartStore.Web.Framework/WebApi/OData/WebApiQueryableAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/WebApi/OData/WebApiQueryableAttribute.cs @@ -17,11 +17,16 @@ protected virtual bool MissingClientPaging(HttpActionExecutedContext actionExecu try { - var responseContent = actionExecutedContext.Response.Content as ObjectContent; - bool singleResult = (responseContent != null && responseContent.Value is SingleResult); + var content = actionExecutedContext.Response.Content as ObjectContent; - if (singleResult) - return false; // 'true' would result in a 500 'internal server error' + if (content != null) + { + if (content.Value is HttpError) + return false; + + if (content.Value is SingleResult) + return false; // 'true' would result in a 500 'internal server error' + } var query = actionExecutedContext.Request.RequestUri.Query; diff --git a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs index 894b9d9400..4354426a9f 100644 --- a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs +++ b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs @@ -258,6 +258,23 @@ protected internal virtual TEntity GetExpandedEntity(int key, string properties) return entity; } + protected internal virtual TEntity GetExpandedEntity(int key, SingleResult result, string path) + { + var query = result.Queryable; + + foreach (var property in path.SplitSafe(",")) + { + query = query.Expand(property.Trim()); + } + + var entity = query.FirstOrDefault(x => x.Id == key); + + if (entity == null) + throw ExceptionEntityNotFound(key); + + return entity; + } + protected internal virtual TProperty GetExpandedProperty(int key, Expression> path) { var entity = GetExpandedEntity(key, path); diff --git a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiExtension.cs b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiExtension.cs index 9051894b20..9b9c41a1c4 100644 --- a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiExtension.cs +++ b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiExtension.cs @@ -1,10 +1,12 @@ using System; +using System.Linq; using System.Linq.Expressions; using System.Net; using System.Net.Http; using System.Reflection; using System.Web.Http; using System.Web.Http.OData.Routing; +using SmartStore.Core; using SmartStore.Utilities; namespace SmartStore.Web.Framework.WebApi diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs index 186f1905b3..f2a096e559 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs @@ -2119,84 +2119,39 @@ public ActionResult AddShipment(int orderId, FormCollection form, bool continueE var order = _orderService.GetOrderById(orderId); if (order == null) - //No order found with the specified id return RedirectToAction("List"); - Shipment shipment = null; - - decimal? totalWeight = null; - foreach (var orderItem in order.OrderItems) - { - //is shippable - if (!orderItem.Product.IsShipEnabled) - continue; - - //ensure that this product can be shipped (have at least one item to ship) - var maxQtyToAdd = orderItem.GetTotalNumberOfItemsCanBeAddedToShipment(); - if (maxQtyToAdd <= 0) - continue; - - int qtyToAdd = 0; //parse quantity - foreach (string formKey in form.AllKeys) - if (formKey.Equals(string.Format("qtyToAdd{0}", orderItem.Id), StringComparison.InvariantCultureIgnoreCase)) - { - int.TryParse(form[formKey], out qtyToAdd); - break; - } - - //validate quantity - if (qtyToAdd <= 0) - continue; - if (qtyToAdd > maxQtyToAdd) - qtyToAdd = maxQtyToAdd; - - //ok. we have at least one item. let's create a shipment (if it does not exist) + var quantities = new Dictionary(); + var trackingNumber = form["TrackingNumber"]; - var orderItemTotalWeight = orderItem.ItemWeight.HasValue ? orderItem.ItemWeight * qtyToAdd : null; - if (orderItemTotalWeight.HasValue) - { - if (!totalWeight.HasValue) - totalWeight = 0; - totalWeight += orderItemTotalWeight.Value; - } + foreach (var orderItem in order.OrderItems) + { + foreach (string formKey in form.AllKeys) + { + if (formKey.Equals(string.Format("qtyToAdd{0}", orderItem.Id), StringComparison.InvariantCultureIgnoreCase)) + { + quantities.Add(orderItem.Id, form[formKey].ToInt()); + break; + } + } + } - if (shipment == null) - { - shipment = new Shipment() - { - OrderId = order.Id, - TrackingNumber = form["TrackingNumber"], - TotalWeight = null, - ShippedDateUtc = null, - DeliveryDateUtc = null, - CreatedOnUtc = DateTime.UtcNow, - }; - } - //create a shipment item - var shipmentItem = new ShipmentItem() - { - OrderItemId = orderItem.Id, - Quantity = qtyToAdd, - }; - shipment.ShipmentItems.Add(shipmentItem); - } + var shipment = _orderProcessingService.AddShipment(order, trackingNumber, quantities); - //if we have at least one item in the shipment, then save it - if (shipment != null && shipment.ShipmentItems.Count > 0) - { - shipment.TotalWeight = totalWeight; - _shipmentService.InsertShipment(shipment); + if (shipment != null) + { + NotifySuccess(_localizationService.GetResource("Admin.Orders.Shipments.Added")); - NotifySuccess(_localizationService.GetResource("Admin.Orders.Shipments.Added")); - return continueEditing - ? RedirectToAction("ShipmentDetails", new {id = shipment.Id}) - : RedirectToAction("Edit", new { id = orderId }); - } - else - { + return continueEditing + ? RedirectToAction("ShipmentDetails", new { id = shipment.Id }) + : RedirectToAction("Edit", new { id = orderId }); + } + else + { NotifyError(_localizationService.GetResource("Admin.Orders.Shipments.NoProductsSelected")); + return RedirectToAction("AddShipment", new { orderId = orderId }); - } + } } public ActionResult ShipmentDetails(int id) From 2abf201e18d5042ba8b134178f70079d61bf7e4b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 5 Nov 2015 21:48:11 +0100 Subject: [PATCH 175/874] Web-API: OData actions should return SingleResult (instead of entity instance) to let expand option be recognized Fixed "Member 'CurrentValues' cannot be called for the entity of type 'TEntity' because the entity does not exist in the context. To add an entity to the context call the Add or Attach method of DbSet." --- .../SmartStore.Data/ObjectContextBase.cs | 18 ++++--- .../Controllers/OData/OrdersController.cs | 49 +++++++++++-------- src/Plugins/SmartStore.WebApi/changelog.md | 2 + 3 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/Libraries/SmartStore.Data/ObjectContextBase.cs b/src/Libraries/SmartStore.Data/ObjectContextBase.cs index 5fff7f7588..5f722e155f 100644 --- a/src/Libraries/SmartStore.Data/ObjectContextBase.cs +++ b/src/Libraries/SmartStore.Data/ObjectContextBase.cs @@ -333,12 +333,18 @@ public IDictionary GetModifiedProperties(BaseEntity entity) var props = new Dictionary(); var entry = this.Entry(entity); - var modifiedPropertyNames = from p in entry.CurrentValues.PropertyNames - where entry.Property(p).IsModified - select p; - foreach (var name in modifiedPropertyNames) + + // be aware of the entity state. you cannot get modified properties for detached entities. + if (entry.State != System.Data.Entity.EntityState.Detached) { - props.Add(name, entry.Property(name).OriginalValue); + var modifiedPropertyNames = from p in entry.CurrentValues.PropertyNames + where entry.Property(p).IsModified + select p; + + foreach (var name in modifiedPropertyNames) + { + props.Add(name, entry.Property(name).OriginalValue); + } } return props; @@ -381,7 +387,7 @@ public override Task SaveChangesAsync() return result; } - // codehint: sm-add (required for UoW implementation) + // required for UoW implementation public string Alias { get; internal set; } // performance on bulk inserts diff --git a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs index 2cf72507b8..f0f1c0eb9b 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/OData/OrdersController.cs @@ -95,24 +95,26 @@ public IQueryable GetOrderItems(int key) // actions [HttpPost] - public Order PaymentPending(int key) + public SingleResult PaymentPending(int key) { - var entity = GetEntityByKeyNotNull(key); + var result = GetSingleResult(key); + var order = GetExpandedEntity(key, result, null); this.ProcessEntity(() => { - entity.PaymentStatus = PaymentStatus.Pending; - Service.UpdateOrder(entity); + order.PaymentStatus = PaymentStatus.Pending; + Service.UpdateOrder(order); return null; }); - return entity; + return result; } [HttpPost] - public Order PaymentPaid(int key, ODataActionParameters parameters) + public SingleResult PaymentPaid(int key, ODataActionParameters parameters) { - var entity = GetEntityByKeyNotNull(key); + var result = GetSingleResult(key); + var order = GetExpandedEntity(key, result, null); this.ProcessEntity(() => { @@ -120,20 +122,23 @@ public Order PaymentPaid(int key, ODataActionParameters parameters) if (paymentMethodName != null) { - entity.PaymentMethodSystemName = paymentMethodName; - Service.UpdateOrder(entity); + order.PaymentMethodSystemName = paymentMethodName; + Service.UpdateOrder(order); } - _orderProcessingService.Value.MarkOrderAsPaid(entity); + _orderProcessingService.Value.MarkOrderAsPaid(order); + return null; }); - return entity; + + return result; } [HttpPost] - public Order PaymentRefund(int key, ODataActionParameters parameters) + public SingleResult PaymentRefund(int key, ODataActionParameters parameters) { - var entity = GetEntityByKeyNotNull(key); + var result = GetSingleResult(key); + var order = GetExpandedEntity(key, result, null); this.ProcessEntity(() => { @@ -141,32 +146,35 @@ public Order PaymentRefund(int key, ODataActionParameters parameters) if (online) { - var errors = _orderProcessingService.Value.Refund(entity); + var errors = _orderProcessingService.Value.Refund(order); if (errors.Count > 0) return errors[0]; } else { - _orderProcessingService.Value.RefundOffline(entity); + _orderProcessingService.Value.RefundOffline(order); } return null; }); - return entity; + + return result; } [HttpPost] - public Order Cancel(int key) + public SingleResult Cancel(int key) { - var entity = GetEntityByKeyNotNull(key); + var result = GetSingleResult(key); + var order = GetExpandedEntity(key, result, "OrderItems, OrderItems.Product"); this.ProcessEntity(() => { - _orderProcessingService.Value.CancelOrder(entity, true); + _orderProcessingService.Value.CancelOrder(order, true); return null; }); - return entity; + + return result; } [HttpPost] @@ -191,6 +199,7 @@ public SingleResult AddShipment(int key, ODataActionParameters parameters } return null; }); + return result; } } diff --git a/src/Plugins/SmartStore.WebApi/changelog.md b/src/Plugins/SmartStore.WebApi/changelog.md index c844fe4dbb..23143ca0fc 100644 --- a/src/Plugins/SmartStore.WebApi/changelog.md +++ b/src/Plugins/SmartStore.WebApi/changelog.md @@ -4,6 +4,8 @@ ###New Features * Added OData endpoint for shipment items * Added OData action to add a shipment to an order and to set it as shipped +###Improvements +* OData actions should return SingleResult (instead of entity instance) to let expand option be recognized ##Web Api 2.2.0.3 ###New Features From 0fee2bbc2539a43a975bcadaab5ae49b7a52b73b Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 5 Nov 2015 23:21:01 +0100 Subject: [PATCH 176/874] Export framework: refactoring --- .../SmartStore.Core/ComponentModel/Expando.cs | 279 +++++++++--------- .../ComponentModel/PropertyBag.cs | 3 +- .../Extensions/TypeExtensions.cs | 18 +- .../SmartStore.Core/Utilities/TypeHelper.cs | 1 + ...portService.cs => ExportProfileService.cs} | 4 +- .../DataExchange/ExportSegmenter.cs | 4 +- .../ExportTask/ExportProfileTask.cs | 16 +- .../DataExchange/IDataExporter.cs | 48 +++ ...ortService.cs => IExportProfileService.cs} | 2 +- .../DataExchange/Internal/DynamicEntity.cs | 53 ++++ .../DataExchange/Internal/ExpandoEntity.cs | 25 -- .../DataExchange/Internal/ExpandoHelpers.cs | 22 +- .../SmartStore.Services.csproj | 7 +- .../Tasks/TaskExecutionContext.cs | 30 +- .../FeedGoogleMerchantCenterController.cs | 10 +- ...XmlProvider.cs => GmcExportXmlProvider.cs} | 4 +- .../SmartStore.GoogleMerchantCenter.csproj | 3 +- .../DependencyRegistrar.cs | 2 +- .../Controllers/ExportController.cs | 4 +- 19 files changed, 293 insertions(+), 242 deletions(-) rename src/Libraries/SmartStore.Services/DataExchange/{ExportService.cs => ExportProfileService.cs} (98%) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs rename src/Libraries/SmartStore.Services/DataExchange/{IExportService.cs => IExportProfileService.cs} (98%) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs delete mode 100644 src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoEntity.cs rename src/Plugins/SmartStore.GoogleMerchantCenter/Providers/{ProductExportXmlProvider.cs => GmcExportXmlProvider.cs} (99%) diff --git a/src/Libraries/SmartStore.Core/ComponentModel/Expando.cs b/src/Libraries/SmartStore.Core/ComponentModel/Expando.cs index c696b77e66..c97e82ed75 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/Expando.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/Expando.cs @@ -36,7 +36,6 @@ using System.Linq; using System.Dynamic; using System.Reflection; -using Fasterflect; namespace SmartStore.ComponentModel { @@ -59,7 +58,7 @@ namespace SmartStore.ComponentModel /// Dictionary: Any of the extended properties are accessible via IDictionary interface /// [Serializable] - public class Expando : DynamicObject, IDynamicMetaObjectProvider + public class Expando : DynamicObject { /// /// Instance of object passed in @@ -71,7 +70,7 @@ public class Expando : DynamicObject, IDynamicMetaObjectProvider /// private Type _instanceType; - private IList _instancePropertyInfo; + private IList _instancePropertyInfos; /// /// String Dictionary that contains the extra dynamic values @@ -104,7 +103,6 @@ public Expando(object instance) Initialize(instance); } - protected virtual void Initialize(object instance) { _instance = instance; @@ -117,13 +115,13 @@ protected object WrappedObject get { return _instance; } } - IList InstancePropertyInfo + IList InstancePropertyInfos { get { - if (_instancePropertyInfo == null && _instance != null) - _instancePropertyInfo = _instance.GetType().Properties(Flags.InstancePublicDeclaredOnly); - return _instancePropertyInfo; + if (_instancePropertyInfos == null && _instance != null) + _instancePropertyInfos = Fasterflect.PropertyExtensions.Properties(_instance.GetType(), Fasterflect.Flags.InstancePublicDeclaredOnly); + return _instancePropertyInfos; } } @@ -143,68 +141,76 @@ public override IEnumerable GetDynamicMemberNames() /// public override bool TryGetMember(GetMemberBinder binder, out object result) { - result = null; - - // first check the Properties collection for member - if (Properties.Keys.Contains(binder.Name)) - { - result = Properties[binder.Name]; - return true; - } - - - // Next check for Public properties via Reflection - if (_instance != null) - { - try - { - return GetProperty(_instance, binder.Name, out result); - } - catch { } - } - - // failed to retrieve a property - result = null; - return false; + return TryGetMemberCore(binder.Name, out result); } + protected virtual bool TryGetMemberCore(string name, out object result) + { + result = null; + + // first check the Properties collection for member + if (Properties.Keys.Contains(name)) + { + result = Properties[name]; + return true; + } + + // Next check for public properties via Reflection + if (_instance != null) + { + try + { + return GetProperty(_instance, name, out result); + } + catch { } + } + + // failed to retrieve a property + result = null; + return false; + } - /// - /// Property setter implementation tries to retrieve value from instance - /// first then into this object - /// - /// - /// - /// - public override bool TrySetMember(SetMemberBinder binder, object value) - { - - // first check to see if there's a native property to set - if (_instance != null) - { - try - { - bool result = SetProperty(_instance, binder.Name, value); - if (result) - return true; - } - catch { } - } - // no match - set or add to dictionary - Properties[binder.Name] = value; - return true; + /// + /// Property setter implementation tries to retrieve value from instance + /// first then into this object + /// + /// + /// + /// + public override bool TrySetMember(SetMemberBinder binder, object value) + { + return TrySetMemberCore(binder.Name, value); } - /// - /// Dynamic invocation method. Currently allows only for Reflection based - /// operation (no ability to add methods dynamically). - /// - /// - /// - /// - /// - public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) + protected virtual bool TrySetMemberCore(string name, object value) + { + // first check to see if there's a native property to set + if (_instance != null) + { + try + { + bool result = SetProperty(_instance, name, value); + if (result) + return true; + } + catch { } + } + + // no match - set or add to dictionary + Properties[name] = value; + return true; + } + + /// + /// Dynamic invocation method. Currently allows only for Reflection based + /// operation (no ability to add methods dynamically). + /// + /// + /// + /// + /// + public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) { if (_instance != null) { @@ -231,13 +237,10 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o /// protected bool GetProperty(object instance, string name, out object result) { - if (instance == null) - instance = this; - - var pi = _instanceType.Property(name, Flags.InstancePublic); + var pi = GetPropertyInfo(name); if (pi != null) { - result = instance.GetPropertyValue(pi.Name); + result = Fasterflect.PropertyExtensions.GetPropertyValue(instance ?? this, pi.Name); return true; } @@ -245,6 +248,16 @@ protected bool GetProperty(object instance, string name, out object result) return false; } + /// + /// Gets a PropertyInfo instance from the wrapped object + /// + /// Property name + protected PropertyInfo GetPropertyInfo(string name) + { + var pi = Fasterflect.PropertyExtensions.Property(_instanceType, name, Fasterflect.Flags.InstancePublic); + return pi; + } + /// /// Reflection helper method to set a property value /// @@ -254,13 +267,10 @@ protected bool GetProperty(object instance, string name, out object result) /// protected bool SetProperty(object instance, string name, object value) { - if (instance == null) - instance = this; - - var pi = _instanceType.Property(name, Flags.InstancePublic); + var pi = GetPropertyInfo(name); if (pi != null) { - instance.SetPropertyValue(pi.Name, value); + Fasterflect.PropertyInfoExtensions.Set(pi, instance ?? this, value); return true; } return false; @@ -276,16 +286,12 @@ protected bool SetProperty(object instance, string name, object value) /// protected bool InvokeMethod(object instance, string name, object[] args, out object result) { - if (instance == null) - instance = this; - // Look at the instanceType - var mi = _instanceType.Method(name, Flags.InstancePublic); - + var mi = Fasterflect.MethodExtensions.Method(_instanceType, name, Fasterflect.Flags.InstancePublic); if (mi != null) { - result = _instance.CallMethod(mi.Name, args); - return true; + result = Fasterflect.MethodInfoExtensions.Call(mi, instance ?? this, args); + return true; } result = null; @@ -293,73 +299,54 @@ protected bool InvokeMethod(object instance, string name, object[] args, out obj } - /// - /// Convenience method that provides a string Indexer - /// to the Properties collection AND the strongly typed - /// properties of the object by name. - /// - /// // dynamic - /// exp["Address"] = "112 nowhere lane"; - /// // strong - /// var name = exp["StronglyTypedProperty"] as string; - /// - /// - /// The getter checks the Properties dictionary first - /// then looks in PropertyInfo for properties. - /// The setter checks the instance properties before - /// checking the Properties dictionary. - /// - /// - /// - /// - public object this[string key] - { - get - { - try - { - // try to get from properties collection first - return Properties[key]; - } - catch (KeyNotFoundException) - { - // try reflection on instanceType - object result = null; - if (GetProperty(_instance, key, out result)) - return result; - - // no doesn't exist - throw; - } - } - set - { - if (Properties.ContainsKey(key)) - { - Properties[key] = value; - return; - } - - // check instance for existance of type first - var pi = _instanceType.Property(key, Flags.Public); - if (pi != null) - SetProperty(_instance, key, value); - else - Properties[key] = value; - } - } + /// + /// Convenience method that provides a string Indexer + /// to the Properties collection AND the strongly typed + /// properties of the object by name. + /// + /// // dynamic + /// exp["Address"] = "112 nowhere lane"; + /// // strong + /// var name = exp["StronglyTypedProperty"] as string; + /// + /// + /// The getter checks the Properties dictionary first + /// then looks in PropertyInfo for properties. + /// The setter checks the instance properties before + /// checking the Properties dictionary. + /// + /// + /// + /// + public object this[string key] + { + get + { + object result = null; + if (TryGetMemberCore(key, out result)) + { + return result; + } + + throw new KeyNotFoundException(); + } + set + { + TrySetMemberCore(key, value); + } + } - /// - /// Returns and the properties of - /// - /// - /// - public IEnumerable> GetProperties(bool includeInstanceProperties = false) + /// + /// Returns all properties + /// + /// + /// + public IEnumerable> GetProperties(bool includeInstanceProperties = false) { if (includeInstanceProperties && _instance != null) { - foreach (var prop in this.InstancePropertyInfo) + foreach (var prop in this.InstancePropertyInfos) yield return new KeyValuePair(prop.Name, prop.GetValue(_instance, null)); } @@ -388,13 +375,13 @@ public bool Contains(KeyValuePair item, bool includeInstanceProp /// public bool Contains(string propertyName, bool includeInstanceProperties = false) { - bool res = Properties.ContainsKey(propertyName); - if (res) + bool contains = Properties.ContainsKey(propertyName); + if (contains) return true; if (includeInstanceProperties && _instance != null) { - foreach (var prop in this.InstancePropertyInfo) + foreach (var prop in this.InstancePropertyInfos) { if (prop.Name == propertyName) return true; diff --git a/src/Libraries/SmartStore.Core/ComponentModel/PropertyBag.cs b/src/Libraries/SmartStore.Core/ComponentModel/PropertyBag.cs index fb2ab77767..d67fbb85b3 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/PropertyBag.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/PropertyBag.cs @@ -306,8 +306,7 @@ public bool FromXml(string xml) if (string.IsNullOrEmpty(xml)) return true; - var result = SerializationUtils.DeSerializeObject(xml, - this.GetType()) as PropertyBag; + var result = SerializationUtils.DeSerializeObject(xml, this.GetType()) as PropertyBag; if (result != null) { foreach (var item in result) diff --git a/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs index 69752367c2..d2ca8d9df9 100644 --- a/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs @@ -45,15 +45,8 @@ public static bool IsPredefinedSimpleType(this Type type) { return true; } + return s_predefinedTypes.Any(t => t == type); - //foreach (Type type2 in s_predefinedTypes) - //{ - // if (type2 == type) - // { - // return true; - // } - //} - //return false; } public static bool IsStruct(this Type type) @@ -75,15 +68,8 @@ public static bool IsPredefinedGenericType(this Type type) { return false; } + return s_predefinedGenericTypes.Any(t => t == type); - //foreach (Type type2 in s_predefinedGenericTypes) - //{ - // if (type2 == type) - // { - // return true; - // } - //} - //return false; } public static bool IsPredefinedType(this Type type) diff --git a/src/Libraries/SmartStore.Core/Utilities/TypeHelper.cs b/src/Libraries/SmartStore.Core/Utilities/TypeHelper.cs index a887d720c1..34dbf27b18 100644 --- a/src/Libraries/SmartStore.Core/Utilities/TypeHelper.cs +++ b/src/Libraries/SmartStore.Core/Utilities/TypeHelper.cs @@ -61,6 +61,7 @@ public static Type GetElementType(Type type) return GetElementType(type3); } } + return type; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs similarity index 98% rename from src/Libraries/SmartStore.Services/DataExchange/ExportService.cs rename to src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs index aea03c6fa9..dac3287770 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs @@ -16,7 +16,7 @@ namespace SmartStore.Services.DataExchange { - public partial class ExportService : IExportService + public partial class ExportProfileService : IExportProfileService { private const string _defaultFileNamePattern = "%Store.Id%-%ExportProfile.Id%-%Misc.FileNumber%-%ExportProfile.SeoName%"; @@ -28,7 +28,7 @@ public partial class ExportService : IExportService private readonly DataExchangeSettings _dataExchangeSettings; private readonly ILocalizationService _localizationService; - public ExportService( + public ExportProfileService( IRepository exportProfileRepository, IRepository exportDeploymentRepository, IEventPublisher eventPublisher, diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs index b86df2cb45..6d5c6fb937 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs @@ -9,7 +9,7 @@ public interface IExportSegmenterConsumer /// /// Total number of records /// - int RecordTotal { get; } + int TotalRecords { get; } /// /// Gets current data segment @@ -78,7 +78,7 @@ public ExportSegmenter( /// /// Total number of records /// - public int RecordTotal + public int TotalRecords { get { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index d8bd7d7d4a..e4e9d02f8f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -65,7 +65,7 @@ public class ExportProfileTask : ITask private ExpandoHelpers _expandoHelpers; private ICommonServices _services; - private Lazy _exportService; + private Lazy _exportProfileService; private Lazy _dataExchangeSettings; private Lazy _productService; private Lazy _pictureService; @@ -101,7 +101,7 @@ public class ExportProfileTask : ITask private void InitDependencies(TaskExecutionContext context) { _services = context.Resolve(); - _exportService = context.Resolve>(); + _exportProfileService = context.Resolve>(); _dataExchangeSettings = context.Resolve>(); _productService = context.Resolve>(); _pictureService = context.Resolve>(); @@ -2546,7 +2546,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) throw new SmartException("Unsupported entity type '{0}'".FormatInvariant(ctx.Provider.Value.EntityType.ToString())); } - if (segmenter.RecordTotal <= 0) + if (segmenter.TotalRecords <= 0) { ctx.Log.Information("There are no records to export"); } @@ -2735,7 +2735,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) { ctx.Profile.ResultInfo = XmlHelper.Serialize(ctx.Result); - _exportService.Value.UpdateExportProfile(ctx.Profile); + _exportProfileService.Value.UpdateExportProfile(ctx.Profile); } } catch { } @@ -2882,12 +2882,12 @@ public void Execute(TaskExecutionContext taskContext) InitDependencies(taskContext); var profileId = taskContext.ScheduleTask.Alias.ToInt(); - var profile = _exportService.Value.GetExportProfileById(profileId); + var profile = _exportProfileService.Value.GetExportProfileById(profileId); var selectedIdsCacheKey = profile.GetSelectedEntityIdsCacheKey(); var selectedEntityIds = HttpRuntime.Cache[selectedIdsCacheKey] as string; - var provider = _exportService.Value.LoadProvider(profile.ProviderSystemName); + var provider = _exportProfileService.Value.LoadProvider(profile.ProviderSystemName); if (provider == null) throw new SmartException(T("Admin.Common.ProviderNotLoaded", profile.ProviderSystemName.NaIfEmpty())); @@ -2929,13 +2929,13 @@ public ExportExecuteResult Execute( InitDependencies(taskContext); - var provider = _exportService.Value.LoadProvider(providerSystemName); + var provider = _exportProfileService.Value.LoadProvider(providerSystemName); if (provider == null) throw new SmartException(T("Admin.Common.ProviderNotLoaded", providerSystemName.NaIfEmpty())); if (profile == null) - profile = _exportService.Value.CreateVolatileProfile(provider); + profile = _exportProfileService.Value.CreateVolatileProfile(provider); var ctx = new ExportProfileTaskContext(taskContext, profile, provider, selectedEntityIds); ctx.QueryProducts = queryProducts; diff --git a/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs new file mode 100644 index 0000000000..a2a31ac863 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs @@ -0,0 +1,48 @@ +using SmartStore.Core.Domain; +using SmartStore.Core.Plugins; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace SmartStore.Services.DataExchange +{ + public delegate void ProgressSetter(int value, int maximum, string message); + + public interface IDataExporter + { + void Export(DataExportRequest request, CancellationToken cancellationToken); + } + + public class DataExportRequest + { + private readonly static ProgressSetter _voidProgressSetter = DataExportRequest.SetProgress; + + public DataExportRequest(ExportProfile profile) + { + Guard.ArgumentNotNull(() => profile); + + CustomData = new Dictionary(StringComparer.OrdinalIgnoreCase); + ProgressSetter = _voidProgressSetter; + Profile = profile; + } + + public ExportProfile Profile { get; private set; } + + public Provider Provider { get; set; } + + public IEnumerable EntitiesToExport { get; set; } + + public ProgressSetter ProgressSetter { get; set; } + + public IDictionary CustomData { get; private set; } + + + private static void SetProgress(int val, int max, string msg) + { + // do nothing + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportProfileService.cs similarity index 98% rename from src/Libraries/SmartStore.Services/DataExchange/IExportService.cs rename to src/Libraries/SmartStore.Services/DataExchange/IExportProfileService.cs index e3823a7c4e..92ccb543a5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IExportService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportProfileService.cs @@ -5,7 +5,7 @@ namespace SmartStore.Services.DataExchange { - public interface IExportService + public interface IExportProfileService { /// /// Creates a volatile export profile diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs new file mode 100644 index 0000000000..0a098c5055 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs @@ -0,0 +1,53 @@ +using SmartStore.ComponentModel; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Dynamic; +using SmartStore.Utilities; + +namespace SmartStore.Services.DataExchange.Internal +{ + internal class DynamicEntity : Expando + { + private readonly object _entity; + + public DynamicEntity(object entity) + : base(entity) + { + } + + // TODO: Umbenennen!!! + public object _Entity + { + get { return base.WrappedObject; } + } + + protected override bool TrySetMemberCore(string name, object value) + { + // Virtual navigation properties of entities should not be set as is. + // A previously created ExpandoEntity instance is set instead. + // But assigning the value to the wrapped entity instance would result + // in a type mismatch error, as both types obviously don't match. + // Therefore we save reference values in the local values dictionary. + + var pi = base.GetPropertyInfo(name); + + if (pi != null && (pi.PropertyType.IsPredefinedType() || !pi.GetMethod.IsVirtual)) + { + // Property exists, is NOT complex, or IS complex but NOT virtual + try + { + Fasterflect.PropertyInfoExtensions.Set(pi, WrappedObject, value); + return true; + } + catch { } + } + + // Property does not exist, or is virtual complex + Properties[name] = value; + return true; + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoEntity.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoEntity.cs deleted file mode 100644 index 565f9e00e5..0000000000 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoEntity.cs +++ /dev/null @@ -1,25 +0,0 @@ -using SmartStore.ComponentModel; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Dynamic; - -namespace SmartStore.Services.DataExchange.Internal -{ - internal class ExpandoEntity : Expando - { - private readonly object _entity; - - public ExpandoEntity(object entity) - : base(entity) - { - } - - public object Entity - { - get { return base.WrappedObject; } - } - } -} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoHelpers.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoHelpers.cs index d8da9e6f8c..657a6675ed 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoHelpers.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoHelpers.cs @@ -42,7 +42,7 @@ public dynamic ToExpando(Currency currency) if (currency == null) return null; - dynamic expando = new ExpandoEntity(currency); + dynamic expando = new DynamicEntity(currency); expando.Name = currency.GetLocalized(x => x.Name, _ctx.Projection.LanguageId ?? 0, true, false); expando._Localized = GetLocalized(currency, x => x.Name); @@ -54,7 +54,7 @@ private dynamic ToExpando(Language language) if (language == null) return null; - dynamic expando = new ExpandoEntity(language); + dynamic expando = new DynamicEntity(language); return expando; } @@ -63,7 +63,7 @@ private dynamic ToExpando(Country country) if (country == null) return null; - dynamic expando = new ExpandoEntity(country); + dynamic expando = new DynamicEntity(country); expando.Name = country.GetLocalized(x => x.Name, _ctx.Projection.LanguageId ?? 0, true, false); expando._Localized = GetLocalized(country, x => x.Name); @@ -75,12 +75,12 @@ private dynamic ToExpando(Address address) if (address == null) return null; - dynamic expando = new ExpandoEntity(address); + dynamic expando = new DynamicEntity(address); expando.Country = ToExpando(address.Country); if (address.StateProvinceId.GetValueOrDefault() > 0) { - dynamic sp = new ExpandoEntity(address.StateProvince); + dynamic sp = new DynamicEntity(address.StateProvince); sp.Name = address.StateProvince.GetLocalized(x => x.Name, _ctx.Projection.LanguageId ?? 0, true, false); sp._Localized = GetLocalized(address.StateProvince, x => x.Name); expando.StateProvince = sp; @@ -98,7 +98,7 @@ private dynamic ToExpando(RewardPointsHistory points) if (points == null) return null; - dynamic expando = new ExpandoEntity(points); + dynamic expando = new DynamicEntity(points); return expando; } @@ -107,7 +107,7 @@ private dynamic ToExpando( Customer customer) if (customer == null) return null; - dynamic expando = new ExpandoEntity(customer); + dynamic expando = new DynamicEntity(customer); expando.BillingAddress = null; expando.ShippingAddress = null; @@ -128,7 +128,7 @@ private dynamic ToExpando(Store store) if (store == null) return null; - dynamic expando = new ExpandoEntity(store); + dynamic expando = new DynamicEntity(store); expando.PrimaryStoreCurrency = ToExpando(store.PrimaryStoreCurrency); expando.PrimaryExchangeRateCurrency = ToExpando(store.PrimaryExchangeRateCurrency); @@ -140,7 +140,7 @@ private dynamic ToExpando(DeliveryTime deliveryTime) if (deliveryTime == null) return null; - dynamic expando = new ExpandoEntity(deliveryTime); + dynamic expando = new DynamicEntity(deliveryTime); expando.Name = deliveryTime.GetLocalized(x => x.Name, _ctx.Projection.LanguageId ?? 0, true, false); expando._Localized = GetLocalized(deliveryTime, x => x.Name); @@ -152,7 +152,7 @@ private dynamic ToExpando(QuantityUnit quantityUnit) if (quantityUnit == null) return null; - dynamic expando = new ExpandoEntity(quantityUnit); + dynamic expando = new DynamicEntity(quantityUnit); expando.Name = quantityUnit.GetLocalized(x => x.Name, _ctx.Projection.LanguageId ?? 0, true, false); expando.Description = quantityUnit.GetLocalized(x => x.Description, _ctx.Projection.LanguageId ?? 0, true, false); expando._Localized = GetLocalized(quantityUnit, @@ -167,7 +167,7 @@ private dynamic ToExpando(Picture picture, int thumbPictureSize, int detailsPict if (picture == null) return null; - dynamic expando = new ExpandoEntity(picture); + dynamic expando = new DynamicEntity(picture); //// TODO!!!!! //expando._ThumbImageUrl = _pictureService.Value.GetPictureUrl(picture, thumbPictureSize, false, _ctx.Store.Url); diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index b5d926c04e..a6af2b2012 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -185,7 +185,8 @@ - + + @@ -198,9 +199,9 @@ - + - + diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs index f18a23e6ca..838065d364 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs @@ -45,6 +45,21 @@ public T ResolveNamed(string name) where T : class public ScheduleTask ScheduleTask { get; set; } + /// + /// Persists a task's progress information to the database + /// + /// Progress value (numerator) + /// Progress maximum (denominator) + /// Progress message. Can be null. + /// if true, saves the updated task entity immediately, or lazily with the next database commit otherwise. + public void SetProgress(int value, int maximum, string message, bool immediately = false) + { + float fraction = (float)value / (float)Math.Max(maximum, 1f); + int percentage = (int)Math.Round(fraction * 100f, 0); + + SetProgress(Math.Min(Math.Max(percentage, 0), 100), message, immediately); + } + /// /// Persists a task's progress information to the database /// @@ -75,20 +90,5 @@ public void SetProgress(int? progress, string message, bool immediately = false catch { } } } - - /// - /// Persists a task's progress information to the database - /// - /// Progress numerator - /// Progress denominator - /// Progress message. Can be null. - /// if true, saves the updated task entity immediately, or lazily with the next database commit otherwise. - public void SetProgress(int numerator, int denominator, string message, bool immediately = false) - { - float fraction = (float)numerator / (float)Math.Max(denominator, 1f); - int percentage = (int)Math.Round(fraction * 100f, 0); - - SetProgress(Math.Min(Math.Max(percentage, 0), 100), message, immediately); - } } } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs index 86eb437175..7a310a4c18 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs @@ -18,12 +18,12 @@ public class FeedGoogleMerchantCenterController : PluginControllerBase { private readonly IGoogleFeedService _googleFeedService; private readonly AdminAreaSettings _adminAreaSettings; - private readonly IExportService _exportService; + private readonly IExportProfileService _exportService; public FeedGoogleMerchantCenterController( IGoogleFeedService googleFeedService, AdminAreaSettings adminAreaSettings, - IExportService exportService) + IExportProfileService exportService) { _googleFeedService = googleFeedService; _adminAreaSettings = adminAreaSettings; @@ -61,7 +61,7 @@ public ActionResult ProductEditTab(int productId) ViewBag.DefaultAgeGroup = T("Common.Auto"); // we do not have export profile context here, so we simply use the first profile - var profile = _exportService.GetExportProfilesBySystemName(ProductExportXmlProvider.SystemName).FirstOrDefault(); + var profile = _exportService.GetExportProfilesBySystemName(GmcExportXmlProvider.SystemName).FirstOrDefault(); if (profile != null) { var config = XmlHelper.Deserialize(profile.ProviderConfigData, typeof(ProfileConfigurationModel)) as ProfileConfigurationModel; @@ -73,12 +73,12 @@ public ActionResult ProductEditTab(int productId) ViewBag.DefaultMaterial = config.Material; ViewBag.DefaultPattern = config.Pattern; - if (config.Gender.HasValue() && config.Gender != ProductExportXmlProvider.Unspecified) + if (config.Gender.HasValue() && config.Gender != GmcExportXmlProvider.Unspecified) { ViewBag.DefaultGender = T("Plugins.Feed.Froogle.Gender" + culture.TextInfo.ToTitleCase(config.Gender)); } - if (config.AgeGroup.HasValue() && config.AgeGroup != ProductExportXmlProvider.Unspecified) + if (config.AgeGroup.HasValue() && config.AgeGroup != GmcExportXmlProvider.Unspecified) { ViewBag.DefaultAgeGroup = T("Plugins.Feed.Froogle.AgeGroup" + culture.TextInfo.ToTitleCase(config.AgeGroup)); } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcExportXmlProvider.cs similarity index 99% rename from src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs rename to src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcExportXmlProvider.cs index bbcb1bcbda..412737afc7 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/ProductExportXmlProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcExportXmlProvider.cs @@ -28,7 +28,7 @@ namespace SmartStore.GoogleMerchantCenter.Providers ExportFeatures.OffersBrandFallback, ExportFeatures.CanIncludeMainPicture, ExportFeatures.UsesSpecialPrice)] - public class ProductExportXmlProvider : IExportProvider + public class GmcExportXmlProvider : IExportProvider { private const string _googleNamespace = "http://base.google.com/ns/1.0"; @@ -36,7 +36,7 @@ public class ProductExportXmlProvider : IExportProvider private readonly IMeasureService _measureService; private readonly MeasureSettings _measureSettings; - public ProductExportXmlProvider( + public GmcExportXmlProvider( IGoogleFeedService googleFeedService, IMeasureService measureService, MeasureSettings measureSettings) diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj index e571790b5d..0826ad5831 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj @@ -42,6 +42,7 @@ + true @@ -175,7 +176,7 @@ - + diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index ee70cae011..1daa71bd03 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -221,7 +221,7 @@ public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, b builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 33d35d9fbb..a244d1f807 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -38,7 +38,7 @@ namespace SmartStore.Admin.Controllers public class ExportController : AdminControllerBase { private readonly ICommonServices _services; - private readonly IExportService _exportService; + private readonly IExportProfileService _exportService; private readonly PluginMediator _pluginMediator; private readonly ICategoryService _categoryService; private readonly IManufacturerService _manufacturerService; @@ -53,7 +53,7 @@ public class ExportController : AdminControllerBase public ExportController( ICommonServices services, - IExportService exportService, + IExportProfileService exportService, PluginMediator pluginMediator, ICategoryService categoryService, IManufacturerService manufacturerService, From cdfb6aad69170a0abd3592945b7c5858919578ae Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 6 Nov 2015 03:14:13 +0100 Subject: [PATCH 177/874] Export framework: further refactoring --- .../{Expando.cs => HybridExpando.cs} | 146 ++++++- .../SmartStore.Core/SmartStore.Core.csproj | 2 +- .../ExportTask/ExportProfileTask.cs | 380 +++++++----------- .../DataExchange/Internal/DynamicEntity.cs | 15 +- .../DataExchange/Internal/ExpandoHelpers.cs | 240 ----------- .../SmartStore.Services.csproj | 5 +- .../Tasks/ITaskExecutor.cs | 6 +- .../Tasks/ITaskScheduler.cs | 3 +- .../Tasks/TaskExecutionContext.cs | 3 + .../SmartStore.Services/Tasks/TaskExecutor.cs | 9 +- .../Tasks/TaskScheduler.cs | 14 +- .../Controllers/TaskSchedulerController.cs | 6 +- 12 files changed, 325 insertions(+), 504 deletions(-) rename src/Libraries/SmartStore.Core/ComponentModel/{Expando.cs => HybridExpando.cs} (80%) delete mode 100644 src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoHelpers.cs diff --git a/src/Libraries/SmartStore.Core/ComponentModel/Expando.cs b/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs similarity index 80% rename from src/Libraries/SmartStore.Core/ComponentModel/Expando.cs rename to src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs index c97e82ed75..42061fad53 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/Expando.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs @@ -36,6 +36,7 @@ using System.Linq; using System.Dynamic; using System.Reflection; +using System.Collections; namespace SmartStore.ComponentModel { @@ -58,7 +59,7 @@ namespace SmartStore.ComponentModel /// Dictionary: Any of the extended properties are accessible via IDictionary interface /// [Serializable] - public class Expando : DynamicObject + public class HybridExpando : DynamicObject, IDictionary { /// /// Instance of object passed in @@ -85,7 +86,7 @@ public class Expando : DynamicObject /// /// Note you can subclass Expando. /// - public Expando() + public HybridExpando() { Initialize(this); } @@ -98,7 +99,7 @@ public Expando() /// check native properties and only check the Dictionary! /// /// - public Expando(object instance) + public HybridExpando(object instance) { Initialize(instance); } @@ -120,12 +121,12 @@ IList InstancePropertyInfos get { if (_instancePropertyInfos == null && _instance != null) - _instancePropertyInfos = Fasterflect.PropertyExtensions.Properties(_instance.GetType(), Fasterflect.Flags.InstancePublicDeclaredOnly); + _instancePropertyInfos = Fasterflect.PropertyExtensions.Properties(_instanceType, Fasterflect.Flags.InstancePublic); return _instancePropertyInfos; } } - public override IEnumerable GetDynamicMemberNames() + public override IEnumerable GetDynamicMemberNames() { foreach (var prop in this.GetProperties(false)) yield return prop.Key; @@ -344,15 +345,22 @@ public object this[string key] /// public IEnumerable> GetProperties(bool includeInstanceProperties = false) { - if (includeInstanceProperties && _instance != null) + foreach (var key in this.Properties.Keys) + { + yield return new KeyValuePair(key, this.Properties[key]); + } + + if (includeInstanceProperties && _instance != null) { foreach (var prop in this.InstancePropertyInfos) - yield return new KeyValuePair(prop.Name, prop.GetValue(_instance, null)); + { + if (!this.Properties.ContainsKey(prop.Name)) + { + yield return new KeyValuePair(prop.Name, prop.GetValue(_instance, null)); + } + } } - foreach (var key in this.Properties.Keys) - yield return new KeyValuePair(key, this.Properties[key]); - } /// @@ -391,18 +399,116 @@ public bool Contains(string propertyName, bool includeInstanceProperties = false return false; } - public bool TryGetValue(string propertyName, out object value) - { - value = null; + #region IDictionary - if (this.Contains(propertyName, true)) - { - value = this[propertyName]; - return true; - } + ICollection IDictionary.Keys + { + get + { + return GetProperties(true).Select(x => x.Key).AsReadOnly(); + } + } - return false; + ICollection IDictionary.Values + { + get + { + return GetProperties(true).Select(x => x.Value).AsReadOnly(); + } + } + + int ICollection>.Count + { + get + { + return Properties.Count + InstancePropertyInfos.Count; + } + } + + bool ICollection>.IsReadOnly + { + get + { + return false; + } + } + + object IDictionary.this[string key] + { + get + { + return this[key]; + } + + set + { + this[key] = value; + } + } + + bool IDictionary.ContainsKey(string key) + { + return Contains(key, true); + } + + void IDictionary.Add(string key, object value) + { + throw new NotImplementedException(); + } + + bool IDictionary.Remove(string key) + { + throw new NotImplementedException(); + } + + public bool TryGetValue(string key, out object value) + { + value = null; + + if (this.Contains(key, true)) + { + value = this[key]; + return true; + } + + return false; + } + + void ICollection>.Add(KeyValuePair item) + { + throw new NotImplementedException(); + } + + void ICollection>.Clear() + { + throw new NotImplementedException(); + } + + bool ICollection>.Contains(KeyValuePair item) + { + return Contains(item.Key, true); + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + bool ICollection>.Remove(KeyValuePair item) + { + throw new NotImplementedException(); + } + + IEnumerator> IEnumerable>.GetEnumerator() + { + return GetProperties(true).GetEnumerator(); } - } + IEnumerator IEnumerable.GetEnumerator() + { + return GetProperties(true).GetEnumerator(); + } + + #endregion + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index afe41e0061..b23a0d7b0f 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -303,7 +303,7 @@ - + diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index e4e9d02f8f..7e8b6e66c7 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -63,7 +63,6 @@ public class ExportProfileTask : ITask #region Dependencies - private ExpandoHelpers _expandoHelpers; private ICommonServices _services; private Lazy _exportProfileService; private Lazy _dataExchangeSettings; @@ -727,22 +726,8 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, Currency currency) if (currency == null) return null; - dynamic expando = new ExpandoObject(); - expando._Entity = currency; - - expando.Id = currency.Id; + dynamic expando = new DynamicEntity(currency); expando.Name = currency.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - expando.CurrencyCode = currency.CurrencyCode; - expando.Rate = currency.Rate; - expando.DisplayLocale = currency.DisplayLocale; - expando.CustomFormatting = currency.CustomFormatting; - expando.LimitedToStores = currency.LimitedToStores; - expando.Published = currency.Published; - expando.DisplayOrder = currency.DisplayOrder; - expando.CreatedOnUtc = currency.CreatedOnUtc; - expando.UpdatedOnUtc = currency.UpdatedOnUtc; - expando.DomainEndings = currency.DomainEndings; - expando._Localized = GetLocalized(ctx, currency, x => x.Name); return expando; @@ -753,19 +738,7 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, Language language) if (language == null) return null; - dynamic expando = new ExpandoObject(); - expando._Entity = language; - - expando.Id = language.Id; - expando.Name = language.Name; - expando.LanguageCulture = language.LanguageCulture; - expando.UniqueSeoCode = language.UniqueSeoCode; - expando.FlagImageFileName = language.FlagImageFileName; - expando.Rtl = language.Rtl; - expando.LimitedToStores = language.LimitedToStores; - expando.Published = language.Published; - expando.DisplayOrder = language.DisplayOrder; - + dynamic expando = new DynamicEntity(language); return expando; } @@ -774,21 +747,8 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, Country country) if (country == null) return null; - dynamic expando = new ExpandoObject(); - expando._Entity = country; - - expando.Id = country.Id; + dynamic expando = new DynamicEntity(country); expando.Name = country.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - expando.AllowsBilling = country.AllowsBilling; - expando.AllowsShipping = country.AllowsShipping; - expando.TwoLetterIsoCode = country.TwoLetterIsoCode; - expando.ThreeLetterIsoCode = country.ThreeLetterIsoCode; - expando.NumericIsoCode = country.NumericIsoCode; - expando.SubjectToVat = country.SubjectToVat; - expando.Published = country.Published; - expando.DisplayOrder = country.DisplayOrder; - expando.LimitedToStores = country.LimitedToStores; - expando._Localized = GetLocalized(ctx, country, x => x.Name); return expando; @@ -799,39 +759,14 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, Address address) if (address == null) return null; - dynamic expando = new ExpandoObject(); - expando._Entity = address; - - expando.Id = address.Id; - expando.FirstName = address.FirstName; - expando.LastName = address.LastName; - expando.Email = address.Email; - expando.Company = address.Company; - expando.CountryId = address.CountryId; - expando.StateProvinceId = address.StateProvinceId; - expando.City = address.City; - expando.Address1 = address.Address1; - expando.Address2 = address.Address2; - expando.ZipPostalCode = address.ZipPostalCode; - expando.PhoneNumber = address.PhoneNumber; - expando.FaxNumber = address.FaxNumber; - expando.CreatedOnUtc = address.CreatedOnUtc; - + dynamic expando = new DynamicEntity(address); expando.Country = ToExpando(ctx, address.Country); - if (address.StateProvince != null) + if (address.StateProvinceId.GetValueOrDefault() > 0) { - dynamic sp = new ExpandoObject(); - sp._Entity = address.StateProvince; - sp.Id = address.StateProvince.Id; - sp.CountryId = address.StateProvince.CountryId; + dynamic sp = new DynamicEntity(address.StateProvince); sp.Name = address.StateProvince.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); - sp.Abbreviation = address.StateProvince.Abbreviation; - sp.Published = address.StateProvince.Published; - sp.DisplayOrder = address.StateProvince.DisplayOrder; - sp._Localized = GetLocalized(ctx, address.StateProvince, x => x.Name); - expando.StateProvince = sp; } else @@ -1053,25 +988,7 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, ProductVariantAttributeC if (pvac == null) return null; - dynamic expando = new ExpandoObject(); - expando._Entity = pvac; - - expando.Id = pvac.Id; - expando.StockQuantity = pvac.StockQuantity; - expando.AllowOutOfStockOrders = pvac.AllowOutOfStockOrders; - expando.AttributesXml = pvac.AttributesXml; - expando.Sku = pvac.Sku; - expando.Gtin = pvac.Gtin; - expando.ManufacturerPartNumber = pvac.ManufacturerPartNumber; - expando.Price = pvac.Price; - expando.Length = pvac.Length; - expando.Width = pvac.Width; - expando.Height = pvac.Height; - expando.BasePriceAmount = pvac.BasePriceAmount; - expando.BasePriceBaseAmount = pvac.BasePriceBaseAmount; - expando.AssignedPictureIds = pvac.AssignedPictureIds; - expando.DeliveryTimeId = pvac.DeliveryTimeId; - expando.IsActive = pvac.IsActive; + dynamic expando = new DynamicEntity(pvac); GetDeliveryTimeAndQuantityUnit(ctx, expando, pvac.DeliveryTimeId, pvac.QuantityUnitId); @@ -1083,27 +1000,13 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, Manufacturer manufacture if (manufacturer == null) return null; - dynamic expando = new ExpandoObject(); - expando._Entity = manufacturer; - - expando.Id = manufacturer.Id; + dynamic expando = new DynamicEntity(manufacturer); expando.Name = manufacturer.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); expando.SeName = manufacturer.GetSeName(ctx.Projection.LanguageId ?? 0, true, false); expando.Description = manufacturer.GetLocalized(x => x.Description, ctx.Projection.LanguageId ?? 0, true, false); - expando.ManufacturerTemplateId = manufacturer.ManufacturerTemplateId; expando.MetaKeywords = manufacturer.GetLocalized(x => x.MetaKeywords, ctx.Projection.LanguageId ?? 0, true, false); expando.MetaDescription = manufacturer.GetLocalized(x => x.MetaDescription, ctx.Projection.LanguageId ?? 0, true, false); expando.MetaTitle = manufacturer.GetLocalized(x => x.MetaTitle, ctx.Projection.LanguageId ?? 0, true, false); - expando.PictureId = manufacturer.PictureId; - expando.PageSize = manufacturer.PageSize; - expando.AllowCustomersToSelectPageSize = manufacturer.AllowCustomersToSelectPageSize; - expando.PageSizeOptions = manufacturer.PageSizeOptions; - expando.PriceRanges = manufacturer.PriceRanges; - expando.Published = manufacturer.Published; - expando.Deleted = manufacturer.Deleted; - expando.DisplayOrder = manufacturer.DisplayOrder; - expando.CreatedOnUtc = manufacturer.CreatedOnUtc; - expando.UpdatedOnUtc = manufacturer.UpdatedOnUtc; expando.Picture = null; @@ -1122,36 +1025,15 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, Category category) if (category == null) return null; - dynamic expando = new ExpandoObject(); - expando._Entity = category; - - expando.Id = category.Id; + dynamic expando = new DynamicEntity(category); expando.Name = category.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); expando.SeName = category.GetSeName(ctx.Projection.LanguageId ?? 0, true, false); expando.FullName = category.GetLocalized(x => x.FullName, ctx.Projection.LanguageId ?? 0, true, false); expando.Description = category.GetLocalized(x => x.Description, ctx.Projection.LanguageId ?? 0, true, false); expando.BottomDescription = category.GetLocalized(x => x.BottomDescription, ctx.Projection.LanguageId ?? 0, true, false); - expando.CategoryTemplateId = category.CategoryTemplateId; expando.MetaKeywords = category.GetLocalized(x => x.MetaKeywords, ctx.Projection.LanguageId ?? 0, true, false); expando.MetaDescription = category.GetLocalized(x => x.MetaDescription, ctx.Projection.LanguageId ?? 0, true, false); expando.MetaTitle = category.GetLocalized(x => x.MetaTitle, ctx.Projection.LanguageId ?? 0, true, false); - expando.ParentCategoryId = category.ParentCategoryId; - expando.PictureId = category.PictureId; - expando.PageSize = category.PageSize; - expando.AllowCustomersToSelectPageSize = category.AllowCustomersToSelectPageSize; - expando.PageSizeOptions = category.PageSizeOptions; - expando.PriceRanges = category.PriceRanges; - expando.ShowOnHomePage = category.ShowOnHomePage; - expando.HasDiscountsApplied = category.HasDiscountsApplied; - expando.Published = category.Published; - expando.Deleted = category.Deleted; - expando.DisplayOrder = category.DisplayOrder; - expando.CreatedOnUtc = category.CreatedOnUtc; - expando.UpdatedOnUtc = category.UpdatedOnUtc; - expando.SubjectToAcl = category.SubjectToAcl; - expando.LimitedToStores = category.LimitedToStores; - expando.Alias = category.Alias; - expando.DefaultViewMode = category.DefaultViewMode; expando.Picture = null; @@ -1172,111 +1054,18 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, Product product) if (product == null) return null; - dynamic expando = new ExpandoObject(); - expando._Entity = product; + dynamic expando = new DynamicEntity(product); - expando.Id = product.Id; - expando.ProductTypeId = product.ProductTypeId; - expando.ParentGroupedProductId = product.ParentGroupedProductId; - expando.VisibleIndividually = product.VisibleIndividually; expando.Name = product.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); expando.SeName = product.GetSeName(ctx.Projection.LanguageId ?? 0, true, false); expando.ShortDescription = product.GetLocalized(x => x.ShortDescription, ctx.Projection.LanguageId ?? 0, true, false); expando.FullDescription = product.GetLocalized(x => x.FullDescription, ctx.Projection.LanguageId ?? 0, true, false); - expando.AdminComment = product.AdminComment; - expando.ProductTemplateId = product.ProductTemplateId; - expando.ShowOnHomePage = product.ShowOnHomePage; - expando.HomePageDisplayOrder = product.HomePageDisplayOrder; expando.MetaKeywords = product.GetLocalized(x => x.MetaKeywords, ctx.Projection.LanguageId ?? 0, true, false); expando.MetaDescription = product.GetLocalized(x => x.MetaDescription, ctx.Projection.LanguageId ?? 0, true, false); expando.MetaTitle = product.GetLocalized(x => x.MetaTitle, ctx.Projection.LanguageId ?? 0, true, false); - expando.AllowCustomerReviews = product.AllowCustomerReviews; - expando.ApprovedRatingSum = product.ApprovedRatingSum; - expando.NotApprovedRatingSum = product.NotApprovedRatingSum; - expando.ApprovedTotalReviews = product.ApprovedTotalReviews; - expando.NotApprovedTotalReviews = product.NotApprovedTotalReviews; - expando.SubjectToAcl = product.SubjectToAcl; - expando.LimitedToStores = product.LimitedToStores; - expando.Sku = product.Sku; - expando.ManufacturerPartNumber = product.ManufacturerPartNumber; - expando.Gtin = product.Gtin; - expando.IsGiftCard = product.IsGiftCard; - expando.GiftCardTypeId = product.GiftCardTypeId; - expando.RequireOtherProducts = product.RequireOtherProducts; - expando.RequiredProductIds = product.RequiredProductIds; - expando.AutomaticallyAddRequiredProducts = product.AutomaticallyAddRequiredProducts; - expando.IsDownload = product.IsDownload; - expando.DownloadId = product.DownloadId; - expando.UnlimitedDownloads = product.UnlimitedDownloads; - expando.MaxNumberOfDownloads = product.MaxNumberOfDownloads; - expando.DownloadExpirationDays = product.DownloadExpirationDays; - expando.DownloadActivationTypeId = product.DownloadActivationTypeId; - expando.HasSampleDownload = product.HasSampleDownload; - expando.SampleDownloadId = product.SampleDownloadId; - expando.HasUserAgreement = product.HasUserAgreement; - expando.UserAgreementText = product.UserAgreementText; - expando.IsRecurring = product.IsRecurring; - expando.RecurringCycleLength = product.RecurringCycleLength; - expando.RecurringCyclePeriodId = product.RecurringCyclePeriodId; - expando.RecurringTotalCycles = product.RecurringTotalCycles; - expando.IsShipEnabled = product.IsShipEnabled; - expando.IsFreeShipping = product.IsFreeShipping; - expando.AdditionalShippingCharge = product.AdditionalShippingCharge; - expando.IsTaxExempt = product.IsTaxExempt; - expando.IsEsd = product.IsEsd; - expando.TaxCategoryId = product.TaxCategoryId; - expando.ManageInventoryMethodId = product.ManageInventoryMethodId; - expando.StockQuantity = product.StockQuantity; - expando.DisplayStockAvailability = product.DisplayStockAvailability; - expando.DisplayStockQuantity = product.DisplayStockQuantity; - expando.MinStockQuantity = product.MinStockQuantity; - expando.LowStockActivityId = product.LowStockActivityId; - expando.NotifyAdminForQuantityBelow = product.NotifyAdminForQuantityBelow; - expando.BackorderModeId = product.BackorderModeId; - expando.AllowBackInStockSubscriptions = product.AllowBackInStockSubscriptions; - expando.OrderMinimumQuantity = product.OrderMinimumQuantity; - expando.OrderMaximumQuantity = product.OrderMaximumQuantity; - expando.AllowedQuantities = product.AllowedQuantities; - expando.DisableBuyButton = product.DisableBuyButton; - expando.DisableWishlistButton = product.DisableWishlistButton; - expando.AvailableForPreOrder = product.AvailableForPreOrder; - expando.CallForPrice = product.CallForPrice; - expando.Price = product.Price; - expando.OldPrice = product.OldPrice; - expando.ProductCost = product.ProductCost; - expando.SpecialPrice = product.SpecialPrice; - expando.SpecialPriceStartDateTimeUtc = product.SpecialPriceStartDateTimeUtc; - expando.SpecialPriceEndDateTimeUtc = product.SpecialPriceEndDateTimeUtc; - expando.CustomerEntersPrice = product.CustomerEntersPrice; - expando.MinimumCustomerEnteredPrice = product.MinimumCustomerEnteredPrice; - expando.MaximumCustomerEnteredPrice = product.MaximumCustomerEnteredPrice; - expando.HasTierPrices = product.HasTierPrices; - expando.LowestAttributeCombinationPrice = product.LowestAttributeCombinationPrice; - expando.HasDiscountsApplied = product.HasDiscountsApplied; - expando.Weight = product.Weight; - expando.Length = product.Length; - expando.Width = product.Width; - expando.Height = product.Height; - expando.AvailableStartDateTimeUtc = product.AvailableStartDateTimeUtc; - expando.AvailableEndDateTimeUtc = product.AvailableEndDateTimeUtc; - expando.DisplayOrder = product.DisplayOrder; - expando.Published = product.Published; - expando.Deleted = product.Deleted; - expando.CreatedOnUtc = product.CreatedOnUtc; - expando.UpdatedOnUtc = product.UpdatedOnUtc; - expando.DeliveryTimeId = product.DeliveryTimeId; - expando.QuantityUnitId = product.QuantityUnitId; - expando.BasePriceEnabled = product.BasePriceEnabled; - expando.BasePriceMeasureUnit = product.BasePriceMeasureUnit; - expando.BasePriceAmount = product.BasePriceAmount; - expando.BasePriceBaseAmount = product.BasePriceBaseAmount; - expando.BasePriceHasValue = product.BasePriceHasValue; expando.BundleTitleText = product.GetLocalized(x => x.BundleTitleText, ctx.Projection.LanguageId ?? 0, true, false); - expando.BundlePerItemShipping = product.BundlePerItemShipping; - expando.BundlePerItemPricing = product.BundlePerItemPricing; - expando.BundlePerItemShoppingCart = product.BundlePerItemShoppingCart; - - expando.AppliedDiscounts = null; + + expando.AppliedDiscounts = null; expando.TierPrices = null; expando.ProductAttributes = null; expando.ProductAttributeCombinations = null; @@ -1299,6 +1088,138 @@ private dynamic ToExpando(ExportProfileTaskContext ctx, Product product) return expando; } + //private dynamic ToExpando(ExportProfileTaskContext ctx, Product product) + //{ + // if (product == null) + // return null; + + // dynamic expando = new ExpandoObject(); + // expando._Entity = product; + + // expando.Id = product.Id; + // expando.ProductTypeId = product.ProductTypeId; + // expando.ParentGroupedProductId = product.ParentGroupedProductId; + // expando.VisibleIndividually = product.VisibleIndividually; + // expando.Name = product.GetLocalized(x => x.Name, ctx.Projection.LanguageId ?? 0, true, false); + // expando.SeName = product.GetSeName(ctx.Projection.LanguageId ?? 0, true, false); + // expando.ShortDescription = product.GetLocalized(x => x.ShortDescription, ctx.Projection.LanguageId ?? 0, true, false); + // expando.FullDescription = product.GetLocalized(x => x.FullDescription, ctx.Projection.LanguageId ?? 0, true, false); + // expando.AdminComment = product.AdminComment; + // expando.ProductTemplateId = product.ProductTemplateId; + // expando.ShowOnHomePage = product.ShowOnHomePage; + // expando.HomePageDisplayOrder = product.HomePageDisplayOrder; + // expando.MetaKeywords = product.GetLocalized(x => x.MetaKeywords, ctx.Projection.LanguageId ?? 0, true, false); + // expando.MetaDescription = product.GetLocalized(x => x.MetaDescription, ctx.Projection.LanguageId ?? 0, true, false); + // expando.MetaTitle = product.GetLocalized(x => x.MetaTitle, ctx.Projection.LanguageId ?? 0, true, false); + // expando.AllowCustomerReviews = product.AllowCustomerReviews; + // expando.ApprovedRatingSum = product.ApprovedRatingSum; + // expando.NotApprovedRatingSum = product.NotApprovedRatingSum; + // expando.ApprovedTotalReviews = product.ApprovedTotalReviews; + // expando.NotApprovedTotalReviews = product.NotApprovedTotalReviews; + // expando.SubjectToAcl = product.SubjectToAcl; + // expando.LimitedToStores = product.LimitedToStores; + // expando.Sku = product.Sku; + // expando.ManufacturerPartNumber = product.ManufacturerPartNumber; + // expando.Gtin = product.Gtin; + // expando.IsGiftCard = product.IsGiftCard; + // expando.GiftCardTypeId = product.GiftCardTypeId; + // expando.RequireOtherProducts = product.RequireOtherProducts; + // expando.RequiredProductIds = product.RequiredProductIds; + // expando.AutomaticallyAddRequiredProducts = product.AutomaticallyAddRequiredProducts; + // expando.IsDownload = product.IsDownload; + // expando.DownloadId = product.DownloadId; + // expando.UnlimitedDownloads = product.UnlimitedDownloads; + // expando.MaxNumberOfDownloads = product.MaxNumberOfDownloads; + // expando.DownloadExpirationDays = product.DownloadExpirationDays; + // expando.DownloadActivationTypeId = product.DownloadActivationTypeId; + // expando.HasSampleDownload = product.HasSampleDownload; + // expando.SampleDownloadId = product.SampleDownloadId; + // expando.HasUserAgreement = product.HasUserAgreement; + // expando.UserAgreementText = product.UserAgreementText; + // expando.IsRecurring = product.IsRecurring; + // expando.RecurringCycleLength = product.RecurringCycleLength; + // expando.RecurringCyclePeriodId = product.RecurringCyclePeriodId; + // expando.RecurringTotalCycles = product.RecurringTotalCycles; + // expando.IsShipEnabled = product.IsShipEnabled; + // expando.IsFreeShipping = product.IsFreeShipping; + // expando.AdditionalShippingCharge = product.AdditionalShippingCharge; + // expando.IsTaxExempt = product.IsTaxExempt; + // expando.IsEsd = product.IsEsd; + // expando.TaxCategoryId = product.TaxCategoryId; + // expando.ManageInventoryMethodId = product.ManageInventoryMethodId; + // expando.StockQuantity = product.StockQuantity; + // expando.DisplayStockAvailability = product.DisplayStockAvailability; + // expando.DisplayStockQuantity = product.DisplayStockQuantity; + // expando.MinStockQuantity = product.MinStockQuantity; + // expando.LowStockActivityId = product.LowStockActivityId; + // expando.NotifyAdminForQuantityBelow = product.NotifyAdminForQuantityBelow; + // expando.BackorderModeId = product.BackorderModeId; + // expando.AllowBackInStockSubscriptions = product.AllowBackInStockSubscriptions; + // expando.OrderMinimumQuantity = product.OrderMinimumQuantity; + // expando.OrderMaximumQuantity = product.OrderMaximumQuantity; + // expando.AllowedQuantities = product.AllowedQuantities; + // expando.DisableBuyButton = product.DisableBuyButton; + // expando.DisableWishlistButton = product.DisableWishlistButton; + // expando.AvailableForPreOrder = product.AvailableForPreOrder; + // expando.CallForPrice = product.CallForPrice; + // expando.Price = product.Price; + // expando.OldPrice = product.OldPrice; + // expando.ProductCost = product.ProductCost; + // expando.SpecialPrice = product.SpecialPrice; + // expando.SpecialPriceStartDateTimeUtc = product.SpecialPriceStartDateTimeUtc; + // expando.SpecialPriceEndDateTimeUtc = product.SpecialPriceEndDateTimeUtc; + // expando.CustomerEntersPrice = product.CustomerEntersPrice; + // expando.MinimumCustomerEnteredPrice = product.MinimumCustomerEnteredPrice; + // expando.MaximumCustomerEnteredPrice = product.MaximumCustomerEnteredPrice; + // expando.HasTierPrices = product.HasTierPrices; + // expando.LowestAttributeCombinationPrice = product.LowestAttributeCombinationPrice; + // expando.HasDiscountsApplied = product.HasDiscountsApplied; + // expando.Weight = product.Weight; + // expando.Length = product.Length; + // expando.Width = product.Width; + // expando.Height = product.Height; + // expando.AvailableStartDateTimeUtc = product.AvailableStartDateTimeUtc; + // expando.AvailableEndDateTimeUtc = product.AvailableEndDateTimeUtc; + // expando.DisplayOrder = product.DisplayOrder; + // expando.Published = product.Published; + // expando.Deleted = product.Deleted; + // expando.CreatedOnUtc = product.CreatedOnUtc; + // expando.UpdatedOnUtc = product.UpdatedOnUtc; + // expando.DeliveryTimeId = product.DeliveryTimeId; + // expando.QuantityUnitId = product.QuantityUnitId; + // expando.BasePriceEnabled = product.BasePriceEnabled; + // expando.BasePriceMeasureUnit = product.BasePriceMeasureUnit; + // expando.BasePriceAmount = product.BasePriceAmount; + // expando.BasePriceBaseAmount = product.BasePriceBaseAmount; + // expando.BasePriceHasValue = product.BasePriceHasValue; + // expando.BundleTitleText = product.GetLocalized(x => x.BundleTitleText, ctx.Projection.LanguageId ?? 0, true, false); + // expando.BundlePerItemShipping = product.BundlePerItemShipping; + // expando.BundlePerItemPricing = product.BundlePerItemPricing; + // expando.BundlePerItemShoppingCart = product.BundlePerItemShoppingCart; + + // expando.AppliedDiscounts = null; + // expando.TierPrices = null; + // expando.ProductAttributes = null; + // expando.ProductAttributeCombinations = null; + // expando.ProductPictures = null; + // expando.ProductCategories = null; + // expando.ProductManufacturers = null; + // expando.ProductTags = null; + // expando.ProductSpecificationAttributes = null; + // expando.ProductBundleItems = null; + + // expando._Localized = GetLocalized(ctx, product, + // x => x.Name, + // x => x.ShortDescription, + // x => x.FullDescription, + // x => x.MetaKeywords, + // x => x.MetaDescription, + // x => x.MetaTitle, + // x => x.BundleTitleText); + + // return expando; + //} + private dynamic ToExpando(ExportProfileTaskContext ctx, Order order) { if (order == null) @@ -1901,10 +1822,15 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro foreach (var combination in productAttributeCombinations.Where(x => x.IsActive)) { - var expandoCombination = ((IDictionary)expando).ToExpandoObject(); // clone - - matterOfDataMerging(expandoCombination, combination); - result.Add(expandoCombination); + // TODO: Das ist nocht nicht zu Ende gedacht, wegen MergeWithCombination etc. + var clone = new ExpandoObject(); + clone.Merge((IDictionary)expando, true); + matterOfDataMerging(clone, combination); + result.Add(clone); + + //var expandoCombination = ((IDictionary)expando).ToExpandoObject(); // clone + //matterOfDataMerging(expandoCombination, combination); + //result.Add(expandoCombination); } } else diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs index 0a098c5055..6cccfb958a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs @@ -9,19 +9,24 @@ namespace SmartStore.Services.DataExchange.Internal { - internal class DynamicEntity : Expando + internal class DynamicEntity : HybridExpando { - private readonly object _entity; + public DynamicEntity(DynamicEntity dynamicEntity) + : this(dynamicEntity.WrappedObject) + { + base.Properties.Merge(dynamicEntity, true); + } public DynamicEntity(object entity) : base(entity) { + // TODO: Umbenennen!!! + base.Properties["_Entity"] = entity; } - // TODO: Umbenennen!!! - public object _Entity + public void Merge() { - get { return base.WrappedObject; } + } protected override bool TrySetMemberCore(string name, object value) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoHelpers.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoHelpers.cs deleted file mode 100644 index 657a6675ed..0000000000 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/ExpandoHelpers.cs +++ /dev/null @@ -1,240 +0,0 @@ -using SmartStore.ComponentModel; -using SmartStore.Core; -using SmartStore.Core.Domain.Common; -using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Directory; -using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Domain.Media; -using SmartStore.Core.Domain.Seo; -using SmartStore.Core.Domain.Stores; -using SmartStore.Services.DataExchange.ExportTask; -using SmartStore.Services.Localization; -using SmartStore.Services.Seo; -using System; -using System.Collections.Generic; -using System.Dynamic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - -namespace SmartStore.Services.DataExchange.Internal -{ - internal class ExpandoHelpers - { - private readonly ExportProfileTaskContext _ctx; - private readonly IUrlRecordService _urlRecordService; - private readonly ILocalizedEntityService _localizedEntityService; - - public ExpandoHelpers( - ExportProfileTaskContext context, - IUrlRecordService urlRecordService, - ILocalizedEntityService localizedEntityService) - { - _ctx = context; - _urlRecordService = urlRecordService; - _localizedEntityService = localizedEntityService; - } - - public dynamic ToExpando(Currency currency) - { - if (currency == null) - return null; - - dynamic expando = new DynamicEntity(currency); - expando.Name = currency.GetLocalized(x => x.Name, _ctx.Projection.LanguageId ?? 0, true, false); - expando._Localized = GetLocalized(currency, x => x.Name); - - return expando; - } - - private dynamic ToExpando(Language language) - { - if (language == null) - return null; - - dynamic expando = new DynamicEntity(language); - return expando; - } - - private dynamic ToExpando(Country country) - { - if (country == null) - return null; - - dynamic expando = new DynamicEntity(country); - expando.Name = country.GetLocalized(x => x.Name, _ctx.Projection.LanguageId ?? 0, true, false); - expando._Localized = GetLocalized(country, x => x.Name); - - return expando; - } - - private dynamic ToExpando(Address address) - { - if (address == null) - return null; - - dynamic expando = new DynamicEntity(address); - expando.Country = ToExpando(address.Country); - - if (address.StateProvinceId.GetValueOrDefault() > 0) - { - dynamic sp = new DynamicEntity(address.StateProvince); - sp.Name = address.StateProvince.GetLocalized(x => x.Name, _ctx.Projection.LanguageId ?? 0, true, false); - sp._Localized = GetLocalized(address.StateProvince, x => x.Name); - expando.StateProvince = sp; - } - else - { - expando.StateProvince = null; - } - - return expando; - } - - private dynamic ToExpando(RewardPointsHistory points) - { - if (points == null) - return null; - - dynamic expando = new DynamicEntity(points); - return expando; - } - - private dynamic ToExpando( Customer customer) - { - if (customer == null) - return null; - - dynamic expando = new DynamicEntity(customer); - - expando.BillingAddress = null; - expando.ShippingAddress = null; - expando.Addresses = null; - expando.CustomerRoles = null; - - expando.RewardPointsHistory = null; - expando._RewardPointsBalance = 0; - - expando._GenericAttributes = null; - expando._HasNewsletterSubscription = false; - - return expando; - } - - private dynamic ToExpando(Store store) - { - if (store == null) - return null; - - dynamic expando = new DynamicEntity(store); - expando.PrimaryStoreCurrency = ToExpando(store.PrimaryStoreCurrency); - expando.PrimaryExchangeRateCurrency = ToExpando(store.PrimaryExchangeRateCurrency); - - return expando; - } - - private dynamic ToExpando(DeliveryTime deliveryTime) - { - if (deliveryTime == null) - return null; - - dynamic expando = new DynamicEntity(deliveryTime); - expando.Name = deliveryTime.GetLocalized(x => x.Name, _ctx.Projection.LanguageId ?? 0, true, false); - expando._Localized = GetLocalized(deliveryTime, x => x.Name); - - return expando; - } - - private dynamic ToExpando(QuantityUnit quantityUnit) - { - if (quantityUnit == null) - return null; - - dynamic expando = new DynamicEntity(quantityUnit); - expando.Name = quantityUnit.GetLocalized(x => x.Name, _ctx.Projection.LanguageId ?? 0, true, false); - expando.Description = quantityUnit.GetLocalized(x => x.Description, _ctx.Projection.LanguageId ?? 0, true, false); - expando._Localized = GetLocalized(quantityUnit, - x => x.Name, - x => x.Description); - - return expando; - } - - private dynamic ToExpando(Picture picture, int thumbPictureSize, int detailsPictureSize) - { - if (picture == null) - return null; - - dynamic expando = new DynamicEntity(picture); - - //// TODO!!!!! - //expando._ThumbImageUrl = _pictureService.Value.GetPictureUrl(picture, thumbPictureSize, false, _ctx.Store.Url); - //expando._ImageUrl = _pictureService.Value.GetPictureUrl(picture, detailsPictureSize, false, _ctx.Store.Url); - //expando._FullSizeImageUrl = _pictureService.Value.GetPictureUrl(picture, 0, false, _ctx.Store.Url); - - //var relativeUrl = _pictureService.Value.GetPictureUrl(picture); - //expando._FileName = relativeUrl.Substring(relativeUrl.LastIndexOf("/") + 1); - - //expando._ThumbLocalPath = _pictureService.Value.GetThumbLocalPath(picture); - - return expando; - } - - // TODO: weitermachen [...] - - private List GetLocalized(T entity, params Expression>[] keySelectors) - where T : BaseEntity, ILocalizedEntity - { - if (_ctx.Languages.Count <= 1) - return null; - - var localized = new List(); - - var localeKeyGroup = typeof(T).Name; - var isSlugSupported = typeof(ISlugSupported).IsAssignableFrom(typeof(T)); - - foreach (var language in _ctx.Languages) - { - var languageCulture = language.Value.LanguageCulture.EmptyNull().ToLower(); - - // add SeName - if (isSlugSupported) - { - var value = _urlRecordService.GetActiveSlug(entity.Id, localeKeyGroup, language.Value.Id); - if (value.HasValue()) - { - dynamic exp = new ExpandoObject(); - exp.Culture = languageCulture; - exp.LocaleKey = "SeName"; - exp.LocaleValue = value; - - localized.Add(exp); - } - } - - foreach (var keySelector in keySelectors) - { - var member = keySelector.Body as MemberExpression; - var propInfo = member.Member as PropertyInfo; - string localeKey = propInfo.Name; - var value = _localizedEntityService.GetLocalizedValue(language.Value.Id, entity.Id, localeKeyGroup, localeKey); - - // we better not export empty values. The risk is too high that they get reimported and unnecessarily pollute databases. - if (value.HasValue()) - { - dynamic exp = new ExpandoObject(); - exp.Culture = languageCulture; - exp.LocaleKey = localeKey; - exp.LocaleValue = value; - - localized.Add(exp); - } - } - } - - return (localized.Count == 0 ? null : localized); - } - } -} diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index a6af2b2012..1c3348629e 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -187,7 +187,6 @@ - @@ -575,7 +574,9 @@ Nop_Services_EuropaCheckVatService_checkVatService - + + + diff --git a/src/Libraries/SmartStore.Services/Tasks/ITaskExecutor.cs b/src/Libraries/SmartStore.Services/Tasks/ITaskExecutor.cs index b14c0e7c22..053cf45937 100644 --- a/src/Libraries/SmartStore.Services/Tasks/ITaskExecutor.cs +++ b/src/Libraries/SmartStore.Services/Tasks/ITaskExecutor.cs @@ -1,10 +1,14 @@ using System; +using System.Collections.Generic; using SmartStore.Core.Domain.Tasks; namespace SmartStore.Services.Tasks { public interface ITaskExecutor { - void Execute(ScheduleTask task, bool throwOnError = false); + void Execute( + ScheduleTask task, + IDictionary taskParameters = null, + bool throwOnError = false); } } diff --git a/src/Libraries/SmartStore.Services/Tasks/ITaskScheduler.cs b/src/Libraries/SmartStore.Services/Tasks/ITaskScheduler.cs index c1805dc7a8..f852d12952 100644 --- a/src/Libraries/SmartStore.Services/Tasks/ITaskScheduler.cs +++ b/src/Libraries/SmartStore.Services/Tasks/ITaskScheduler.cs @@ -53,7 +53,8 @@ public interface ITaskScheduler /// Executes a single task immediately /// /// - void RunSingleTask(int scheduleTaskId); + /// Optional task parameters + void RunSingleTask(int scheduleTaskId, IDictionary taskParameters = null); /// /// Verifies the authentication token which is generated right before the HTTP endpoint gets called. diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs index 838065d364..8efba4376f 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutionContext.cs @@ -21,6 +21,7 @@ internal TaskExecutionContext(IComponentContext componentContext, ScheduleTask o { this._componentContext = componentContext; this._originalTask = originalTask; + this.Parameters = new Dictionary(); } public T Resolve(object key = null) where T : class @@ -45,6 +46,8 @@ public T ResolveNamed(string name) where T : class public ScheduleTask ScheduleTask { get; set; } + public IDictionary Parameters { get; set; } + /// /// Persists a task's progress information to the database /// diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs index 92e7fa4668..18408a15c0 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs @@ -10,6 +10,7 @@ using SmartStore.Core; using SmartStore.Core.Domain.Customers; using System.Diagnostics; +using System.Collections.Generic; namespace SmartStore.Services.Tasks { @@ -43,7 +44,10 @@ public TaskExecutor( public ILogger Logger { get; set; } - public void Execute(ScheduleTask task, bool throwOnError = false) + public void Execute( + ScheduleTask task, + IDictionary taskParameters = null, + bool throwOnError = false) { if (task.IsRunning) return; @@ -102,7 +106,8 @@ public void Execute(ScheduleTask task, bool throwOnError = false) var ctx = new TaskExecutionContext(_componentContext, task) { ScheduleTask = task.Clone(), - CancellationToken = cts.Token + CancellationToken = cts.Token, + Parameters = taskParameters ?? new Dictionary() }; instance.Execute(ctx); diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs index 30eb8b8288..3ff2e5e04f 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs @@ -10,6 +10,7 @@ using SmartStore.Core.Async; using SmartStore.Core.Domain.Tasks; using SmartStore.Core.Logging; +using SmartStore.Collections; namespace SmartStore.Services.Tasks { @@ -101,9 +102,18 @@ public bool VerifyAuthToken(string authToken) return _authTokens.TryRemove(authToken, out val); } - public void RunSingleTask(int scheduleTaskId) + public void RunSingleTask(int scheduleTaskId, IDictionary taskParameters = null) { - CallEndpoint(_baseUrl + "/Execute/{0}".FormatInvariant(scheduleTaskId)); + string query = ""; + + if (taskParameters != null && taskParameters.Any()) + { + var qs = new QueryString(); + taskParameters.Each(x => qs.Add(x.Key, x.Value)); + query = qs.ToString(); + } + + CallEndpoint(_baseUrl + "/Execute/{0}{1}".FormatInvariant(scheduleTaskId, query)); } private void Elapsed(object sender, System.Timers.ElapsedEventArgs e) diff --git a/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs b/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs index b52e1c033b..9b7b6763c2 100644 --- a/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs @@ -9,7 +9,7 @@ using SmartStore.Web.Framework.Controllers; using SmartStore.Services.Security; using SmartStore.Services; - +using SmartStore.Collections; namespace SmartStore.Web.Controllers { @@ -77,8 +77,8 @@ public ActionResult Execute(int id /* taskId */) var task = _scheduledTaskService.GetTaskById(id); if (task == null) return HttpNotFound(); - - _taskExecutor.Execute(task); + + _taskExecutor.Execute(task, QueryString.Current.ToDictionary()); return Content("Task '{0}' executed".FormatCurrent(task.Name)); } From daf63a30a9745770b3c3bf43bafee33bd94062c1 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 6 Nov 2015 18:14:34 +0100 Subject: [PATCH 178/874] Export framework: minor refactoring --- .../Extensions/DictionaryExtensions.cs | 29 ++++++++++--------- .../ExportTask/ExportProfileTask.cs | 7 ++++- .../DataExchange/Internal/DynamicEntity.cs | 10 ++++++- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs index 6b91605920..8cfb8c1142 100644 --- a/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs @@ -12,7 +12,7 @@ namespace SmartStore public static class DictionaryExtensions { - public static void AddRange(this IDictionary values, IEnumerable> other) + public static void AddRange(this IDictionary values, IEnumerable> other) { foreach (var kvp in other) { @@ -37,13 +37,13 @@ public static void Merge(this IDictionary instance, object value instance.Merge(new RouteValueDictionary(values), replaceExisting); } - public static void Merge(this IDictionary instance, IDictionary from, bool replaceExisting = true) + public static void Merge(this IDictionary instance, IDictionary from, bool replaceExisting = true) { - foreach (KeyValuePair keyValuePair in from) + foreach (var kvp in from) { - if (replaceExisting || !instance.ContainsKey(keyValuePair.Key)) + if (replaceExisting || !instance.ContainsKey(kvp.Key)) { - instance[keyValuePair.Key] = keyValuePair.Value; + instance[kvp.Key] = kvp.Value; } } } @@ -60,28 +60,31 @@ public static void PrependInValue(this IDictionary instance, str public static string ToAttributeString(this IDictionary instance) { - StringBuilder builder = new StringBuilder(); + var sb = new StringBuilder(); foreach (KeyValuePair pair in instance) { object[] args = new object[] { HttpUtility.HtmlAttributeEncode(pair.Key), HttpUtility.HtmlAttributeEncode(pair.Value.ToString()) }; - builder.Append(" {0}=\"{1}\"".FormatWith(args)); + sb.Append(" {0}=\"{1}\"".FormatWith(args)); } - return builder.ToString(); + return sb.ToString(); } - public static T GetValue(this IDictionary instance, K key) + public static TValue GetValue(this IDictionary instance, TKey key) { try { object val; if (instance != null && instance.TryGetValue(key, out val) && val != null) - return (T)Convert.ChangeType(val, typeof(T), CultureInfo.InvariantCulture); + { + return (TValue)Convert.ChangeType(val, typeof(TValue), CultureInfo.InvariantCulture); + } } - catch (Exception exc) + catch (Exception ex) { - exc.Dump(); + ex.Dump(); } - return default(T); + + return default(TValue); } public static ExpandoObject ToExpandoObject(this IDictionary source, bool castIfPossible = false) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 7e8b6e66c7..b36699541b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -1824,10 +1824,15 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro { // TODO: Das ist nocht nicht zu Ende gedacht, wegen MergeWithCombination etc. var clone = new ExpandoObject(); - clone.Merge((IDictionary)expando, true); + clone.Merge((IDictionary)expando); matterOfDataMerging(clone, combination); result.Add(clone); + // TODO: das hier ist besser. Aber ein Product-Klon muss immer noch erstellt werden. + //var clone = new DynamicEntity((DynamicEntity)expando); + //matterOfDataMerging(clone, combination); + //result.Add(clone); + //var expandoCombination = ((IDictionary)expando).ToExpandoObject(); // clone //matterOfDataMerging(expandoCombination, combination); //result.Add(expandoCombination); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs index 6cccfb958a..6940371341 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs @@ -24,9 +24,17 @@ public DynamicEntity(object entity) base.Properties["_Entity"] = entity; } - public void Merge() + public void Merge(string name, object value) { + Properties[name] = value; + } + public void MergeRange(IDictionary other) + { + foreach (var kvp in other) + { + Properties[kvp.Key] = kvp.Value; + } } protected override bool TrySetMemberCore(string name, object value) From 3fd05481dbd7b12b53313cbb44a0606651820f8c Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 6 Nov 2015 19:28:04 +0100 Subject: [PATCH 179/874] Export framework: renaming --- ...rtExecuteResult.cs => DataExportResult.cs} | 8 +-- .../DataExchange/Deployment/IFilePublisher.cs | 18 +++++ ...ortSegmenter.cs => ExportDataSegmenter.cs} | 14 ++-- .../DataExchange/ExportExecuteContext.cs | 8 +-- .../ExportTask/ExportProfileTask.cs | 36 +++++----- .../ExportTask/ExportProfileTaskContext.cs | 65 ++++++++++--------- .../DataExchange/IDataExporter.cs | 9 ++- .../CategoryExportContext.cs} | 6 +- .../CustomerExportContext.cs} | 6 +- .../ManufacturerExportContext.cs} | 6 +- .../OrderExportContext.cs} | 6 +- .../ProductExportContext.cs} | 6 +- .../CategoryXmlExportProvider.cs} | 4 +- .../CustomerXlsxExportProvider.cs} | 4 +- .../CustomerXmlExportProvider.cs} | 4 +- .../ManufacturerXmlExportProvider.cs} | 4 +- .../OrderXlsxExportProvider.cs} | 4 +- .../OrderXmlExportProvider.cs} | 4 +- .../ProductXlsxExportProvider.cs} | 6 +- .../ProductXmlExportProvider.cs} | 4 +- .../SubscriberCsvExportProvider.cs} | 4 +- .../DataExchange/_Pseudo/AdminController.cs | 19 ++++++ .../DataExchange/_Pseudo/DataExportTask.cs | 62 ++++++++++++++++++ .../SmartStore.Services.csproj | 39 +++++------ .../FeedGoogleMerchantCenterController.cs | 6 +- ...XmlProvider.cs => GmcXmlExportProvider.cs} | 4 +- .../SmartStore.GoogleMerchantCenter.csproj | 2 +- .../Controllers/CategoryController.cs | 4 +- .../Controllers/CustomerController.cs | 10 +-- .../Controllers/ExportController.cs | 2 +- .../Controllers/ManufacturerController.cs | 4 +- .../NewsLetterSubscriptionController.cs | 4 +- .../Controllers/OrderController.cs | 10 +-- .../Controllers/ProductController.cs | 10 +-- 34 files changed, 255 insertions(+), 147 deletions(-) rename src/Libraries/SmartStore.Services/DataExchange/{ExportExecuteResult.cs => DataExportResult.cs} (85%) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs rename src/Libraries/SmartStore.Services/DataExchange/{ExportSegmenter.cs => ExportDataSegmenter.cs} (89%) rename src/Libraries/SmartStore.Services/DataExchange/{ExportTask/ExportDataContextCategory.cs => Internal/CategoryExportContext.cs} (92%) rename src/Libraries/SmartStore.Services/DataExchange/{ExportTask/ExportDataContextCustomer.cs => Internal/CustomerExportContext.cs} (89%) rename src/Libraries/SmartStore.Services/DataExchange/{ExportTask/ExportDataContextManufacturer.cs => Internal/ManufacturerExportContext.cs} (92%) rename src/Libraries/SmartStore.Services/DataExchange/{ExportTask/ExportDataContextOrder.cs => Internal/OrderExportContext.cs} (95%) rename src/Libraries/SmartStore.Services/DataExchange/{ExportTask/ExportDataContextProduct.cs => Internal/ProductExportContext.cs} (96%) rename src/Libraries/SmartStore.Services/DataExchange/{ExportProvider/ExportCategoryXmlProvider.cs => Providers/CategoryXmlExportProvider.cs} (96%) rename src/Libraries/SmartStore.Services/DataExchange/{ExportProvider/ExportCustomerXlsxProvider.cs => Providers/CustomerXlsxExportProvider.cs} (98%) rename src/Libraries/SmartStore.Services/DataExchange/{ExportProvider/ExportCustomerXmlProvider.cs => Providers/CustomerXmlExportProvider.cs} (94%) rename src/Libraries/SmartStore.Services/DataExchange/{ExportProvider/ExportManufacturerXmlProvider.cs => Providers/ManufacturerXmlExportProvider.cs} (96%) rename src/Libraries/SmartStore.Services/DataExchange/{ExportProvider/ExportOrderXlsxProvider.cs => Providers/OrderXlsxExportProvider.cs} (98%) rename src/Libraries/SmartStore.Services/DataExchange/{ExportProvider/ExportOrderXmlProvider.cs => Providers/OrderXmlExportProvider.cs} (99%) rename src/Libraries/SmartStore.Services/DataExchange/{ExportProvider/ExportProductXlsxProvider.cs => Providers/ProductXlsxExportProvider.cs} (99%) rename src/Libraries/SmartStore.Services/DataExchange/{ExportProvider/ExportProductXmlProvider.cs => Providers/ProductXmlExportProvider.cs} (94%) rename src/Libraries/SmartStore.Services/DataExchange/{ExportProvider/ExportNewsSubscriptionCsvProvider.cs => Providers/SubscriberCsvExportProvider.cs} (93%) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/_Pseudo/AdminController.cs create mode 100644 src/Libraries/SmartStore.Services/DataExchange/_Pseudo/DataExportTask.cs rename src/Plugins/SmartStore.GoogleMerchantCenter/Providers/{GmcExportXmlProvider.cs => GmcXmlExportProvider.cs} (99%) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteResult.cs b/src/Libraries/SmartStore.Services/DataExchange/DataExportResult.cs similarity index 85% rename from src/Libraries/SmartStore.Services/DataExchange/ExportExecuteResult.cs rename to src/Libraries/SmartStore.Services/DataExchange/DataExportResult.cs index 6bb8a87429..0e1f708763 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteResult.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DataExportResult.cs @@ -5,11 +5,11 @@ namespace SmartStore.Services.DataExchange { [Serializable] - public class ExportExecuteResult + public class DataExportResult { - public ExportExecuteResult() + public DataExportResult() { - Files = new List(); + Files = new List(); } /// @@ -29,7 +29,7 @@ public bool Succeeded /// /// Files created by last export /// - public List Files { get; set; } + public IList Files { get; set; } /// /// The path of the folder with the export files diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs new file mode 100644 index 0000000000..2f0211fd2b --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs @@ -0,0 +1,18 @@ +using SmartStore.Core.Domain; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SmartStore.Services.DataExchange.Deployment +{ + public interface IFilePublisher + { + void Publish(DataExportResult result, ExportDeployment deployment); + } + + /* + Implement without IoC: HttpFilePublisher, EmailFilePublisher, FtpFilePublisher, FileSystemFilePublisher + */ +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportDataSegmenter.cs similarity index 89% rename from src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs rename to src/Libraries/SmartStore.Services/DataExchange/ExportDataSegmenter.cs index 6d5c6fb937..a5be05b008 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportSegmenter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportDataSegmenter.cs @@ -4,7 +4,7 @@ namespace SmartStore.Services.DataExchange { - public interface IExportSegmenterConsumer + public interface IExportDataSegmenterConsumer { /// /// Total number of records @@ -14,7 +14,7 @@ public interface IExportSegmenterConsumer /// /// Gets current data segment /// - List CurrentSegment { get; } + IReadOnlyCollection CurrentSegment { get; } /// /// Reads the next segment @@ -23,7 +23,7 @@ public interface IExportSegmenterConsumer bool ReadNextSegment(); } - internal interface IExportSegmenterProvider : IExportSegmenterConsumer, IDisposable + internal interface IExportDataSegmenterProvider : IExportDataSegmenterConsumer, IDisposable { /// /// Whether there is data available @@ -36,7 +36,7 @@ internal interface IExportSegmenterProvider : IExportSegmenterConsumer, IDisposa int RecordPerSegmentCount { get; set; } } - public class ExportSegmenter : IExportSegmenterProvider where T : BaseEntity + public class ExportDataSegmenter : IExportDataSegmenterProvider where T : BaseEntity { private Func> _load; private Action> _loaded; @@ -53,7 +53,7 @@ public class ExportSegmenter : IExportSegmenterProvider where T : BaseEntity private Queue _data; - public ExportSegmenter( + public ExportDataSegmenter( Func> load, Action> loaded, Func> convert, @@ -127,7 +127,7 @@ public bool HasData /// /// Gets current data segment /// - public List CurrentSegment + public IReadOnlyCollection CurrentSegment { get { @@ -136,7 +136,7 @@ public List CurrentSegment while (_data.Count > 0 && (entity = _data.Dequeue()) != null) { - _convert(entity).ForEach(x => records.Add(x)); + _convert(entity).Each(x => records.Add(x)); if (++_countRecords >= _limit && _limit > 0) return records; diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs index 1459463bfb..17ae53a8bf 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs @@ -11,7 +11,7 @@ public interface IExportExecuteContext /// /// Provides the data to be exported /// - IExportSegmenterConsumer Segmenter { get; } + IExportDataSegmenterConsumer Segmenter { get; } /// /// The store context to be used for the export @@ -116,11 +116,11 @@ public interface IExportExecuteContext public class ExportExecuteContext : IExportExecuteContext { - private ExportExecuteResult _result; + private DataExportResult _result; private CancellationToken _cancellation; private ExportAbortion _providerAbort; - internal ExportExecuteContext(ExportExecuteResult result, CancellationToken cancellation, string folder) + internal ExportExecuteContext(DataExportResult result, CancellationToken cancellation, string folder) { _result = result; _cancellation = cancellation; @@ -129,7 +129,7 @@ internal ExportExecuteContext(ExportExecuteResult result, CancellationToken canc CustomProperties = new Dictionary(); } - public IExportSegmenterConsumer Segmenter { get; set; } + public IExportDataSegmenterConsumer Segmenter { get; set; } public dynamic Store { get; internal set; } public dynamic Customer { get; internal set; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index b36699541b..48c50afc11 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -193,7 +193,7 @@ private List GetLocalized(ExportProfileTaskContext ctx, T entity, pa return (localized.Count == 0 ? null : localized); } - private IExportSegmenterProvider CreateSegmenter(ExportProfileTaskContext ctx, int pageIndex = 0) + private IExportDataSegmenterProvider CreateSegmenter(ExportProfileTaskContext ctx, int pageIndex = 0) { var offset = ctx.Profile.Offset + (pageIndex * PageSize); @@ -206,13 +206,13 @@ private IExportSegmenterProvider CreateSegmenter(ExportProfileTaskContext ctx, i switch (ctx.Provider.Value.EntityType) { case ExportEntityType.Product: - ctx.Export.Segmenter = new ExportSegmenter + ctx.Export.Segmenter = new ExportDataSegmenter ( skip => GetProducts(ctx, skip), entities => { // load data behind navigation properties for current queue in one go - ctx.DataContextProduct = new ExportDataContextProduct(entities, + ctx.DataContextProduct = new ProductExportContext(entities, 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), @@ -231,12 +231,12 @@ private IExportSegmenterProvider CreateSegmenter(ExportProfileTaskContext ctx, i break; case ExportEntityType.Order: - ctx.Export.Segmenter = new ExportSegmenter + ctx.Export.Segmenter = new ExportDataSegmenter ( skip => GetOrders(ctx, skip), entities => { - ctx.DataContextOrder = new ExportDataContextOrder(entities, + ctx.DataContextOrder = new OrderExportContext(entities, x => _customerService.Value.GetCustomersByIds(x), x => _customerService.Value.GetRewardPointsHistoriesByCustomerIds(x), x => _addressesService.Value.GetAddressByIds(x), @@ -250,12 +250,12 @@ private IExportSegmenterProvider CreateSegmenter(ExportProfileTaskContext ctx, i break; case ExportEntityType.Manufacturer: - ctx.Export.Segmenter = new ExportSegmenter + ctx.Export.Segmenter = new ExportDataSegmenter ( skip => GetManufacturers(ctx, skip), entities => { - ctx.DataContextManufacturer = new ExportDataContextManufacturer(entities, + ctx.DataContextManufacturer = new ManufacturerExportContext(entities, x => _manufacturerService.Value.GetProductManufacturersByManufacturerIds(x), x => _pictureService.Value.GetPicturesByIds(x) ); @@ -266,12 +266,12 @@ private IExportSegmenterProvider CreateSegmenter(ExportProfileTaskContext ctx, i break; case ExportEntityType.Category: - ctx.Export.Segmenter = new ExportSegmenter + ctx.Export.Segmenter = new ExportDataSegmenter ( skip => GetCategories(ctx, skip), entities => { - ctx.DataContextCategory = new ExportDataContextCategory(entities, + ctx.DataContextCategory = new CategoryExportContext(entities, x => _categoryService.Value.GetProductCategoriesByCategoryIds(x), x => _pictureService.Value.GetPicturesByIds(x) ); @@ -282,12 +282,12 @@ private IExportSegmenterProvider CreateSegmenter(ExportProfileTaskContext ctx, i break; case ExportEntityType.Customer: - ctx.Export.Segmenter = new ExportSegmenter + ctx.Export.Segmenter = new ExportDataSegmenter ( skip => GetCustomers(ctx, skip), entities => { - ctx.DataContextCustomer = new ExportDataContextCustomer(entities, + ctx.DataContextCustomer = new CustomerExportContext(entities, x => _genericAttributeService.Value.GetAttributesForEntity(x, "Customer") ); }, @@ -297,7 +297,7 @@ private IExportSegmenterProvider CreateSegmenter(ExportProfileTaskContext ctx, i break; case ExportEntityType.NewsLetterSubscription: - ctx.Export.Segmenter = new ExportSegmenter + ctx.Export.Segmenter = new ExportDataSegmenter ( skip => GetNewsLetterSubscriptions(ctx, skip), null, @@ -311,7 +311,7 @@ private IExportSegmenterProvider CreateSegmenter(ExportProfileTaskContext ctx, i break; } - return ctx.Export.Segmenter as IExportSegmenterProvider; + return ctx.Export.Segmenter as IExportDataSegmenterProvider; } private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic expando, Product product) @@ -2504,7 +2504,7 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) // create info for deployment list in profile edit if (File.Exists(ctx.Export.FilePath)) { - ctx.Result.Files.Add(new ExportExecuteResult.ExportFileInfo + ctx.Result.Files.Add(new DataExportResult.ExportFileInfo { StoreId = ctx.Store.Id, FileName = ctx.Export.FileName @@ -2664,7 +2664,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) { if (!ctx.IsPreview && ctx.Profile.Id != 0) { - ctx.Profile.ResultInfo = XmlHelper.Serialize(ctx.Result); + ctx.Profile.ResultInfo = XmlHelper.Serialize(ctx.Result); _exportProfileService.Value.UpdateExportProfile(ctx.Profile); } @@ -2773,7 +2773,7 @@ public static FileStreamResult Export(string providerSystemName, string selected error = null; var cancellation = new CancellationTokenSource(TimeSpan.FromHours(3.0)); - var task = AsyncRunner.Run((container, ct) => + var task = AsyncRunner.Run((container, ct) => { var exportTask = new ExportProfileTask(); return exportTask.Execute(providerSystemName, container, ct, null, selectedEntityIds); @@ -2843,7 +2843,7 @@ public void Execute(TaskExecutionContext taskContext) /// Any data passed on IExportExecuteContext.CustomProperties /// Product query that supersede profile filtering /// Export execute result - public ExportExecuteResult Execute( + public DataExportResult Execute( string providerSystemName, IComponentContext context, CancellationToken cancellationToken, @@ -2949,7 +2949,7 @@ public void Preview(ExportProfile profile, Provider provider, I while (segmenter.ReadNextSegment()) { - segmenter.CurrentSegment.ForEach(x => ctx.PreviewData(x)); + segmenter.CurrentSegment.Each(x => ctx.PreviewData(x)); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs index 5fee680634..576b1ffc08 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs @@ -14,16 +14,17 @@ using SmartStore.Core.Plugins; using SmartStore.Services.Tasks; using SmartStore.Utilities; +using SmartStore.Services.DataExchange.Internal; namespace SmartStore.Services.DataExchange.ExportTask { internal class ExportProfileTaskContext { - private ExportDataContextProduct _dataContextProduct; - private ExportDataContextOrder _dataContextOrder; - private ExportDataContextManufacturer _dataContextManufacturer; - private ExportDataContextCategory _dataContextCategory; - private ExportDataContextCustomer _dataContextCustomer; + private ProductExportContext _productExportContext; + private OrderExportContext _orderExportContext; + private ManufacturerExportContext _manufacturerExportContext; + private CategoryExportContext _categoryExportContext; + private CustomerExportContext _customerExportContext; public ExportProfileTaskContext( TaskExecutionContext taskContext, @@ -56,7 +57,7 @@ public ExportProfileTaskContext( RecordsPerStore = new Dictionary(); EntityIdsLoaded = new List(); - Result = new ExportExecuteResult + Result = new DataExportResult { FileFolder = (IsFileBasedExport ? FolderContent : null) }; @@ -140,78 +141,78 @@ public string[] GetDeploymentFiles(ExportDeployment deployment) public HashSet NewsletterSubscriptions { get; set; } // data loaded once per page - public ExportDataContextProduct DataContextProduct + public ProductExportContext DataContextProduct { get { - return _dataContextProduct; + return _productExportContext; } set { - if (_dataContextProduct != null) - _dataContextProduct.Clear(); + if (_productExportContext != null) + _productExportContext.Clear(); - _dataContextProduct = value; + _productExportContext = value; } } - public ExportDataContextOrder DataContextOrder + public OrderExportContext DataContextOrder { get { - return _dataContextOrder; + return _orderExportContext; } set { - if (_dataContextOrder != null) - _dataContextOrder.Clear(); + if (_orderExportContext != null) + _orderExportContext.Clear(); - _dataContextOrder = value; + _orderExportContext = value; } } - public ExportDataContextManufacturer DataContextManufacturer + public ManufacturerExportContext DataContextManufacturer { get { - return _dataContextManufacturer; + return _manufacturerExportContext; } set { - if (_dataContextManufacturer != null) - _dataContextManufacturer.Clear(); + if (_manufacturerExportContext != null) + _manufacturerExportContext.Clear(); - _dataContextManufacturer = value; + _manufacturerExportContext = value; } } - public ExportDataContextCategory DataContextCategory + public CategoryExportContext DataContextCategory { get { - return _dataContextCategory; + return _categoryExportContext; } set { - if (_dataContextCategory != null) - _dataContextCategory.Clear(); + if (_categoryExportContext != null) + _categoryExportContext.Clear(); - _dataContextCategory = value; + _categoryExportContext = value; } } - public ExportDataContextCustomer DataContextCustomer + public CustomerExportContext DataContextCustomer { get { - return _dataContextCustomer; + return _customerExportContext; } set { - if (_dataContextCustomer != null) - _dataContextCustomer.Clear(); + if (_customerExportContext != null) + _customerExportContext.Clear(); - _dataContextCustomer = value; + _customerExportContext = value; } } public ExportExecuteContext Export { get; set; } - public ExportExecuteResult Result { get; set; } + public DataExportResult Result { get; set; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs index a2a31ac863..036fa99739 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs @@ -13,7 +13,14 @@ namespace SmartStore.Services.DataExchange public interface IDataExporter { - void Export(DataExportRequest request, CancellationToken cancellationToken); + DataExportResult Export(DataExportRequest request, CancellationToken cancellationToken); + + // Handle model conversion for grid in backend's controller + IList Preview(DataExportRequest request); + + // useful for decision making whether export should + // be processed sync or async + long GetDataCount(DataExportRequest request); } public class DataExportRequest diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCategory.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/CategoryExportContext.cs similarity index 92% rename from src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCategory.cs rename to src/Libraries/SmartStore.Services/DataExchange/Internal/CategoryExportContext.cs index 8e2efa0f93..179189c0db 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCategory.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/CategoryExportContext.cs @@ -5,9 +5,9 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Media; -namespace SmartStore.Services.DataExchange.ExportTask +namespace SmartStore.Services.DataExchange.Internal { - internal class ExportDataContextCategory + internal class CategoryExportContext { protected List _categoryIds; protected List _pictureIds; @@ -18,7 +18,7 @@ internal class ExportDataContextCategory private LazyMultimap _productCategories; private LazyMultimap _pictures; - public ExportDataContextCategory( + public CategoryExportContext( IEnumerable categories, Func> productCategories, Func> pictures) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCustomer.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/CustomerExportContext.cs similarity index 89% rename from src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCustomer.cs rename to src/Libraries/SmartStore.Services/DataExchange/Internal/CustomerExportContext.cs index f7c027a27d..e765815557 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextCustomer.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/CustomerExportContext.cs @@ -5,9 +5,9 @@ using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; -namespace SmartStore.Services.DataExchange.ExportTask +namespace SmartStore.Services.DataExchange.Internal { - public class ExportDataContextCustomer + public class CustomerExportContext { protected List _customerIds; @@ -15,7 +15,7 @@ public class ExportDataContextCustomer private LazyMultimap _genericAttributes; - public ExportDataContextCustomer( + public CustomerExportContext( IEnumerable customers, Func> genericAttributes) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextManufacturer.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/ManufacturerExportContext.cs similarity index 92% rename from src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextManufacturer.cs rename to src/Libraries/SmartStore.Services/DataExchange/Internal/ManufacturerExportContext.cs index 9d2002be35..83445889cc 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextManufacturer.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/ManufacturerExportContext.cs @@ -5,9 +5,9 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Media; -namespace SmartStore.Services.DataExchange.ExportTask +namespace SmartStore.Services.DataExchange.Internal { - internal class ExportDataContextManufacturer + internal class ManufacturerExportContext { protected List _manufacturerIds; protected List _pictureIds; @@ -18,7 +18,7 @@ internal class ExportDataContextManufacturer private LazyMultimap _productManufacturers; private LazyMultimap _pictures; - public ExportDataContextManufacturer( + public ManufacturerExportContext( IEnumerable manufacturers, Func> productManufacturers, Func> pictures) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextOrder.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/OrderExportContext.cs similarity index 95% rename from src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextOrder.cs rename to src/Libraries/SmartStore.Services/DataExchange/Internal/OrderExportContext.cs index 38fa4893ae..6ae1998304 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextOrder.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/OrderExportContext.cs @@ -7,9 +7,9 @@ using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; -namespace SmartStore.Services.DataExchange.ExportTask +namespace SmartStore.Services.DataExchange.Internal { - internal class ExportDataContextOrder + internal class OrderExportContext { protected List _orderIds; protected List _customerIds; @@ -27,7 +27,7 @@ internal class ExportDataContextOrder private LazyMultimap _orderItems; private LazyMultimap _shipments; - public ExportDataContextOrder(IEnumerable orders, + public OrderExportContext(IEnumerable orders, Func> customers, Func> rewardPointsHistory, Func> addresses, diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextProduct.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/ProductExportContext.cs similarity index 96% rename from src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextProduct.cs rename to src/Libraries/SmartStore.Services/DataExchange/Internal/ProductExportContext.cs index bb0d6ea6c1..503e3db6db 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportDataContextProduct.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/ProductExportContext.cs @@ -6,9 +6,9 @@ using SmartStore.Core.Domain.Discounts; using SmartStore.Services.Catalog; -namespace SmartStore.Services.DataExchange.ExportTask +namespace SmartStore.Services.DataExchange.Internal { - internal class ExportDataContextProduct : PriceCalculationContext + internal class ProductExportContext : PriceCalculationContext { private List _productIdsBundleItems; @@ -24,7 +24,7 @@ internal class ExportDataContextProduct : PriceCalculationContext private LazyMultimap _productSpecificationAttributes; private LazyMultimap _productBundleItems; - public ExportDataContextProduct( + public ProductExportContext( IEnumerable products, Func> attributes, Func> attributeCombinations, diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs similarity index 96% rename from src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs rename to src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs index 6bd4aa3d2f..5b72bc9d64 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCategoryXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs @@ -8,7 +8,7 @@ using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -namespace SmartStore.Services.DataExchange.ExportProvider +namespace SmartStore.Services.DataExchange.Providers { /// /// Exports XML formatted category data to a file @@ -16,7 +16,7 @@ namespace SmartStore.Services.DataExchange.ExportProvider [SystemName("Exports.SmartStoreCategoryXml")] [FriendlyName("SmartStore XML category export")] [IsHidden(true)] - public class ExportCategoryXmlProvider : IExportProvider + public class CategoryXmlExportProvider : IExportProvider { public static string SystemName { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXlsxExportProvider.cs similarity index 98% rename from src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs rename to src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXlsxExportProvider.cs index 2ab8dcb6b0..22a9d229ec 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXlsxExportProvider.cs @@ -10,7 +10,7 @@ using SmartStore.Core.Plugins; using SmartStore.Services.Customers; -namespace SmartStore.Services.DataExchange.ExportProvider +namespace SmartStore.Services.DataExchange.Providers { /// /// Exports Excel formatted customer data to a file @@ -18,7 +18,7 @@ namespace SmartStore.Services.DataExchange.ExportProvider [SystemName("Exports.SmartStoreCustomerXlsx")] [FriendlyName("SmartStore Excel customer export")] [IsHidden(true)] - public class ExportCustomerXlsxProvider : IExportProvider + public class CustomerXlsxExportProvider : IExportProvider { private string[] Properties { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs similarity index 94% rename from src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs rename to src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs index 4573e6c69c..b88d3d8cd3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportCustomerXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs @@ -8,7 +8,7 @@ using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -namespace SmartStore.Services.DataExchange.ExportProvider +namespace SmartStore.Services.DataExchange.Providers { /// /// Exports XML formatted customer data to a file @@ -16,7 +16,7 @@ namespace SmartStore.Services.DataExchange.ExportProvider [SystemName("Exports.SmartStoreCustomerXml")] [FriendlyName("SmartStore XML customer export")] [IsHidden(true)] - public class ExportCustomerXmlProvider : IExportProvider + public class CustomerXmlExportProvider : IExportProvider { public static string SystemName { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs similarity index 96% rename from src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs rename to src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs index 5341411be7..edc60a007d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportManufacturerXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs @@ -8,7 +8,7 @@ using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -namespace SmartStore.Services.DataExchange.ExportProvider +namespace SmartStore.Services.DataExchange.Providers { /// /// Exports XML formatted manufacturer data to a file @@ -16,7 +16,7 @@ namespace SmartStore.Services.DataExchange.ExportProvider [SystemName("Exports.SmartStoreManufacturerXml")] [FriendlyName("SmartStore XML manufacturer export")] [IsHidden(true)] - public class ExportManufacturerXmlProvider : IExportProvider + public class ManufacturerXmlExportProvider : IExportProvider { public static string SystemName { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXlsxExportProvider.cs similarity index 98% rename from src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs rename to src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXlsxExportProvider.cs index ba85cc3b0d..9f468e071f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXlsxExportProvider.cs @@ -7,7 +7,7 @@ using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -namespace SmartStore.Services.DataExchange.ExportProvider +namespace SmartStore.Services.DataExchange.Providers { /// /// Exports Excel formatted order data to a file @@ -15,7 +15,7 @@ namespace SmartStore.Services.DataExchange.ExportProvider [SystemName("Exports.SmartStoreOrderXlsx")] [FriendlyName("SmartStore Excel order export")] [IsHidden(true)] - public class ExportOrderXlsxProvider : IExportProvider + public class OrderXlsxExportProvider : IExportProvider { private string[] Properties { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs similarity index 99% rename from src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs rename to src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs index 185181c013..a21a94d2f9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportOrderXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs @@ -8,7 +8,7 @@ using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -namespace SmartStore.Services.DataExchange.ExportProvider +namespace SmartStore.Services.DataExchange.Providers { /// /// Exports XML formatted order data to a file @@ -16,7 +16,7 @@ namespace SmartStore.Services.DataExchange.ExportProvider [SystemName("Exports.SmartStoreOrderXml")] [FriendlyName("SmartStore XML order export")] [IsHidden(true)] - public class ExportOrderXmlProvider : IExportProvider + public class OrderXmlExportProvider : IExportProvider { public static string SystemName { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXlsxExportProvider.cs similarity index 99% rename from src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs rename to src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXlsxExportProvider.cs index e9ec1bbc59..a433bf5c8d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXlsxProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXlsxExportProvider.cs @@ -14,7 +14,7 @@ using SmartStore.Services.Localization; using SmartStore.Services.Stores; -namespace SmartStore.Services.DataExchange.ExportProvider +namespace SmartStore.Services.DataExchange.Providers { /// /// Exports Excel formatted product data to a file @@ -22,7 +22,7 @@ namespace SmartStore.Services.DataExchange.ExportProvider [SystemName("Exports.SmartStoreProductXlsx")] [FriendlyName("SmartStore Excel product export")] [IsHidden(true)] - public class ExportProductXlsxProvider : IExportProvider + public class ProductXlsxExportProvider : IExportProvider { private readonly ILanguageService _languageService; private readonly IProductService _productService; @@ -130,7 +130,7 @@ private string[] Properties } } - public ExportProductXlsxProvider( + public ProductXlsxExportProvider( ILanguageService languageService, IProductService productService, IStoreMappingService storeMappingService) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs similarity index 94% rename from src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs rename to src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs index 6352c98b03..a0cc291bf1 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportProductXmlProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs @@ -8,7 +8,7 @@ using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -namespace SmartStore.Services.DataExchange.ExportProvider +namespace SmartStore.Services.DataExchange.Providers { /// /// Exports XML formatted product data to a file @@ -16,7 +16,7 @@ namespace SmartStore.Services.DataExchange.ExportProvider [SystemName("Exports.SmartStoreProductXml")] [FriendlyName("SmartStore XML product export")] [IsHidden(true)] - public class ExportProductXmlProvider : IExportProvider + public class ProductXmlExportProvider : IExportProvider { public static string SystemName { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/SubscriberCsvExportProvider.cs similarity index 93% rename from src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs rename to src/Libraries/SmartStore.Services/DataExchange/Providers/SubscriberCsvExportProvider.cs index 36479f946f..2ca56f48f8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProvider/ExportNewsSubscriptionCsvProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/SubscriberCsvExportProvider.cs @@ -5,7 +5,7 @@ using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -namespace SmartStore.Services.DataExchange.ExportProvider +namespace SmartStore.Services.DataExchange.Providers { /// /// Exports CSV formatted newsletter subscription data to a file @@ -13,7 +13,7 @@ namespace SmartStore.Services.DataExchange.ExportProvider [SystemName("Exports.SmartStoreNewsSubscriptionCsv")] [FriendlyName("SmartStore CSV newsletter subscription export")] [IsHidden(true)] - public class ExportNewsSubscriptionCsvProvider : IExportProvider + public class SubscriberCsvExportProvider : IExportProvider { public static string SystemName { diff --git a/src/Libraries/SmartStore.Services/DataExchange/_Pseudo/AdminController.cs b/src/Libraries/SmartStore.Services/DataExchange/_Pseudo/AdminController.cs new file mode 100644 index 0000000000..e0661ccf40 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/_Pseudo/AdminController.cs @@ -0,0 +1,19 @@ +//using SmartStore.Core.Infrastructure; +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; +//using System.Web; +//using System.Web.Mvc; + +//namespace SmartStore.Services.DataExchange +//{ +// internal class AdminController : Controller +// { +// protected ActionResult Export(string providerSystemName, string selectedIds) +// { +// var exporter = EngineContext.Current.Resolve(); +// } +// } +//} diff --git a/src/Libraries/SmartStore.Services/DataExchange/_Pseudo/DataExportTask.cs b/src/Libraries/SmartStore.Services/DataExchange/_Pseudo/DataExportTask.cs new file mode 100644 index 0000000000..57c80834f6 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/_Pseudo/DataExportTask.cs @@ -0,0 +1,62 @@ +using SmartStore.Core.Localization; +using SmartStore.Services.Tasks; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Web; + +namespace SmartStore.Services.DataExchange +{ + internal class DataExportTask : ITask + { + private readonly IDataExporter _exporter; + private readonly IExportProfileService _exportProfileService; + + public DataExportTask( + IDataExporter exporter, + IExportProfileService exportProfileService) + { + _exporter = exporter; + _exportProfileService = exportProfileService; + } + + public Localizer T { get; set; } + + public void Execute(TaskExecutionContext ctx) + { + // TODO: proper error handling + + var profileId = ctx.ScheduleTask.Alias.ToInt(); + var profile = _exportProfileService.GetExportProfileById(profileId); + + // TODO: find a better way to transmit selected entity ids (e.g. new TaskExecutionContext.Parameters property) + var selectedIdsCacheKey = profile.GetSelectedEntityIdsCacheKey(); + var selectedEntityIds = HttpRuntime.Cache[selectedIdsCacheKey] as string; + HttpRuntime.Cache.Remove(selectedIdsCacheKey); + + // load provider + var provider = _exportProfileService.LoadProvider(profile.ProviderSystemName); + if (provider == null) + throw new SmartException(T("Admin.Common.ProviderNotLoaded", profile.ProviderSystemName.NaIfEmpty())); + + // build export request + var request = new DataExportRequest(profile); + request.ProgressSetter = delegate(int val, int max, string msg) + { + ctx.SetProgress(val, max, msg); + }; + if (selectedEntityIds.HasValue()) + { + request.EntitiesToExport = selectedEntityIds.ToIntArray(); + } + + // process! + _exporter.Export(request, ctx.CancellationToken); + + ctx.CancellationToken.ThrowIfCancellationRequested(); + } + } +} diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 1c3348629e..f63d565765 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -169,33 +169,36 @@ + - - - - - - - - - - - - + + + + + + + + + + + + - + - - + + - + + + @@ -574,9 +577,7 @@ Nop_Services_EuropaCheckVatService_checkVatService - - - + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs index 7a310a4c18..55425cbc7e 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs @@ -61,7 +61,7 @@ public ActionResult ProductEditTab(int productId) ViewBag.DefaultAgeGroup = T("Common.Auto"); // we do not have export profile context here, so we simply use the first profile - var profile = _exportService.GetExportProfilesBySystemName(GmcExportXmlProvider.SystemName).FirstOrDefault(); + var profile = _exportService.GetExportProfilesBySystemName(GmcXmlExportProvider.SystemName).FirstOrDefault(); if (profile != null) { var config = XmlHelper.Deserialize(profile.ProviderConfigData, typeof(ProfileConfigurationModel)) as ProfileConfigurationModel; @@ -73,12 +73,12 @@ public ActionResult ProductEditTab(int productId) ViewBag.DefaultMaterial = config.Material; ViewBag.DefaultPattern = config.Pattern; - if (config.Gender.HasValue() && config.Gender != GmcExportXmlProvider.Unspecified) + if (config.Gender.HasValue() && config.Gender != GmcXmlExportProvider.Unspecified) { ViewBag.DefaultGender = T("Plugins.Feed.Froogle.Gender" + culture.TextInfo.ToTitleCase(config.Gender)); } - if (config.AgeGroup.HasValue() && config.AgeGroup != GmcExportXmlProvider.Unspecified) + if (config.AgeGroup.HasValue() && config.AgeGroup != GmcXmlExportProvider.Unspecified) { ViewBag.DefaultAgeGroup = T("Plugins.Feed.Froogle.AgeGroup" + culture.TextInfo.ToTitleCase(config.AgeGroup)); } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcExportXmlProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs similarity index 99% rename from src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcExportXmlProvider.cs rename to src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index 412737afc7..3f1dac0bd2 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcExportXmlProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -28,7 +28,7 @@ namespace SmartStore.GoogleMerchantCenter.Providers ExportFeatures.OffersBrandFallback, ExportFeatures.CanIncludeMainPicture, ExportFeatures.UsesSpecialPrice)] - public class GmcExportXmlProvider : IExportProvider + public class GmcXmlExportProvider : IExportProvider { private const string _googleNamespace = "http://base.google.com/ns/1.0"; @@ -36,7 +36,7 @@ public class GmcExportXmlProvider : IExportProvider private readonly IMeasureService _measureService; private readonly MeasureSettings _measureSettings; - public GmcExportXmlProvider( + public GmcXmlExportProvider( IGoogleFeedService googleFeedService, IMeasureService measureService, MeasureSettings measureSettings) diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj index 0826ad5831..a2b1919903 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj @@ -176,7 +176,7 @@ - + diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs index 76d0abdaf9..10b7fab549 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs @@ -13,7 +13,7 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; -using SmartStore.Services.DataExchange.ExportProvider; +using SmartStore.Services.DataExchange.Providers; using SmartStore.Services.Discounts; using SmartStore.Services.Filter; using SmartStore.Services.Helpers; @@ -759,7 +759,7 @@ public ActionResult ExportXml() if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - return Export(ExportCategoryXmlProvider.SystemName, null); + return Export(CategoryXmlExportProvider.SystemName, null); } #endregion diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs index 96de99a9d5..ab57549c4e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs @@ -24,7 +24,7 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; -using SmartStore.Services.DataExchange.ExportProvider; +using SmartStore.Services.DataExchange.Providers; using SmartStore.Services.Directory; using SmartStore.Services.Forums; using SmartStore.Services.Helpers; @@ -1765,7 +1765,7 @@ public ActionResult ExportExcelAll() if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); - return Export(ExportCustomerXlsxProvider.SystemName, null); + return Export(CustomerXlsxExportProvider.SystemName, null); } [Compress] @@ -1774,7 +1774,7 @@ public ActionResult ExportExcelSelected(string selectedIds) if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); - return Export(ExportCustomerXlsxProvider.SystemName, selectedIds); + return Export(CustomerXlsxExportProvider.SystemName, selectedIds); } [Compress] @@ -1783,7 +1783,7 @@ public ActionResult ExportXmlAll() if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); - return Export(ExportCustomerXmlProvider.SystemName, null); + return Export(CustomerXmlExportProvider.SystemName, null); } [Compress] @@ -1792,7 +1792,7 @@ public ActionResult ExportXmlSelected(string selectedIds) if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) return AccessDeniedView(); - return Export(ExportCustomerXmlProvider.SystemName, selectedIds); + return Export(CustomerXmlExportProvider.SystemName, selectedIds); } #endregion diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index a244d1f807..984f3641a1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -244,7 +244,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile try { var publicFolder = Path.Combine(HttpRuntime.AppDomainAppPath, ExportProfileTask.PublicFolder); - var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); + var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); if (resultInfo != null && resultInfo.Files != null) { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs index 840cecc2e4..d08dd31393 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs @@ -10,7 +10,7 @@ using SmartStore.Core.Logging; using SmartStore.Services.Catalog; using SmartStore.Services.Common; -using SmartStore.Services.DataExchange.ExportProvider; +using SmartStore.Services.DataExchange.Providers; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Media; @@ -444,7 +444,7 @@ public ActionResult ExportXml() if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - return Export(ExportManufacturerXmlProvider.SystemName, null); + return Export(ManufacturerXmlExportProvider.SystemName, null); } #endregion diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/NewsLetterSubscriptionController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/NewsLetterSubscriptionController.cs index 8ba0f92043..19cccae106 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/NewsLetterSubscriptionController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/NewsLetterSubscriptionController.cs @@ -3,7 +3,7 @@ using System.Web.Mvc; using SmartStore.Admin.Models.Messages; using SmartStore.Core.Domain.Common; -using SmartStore.Services.DataExchange.ExportProvider; +using SmartStore.Services.DataExchange.Providers; using SmartStore.Services.Helpers; using SmartStore.Services.Messages; using SmartStore.Services.Security; @@ -156,7 +156,7 @@ public ActionResult ExportCsv() if (!_permissionService.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers)) return AccessDeniedView(); - return Export(ExportNewsSubscriptionCsvProvider.SystemName, null); + return Export(SubscriberCsvExportProvider.SystemName, null); } [HttpPost] diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs index f2a096e559..55e8e13585 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs @@ -20,7 +20,7 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; -using SmartStore.Services.DataExchange.ExportProvider; +using SmartStore.Services.DataExchange.Providers; using SmartStore.Services.Directory; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; @@ -877,7 +877,7 @@ public ActionResult ExportXmlAll() if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - return Export(ExportOrderXmlProvider.SystemName, null); + return Export(OrderXmlExportProvider.SystemName, null); } [HttpPost, Compress] @@ -886,7 +886,7 @@ public ActionResult ExportXmlSelected(string selectedIds) if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - return Export(ExportOrderXmlProvider.SystemName, selectedIds); + return Export(OrderXmlExportProvider.SystemName, selectedIds); } [Compress] @@ -895,7 +895,7 @@ public ActionResult ExportExcelAll() if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - return Export(ExportOrderXlsxProvider.SystemName, null); + return Export(OrderXlsxExportProvider.SystemName, null); } [HttpPost, Compress] @@ -904,7 +904,7 @@ public ActionResult ExportExcelSelected(string selectedIds) if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - return Export(ExportOrderXlsxProvider.SystemName, selectedIds); + return Export(OrderXlsxExportProvider.SystemName, selectedIds); } public ActionResult ExportPdf(bool all, string selectedIds = null) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 5b79e00770..5e22159a4c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -27,7 +27,7 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; -using SmartStore.Services.DataExchange.ExportProvider; +using SmartStore.Services.DataExchange.Providers; using SmartStore.Services.Directory; using SmartStore.Services.Discounts; using SmartStore.Services.ExportImport; @@ -2796,7 +2796,7 @@ public ActionResult ExportXmlAll() if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - return Export(ExportProductXmlProvider.SystemName, null); + return Export(ProductXmlExportProvider.SystemName, null); } [HttpPost, Compress] @@ -2805,7 +2805,7 @@ public ActionResult ExportXmlSelected(string selectedIds) if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - return Export(ExportProductXmlProvider.SystemName, selectedIds); + return Export(ProductXmlExportProvider.SystemName, selectedIds); } [Compress] @@ -2814,7 +2814,7 @@ public ActionResult ExportExcelAll() if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - return Export(ExportProductXlsxProvider.SystemName, null); + return Export(ProductXlsxExportProvider.SystemName, null); } [HttpPost, Compress] @@ -2823,7 +2823,7 @@ public ActionResult ExportExcelSelected(string selectedIds) if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - return Export(ExportProductXlsxProvider.SystemName, selectedIds); + return Export(ProductXlsxExportProvider.SystemName, selectedIds); } public ActionResult ExportPdf(bool all, string selectedIds = null) From f84479657188817ff57b96744375f0927720342f Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 7 Nov 2015 02:12:51 +0100 Subject: [PATCH 180/874] Export framework: minor renaming --- .../ComponentModel/HybridExpando.cs | 13 +- .../ExportTask/ExportProfileTask.cs | 154 +++++++++--------- .../ExportTask/ExportProfileTaskContext.cs | 19 ++- .../DataExchange/Internal/DynamicEntity.cs | 32 +--- 4 files changed, 98 insertions(+), 120 deletions(-) diff --git a/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs b/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs index 42061fad53..6697e19d0c 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs @@ -324,12 +324,12 @@ public object this[string key] get { object result = null; - if (TryGetMemberCore(key, out result)) + if (!TryGetMemberCore(key, out result)) { - return result; + throw new KeyNotFoundException(); } - throw new KeyNotFoundException(); + return result; } set { @@ -383,9 +383,10 @@ public bool Contains(KeyValuePair item, bool includeInstanceProp /// public bool Contains(string propertyName, bool includeInstanceProperties = false) { - bool contains = Properties.ContainsKey(propertyName); - if (contains) - return true; + if (Properties.ContainsKey(propertyName)) + { + return true; + } if (includeInstanceProperties && _instance != null) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 48c50afc11..86c909fc27 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -206,13 +206,13 @@ private IExportDataSegmenterProvider CreateSegmenter(ExportProfileTaskContext ct switch (ctx.Provider.Value.EntityType) { case ExportEntityType.Product: - ctx.Export.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.Segmenter = new ExportDataSegmenter ( skip => GetProducts(ctx, skip), entities => { // load data behind navigation properties for current queue in one go - ctx.DataContextProduct = new ProductExportContext(entities, + ctx.ProductExportContext = new ProductExportContext(entities, 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), @@ -231,12 +231,12 @@ private IExportDataSegmenterProvider CreateSegmenter(ExportProfileTaskContext ct break; case ExportEntityType.Order: - ctx.Export.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.Segmenter = new ExportDataSegmenter ( skip => GetOrders(ctx, skip), entities => { - ctx.DataContextOrder = new OrderExportContext(entities, + ctx.OrderExportContext = new OrderExportContext(entities, x => _customerService.Value.GetCustomersByIds(x), x => _customerService.Value.GetRewardPointsHistoriesByCustomerIds(x), x => _addressesService.Value.GetAddressByIds(x), @@ -250,12 +250,12 @@ private IExportDataSegmenterProvider CreateSegmenter(ExportProfileTaskContext ct break; case ExportEntityType.Manufacturer: - ctx.Export.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.Segmenter = new ExportDataSegmenter ( skip => GetManufacturers(ctx, skip), entities => { - ctx.DataContextManufacturer = new ManufacturerExportContext(entities, + ctx.ManufacturerExportContext = new ManufacturerExportContext(entities, x => _manufacturerService.Value.GetProductManufacturersByManufacturerIds(x), x => _pictureService.Value.GetPicturesByIds(x) ); @@ -266,12 +266,12 @@ private IExportDataSegmenterProvider CreateSegmenter(ExportProfileTaskContext ct break; case ExportEntityType.Category: - ctx.Export.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.Segmenter = new ExportDataSegmenter ( skip => GetCategories(ctx, skip), entities => { - ctx.DataContextCategory = new CategoryExportContext(entities, + ctx.CategoryExportContext = new CategoryExportContext(entities, x => _categoryService.Value.GetProductCategoriesByCategoryIds(x), x => _pictureService.Value.GetPicturesByIds(x) ); @@ -282,12 +282,12 @@ private IExportDataSegmenterProvider CreateSegmenter(ExportProfileTaskContext ct break; case ExportEntityType.Customer: - ctx.Export.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.Segmenter = new ExportDataSegmenter ( skip => GetCustomers(ctx, skip), entities => { - ctx.DataContextCustomer = new CustomerExportContext(entities, + ctx.CustomerExportContext = new CustomerExportContext(entities, x => _genericAttributeService.Value.GetAttributesForEntity(x, "Customer") ); }, @@ -297,7 +297,7 @@ private IExportDataSegmenterProvider CreateSegmenter(ExportProfileTaskContext ct break; case ExportEntityType.NewsLetterSubscription: - ctx.Export.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.Segmenter = new ExportDataSegmenter ( skip => GetNewsLetterSubscriptions(ctx, skip), null, @@ -307,11 +307,11 @@ private IExportDataSegmenterProvider CreateSegmenter(ExportProfileTaskContext ct break; default: - ctx.Export.Segmenter = null; + ctx.ExecuteContext.Segmenter = null; break; } - return ctx.Export.Segmenter as IExportDataSegmenterProvider; + return ctx.ExecuteContext.Segmenter as IExportDataSegmenterProvider; } private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic expando, Product product) @@ -352,7 +352,7 @@ private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic exp else if (ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ManufacturerAndNameAndShortDescription || ctx.Projection.DescriptionMerging == ExportDescriptionMerging.ManufacturerAndNameAndDescription) { - var productManus = ctx.DataContextProduct.ProductManufacturers.Load(product.Id); + var productManus = ctx.ProductExportContext.ProductManufacturers.Load(product.Id); if (productManus != null && productManus.Any()) description = productManus.First().Manufacturer.GetLocalized(x => x.Name, languageId, true, false); @@ -429,7 +429,7 @@ private decimal CalculatePrice(ExportProfileTaskContext ctx, Product product, bo // price type if (ctx.Projection.PriceType.HasValue && !forAttributeCombination) { - var priceCalculationContext = ctx.DataContextProduct as PriceCalculationContext; + var priceCalculationContext = ctx.ProductExportContext as PriceCalculationContext; if (ctx.Projection.PriceType.Value == PriceDisplayType.LowestPrice) { @@ -1486,13 +1486,13 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro pictureSize = ctx.Projection.PictureSize; var perfLoadId = (ctx.IsPreview ? 0 : product.Id); // perf preview (it's a compromise) - var productPictures = ctx.DataContextProduct.ProductPictures.Load(perfLoadId); - var productManufacturers = ctx.DataContextProduct.ProductManufacturers.Load(perfLoadId); - var productCategories = ctx.DataContextProduct.ProductCategories.Load(product.Id); - var productAttributes = ctx.DataContextProduct.Attributes.Load(product.Id); - var productAttributeCombinations = ctx.DataContextProduct.AttributeCombinations.Load(product.Id); - var productTags = ctx.DataContextProduct.ProductTags.Load(perfLoadId); - var specificationAttributes = ctx.DataContextProduct.ProductSpecificationAttributes.Load(perfLoadId); + var productPictures = ctx.ProductExportContext.ProductPictures.Load(perfLoadId); + var productManufacturers = ctx.ProductExportContext.ProductManufacturers.Load(perfLoadId); + var productCategories = ctx.ProductExportContext.ProductCategories.Load(product.Id); + var productAttributes = ctx.ProductExportContext.Attributes.Load(product.Id); + var productAttributeCombinations = ctx.ProductExportContext.AttributeCombinations.Load(product.Id); + var productTags = ctx.ProductExportContext.ProductTags.Load(perfLoadId); + var specificationAttributes = ctx.ProductExportContext.ProductSpecificationAttributes.Load(perfLoadId); dynamic expando = ToExpando(ctx, product); @@ -1609,7 +1609,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro if (product.HasTierPrices) { - var tierPrices = ctx.DataContextProduct.TierPrices.Load(product.Id) + var tierPrices = ctx.ProductExportContext.TierPrices.Load(product.Id) .RemoveDuplicatedQuantities(); expando.TierPrices = tierPrices @@ -1630,7 +1630,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro if (product.HasDiscountsApplied) { - var appliedDiscounts = ctx.DataContextProduct.AppliedDiscounts.Load(product.Id); + var appliedDiscounts = ctx.ProductExportContext.AppliedDiscounts.Load(product.Id); expando.AppliedDiscounts = appliedDiscounts .Select(x => ToExpando(ctx, x)) @@ -1656,7 +1656,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro if (product.ProductType == ProductType.BundledProduct) { - var bundleItems = ctx.DataContextProduct.ProductBundleItems.Load(perfLoadId); + var bundleItems = ctx.ProductExportContext.ProductBundleItems.Load(perfLoadId); expando.ProductBundleItems = bundleItems .Select(x => @@ -1696,7 +1696,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro if (ctx.Supports(ExportFeatures.OffersBrandFallback)) { string brand = null; - var productManus = ctx.DataContextProduct.ProductManufacturers.Load(perfLoadId); + var productManus = ctx.ProductExportContext.ProductManufacturers.Load(perfLoadId); if (productManus != null && productManus.Any()) brand = productManus.First().Manufacturer.GetLocalized(x => x.Name, languageId, true, false); @@ -1851,14 +1851,14 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Order order { var result = new List(); - ctx.DataContextOrder.Addresses.Collect(order.ShippingAddressId ?? 0); + ctx.OrderExportContext.Addresses.Collect(order.ShippingAddressId ?? 0); var perfLoadId = (ctx.IsPreview ? 0 : order.Id); - var addresses = ctx.DataContextOrder.Addresses.Load(ctx.IsPreview ? 0 : order.BillingAddressId); - var customers = ctx.DataContextOrder.Customers.Load(order.CustomerId); - var rewardPointsHistories = ctx.DataContextOrder.RewardPointsHistories.Load(ctx.IsPreview ? 0 : order.CustomerId); - var orderItems = ctx.DataContextOrder.OrderItems.Load(perfLoadId); - var shipments = ctx.DataContextOrder.Shipments.Load(perfLoadId); + var addresses = ctx.OrderExportContext.Addresses.Load(ctx.IsPreview ? 0 : order.BillingAddressId); + var customers = ctx.OrderExportContext.Customers.Load(order.CustomerId); + var rewardPointsHistories = ctx.OrderExportContext.RewardPointsHistories.Load(ctx.IsPreview ? 0 : order.CustomerId); + var orderItems = ctx.OrderExportContext.OrderItems.Load(perfLoadId); + var shipments = ctx.OrderExportContext.Shipments.Load(perfLoadId); dynamic expando = ToExpando(ctx, order); @@ -1918,13 +1918,13 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Manufacture { var result = new List(); - var productManufacturers = ctx.DataContextManufacturer.ProductManufacturers.Load(manu.Id); + var productManufacturers = ctx.ManufacturerExportContext.ProductManufacturers.Load(manu.Id); dynamic expando = ToExpando(ctx, manu); if (!ctx.IsPreview && manu.PictureId.HasValue) { - var pictures = ctx.DataContextManufacturer.Pictures.Load(manu.PictureId.Value); + var pictures = ctx.ManufacturerExportContext.Pictures.Load(manu.PictureId.Value); if (pictures.Count > 0) expando.Picture = ToExpando(ctx, pictures.First(), _mediaSettings.Value.ManufacturerThumbPictureSize, _mediaSettings.Value.ManufacturerThumbPictureSize); @@ -1954,13 +1954,13 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Category ca { var result = new List(); - var productCategories = ctx.DataContextCategory.ProductCategories.Load(category.Id); + var productCategories = ctx.CategoryExportContext.ProductCategories.Load(category.Id); dynamic expando = ToExpando(ctx, category); if (!ctx.IsPreview && category.PictureId.HasValue) { - var pictures = ctx.DataContextCategory.Pictures.Load(category.PictureId.Value); + var pictures = ctx.CategoryExportContext.Pictures.Load(category.PictureId.Value); if (pictures.Count > 0) expando.Picture = ToExpando(ctx, pictures.First(), _mediaSettings.Value.CategoryThumbPictureSize, _mediaSettings.Value.CategoryThumbPictureSize); @@ -1991,7 +1991,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Customer cu var result = new List(); var perfLoadId = (ctx.IsPreview ? 0 : customer.Id); - var genericAttributes = ctx.DataContextCustomer.GenericAttributes.Load(perfLoadId); + var genericAttributes = ctx.CustomerExportContext.GenericAttributes.Load(perfLoadId); dynamic expando = ToExpando(ctx, customer); @@ -2142,7 +2142,7 @@ private List GetProducts(ExportProfileTaskContext ctx, int skip) { SetProgress(ctx, products.Count); - _services.DbContext.DetachEntities(result); + _services.DbContext.DetachEntities(result); } catch { } @@ -2430,7 +2430,7 @@ private List Init(ExportProfileTaskContext ctx, int? totalRecords = null) private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) { - if (ctx.Export.Abort != ExportAbortion.None) + if (ctx.ExecuteContext.Abort != ExportAbortion.None) return; int fileIndex = 0; @@ -2459,15 +2459,15 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Log.Information(logHead.ToString()); } - ctx.Export.Store = ToExpando(ctx, ctx.Store); + ctx.ExecuteContext.Store = ToExpando(ctx, ctx.Store); - ctx.Export.MaxFileNameLength = _dataExchangeSettings.Value.MaxFileNameLength; + ctx.ExecuteContext.MaxFileNameLength = _dataExchangeSettings.Value.MaxFileNameLength; - ctx.Export.FileExtension = (ctx.Provider.Value.FileExtension.HasValue() ? ctx.Provider.Value.FileExtension.ToLower().EnsureStartsWith(".") : ""); + ctx.ExecuteContext.FileExtension = (ctx.Provider.Value.FileExtension.HasValue() ? ctx.Provider.Value.FileExtension.ToLower().EnsureStartsWith(".") : ""); - ctx.Export.HasPublicDeployment = ctx.Profile.Deployments.Any(x => x.IsPublic && x.DeploymentType == ExportDeploymentType.FileSystem); + ctx.ExecuteContext.HasPublicDeployment = ctx.Profile.Deployments.Any(x => x.IsPublic && x.DeploymentType == ExportDeploymentType.FileSystem); - ctx.Export.PublicFolderPath = (ctx.Export.HasPublicDeployment ? Path.Combine(HttpRuntime.AppDomainAppPath, PublicFolder) : null); + ctx.ExecuteContext.PublicFolderPath = (ctx.ExecuteContext.HasPublicDeployment ? Path.Combine(HttpRuntime.AppDomainAppPath, PublicFolder) : null); using (var segmenter = CreateSegmenter(ctx)) @@ -2482,59 +2482,59 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.Log.Information("There are no records to export"); } - while (ctx.Export.Abort == ExportAbortion.None && segmenter.HasData) + while (ctx.ExecuteContext.Abort == ExportAbortion.None && segmenter.HasData) { segmenter.RecordPerSegmentCount = 0; - ctx.Export.RecordsSucceeded = 0; + ctx.ExecuteContext.RecordsSucceeded = 0; try { if (ctx.IsFileBasedExport) { - var resolvedPattern = ctx.Profile.ResolveFileNamePattern(ctx.Store, ++fileIndex, ctx.Export.MaxFileNameLength); + var resolvedPattern = ctx.Profile.ResolveFileNamePattern(ctx.Store, ++fileIndex, ctx.ExecuteContext.MaxFileNameLength); - ctx.Export.FileName = resolvedPattern + ctx.Export.FileExtension; - ctx.Export.FilePath = Path.Combine(ctx.Export.Folder, ctx.Export.FileName); + ctx.ExecuteContext.FileName = resolvedPattern + ctx.ExecuteContext.FileExtension; + ctx.ExecuteContext.FilePath = Path.Combine(ctx.ExecuteContext.Folder, ctx.ExecuteContext.FileName); - if (ctx.Export.HasPublicDeployment) - ctx.Export.PublicFileUrl = ctx.Store.Url.EnsureEndsWith("/") + PublicFolder.EnsureEndsWith("/") + ctx.Export.FileName; + if (ctx.ExecuteContext.HasPublicDeployment) + ctx.ExecuteContext.PublicFileUrl = ctx.Store.Url.EnsureEndsWith("/") + PublicFolder.EnsureEndsWith("/") + ctx.ExecuteContext.FileName; - ctx.Provider.Value.Execute(ctx.Export); + ctx.Provider.Value.Execute(ctx.ExecuteContext); // create info for deployment list in profile edit - if (File.Exists(ctx.Export.FilePath)) + if (File.Exists(ctx.ExecuteContext.FilePath)) { ctx.Result.Files.Add(new DataExportResult.ExportFileInfo { StoreId = ctx.Store.Id, - FileName = ctx.Export.FileName + FileName = ctx.ExecuteContext.FileName }); } } else { - ctx.Provider.Value.Execute(ctx.Export); + ctx.Provider.Value.Execute(ctx.ExecuteContext); } - ctx.Log.Information("Provider reports {0} successful exported record(s)".FormatInvariant(ctx.Export.RecordsSucceeded)); + ctx.Log.Information("Provider reports {0} successful exported record(s)".FormatInvariant(ctx.ExecuteContext.RecordsSucceeded)); } catch (Exception exc) { - ctx.Export.Abort = ExportAbortion.Hard; + ctx.ExecuteContext.Abort = ExportAbortion.Hard; ctx.Log.Error("The provider failed to execute the export: " + exc.ToAllMessages(), exc); ctx.Result.LastError = exc.ToString(); } - if (ctx.Export.IsMaxFailures) + if (ctx.ExecuteContext.IsMaxFailures) ctx.Log.Warning("Export aborted. The maximum number of failures has been reached"); if (ctx.TaskContext.CancellationToken.IsCancellationRequested) ctx.Log.Warning("Export aborted. A cancellation has been requested"); } - if (ctx.Export.Abort != ExportAbortion.Hard) + if (ctx.ExecuteContext.Abort != ExportAbortion.Hard) { - ctx.Provider.Value.OnExecuted(ctx.Export); + ctx.Provider.Value.OnExecuted(ctx.ExecuteContext); } } } @@ -2559,7 +2559,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) } ctx.Log = logger; - ctx.Export.Log = logger; + ctx.ExecuteContext.Log = logger; ctx.ProgressInfo = T("Admin.DataExchange.Export.ProgressInfo"); if (ctx.Profile.ProviderConfigData.HasValue()) @@ -2567,7 +2567,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) var configInfo = ctx.Provider.Value.ConfigurationInfo; if (configInfo != null) { - ctx.Export.ConfigurationData = XmlHelper.Deserialize(ctx.Profile.ProviderConfigData, configInfo.ModelType); + ctx.ExecuteContext.ConfigurationData = XmlHelper.Deserialize(ctx.Profile.ProviderConfigData, configInfo.ModelType); } } @@ -2601,14 +2601,14 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) var stores = Init(ctx); - ctx.Export.Language = ToExpando(ctx, ctx.ContextLanguage); - ctx.Export.Customer = ToExpando(ctx, ctx.ContextCustomer); - ctx.Export.Currency = ToExpando(ctx, ctx.ContextCurrency); + ctx.ExecuteContext.Language = ToExpando(ctx, ctx.ContextLanguage); + ctx.ExecuteContext.Customer = ToExpando(ctx, ctx.ContextCustomer); + ctx.ExecuteContext.Currency = ToExpando(ctx, ctx.ContextCurrency); stores.ForEach(x => ExportCoreInner(ctx, x)); } - if (!ctx.IsPreview && ctx.Export.Abort != ExportAbortion.Hard) + if (!ctx.IsPreview && ctx.ExecuteContext.Abort != ExportAbortion.Hard) { if (ctx.IsFileBasedExport) { @@ -2673,7 +2673,7 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) try { - if (ctx.IsFileBasedExport && ctx.Export.Abort != ExportAbortion.Hard && ctx.Profile.Cleanup) + if (ctx.IsFileBasedExport && ctx.ExecuteContext.Abort != ExportAbortion.Hard && ctx.Profile.Cleanup) { FileSystemHelper.ClearDirectory(ctx.FolderContent, false); } @@ -2691,21 +2691,21 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) ctx.CategoryPathes.Clear(); ctx.Categories.Clear(); ctx.EntityIdsSelected.Clear(); - ctx.DataContextProduct = null; - ctx.DataContextOrder = null; - ctx.DataContextManufacturer = null; - ctx.DataContextCategory = null; - ctx.DataContextCustomer = null; - - ctx.Export.CustomProperties.Clear(); - ctx.Export.Log = null; + ctx.ProductExportContext = null; + ctx.OrderExportContext = null; + ctx.ManufacturerExportContext = null; + ctx.CategoryExportContext = null; + ctx.CustomerExportContext = null; + + ctx.ExecuteContext.CustomProperties.Clear(); + ctx.ExecuteContext.Log = null; ctx.Log = null; } catch { } } } - if (ctx.IsPreview || ctx.Export.Abort == ExportAbortion.Hard) + if (ctx.IsPreview || ctx.ExecuteContext.Abort == ExportAbortion.Hard) return; // post process order entities @@ -2874,7 +2874,7 @@ public DataExportResult Execute( if (customProperties != null) { foreach (var item in customProperties) - ctx.Export.CustomProperties.Add(item.Key, item.Value); + ctx.ExecuteContext.CustomProperties.Add(item.Key, item.Value); } ExportCoreOuter(ctx); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs index 576b1ffc08..8c1286b5d6 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs @@ -62,8 +62,8 @@ public ExportProfileTaskContext( FileFolder = (IsFileBasedExport ? FolderContent : null) }; - Export = new ExportExecuteContext(Result, TaskContext.CancellationToken, FolderContent); - Export.Projection = XmlHelper.Deserialize(profile.Projection); + ExecuteContext = new ExportExecuteContext(Result, TaskContext.CancellationToken, FolderContent); + ExecuteContext.Projection = XmlHelper.Deserialize(profile.Projection); } public List EntityIdsSelected { get; private set; } @@ -141,7 +141,7 @@ public string[] GetDeploymentFiles(ExportDeployment deployment) public HashSet NewsletterSubscriptions { get; set; } // data loaded once per page - public ProductExportContext DataContextProduct + public ProductExportContext ProductExportContext { get { @@ -155,7 +155,8 @@ public ProductExportContext DataContextProduct _productExportContext = value; } } - public OrderExportContext DataContextOrder + + public OrderExportContext OrderExportContext { get { @@ -169,7 +170,8 @@ public OrderExportContext DataContextOrder _orderExportContext = value; } } - public ManufacturerExportContext DataContextManufacturer + + public ManufacturerExportContext ManufacturerExportContext { get { @@ -183,7 +185,8 @@ public ManufacturerExportContext DataContextManufacturer _manufacturerExportContext = value; } } - public CategoryExportContext DataContextCategory + + public CategoryExportContext CategoryExportContext { get { @@ -197,7 +200,7 @@ public CategoryExportContext DataContextCategory _categoryExportContext = value; } } - public CustomerExportContext DataContextCustomer + public CustomerExportContext CustomerExportContext { get { @@ -212,7 +215,7 @@ public CustomerExportContext DataContextCustomer } } - public ExportExecuteContext Export { get; set; } + public ExportExecuteContext ExecuteContext { get; set; } public DataExportResult Result { get; set; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs index 6940371341..287927315d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs @@ -1,11 +1,5 @@ -using SmartStore.ComponentModel; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Dynamic; -using SmartStore.Utilities; +using System.Collections.Generic; +using SmartStore.ComponentModel; namespace SmartStore.Services.DataExchange.Internal { @@ -14,7 +8,7 @@ internal class DynamicEntity : HybridExpando public DynamicEntity(DynamicEntity dynamicEntity) : this(dynamicEntity.WrappedObject) { - base.Properties.Merge(dynamicEntity, true); + MergeRange(dynamicEntity); } public DynamicEntity(object entity) @@ -39,26 +33,6 @@ public void MergeRange(IDictionary other) protected override bool TrySetMemberCore(string name, object value) { - // Virtual navigation properties of entities should not be set as is. - // A previously created ExpandoEntity instance is set instead. - // But assigning the value to the wrapped entity instance would result - // in a type mismatch error, as both types obviously don't match. - // Therefore we save reference values in the local values dictionary. - - var pi = base.GetPropertyInfo(name); - - if (pi != null && (pi.PropertyType.IsPredefinedType() || !pi.GetMethod.IsVirtual)) - { - // Property exists, is NOT complex, or IS complex but NOT virtual - try - { - Fasterflect.PropertyInfoExtensions.Set(pi, WrappedObject, value); - return true; - } - catch { } - } - - // Property does not exist, or is virtual complex Properties[name] = value; return true; } From 2123d82c033d56894346cb9047e8744fa46689f9 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 7 Nov 2015 11:33:03 +0100 Subject: [PATCH 181/874] Refactoring: Cleaner not to have MVC related code in service layer --- .../201509150931528_ExportFramework2.cs | 4 + .../SmartStore.Services/Blogs/BlogService.cs | 51 --------- .../SmartStore.Services/Blogs/IBlogService.cs | 10 -- .../Catalog/IProductService.cs | 9 -- .../Catalog/ProductService.cs | 87 +-------------- .../Forums/ForumService.cs | 103 ------------------ .../Forums/IForumService.cs | 16 --- .../SmartStore.Services/News/INewsService.cs | 10 -- .../SmartStore.Services/News/NewsService.cs | 56 ---------- .../Controllers/BlogController.cs | 43 +++++++- .../Controllers/BoardsController.cs | 76 ++++++++++++- .../Controllers/CatalogController.cs | 58 +++++++++- .../Controllers/NewsController.cs | 39 ++++++- 13 files changed, 212 insertions(+), 350 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index 4cf5af7438..174f2d1f44 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -59,6 +59,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Edited payment method '{0}' ({1})", "Zahlungsart '{0}' ({1}) bearbeitet"); + builder.AddOrUpdate("Admin.Configuration.Settings.Blog.ShowHeaderRSSUrl.Hint", + "Check to enable the blog RSS feed link in customers browser address bar.", + "Legt fest, ob der RSS-Feed-Link in der Adressleiste des Browsers angezeigt werden soll."); + builder.AddOrUpdate("Admin.System.SeNames", "SEO Names", "SEO Namen"); builder.Delete("Admin.System.SeNames.DeleteSelected"); diff --git a/src/Libraries/SmartStore.Services/Blogs/BlogService.cs b/src/Libraries/SmartStore.Services/Blogs/BlogService.cs index c616abefbe..f3bb8103bb 100644 --- a/src/Libraries/SmartStore.Services/Blogs/BlogService.cs +++ b/src/Libraries/SmartStore.Services/Blogs/BlogService.cs @@ -1,16 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; -using System.ServiceModel.Syndication; -using System.Web.Mvc; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Blogs; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; using SmartStore.Services.Localization; -using SmartStore.Services.Seo; -using SmartStore.Utilities; namespace SmartStore.Services.Blogs { @@ -260,53 +256,6 @@ public virtual void UpdateCommentTotals(BlogPost blogPost) UpdateBlogPost(blogPost); } - /// - /// Creates a RSS feed with blog posts - /// - /// UrlHelper to generate URLs - /// Language identifier - /// SmartSyndicationFeed object - public virtual SmartSyndicationFeed CreateRssFeed(UrlHelper urlHelper, int languageId) - { - if (urlHelper == null) - throw new ArgumentNullException("urlHelper"); - - DateTime? maxAge = null; - var protocol = _services.WebHelper.IsCurrentConnectionSecured() ? "https" : "http"; - var selfLink = urlHelper.RouteUrl("BlogRSS", new { languageId = languageId }, protocol); - var blogLink = urlHelper.RouteUrl("Blog", null, protocol); - - var title = "{0} - Blog".FormatInvariant(_services.StoreContext.CurrentStore.Name); - - if (_blogSettings.MaxAgeInDays > 0) - maxAge = DateTime.UtcNow.Subtract(new TimeSpan(_blogSettings.MaxAgeInDays, 0, 0, 0)); - - var language = _languageService.GetLanguageById(languageId); - var feed = new SmartSyndicationFeed(new Uri(blogLink), title); - - feed.AddNamespaces(false); - feed.Init(selfLink, language); - - if (!_blogSettings.Enabled) - return feed; - - var items = new List(); - var blogPosts = GetAllBlogPosts(_services.StoreContext.CurrentStore.Id, languageId, null, null, 0, int.MaxValue, false, maxAge); - - foreach (var blogPost in blogPosts) - { - var blogPostUrl = urlHelper.RouteUrl("BlogPost", new { SeName = blogPost.GetSeName(blogPost.LanguageId, ensureTwoPublishedLanguages: false) }, "http"); - - var item = feed.CreateItem(blogPost.Title, blogPost.Body, blogPostUrl, blogPost.CreatedOnUtc); - - items.Add(item); - } - - feed.Items = items; - - return feed; - } - #endregion } } diff --git a/src/Libraries/SmartStore.Services/Blogs/IBlogService.cs b/src/Libraries/SmartStore.Services/Blogs/IBlogService.cs index 983932b0bf..682ddbe191 100644 --- a/src/Libraries/SmartStore.Services/Blogs/IBlogService.cs +++ b/src/Libraries/SmartStore.Services/Blogs/IBlogService.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; -using System.Web.Mvc; using SmartStore.Core; using SmartStore.Core.Domain.Blogs; -using SmartStore.Utilities; namespace SmartStore.Services.Blogs { @@ -79,13 +77,5 @@ IPagedList GetAllBlogPostsByTag(int storeId, int languageId, string ta /// /// Blog post void UpdateCommentTotals(BlogPost blogPost); - - /// - /// Creates a RSS feed with blog posts - /// - /// UrlHelper to generate URLs - /// Language identifier - /// SmartSyndicationFeed object - SmartSyndicationFeed CreateRssFeed(UrlHelper urlHelper, int languageId); } } diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs index 69a8c2254b..eb7dfa9fb9 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductService.cs @@ -2,14 +2,12 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Web.Mvc; using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Orders; -using SmartStore.Utilities; namespace SmartStore.Services.Catalog { @@ -156,13 +154,6 @@ public partial interface IProductService /// Product void UpdateHasDiscountsApplied(Product product); - /// - /// Creates a RSS feed with recently added products - /// - /// UrlHelper to generate URLs - /// SmartSyndicationFeed object - SmartSyndicationFeed CreateRecentlyAddedProductsRssFeed(UrlHelper urlHelper); - /// /// Get product tags by product identifiers /// diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs index c18f230bf5..6f273c7bfa 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs @@ -4,8 +4,6 @@ using System.Globalization; using System.Linq; using System.Linq.Expressions; -using System.ServiceModel.Syndication; -using System.Web.Mvc; using SmartStore.Collections; using SmartStore.Core; using SmartStore.Core.Caching; @@ -15,17 +13,13 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Security; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; using SmartStore.Services.Localization; -using SmartStore.Services.Media; using SmartStore.Services.Messages; using SmartStore.Services.Orders; -using SmartStore.Services.Seo; -using SmartStore.Utilities; namespace SmartStore.Services.Catalog { @@ -64,9 +58,6 @@ public partial class ProductService : IProductService private readonly LocalizationSettings _localizationSettings; private readonly CommonSettings _commonSettings; private readonly ICommonServices _services; - private readonly CatalogSettings _catalogSettings; - private readonly MediaSettings _mediaSettings; - private readonly IPictureService _pictureService; #endregion @@ -117,10 +108,7 @@ public ProductService( ICacheManager cacheManager, LocalizationSettings localizationSettings, CommonSettings commonSettings, - ICommonServices services, - CatalogSettings catalogSettings, - MediaSettings mediaSettings, - IPictureService pictureService) + ICommonServices services) { this._productRepository = productRepository; this._relatedProductRepository = relatedProductRepository; @@ -143,9 +131,6 @@ public ProductService( this._localizationSettings = localizationSettings; this._commonSettings = commonSettings; this._services = services; - this._catalogSettings = catalogSettings; - this._mediaSettings = mediaSettings; - this._pictureService = pictureService; this.QuerySettings = DbQuerySettings.Default; } @@ -1369,76 +1354,6 @@ public virtual void UpdateHasDiscountsApplied(Product product) UpdateProduct(product); } - /// - /// Creates a RSS feed with recently added products - /// - /// UrlHelper to generate URLs - /// SmartSyndicationFeed object - public virtual SmartSyndicationFeed CreateRecentlyAddedProductsRssFeed(UrlHelper urlHelper) - { - if (urlHelper == null) - throw new ArgumentNullException("urlHelper"); - - var protocol = _services.WebHelper.IsCurrentConnectionSecured() ? "https" : "http"; - var selfLink = urlHelper.RouteUrl("RecentlyAddedProductsRSS", null, protocol); - var recentProductsLink = urlHelper.RouteUrl("RecentlyAddedProducts", null, protocol); - - var title = "{0} - {1}".FormatInvariant(_services.StoreContext.CurrentStore.Name, _services.Localization.GetResource("RSS.RecentlyAddedProducts")); - - var feed = new SmartSyndicationFeed(new Uri(recentProductsLink), title, _services.Localization.GetResource("RSS.InformationAboutProducts")); - - feed.AddNamespaces(true); - feed.Init(selfLink, _services.WorkContext.WorkingLanguage); - - if (!_catalogSettings.RecentlyAddedProductsEnabled) - return feed; - - var items = new List(); - var searchContext = new ProductSearchContext - { - LanguageId = _services.WorkContext.WorkingLanguage.Id, - OrderBy = ProductSortingEnum.CreatedOn, - PageSize = _catalogSettings.RecentlyAddedProductsNumber, - StoreId = _services.StoreContext.CurrentStoreIdIfMultiStoreMode, - VisibleIndividuallyOnly = true - }; - - var products = SearchProducts(searchContext); - var storeUrl = _services.StoreContext.CurrentStore.Url; - - foreach (var product in products) - { - string productUrl = urlHelper.RouteUrl("Product", new { SeName = product.GetSeName() }, "http"); - if (productUrl.HasValue()) - { - var item = feed.CreateItem( - product.GetLocalized(x => x.Name), - product.GetLocalized(x => x.ShortDescription), - productUrl, - product.CreatedOnUtc, - product.FullDescription); - - try - { - // we add only the first picture - var picture = product.ProductPictures.OrderBy(x => x.DisplayOrder).Select(x => x.Picture).FirstOrDefault(); - - if (picture != null) - { - feed.AddEnclosue(item, picture, _pictureService.GetPictureUrl(picture, _mediaSettings.ProductDetailsPictureSize, false, storeUrl)); - } - } - catch { } - - items.Add(item); - } - } - - feed.Items = items; - - return feed; - } - public virtual Multimap GetProductTagsByProductIds(int[] productIds) { Guard.ArgumentNotNull(() => productIds); diff --git a/src/Libraries/SmartStore.Services/Forums/ForumService.cs b/src/Libraries/SmartStore.Services/Forums/ForumService.cs index c9f9e63dbd..5871f34ab6 100644 --- a/src/Libraries/SmartStore.Services/Forums/ForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/ForumService.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.ServiceModel.Syndication; -using System.Web.Mvc; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; @@ -12,10 +10,7 @@ using SmartStore.Core.Events; using SmartStore.Services.Common; using SmartStore.Services.Customers; -using SmartStore.Services.Localization; using SmartStore.Services.Messages; -using SmartStore.Services.Seo; -using SmartStore.Utilities; namespace SmartStore.Services.Forums { @@ -1477,104 +1472,6 @@ public virtual int CalculateTopicPageIndex(int forumTopicId, int pageSize, int p return pageIndex; } - /// - /// Creates a RSS feed with active discussions - /// - /// UrlHelper to generate URLs - /// SmartSyndicationFeed object - public virtual SmartSyndicationFeed CreateActiveDiscussionsRssFeed(UrlHelper urlHelper, int forumId) - { - if (urlHelper == null) - throw new ArgumentNullException("urlHelper"); - - var language = _services.WorkContext.WorkingLanguage; - var protocol = _services.WebHelper.IsCurrentConnectionSecured() ? "https" : "http"; - var selfLink = urlHelper.Action("ActiveDiscussionsRSS", "Boards", null, protocol); - var discussionLink = urlHelper.Action("ActiveDiscussions", "Boards", null, protocol); - - var title = "{0} - {1}".FormatInvariant(_services.StoreContext.CurrentStore.Name, _services.Localization.GetResource("Forum.ActiveDiscussionsFeedTitle")); - - var feed = new SmartSyndicationFeed(new Uri(discussionLink), title, _services.Localization.GetResource("Forum.ActiveDiscussionsFeedDescription")); - - feed.AddNamespaces(false); - feed.Init(selfLink, language); - - if (!_forumSettings.ActiveDiscussionsFeedEnabled) - return feed; - - var items = new List(); - var topics = GetActiveTopics(forumId, _forumSettings.ActiveDiscussionsFeedCount); - - var viewsText = _services.Localization.GetResource("Forum.Views"); - var repliesText = _services.Localization.GetResource("Forum.Replies"); - - foreach (var topic in topics) - { - string topicUrl = urlHelper.RouteUrl("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }, "http"); - var synopsis = "{0}: {1}, {2}: {3}".FormatInvariant(repliesText, topic.NumReplies, viewsText, topic.Views); - - var item = feed.CreateItem(topic.Subject, synopsis, topicUrl, topic.LastPostTime ?? topic.UpdatedOnUtc); - - items.Add(item); - } - - feed.Items = items; - - return feed; - } - - /// - /// Creates a RSS feed with forum topics - /// - /// UrlHelper to generate URLs - /// SmartSyndicationFeed object - public virtual SmartSyndicationFeed CreateForumRssFeed(UrlHelper urlHelper, int forumId) - { - if (urlHelper == null) - throw new ArgumentNullException("urlHelper"); - - var language = _services.WorkContext.WorkingLanguage; - var protocol = _services.WebHelper.IsCurrentConnectionSecured() ? "https" : "http"; - var selfLink = urlHelper.Action("ForumRSS", "Boards", null, protocol); - var forumLink = urlHelper.Action("Forum", "Boards", new { id = forumId }, protocol); - - var feed = new SmartSyndicationFeed(new Uri(forumLink), _services.StoreContext.CurrentStore.Name, _services.Localization.GetResource("Forum.ForumFeedDescription")); - - feed.AddNamespaces(false); - feed.Init(selfLink, language); - - if (!_forumSettings.ForumFeedsEnabled) - return feed; - - var forum = GetForumById(forumId); - - if (forum == null) - return feed; - - feed.Title = new TextSyndicationContent("{0} - {1}".FormatInvariant(_services.StoreContext.CurrentStore.Name, forum.GetLocalized(x => x.Name, language.Id))); - - var items = new List(); - var topics = GetAllTopics(forumId, 0, string.Empty, ForumSearchType.All, 0, 0, _forumSettings.ForumFeedCount); - - var viewsText = _services.Localization.GetResource("Forum.Views"); - var repliesText = _services.Localization.GetResource("Forum.Replies"); - - foreach (var topic in topics) - { - string topicUrl = urlHelper.RouteUrl("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }, "http"); - var synopsis = "{0}: {1}, {2}: {3}".FormatInvariant(repliesText, topic.NumReplies, viewsText, topic.Views); - - var item = feed.CreateItem(topic.Subject, synopsis, topicUrl, topic.LastPostTime ?? topic.UpdatedOnUtc); - - items.Add(item); - } - - feed.Items = items; - - return feed; - } - - #endregion } } diff --git a/src/Libraries/SmartStore.Services/Forums/IForumService.cs b/src/Libraries/SmartStore.Services/Forums/IForumService.cs index efcd21fa32..46e0f2f428 100644 --- a/src/Libraries/SmartStore.Services/Forums/IForumService.cs +++ b/src/Libraries/SmartStore.Services/Forums/IForumService.cs @@ -1,9 +1,7 @@ using System.Collections.Generic; -using System.Web.Mvc; using SmartStore.Core; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Forums; -using SmartStore.Utilities; namespace SmartStore.Services.Forums { @@ -346,19 +344,5 @@ IPagedList GetAllSubscriptions(int customerId, int forumId, /// Post identifier /// Page index int CalculateTopicPageIndex(int forumTopicId, int pageSize, int postId); - - /// - /// Creates a RSS feed with active discussions - /// - /// UrlHelper to generate URLs - /// SmartSyndicationFeed object - SmartSyndicationFeed CreateActiveDiscussionsRssFeed(UrlHelper urlHelper, int forumId); - - /// - /// Creates a RSS feed with forum topics - /// - /// UrlHelper to generate URLs - /// SmartSyndicationFeed object - SmartSyndicationFeed CreateForumRssFeed(UrlHelper urlHelper, int forumId); } } diff --git a/src/Libraries/SmartStore.Services/News/INewsService.cs b/src/Libraries/SmartStore.Services/News/INewsService.cs index c07cceb570..d25a8d8afa 100644 --- a/src/Libraries/SmartStore.Services/News/INewsService.cs +++ b/src/Libraries/SmartStore.Services/News/INewsService.cs @@ -1,9 +1,7 @@ using System; using System.Linq; -using System.Web.Mvc; using SmartStore.Core; using SmartStore.Core.Domain.News; -using SmartStore.Utilities; namespace SmartStore.Services.News { @@ -61,13 +59,5 @@ public partial interface INewsService /// /// News item void UpdateCommentTotals(NewsItem newsItem); - - /// - /// Creates a RSS feed with news items - /// - /// UrlHelper to generate URLs - /// Language identifier - /// SmartSyndicationFeed object - SmartSyndicationFeed CreateRssFeed(UrlHelper urlHelper, int languageId); } } diff --git a/src/Libraries/SmartStore.Services/News/NewsService.cs b/src/Libraries/SmartStore.Services/News/NewsService.cs index 66adadf90e..743ab7f9f4 100644 --- a/src/Libraries/SmartStore.Services/News/NewsService.cs +++ b/src/Libraries/SmartStore.Services/News/NewsService.cs @@ -1,16 +1,10 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.ServiceModel.Syndication; -using System.Web.Mvc; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.News; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; -using SmartStore.Services.Localization; -using SmartStore.Services.Seo; -using SmartStore.Utilities; namespace SmartStore.Services.News { @@ -24,7 +18,6 @@ public partial class NewsService : INewsService private readonly IRepository _newsItemRepository; private readonly IRepository _storeMappingRepository; private readonly ICommonServices _services; - private readonly ILanguageService _languageService; private readonly NewsSettings _newsSettings; @@ -35,13 +28,11 @@ public partial class NewsService : INewsService public NewsService(IRepository newsItemRepository, IRepository storeMappingRepository, ICommonServices services, - ILanguageService languageService, NewsSettings newsSettings) { _newsItemRepository = newsItemRepository; _storeMappingRepository = storeMappingRepository; _services = services; - _languageService = languageService; _newsSettings = newsSettings; this.QuerySettings = DbQuerySettings.Default; @@ -211,53 +202,6 @@ public virtual void UpdateCommentTotals(NewsItem newsItem) UpdateNews(newsItem); } - /// - /// Creates a RSS feed with news items - /// - /// UrlHelper to generate URLs - /// Language identifier - /// SmartSyndicationFeed object - public virtual SmartSyndicationFeed CreateRssFeed(UrlHelper urlHelper, int languageId) - { - if (urlHelper == null) - throw new ArgumentNullException("urlHelper"); - - DateTime? maxAge = null; - var protocol = _services.WebHelper.IsCurrentConnectionSecured() ? "https" : "http"; - var selfLink = urlHelper.Action("rss", "News", new { languageId = languageId }, protocol); - var newsLink = urlHelper.RouteUrl("NewsArchive", null, protocol); - - var title = "{0} - News".FormatInvariant(_services.StoreContext.CurrentStore.Name); - - if (_newsSettings.MaxAgeInDays > 0) - maxAge = DateTime.UtcNow.Subtract(new TimeSpan(_newsSettings.MaxAgeInDays, 0, 0, 0)); - - var language = _languageService.GetLanguageById(languageId); - var feed = new SmartSyndicationFeed(new Uri(newsLink), title); - - feed.AddNamespaces(true); - feed.Init(selfLink, language); - - if (!_newsSettings.Enabled) - return feed; - - var items = new List(); - var newsItems = GetAllNews(languageId, _services.StoreContext.CurrentStore.Id, 0, int.MaxValue, false, maxAge); - - foreach (var news in newsItems) - { - var newsUrl = urlHelper.RouteUrl("NewsItem", new { SeName = news.GetSeName(news.LanguageId, ensureTwoPublishedLanguages: false) }, "http"); - - var item = feed.CreateItem(news.Title, news.Short, newsUrl, news.CreatedOnUtc, news.Full); - - items.Add(item); - } - - feed.Items = items; - - return feed; - } - #endregion } } diff --git a/src/Presentation/SmartStore.Web/Controllers/BlogController.cs b/src/Presentation/SmartStore.Web/Controllers/BlogController.cs index ad683bda9f..88aa37ffba 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BlogController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BlogController.cs @@ -6,28 +6,27 @@ using System.Web.Routing; using SmartStore.Core; using SmartStore.Core.Caching; -using SmartStore.Core.Domain; using SmartStore.Core.Domain.Blogs; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; +using SmartStore.Core.Logging; using SmartStore.Services.Blogs; using SmartStore.Services.Common; using SmartStore.Services.Customers; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; -using SmartStore.Core.Logging; using SmartStore.Services.Media; using SmartStore.Services.Messages; using SmartStore.Services.Seo; using SmartStore.Services.Stores; -using SmartStore.Web.Framework; +using SmartStore.Utilities; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Mvc; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.UI.Captcha; using SmartStore.Web.Infrastructure.Cache; using SmartStore.Web.Models.Blogs; -using SmartStore.Web.Framework.Mvc; namespace SmartStore.Web.Controllers { @@ -48,6 +47,7 @@ public partial class BlogController : PublicControllerBase private readonly ICacheManager _cacheManager; private readonly ICustomerActivityService _customerActivityService; private readonly IStoreMappingService _storeMappingService; + private readonly ILanguageService _languageService; private readonly MediaSettings _mediaSettings; private readonly BlogSettings _blogSettings; @@ -71,6 +71,7 @@ public BlogController(IBlogService blogService, ICacheManager cacheManager, ICustomerActivityService customerActivityService, IStoreMappingService storeMappingService, + ILanguageService languageService, MediaSettings mediaSettings, BlogSettings blogSettings, LocalizationSettings localizationSettings, @@ -89,6 +90,7 @@ public BlogController(IBlogService blogService, this._cacheManager = cacheManager; this._customerActivityService = customerActivityService; this._storeMappingService = storeMappingService; + this._languageService = languageService; this._mediaSettings = mediaSettings; this._blogSettings = blogSettings; @@ -232,7 +234,38 @@ public ActionResult BlogByMonth(BlogPagingFilteringModel command) [Compress] public ActionResult ListRss(int languageId) { - var feed = _blogService.CreateRssFeed(Url, languageId); + DateTime? maxAge = null; + var protocol = _webHelper.IsCurrentConnectionSecured() ? "https" : "http"; + var selfLink = Url.RouteUrl("BlogRSS", new { languageId = languageId }, protocol); + var blogLink = Url.RouteUrl("Blog", null, protocol); + + var title = "{0} - Blog".FormatInvariant(_storeContext.CurrentStore.Name); + + if (_blogSettings.MaxAgeInDays > 0) + maxAge = DateTime.UtcNow.Subtract(new TimeSpan(_blogSettings.MaxAgeInDays, 0, 0, 0)); + + var language = _languageService.GetLanguageById(languageId); + var feed = new SmartSyndicationFeed(new Uri(blogLink), title); + + feed.AddNamespaces(false); + feed.Init(selfLink, language); + + if (!_blogSettings.Enabled) + return new RssActionResult { Feed = feed }; + + var items = new List(); + var blogPosts = _blogService.GetAllBlogPosts(_storeContext.CurrentStore.Id, languageId, null, null, 0, int.MaxValue, false, maxAge); + + foreach (var blogPost in blogPosts) + { + var blogPostUrl = Url.RouteUrl("BlogPost", new { SeName = blogPost.GetSeName(blogPost.LanguageId, ensureTwoPublishedLanguages: false) }, "http"); + + var item = feed.CreateItem(blogPost.Title, blogPost.Body, blogPostUrl, blogPost.CreatedOnUtc); + + items.Add(item); + } + + feed.Items = items; return new RssActionResult { Feed = feed }; } diff --git a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs index 441d1a6b9d..10d668eb81 100644 --- a/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/BoardsController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.ServiceModel.Syndication; using System.Web.Mvc; using SmartStore.Core; using SmartStore.Core.Domain.Customers; @@ -15,6 +16,7 @@ using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Seo; +using SmartStore.Utilities; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Mvc; @@ -34,6 +36,7 @@ public partial class BoardsController : PublicControllerBase private readonly ICountryService _countryService; private readonly IWebHelper _webHelper; private readonly IWorkContext _workContext; + private readonly IStoreContext _storeContext; private readonly ForumSettings _forumSettings; private readonly CustomerSettings _customerSettings; private readonly MediaSettings _mediaSettings; @@ -49,6 +52,7 @@ public BoardsController(IForumService forumService, ICountryService countryService, IWebHelper webHelper, IWorkContext workContext, + IStoreContext storeContext, ForumSettings forumSettings, CustomerSettings customerSettings, MediaSettings mediaSettings, @@ -60,6 +64,7 @@ public BoardsController(IForumService forumService, this._countryService = countryService; this._webHelper = webHelper; this._workContext = workContext; + this._storeContext = storeContext; this._forumSettings = forumSettings; this._customerSettings = customerSettings; this._mediaSettings = mediaSettings; @@ -266,7 +271,38 @@ public ActionResult ActiveDiscussionsRss(int forumId = 0) if (!_forumSettings.ForumsEnabled) return HttpNotFound(); - var feed = _forumService.CreateActiveDiscussionsRssFeed(Url, forumId); + var language = _workContext.WorkingLanguage; + var protocol = _webHelper.IsCurrentConnectionSecured() ? "https" : "http"; + var selfLink = Url.Action("ActiveDiscussionsRSS", "Boards", null, protocol); + var discussionLink = Url.Action("ActiveDiscussions", "Boards", null, protocol); + + var title = "{0} - {1}".FormatInvariant(_storeContext.CurrentStore.Name, T("Forum.ActiveDiscussionsFeedTitle")); + + var feed = new SmartSyndicationFeed(new Uri(discussionLink), title, T("Forum.ActiveDiscussionsFeedDescription")); + + feed.AddNamespaces(false); + feed.Init(selfLink, language); + + if (!_forumSettings.ActiveDiscussionsFeedEnabled) + return new RssActionResult { Feed = feed }; + + var items = new List(); + var topics = _forumService.GetActiveTopics(forumId, _forumSettings.ActiveDiscussionsFeedCount); + + var viewsText = T("Forum.Views"); + var repliesText = T("Forum.Replies"); + + foreach (var topic in topics) + { + string topicUrl = Url.RouteUrl("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }, "http"); + var synopsis = "{0}: {1}, {2}: {3}".FormatInvariant(repliesText, topic.NumReplies, viewsText, topic.Views); + + var item = feed.CreateItem(topic.Subject, synopsis, topicUrl, topic.LastPostTime ?? topic.UpdatedOnUtc); + + items.Add(item); + } + + feed.Items = items; return new RssActionResult { Feed = feed }; } @@ -349,7 +385,43 @@ public ActionResult ForumRss(int id) if (!_forumSettings.ForumsEnabled) return HttpNotFound(); - var feed = _forumService.CreateForumRssFeed(Url, id); + var language = _workContext.WorkingLanguage; + var protocol = _webHelper.IsCurrentConnectionSecured() ? "https" : "http"; + var selfLink = Url.Action("ForumRSS", "Boards", null, protocol); + var forumLink = Url.Action("Forum", "Boards", new { id = id }, protocol); + + var feed = new SmartSyndicationFeed(new Uri(forumLink), _storeContext.CurrentStore.Name, T("Forum.ForumFeedDescription")); + + feed.AddNamespaces(false); + feed.Init(selfLink, language); + + if (!_forumSettings.ForumFeedsEnabled) + return new RssActionResult { Feed = feed }; + + var forum = _forumService.GetForumById(id); + + if (forum == null) + return new RssActionResult { Feed = feed }; + + feed.Title = new TextSyndicationContent("{0} - {1}".FormatInvariant(_storeContext.CurrentStore.Name, forum.GetLocalized(x => x.Name, language.Id))); + + var items = new List(); + var topics = _forumService.GetAllTopics(id, 0, string.Empty, ForumSearchType.All, 0, 0, _forumSettings.ForumFeedCount); + + var viewsText = T("Forum.Views"); + var repliesText = T("Forum.Replies"); + + foreach (var topic in topics) + { + string topicUrl = Url.RouteUrl("TopicSlug", new { id = topic.Id, slug = topic.GetSeName() }, "http"); + var synopsis = "{0}: {1}, {2}: {3}".FormatInvariant(repliesText, topic.NumReplies, viewsText, topic.Views); + + var item = feed.CreateItem(topic.Subject, synopsis, topicUrl, topic.LastPostTime ?? topic.UpdatedOnUtc); + + items.Add(item); + } + + feed.Items = items; return new RssActionResult { Feed = feed }; } diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs index 7c18223245..f5957907fa 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs @@ -21,6 +21,7 @@ using SmartStore.Services.Security; using SmartStore.Services.Seo; using SmartStore.Services.Stores; +using SmartStore.Utilities; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Mvc; using SmartStore.Web.Framework.Security; @@ -847,7 +848,62 @@ public ActionResult RecentlyAddedProducts(CatalogPagingFilteringModel command) [Compress] public ActionResult RecentlyAddedProductsRss() { - var feed = _productService.CreateRecentlyAddedProductsRssFeed(Url); + var protocol = _services.WebHelper.IsCurrentConnectionSecured() ? "https" : "http"; + var selfLink = Url.RouteUrl("RecentlyAddedProductsRSS", null, protocol); + var recentProductsLink = Url.RouteUrl("RecentlyAddedProducts", null, protocol); + + var title = "{0} - {1}".FormatInvariant(_services.StoreContext.CurrentStore.Name, T("RSS.RecentlyAddedProducts")); + + var feed = new SmartSyndicationFeed(new Uri(recentProductsLink), title, T("RSS.InformationAboutProducts")); + + feed.AddNamespaces(true); + feed.Init(selfLink, _services.WorkContext.WorkingLanguage); + + if (!_catalogSettings.RecentlyAddedProductsEnabled) + return new RssActionResult { Feed = feed }; + + var items = new List(); + var searchContext = new ProductSearchContext + { + LanguageId = _services.WorkContext.WorkingLanguage.Id, + OrderBy = ProductSortingEnum.CreatedOn, + PageSize = _catalogSettings.RecentlyAddedProductsNumber, + StoreId = _services.StoreContext.CurrentStoreIdIfMultiStoreMode, + VisibleIndividuallyOnly = true + }; + + var products = _productService.SearchProducts(searchContext); + var storeUrl = _services.StoreContext.CurrentStore.Url; + + foreach (var product in products) + { + string productUrl = Url.RouteUrl("Product", new { SeName = product.GetSeName() }, "http"); + if (productUrl.HasValue()) + { + var item = feed.CreateItem( + product.GetLocalized(x => x.Name), + product.GetLocalized(x => x.ShortDescription), + productUrl, + product.CreatedOnUtc, + product.FullDescription); + + try + { + // we add only the first picture + var picture = product.ProductPictures.OrderBy(x => x.DisplayOrder).Select(x => x.Picture).FirstOrDefault(); + + if (picture != null) + { + feed.AddEnclosue(item, picture, _pictureService.GetPictureUrl(picture, _mediaSettings.ProductDetailsPictureSize, false, storeUrl)); + } + } + catch { } + + items.Add(item); + } + } + + feed.Items = items; return new RssActionResult { Feed = feed }; } diff --git a/src/Presentation/SmartStore.Web/Controllers/NewsController.cs b/src/Presentation/SmartStore.Web/Controllers/NewsController.cs index 2234247d80..dacb180485 100644 --- a/src/Presentation/SmartStore.Web/Controllers/NewsController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/NewsController.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Linq; +using System.ServiceModel.Syndication; using System.Web.Mvc; using SmartStore.Core; using SmartStore.Core.Caching; @@ -17,6 +19,7 @@ using SmartStore.Services.News; using SmartStore.Services.Seo; using SmartStore.Services.Stores; +using SmartStore.Utilities; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Mvc; using SmartStore.Web.Framework.Security; @@ -43,6 +46,7 @@ public partial class NewsController : PublicControllerBase private readonly ICacheManager _cacheManager; private readonly ICustomerActivityService _customerActivityService; private readonly IStoreMappingService _storeMappingService; + private readonly ILanguageService _languageService; private readonly MediaSettings _mediaSettings; private readonly NewsSettings _newsSettings; @@ -61,6 +65,7 @@ public NewsController(INewsService newsService, IWorkflowMessageService workflowMessageService, IWebHelper webHelper, ICacheManager cacheManager, ICustomerActivityService customerActivityService, IStoreMappingService storeMappingService, + ILanguageService languageService, MediaSettings mediaSettings, NewsSettings newsSettings, LocalizationSettings localizationSettings, CustomerSettings customerSettings, CaptchaSettings captchaSettings) @@ -77,6 +82,7 @@ public NewsController(INewsService newsService, this._cacheManager = cacheManager; this._customerActivityService = customerActivityService; this._storeMappingService = storeMappingService; + this._languageService = languageService; this._mediaSettings = mediaSettings; this._newsSettings = newsSettings; @@ -208,7 +214,38 @@ public ActionResult List(NewsPagingFilteringModel command) [ActionName("rss"), Compress] public ActionResult ListRss(int languageId) { - var feed = _newsService.CreateRssFeed(Url, languageId); + DateTime? maxAge = null; + var protocol = _webHelper.IsCurrentConnectionSecured() ? "https" : "http"; + var selfLink = Url.Action("rss", "News", new { languageId = languageId }, protocol); + var newsLink = Url.RouteUrl("NewsArchive", null, protocol); + + var title = "{0} - News".FormatInvariant(_storeContext.CurrentStore.Name); + + if (_newsSettings.MaxAgeInDays > 0) + maxAge = DateTime.UtcNow.Subtract(new TimeSpan(_newsSettings.MaxAgeInDays, 0, 0, 0)); + + var language = _languageService.GetLanguageById(languageId); + var feed = new SmartSyndicationFeed(new Uri(newsLink), title); + + feed.AddNamespaces(true); + feed.Init(selfLink, language); + + if (!_newsSettings.Enabled) + return new RssActionResult { Feed = feed }; + + var items = new List(); + var newsItems = _newsService.GetAllNews(languageId, _storeContext.CurrentStore.Id, 0, int.MaxValue, false, maxAge); + + foreach (var news in newsItems) + { + var newsUrl = Url.RouteUrl("NewsItem", new { SeName = news.GetSeName(news.LanguageId, ensureTwoPublishedLanguages: false) }, "http"); + + var item = feed.CreateItem(news.Title, news.Short, newsUrl, news.CreatedOnUtc, news.Full); + + items.Add(item); + } + + feed.Items = items; return new RssActionResult { Feed = feed }; } From f7382dea1c622148fbc1e3ef1adbe13bd70eb75d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 7 Nov 2015 12:22:39 +0100 Subject: [PATCH 182/874] Added class ExportProviderBase --- .../DataExchange/ExportProviderBase.cs | 47 +++++++++++++++++++ .../DataExchange/IExportProvider.cs | 2 +- .../Providers/CategoryXmlExportProvider.cs | 18 ++----- .../Providers/CustomerXlsxExportProvider.cs | 18 ++----- .../Providers/CustomerXmlExportProvider.cs | 18 ++----- .../ManufacturerXmlExportProvider.cs | 18 ++----- .../Providers/OrderXlsxExportProvider.cs | 18 ++----- .../Providers/OrderXmlExportProvider.cs | 18 ++----- .../Providers/ProductXlsxExportProvider.cs | 18 ++----- .../Providers/ProductXmlExportProvider.cs | 18 ++----- .../Providers/SubscriberCsvExportProvider.cs | 18 ++----- .../SmartStore.Services.csproj | 1 + .../Providers/GmcXmlExportProvider.cs | 18 ++----- 13 files changed, 89 insertions(+), 141 deletions(-) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/ExportProviderBase.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProviderBase.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProviderBase.cs new file mode 100644 index 0000000000..f0dd39a95e --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProviderBase.cs @@ -0,0 +1,47 @@ +using SmartStore.Core.Domain.DataExchange; + +namespace SmartStore.Services.DataExchange +{ + public abstract class ExportProviderBase : IExportProvider + { + /// + /// The exported entity type + /// + public virtual ExportEntityType EntityType + { + get { return ExportEntityType.Product; } + } + + /// + /// File extension of the export files (without dot). Return null for a non file based, on-the-fly export. + /// + public virtual string FileExtension + { + get { return null; } + } + + /// + /// Get provider specific configuration information. Return null when no provider specific configuration is required. + /// + public virtual ExportConfigurationInfo ConfigurationInfo + { + get { return null; } + } + + /// + /// Export data to a file + /// + /// Export execution context + public virtual void Execute(IExportExecuteContext context) + { + } + + /// + /// Called once per store when export execution ended + /// + /// Export execution context + public virtual void OnExecuted(IExportExecuteContext context) + { + } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs index b0872ef1eb..450c30ed77 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs @@ -6,7 +6,7 @@ namespace SmartStore.Services.DataExchange public partial interface IExportProvider : IProvider, IUserEditable { /// - /// Th exported entity type + /// The exported entity type /// ExportEntityType EntityType { get; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs index 5b72bc9d64..e473a28ad2 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs @@ -16,29 +16,24 @@ namespace SmartStore.Services.DataExchange.Providers [SystemName("Exports.SmartStoreCategoryXml")] [FriendlyName("SmartStore XML category export")] [IsHidden(true)] - public class CategoryXmlExportProvider : IExportProvider + public class CategoryXmlExportProvider : ExportProviderBase { public static string SystemName { get { return "Exports.SmartStoreCategoryXml"; } } - public ExportConfigurationInfo ConfigurationInfo - { - get { return null; } - } - - public ExportEntityType EntityType + public override ExportEntityType EntityType { get { return ExportEntityType.Category; } } - public string FileExtension + public override string FileExtension { get { return "XML"; } } - public void Execute(IExportExecuteContext context) + public override void Execute(IExportExecuteContext context) { var settings = new XmlWriterSettings { @@ -104,10 +99,5 @@ public void Execute(IExportExecuteContext context) writer.WriteEndDocument(); } } - - public void OnExecuted(IExportExecuteContext context) - { - // nothing to do - } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXlsxExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXlsxExportProvider.cs index 22a9d229ec..4ab09bd5c5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXlsxExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXlsxExportProvider.cs @@ -18,7 +18,7 @@ namespace SmartStore.Services.DataExchange.Providers [SystemName("Exports.SmartStoreCustomerXlsx")] [FriendlyName("SmartStore Excel customer export")] [IsHidden(true)] - public class CustomerXlsxExportProvider : IExportProvider + public class CustomerXlsxExportProvider : ExportProviderBase { private string[] Properties { @@ -97,22 +97,17 @@ public static string SystemName get { return "Exports.SmartStoreCustomerXlsx"; } } - public ExportConfigurationInfo ConfigurationInfo - { - get { return null; } - } - - public ExportEntityType EntityType + public override ExportEntityType EntityType { get { return ExportEntityType.Customer; } } - public string FileExtension + public override string FileExtension { get { return "XLSX"; } } - public void Execute(IExportExecuteContext context) + public override void Execute(IExportExecuteContext context) { var invariantCulture = CultureInfo.InvariantCulture; var path = context.FilePath; @@ -214,10 +209,5 @@ public void Execute(IExportExecuteContext context) xlPackage.Save(); } } - - public void OnExecuted(IExportExecuteContext context) - { - // nothing to do - } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs index b88d3d8cd3..93d9c4462b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs @@ -16,29 +16,24 @@ namespace SmartStore.Services.DataExchange.Providers [SystemName("Exports.SmartStoreCustomerXml")] [FriendlyName("SmartStore XML customer export")] [IsHidden(true)] - public class CustomerXmlExportProvider : IExportProvider + public class CustomerXmlExportProvider : ExportProviderBase { public static string SystemName { get { return "Exports.SmartStoreCustomerXml"; } } - public ExportConfigurationInfo ConfigurationInfo - { - get { return null; } - } - - public ExportEntityType EntityType + public override ExportEntityType EntityType { get { return ExportEntityType.Customer; } } - public string FileExtension + public override string FileExtension { get { return "XML"; } } - public void Execute(IExportExecuteContext context) + public override void Execute(IExportExecuteContext context) { var settings = new XmlWriterSettings { @@ -87,10 +82,5 @@ public void Execute(IExportExecuteContext context) writer.WriteEndDocument(); } } - - public void OnExecuted(IExportExecuteContext context) - { - // nothing to do - } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs index edc60a007d..8d26a9ab4e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs @@ -16,29 +16,24 @@ namespace SmartStore.Services.DataExchange.Providers [SystemName("Exports.SmartStoreManufacturerXml")] [FriendlyName("SmartStore XML manufacturer export")] [IsHidden(true)] - public class ManufacturerXmlExportProvider : IExportProvider + public class ManufacturerXmlExportProvider : ExportProviderBase { public static string SystemName { get { return "Exports.SmartStoreManufacturerXml"; } } - public ExportConfigurationInfo ConfigurationInfo - { - get { return null; } - } - - public ExportEntityType EntityType + public override ExportEntityType EntityType { get { return ExportEntityType.Manufacturer; } } - public string FileExtension + public override string FileExtension { get { return "XML"; } } - public void Execute(IExportExecuteContext context) + public override void Execute(IExportExecuteContext context) { var settings = new XmlWriterSettings { @@ -104,10 +99,5 @@ public void Execute(IExportExecuteContext context) writer.WriteEndDocument(); } } - - public void OnExecuted(IExportExecuteContext context) - { - // nothing to do - } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXlsxExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXlsxExportProvider.cs index 9f468e071f..ae6ce4271d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXlsxExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXlsxExportProvider.cs @@ -15,7 +15,7 @@ namespace SmartStore.Services.DataExchange.Providers [SystemName("Exports.SmartStoreOrderXlsx")] [FriendlyName("SmartStore Excel order export")] [IsHidden(true)] - public class OrderXlsxExportProvider : IExportProvider + public class OrderXlsxExportProvider : ExportProviderBase { private string[] Properties { @@ -96,22 +96,17 @@ public static string SystemName get { return "Exports.SmartStoreOrderXlsx"; } } - public ExportConfigurationInfo ConfigurationInfo - { - get { return null; } - } - - public ExportEntityType EntityType + public override ExportEntityType EntityType { get { return ExportEntityType.Order; } } - public string FileExtension + public override string FileExtension { get { return "XLSX"; } } - public void Execute(IExportExecuteContext context) + public override void Execute(IExportExecuteContext context) { var path = context.FilePath; @@ -250,10 +245,5 @@ public void Execute(IExportExecuteContext context) xlPackage.Save(); } } - - public void OnExecuted(IExportExecuteContext context) - { - // nothing to do - } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs index a21a94d2f9..15d347847f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs @@ -16,29 +16,24 @@ namespace SmartStore.Services.DataExchange.Providers [SystemName("Exports.SmartStoreOrderXml")] [FriendlyName("SmartStore XML order export")] [IsHidden(true)] - public class OrderXmlExportProvider : IExportProvider + public class OrderXmlExportProvider : ExportProviderBase { public static string SystemName { get { return "Exports.SmartStoreOrderXml"; } } - public ExportConfigurationInfo ConfigurationInfo - { - get { return null; } - } - - public ExportEntityType EntityType + public override ExportEntityType EntityType { get { return ExportEntityType.Order; } } - public string FileExtension + public override string FileExtension { get { return "XML"; } } - public void Execute(IExportExecuteContext context) + public override void Execute(IExportExecuteContext context) { var settings = new XmlWriterSettings { @@ -260,10 +255,5 @@ public void Execute(IExportExecuteContext context) writer.WriteEndDocument(); } } - - public void OnExecuted(IExportExecuteContext context) - { - // nothing to do - } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXlsxExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXlsxExportProvider.cs index a433bf5c8d..3da13558c0 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXlsxExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXlsxExportProvider.cs @@ -22,7 +22,7 @@ namespace SmartStore.Services.DataExchange.Providers [SystemName("Exports.SmartStoreProductXlsx")] [FriendlyName("SmartStore Excel product export")] [IsHidden(true)] - public class ProductXlsxExportProvider : IExportProvider + public class ProductXlsxExportProvider : ExportProviderBase { private readonly ILanguageService _languageService; private readonly IProductService _productService; @@ -165,22 +165,17 @@ public static string SystemName get { return "Exports.SmartStoreProductXlsx"; } } - public ExportConfigurationInfo ConfigurationInfo - { - get { return null; } - } - - public ExportEntityType EntityType + public override ExportEntityType EntityType { get { return ExportEntityType.Product; } } - public string FileExtension + public override string FileExtension { get { return "XLSX"; } } - public void Execute(IExportExecuteContext context) + public override void Execute(IExportExecuteContext context) { var path = context.FilePath; @@ -414,10 +409,5 @@ public void Execute(IExportExecuteContext context) // EPPLus had serious memory leak problems in V3. We enforce the garbage collector to release unused memory, it's not perfect, but better than nothing. GC.Collect(); } - - public void OnExecuted(IExportExecuteContext context) - { - // nothing to do - } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs index a0cc291bf1..e4154f4804 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs @@ -16,29 +16,24 @@ namespace SmartStore.Services.DataExchange.Providers [SystemName("Exports.SmartStoreProductXml")] [FriendlyName("SmartStore XML product export")] [IsHidden(true)] - public class ProductXmlExportProvider : IExportProvider + public class ProductXmlExportProvider : ExportProviderBase { public static string SystemName { get { return "Exports.SmartStoreProductXml"; } } - public ExportConfigurationInfo ConfigurationInfo - { - get { return null; } - } - - public ExportEntityType EntityType + public override ExportEntityType EntityType { get { return ExportEntityType.Product; } } - public string FileExtension + public override string FileExtension { get { return "XML"; } } - public void Execute(IExportExecuteContext context) + public override void Execute(IExportExecuteContext context) { var settings = new XmlWriterSettings { @@ -87,10 +82,5 @@ public void Execute(IExportExecuteContext context) writer.WriteEndDocument(); } } - - public void OnExecuted(IExportExecuteContext context) - { - // nothing to do - } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/SubscriberCsvExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/SubscriberCsvExportProvider.cs index 2ca56f48f8..2a7e299b51 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/SubscriberCsvExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/SubscriberCsvExportProvider.cs @@ -13,29 +13,24 @@ namespace SmartStore.Services.DataExchange.Providers [SystemName("Exports.SmartStoreNewsSubscriptionCsv")] [FriendlyName("SmartStore CSV newsletter subscription export")] [IsHidden(true)] - public class SubscriberCsvExportProvider : IExportProvider + public class SubscriberCsvExportProvider : ExportProviderBase { public static string SystemName { get { return "Exports.SmartStoreNewsSubscriptionCsv"; } } - public ExportConfigurationInfo ConfigurationInfo - { - get { return null; } - } - - public ExportEntityType EntityType + public override ExportEntityType EntityType { get { return ExportEntityType.NewsLetterSubscription; } } - public string FileExtension + public override string FileExtension { get { return "CSV"; } } - public void Execute(IExportExecuteContext context) + public override void Execute(IExportExecuteContext context) { var path = context.FilePath; @@ -73,10 +68,5 @@ public void Execute(IExportExecuteContext context) } } } - - public void OnExecuted(IExportExecuteContext context) - { - // nothing to do - } } } diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index f63d565765..7b4dc44e1e 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -171,6 +171,7 @@ + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index 3f1dac0bd2..b1a68d4591 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -28,7 +28,7 @@ namespace SmartStore.GoogleMerchantCenter.Providers ExportFeatures.OffersBrandFallback, ExportFeatures.CanIncludeMainPicture, ExportFeatures.UsesSpecialPrice)] - public class GmcXmlExportProvider : IExportProvider + public class GmcXmlExportProvider : ExportProviderBase { private const string _googleNamespace = "http://base.google.com/ns/1.0"; @@ -132,7 +132,7 @@ public static string Unspecified get { return "__nospec__"; } } - public ExportConfigurationInfo ConfigurationInfo + public override ExportConfigurationInfo ConfigurationInfo { get { @@ -150,17 +150,12 @@ public ExportConfigurationInfo ConfigurationInfo } } - public ExportEntityType EntityType - { - get { return ExportEntityType.Product; } - } - - public string FileExtension + public override string FileExtension { get { return "XML"; } } - public void Execute(IExportExecuteContext context) + public override void Execute(IExportExecuteContext context) { var settings = new XmlWriterSettings { @@ -400,10 +395,5 @@ public void Execute(IExportExecuteContext context) writer.WriteEndDocument(); } } - - public void OnExecuted(IExportExecuteContext context) - { - // nothing to do - } } } \ No newline at end of file From 2289b5c30629e9b4363644042cdfbe3780f09b2b Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sun, 8 Nov 2015 03:19:21 +0100 Subject: [PATCH 183/874] (Perf) Faster reflection with new FastProperty helper class (makes Fasterflect obsolete soon) --- .../ComponentModel/HybridExpando.cs | 72 +-- .../SmartStore.Core/Plugins/PluginManager.cs | 1 - .../SmartStore.Core/SmartStore.Core.csproj | 1 + .../Utilities/Reflection/FastProperty.cs | 553 ++++++++++++++++++ .../Configuration/SettingService.cs | 11 +- .../DependencyRegistrar.cs | 38 +- 6 files changed, 609 insertions(+), 67 deletions(-) create mode 100644 src/Libraries/SmartStore.Core/Utilities/Reflection/FastProperty.cs diff --git a/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs b/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs index 6697e19d0c..eebf907175 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/HybridExpando.cs @@ -37,6 +37,7 @@ using System.Dynamic; using System.Reflection; using System.Collections; +using SmartStore.Utilities.Reflection; namespace SmartStore.ComponentModel { @@ -71,8 +72,6 @@ public class HybridExpando : DynamicObject, IDictionary /// private Type _instanceType; - private IList _instancePropertyInfos; - /// /// String Dictionary that contains the extra dynamic values /// stored on this object/instance @@ -116,16 +115,6 @@ protected object WrappedObject get { return _instance; } } - IList InstancePropertyInfos - { - get - { - if (_instancePropertyInfos == null && _instance != null) - _instancePropertyInfos = Fasterflect.PropertyExtensions.Properties(_instanceType, Fasterflect.Flags.InstancePublic); - return _instancePropertyInfos; - } - } - public override IEnumerable GetDynamicMemberNames() { foreach (var prop in this.GetProperties(false)) @@ -238,25 +227,15 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o /// protected bool GetProperty(object instance, string name, out object result) { - var pi = GetPropertyInfo(name); - if (pi != null) - { - result = Fasterflect.PropertyExtensions.GetPropertyValue(instance ?? this, pi.Name); - return true; - } + var fastProp = _instanceType != null ? FastProperty.GetProperty(_instanceType, name, true) : null; + if (fastProp != null) + { + result = fastProp.GetValue(instance ?? this); + return true; + } - result = null; + result = null; return false; - } - - /// - /// Gets a PropertyInfo instance from the wrapped object - /// - /// Property name - protected PropertyInfo GetPropertyInfo(string name) - { - var pi = Fasterflect.PropertyExtensions.Property(_instanceType, name, Fasterflect.Flags.InstancePublic); - return pi; } /// @@ -268,13 +247,14 @@ protected PropertyInfo GetPropertyInfo(string name) /// protected bool SetProperty(object instance, string name, object value) { - var pi = GetPropertyInfo(name); - if (pi != null) - { - Fasterflect.PropertyInfoExtensions.Set(pi, instance ?? this, value); - return true; + var fastProp = _instanceType != null ? FastProperty.GetProperty(_instanceType, name, true) : null; + if (fastProp != null) + { + fastProp.SetValue(instance ?? this, value); + return true; } - return false; + + return false; } /// @@ -288,10 +268,10 @@ protected bool SetProperty(object instance, string name, object value) protected bool InvokeMethod(object instance, string name, object[] args, out object result) { // Look at the instanceType - var mi = Fasterflect.MethodExtensions.Method(_instanceType, name, Fasterflect.Flags.InstancePublic); + var mi = _instanceType != null ? _instanceType.GetMethod(name, BindingFlags.Instance | BindingFlags.Public) : null; if (mi != null) { - result = Fasterflect.MethodInfoExtensions.Call(mi, instance ?? this, args); + result = mi.Invoke(instance ?? this, args); return true; } @@ -352,11 +332,11 @@ public IEnumerable> GetProperties(bool includeInsta if (includeInstanceProperties && _instance != null) { - foreach (var prop in this.InstancePropertyInfos) + foreach (var prop in FastProperty.GetProperties(_instance).Values) { if (!this.Properties.ContainsKey(prop.Name)) { - yield return new KeyValuePair(prop.Name, prop.GetValue(_instance, null)); + yield return new KeyValuePair(prop.Name, prop.GetValue(_instance)); } } } @@ -390,11 +370,7 @@ public bool Contains(string propertyName, bool includeInstanceProperties = false if (includeInstanceProperties && _instance != null) { - foreach (var prop in this.InstancePropertyInfos) - { - if (prop.Name == propertyName) - return true; - } + return FastProperty.GetProperties(_instance).ContainsKey(propertyName); } return false; @@ -422,7 +398,13 @@ int ICollection>.Count { get { - return Properties.Count + InstancePropertyInfos.Count; + var count = Properties.Count; + if (_instanceType != null) + { + count += FastProperty.GetProperties(_instanceType).Count; + } + + return count; } } diff --git a/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs b/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs index dfa85d8ef7..fa6e5e7aa5 100644 --- a/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs +++ b/src/Libraries/SmartStore.Core/Plugins/PluginManager.cs @@ -178,7 +178,6 @@ public static void Initialize() } IncompatiblePlugins = incompatiblePlugins.AsReadOnly(); - } } diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index b23a0d7b0f..7d39d16e05 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -578,6 +578,7 @@ + diff --git a/src/Libraries/SmartStore.Core/Utilities/Reflection/FastProperty.cs b/src/Libraries/SmartStore.Core/Utilities/Reflection/FastProperty.cs new file mode 100644 index 0000000000..27498b5576 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Utilities/Reflection/FastProperty.cs @@ -0,0 +1,553 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; + +namespace SmartStore.Utilities.Reflection +{ + + public class FastProperty + { + // Delegate type for a by-ref property getter + private delegate TValue ByRefFunc(ref TDeclaringType arg); + + private static readonly MethodInfo CallPropertyGetterOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyGetter)); + private static readonly MethodInfo CallPropertyGetterByReferenceOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyGetterByReference)); + private static readonly MethodInfo CallNullSafePropertyGetterOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod(nameof(CallNullSafePropertyGetter)); + private static readonly MethodInfo CallNullSafePropertyGetterByReferenceOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod(nameof(CallNullSafePropertyGetterByReference)); + private static readonly MethodInfo CallPropertySetterOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertySetter)); + + private static readonly ConcurrentDictionary SinglePropertiesCache = new ConcurrentDictionary(); + + // Using an array rather than IEnumerable, as target will be called on the hot path numerous times. + private static readonly ConcurrentDictionary> PropertiesCache = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> VisiblePropertiesCache = new ConcurrentDictionary>(); + + private Action _valueSetter; + + /// + /// Initializes a fast . + /// This constructor does not cache the helper. For caching, use . + /// + public FastProperty(PropertyInfo property) + { + if (property == null) + { + throw new ArgumentNullException(nameof(property)); + } + + Property = property; + Name = property.Name; + ValueGetter = MakeFastPropertyGetter(property); + } + + /// + /// Gets the backing . + /// + public PropertyInfo Property { get; } + + /// + /// Gets (or sets in derived types) the property name. + /// + public virtual string Name { get; protected set; } + + /// + /// Gets the property value getter. + /// + public Func ValueGetter { get; } + + /// + /// Gets the property value setter. + /// + public Action ValueSetter + { + get + { + if (_valueSetter == null) + { + // We'll allow safe races here. + _valueSetter = MakeFastPropertySetter(Property); + } + + return _valueSetter; + } + } + + /// + /// Returns the property value for the specified . + /// + /// The object whose property value will be returned. + /// The property value. + public object GetValue(object instance) + { + return ValueGetter(instance); + } + + /// + /// Sets the property value for the specified . + /// + /// The object whose property value will be set. + /// The property value. + public void SetValue(object instance, object value) + { + ValueSetter(instance, value); + } + + /// + /// Creates and caches fast property helpers that expose getters for every public get property on the + /// underlying type. + /// + /// the instance to extract property accessors for. + /// a cached array of all public property getters from the underlying type of target instance. + /// + public static IReadOnlyDictionary GetProperties(object instance) + { + return GetProperties(instance.GetType()); + } + + /// + /// Creates and caches fast property helpers that expose getters for every public get property on the + /// specified type. + /// + /// the type to extract property accessors for. + /// a cached array of all public property getters from the type of target instance. + /// + public static IReadOnlyDictionary GetProperties(Type type) + { + return (IReadOnlyDictionary)GetProperties(type, CreateInstance, PropertiesCache); + } + + /// + /// + /// Creates and caches fast property helpers that expose getters for every non-hidden get property + /// on the specified type. + /// + /// + /// excludes properties defined on base types that have been + /// hidden by definitions using the new keyword. + /// + /// + /// The instance to extract property accessors for. + /// + /// A cached array of all public property getters from the instance's type. + /// + public static IReadOnlyDictionary GetVisibleProperties(object instance) + { + return (IReadOnlyDictionary)GetVisibleProperties(instance.GetType(), CreateInstance, PropertiesCache, VisiblePropertiesCache); + } + + /// + /// + /// Creates and caches fast property helpers that expose getters for every non-hidden get property + /// on the specified type. + /// + /// + /// excludes properties defined on base types that have been + /// hidden by definitions using the new keyword. + /// + /// + /// The type to extract property accessors for. + /// + /// A cached array of all public property getters from the type. + /// + public static IReadOnlyDictionary GetVisibleProperties(Type type) + { + return (IReadOnlyDictionary)GetVisibleProperties(type, CreateInstance, PropertiesCache, VisiblePropertiesCache); + } + + public static FastProperty GetProperty(Type type, string propertyName, bool cacheAllProperties = false) + { + FastProperty fastProperty = null; + IDictionary allProperties; + + if (cacheAllProperties) + { + allProperties = (IDictionary)GetProperties(type); + allProperties.TryGetValue(propertyName, out fastProperty); + return fastProperty; + } + + if (PropertiesCache.TryGetValue(type, out allProperties)) + { + allProperties.TryGetValue(propertyName, out fastProperty); + return fastProperty; + } + + var key = new PropertyKey(type, propertyName); + if (!SinglePropertiesCache.TryGetValue(key, out fastProperty)) + { + var pi = GetPropertyCandidates(type).Where(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); + //var pi = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); + if (pi != null) + { + fastProperty = CreateInstance(pi); + SinglePropertiesCache.TryAdd(key, fastProperty); + } + } + + return fastProperty; + } + + /// + /// Creates a single fast property getter. The result is not cached. + /// + /// propertyInfo to extract the getter for. + /// a fast getter. + /// + /// This method is more memory efficient than a dynamically compiled lambda, and about the + /// same speed. + /// + public static Func MakeFastPropertyGetter(PropertyInfo propertyInfo) + { + Debug.Assert(propertyInfo != null); + + return MakeFastPropertyGetter( + propertyInfo, + CallPropertyGetterOpenGenericMethod, + CallPropertyGetterByReferenceOpenGenericMethod); + } + + /// + /// Creates a single fast property getter which is safe for a null input object. The result is not cached. + /// + /// propertyInfo to extract the getter for. + /// a fast getter. + /// + /// This method is more memory efficient than a dynamically compiled lambda, and about the + /// same speed. + /// + public static Func MakeNullSafeFastPropertyGetter(PropertyInfo propertyInfo) + { + Debug.Assert(propertyInfo != null); + + return MakeFastPropertyGetter( + propertyInfo, + CallNullSafePropertyGetterOpenGenericMethod, + CallNullSafePropertyGetterByReferenceOpenGenericMethod); + } + + private static Func MakeFastPropertyGetter( + PropertyInfo propertyInfo, + MethodInfo propertyGetterWrapperMethod, + MethodInfo propertyGetterByRefWrapperMethod) + { + Debug.Assert(propertyInfo != null); + + // Must be a generic method with a Func<,> parameter + Debug.Assert(propertyGetterWrapperMethod != null); + Debug.Assert(propertyGetterWrapperMethod.IsGenericMethodDefinition); + Debug.Assert(propertyGetterWrapperMethod.GetParameters().Length == 2); + + // Must be a generic method with a ByRefFunc<,> parameter + Debug.Assert(propertyGetterByRefWrapperMethod != null); + Debug.Assert(propertyGetterByRefWrapperMethod.IsGenericMethodDefinition); + Debug.Assert(propertyGetterByRefWrapperMethod.GetParameters().Length == 2); + + var getMethod = propertyInfo.GetMethod; + Debug.Assert(getMethod != null); + Debug.Assert(!getMethod.IsStatic); + Debug.Assert(getMethod.GetParameters().Length == 0); + + // Instance methods in the CLR can be turned into static methods where the first parameter + // is open over "target". This parameter is always passed by reference, so we have a code + // path for value types and a code path for reference types. + if (getMethod.DeclaringType.GetTypeInfo().IsValueType) + { + // Create a delegate (ref TDeclaringType) -> TValue + return MakeFastPropertyGetter( + typeof(ByRefFunc<,>), + getMethod, + propertyGetterByRefWrapperMethod); + } + else + { + // Create a delegate TDeclaringType -> TValue + return MakeFastPropertyGetter( + typeof(Func<,>), + getMethod, + propertyGetterWrapperMethod); + } + } + + private static Func MakeFastPropertyGetter( + Type openGenericDelegateType, + MethodInfo propertyGetMethod, + MethodInfo openGenericWrapperMethod) + { + var typeInput = propertyGetMethod.DeclaringType; + var typeOutput = propertyGetMethod.ReturnType; + + var delegateType = openGenericDelegateType.MakeGenericType(typeInput, typeOutput); + var propertyGetterDelegate = propertyGetMethod.CreateDelegate(delegateType); + + var wrapperDelegateMethod = openGenericWrapperMethod.MakeGenericMethod(typeInput, typeOutput); + var accessorDelegate = wrapperDelegateMethod.CreateDelegate( + typeof(Func), + propertyGetterDelegate); + + return (Func)accessorDelegate; + } + + /// + /// Creates a single fast property setter for reference types. The result is not cached. + /// + /// propertyInfo to extract the setter for. + /// a fast getter. + /// + /// This method is more memory efficient than a dynamically compiled lambda, and about the + /// same speed. This only works for reference types. + /// + public static Action MakeFastPropertySetter(PropertyInfo propertyInfo) + { + Debug.Assert(propertyInfo != null); + Debug.Assert(!propertyInfo.DeclaringType.GetTypeInfo().IsValueType); + + var setMethod = propertyInfo.SetMethod; + Debug.Assert(setMethod != null); + Debug.Assert(!setMethod.IsStatic); + Debug.Assert(setMethod.ReturnType == typeof(void)); + var parameters = setMethod.GetParameters(); + Debug.Assert(parameters.Length == 1); + + // Instance methods in the CLR can be turned into static methods where the first parameter + // is open over "target". This parameter is always passed by reference, so we have a code + // path for value types and a code path for reference types. + var typeInput = setMethod.DeclaringType; + var parameterType = parameters[0].ParameterType; + + // Create a delegate TDeclaringType -> { TDeclaringType.Property = TValue; } + var propertySetterAsAction = + setMethod.CreateDelegate(typeof(Action<,>).MakeGenericType(typeInput, parameterType)); + var callPropertySetterClosedGenericMethod = + CallPropertySetterOpenGenericMethod.MakeGenericMethod(typeInput, parameterType); + var callPropertySetterDelegate = + callPropertySetterClosedGenericMethod.CreateDelegate( + typeof(Action), propertySetterAsAction); + + return (Action)callPropertySetterDelegate; + } + + /// + /// Given an object, adds each instance property with a public get method as a key and its + /// associated value to a dictionary. + /// + /// If the object is already an instance, then a copy + /// is returned. + /// + /// + /// The implementation of PropertyHelper will cache the property accessors per-type. This is + /// faster when the the same type is used multiple times with ObjectToDictionary. + /// + public static IDictionary ObjectToDictionary(object value) + { + var dictionary = value as IDictionary; + if (dictionary != null) + { + return new Dictionary(dictionary, StringComparer.OrdinalIgnoreCase); + } + + dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); + + if (value != null) + { + foreach (var helper in GetProperties(value).Values) + { + dictionary[helper.Name] = helper.GetValue(value); + } + } + + return dictionary; + } + + private static FastProperty CreateInstance(PropertyInfo property) + { + return new FastProperty(property); + } + + // Called via reflection + private static object CallPropertyGetter( + Func getter, + object target) + { + return getter((TDeclaringType)target); + } + + // Called via reflection + private static object CallPropertyGetterByReference( + ByRefFunc getter, + object target) + { + var unboxed = (TDeclaringType)target; + return getter(ref unboxed); + } + + // Called via reflection + private static object CallNullSafePropertyGetter( + Func getter, + object target) + { + if (target == null) + { + return null; + } + + return getter((TDeclaringType)target); + } + + // Called via reflection + private static object CallNullSafePropertyGetterByReference( + ByRefFunc getter, + object target) + { + if (target == null) + { + return null; + } + + var unboxed = (TDeclaringType)target; + return getter(ref unboxed); + } + + private static void CallPropertySetter( + Action setter, + object target, + object value) + { + setter((TDeclaringType)target, (TValue)value); + } + + protected static IDictionary GetVisibleProperties( + Type type, + Func createPropertyHelper, + ConcurrentDictionary> allPropertiesCache, + ConcurrentDictionary> visiblePropertiesCache) + { + IDictionary result; + if (visiblePropertiesCache.TryGetValue(type, out result)) + { + return result; + } + + // The simple and common case, this is normal POCO object - no need to allocate. + var allPropertiesDefinedOnType = true; + var allProperties = GetProperties(type, createPropertyHelper, allPropertiesCache); + foreach (var propertyHelper in allProperties.Values) + { + if (propertyHelper.Property.DeclaringType != type) + { + allPropertiesDefinedOnType = false; + break; + } + } + + if (allPropertiesDefinedOnType) + { + result = allProperties; + visiblePropertiesCache.TryAdd(type, result); + return result; + } + + // There's some inherited properties here, so we need to check for hiding via 'new'. + var filteredProperties = new List(allProperties.Count); + foreach (var propertyHelper in allProperties.Values) + { + var declaringType = propertyHelper.Property.DeclaringType; + if (declaringType == type) + { + filteredProperties.Add(propertyHelper); + continue; + } + + // If this property was declared on a base type then look for the definition closest to the + // the type to see if we should include it. + var ignoreProperty = false; + + // Walk up the hierarchy until we find the type that actally declares this + // PropertyInfo. + var currentTypeInfo = type.GetTypeInfo(); + var declaringTypeInfo = declaringType.GetTypeInfo(); + while (currentTypeInfo != null && currentTypeInfo != declaringTypeInfo) + { + // We've found a 'more proximal' public definition + var declaredProperty = currentTypeInfo.GetDeclaredProperty(propertyHelper.Name); + if (declaredProperty != null) + { + ignoreProperty = true; + break; + } + + currentTypeInfo = currentTypeInfo.BaseType?.GetTypeInfo(); + } + + if (!ignoreProperty) + { + filteredProperties.Add(propertyHelper); + } + } + + result = filteredProperties.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); + visiblePropertiesCache.TryAdd(type, result); + return result; + } + + protected static IDictionary GetProperties( + Type type, + Func createPropertyHelper, + ConcurrentDictionary> cache) + { + // Unwrap nullable types. This means Nullable.Value and Nullable.HasValue will not be + // part of the sequence of properties returned by this method. + type = Nullable.GetUnderlyingType(type) ?? type; + + IDictionary fastProperties; + if (!cache.TryGetValue(type, out fastProperties)) + { + var candidates = GetPropertyCandidates(type); + fastProperties = candidates.Select(p => createPropertyHelper(p)).ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); + cache.TryAdd(type, fastProperties); + } + + return fastProperties; + } + + private static IEnumerable GetPropertyCandidates(Type type) + { + // We avoid loading indexed properties using the Where statement. + var properties = type.GetRuntimeProperties().Where(IsCandidateProperty); + + var typeInfo = type.GetTypeInfo(); + if (typeInfo.IsInterface) + { + // Reflection does not return information about inherited properties on the interface itself. + properties = properties.Concat(typeInfo.ImplementedInterfaces.SelectMany( + interfaceType => interfaceType.GetRuntimeProperties().Where(IsCandidateProperty))); + } + + return properties; + } + + // Indexed properties are not useful (or valid) for grabbing properties off an object. + private static bool IsCandidateProperty(PropertyInfo property) + { + return property.GetIndexParameters().Length == 0 && + property.GetMethod != null && + property.GetMethod.IsPublic && + !property.GetMethod.IsStatic; + } + + class PropertyKey : Tuple + { + public PropertyKey(Type type, string propertyName) + : base(type, propertyName) + { + } + public Type Type { get { return base.Item1; } } + public string PropertyName { get { return base.Item2; } } + } + } +} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs index 9bdcecbfcb..c439ee40b5 100644 --- a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs +++ b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs @@ -15,6 +15,7 @@ using SmartStore.Core.Plugins; using System.ComponentModel; using SmartStore.Utilities; +using SmartStore.Utilities.Reflection; namespace SmartStore.Services.Configuration { @@ -303,10 +304,12 @@ public virtual bool SettingExists(T settings, var settings = Activator.CreateInstance(); - foreach (var prop in typeof(T).GetProperties()) + foreach (var fastProp in FastProperty.GetProperties(typeof(T)).Values) { + var prop = fastProp.Property; + // get properties we can read and write to - if (!prop.CanRead || !prop.CanWrite) + if (!prop.CanWrite) continue; var key = typeof(T).Name + "." + prop.Name; @@ -330,7 +333,7 @@ public virtual bool SettingExists(T settings, if (list != null) { - prop.SetValue(settings, list, null); + fastProp.SetValue(settings, list); } } @@ -348,7 +351,7 @@ public virtual bool SettingExists(T settings, object value = converter.ConvertFromInvariantString(setting); //set property - prop.SetValue(settings, value, null); + fastProp.SetValue(settings, value); } return settings; diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index 1daa71bd03..3820f326d8 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -75,6 +75,7 @@ using SmartStore.Web.Framework.WebApi.Configuration; using Module = Autofac.Module; using SmartStore.Core.Domain.DataExchange; +using SmartStore.Utilities.Reflection; namespace SmartStore.Web.Framework { @@ -327,12 +328,14 @@ protected override void AttachToComponentRegistration(IComponentRegistry compone if (querySettingsProperty == null) return; + var fastProperty = new FastProperty(querySettingsProperty); + registration.Activated += (sender, e) => { if (DataSettings.DatabaseIsInstalled()) { var querySettings = e.Context.Resolve(); - querySettingsProperty.SetValue(e.Instance, querySettings, null); + fastProperty.SetValue(e.Instance, querySettings); } }; } @@ -390,18 +393,17 @@ private IEnumerable> BuildLoggerInjectors(Type }) .Where(x => x.PropertyType == typeof(ILogger)) // must be a logger .Where(x => x.IndexParameters.Count() == 0) // must not be an indexer - .Where(x => x.Accessors.Length != 1 || x.Accessors[0].ReturnType == typeof(void)); //must have get/set, or only set + .Where(x => x.Accessors.Length != 1 || x.Accessors[0].ReturnType == typeof(void)) //must have get/set, or only set + .Select(x => new FastProperty(x.PropertyInfo)); // Return an array of actions that resolve a logger and assign the property - foreach (var entry in loggerProperties) + foreach (var prop in loggerProperties) { - var propertyInfo = entry.PropertyInfo; - yield return (ctx, instance) => { string component = componentType.ToString(); var logger = ctx.Resolve(); - propertyInfo.SetValue(instance, logger, null); + prop.SetValue(instance, logger); }; } } @@ -433,12 +435,14 @@ protected override void AttachToComponentRegistration(IComponentRegistry compone if (userProperty == null) return; + var fastProperty = new FastProperty(userProperty); + registration.Activated += (sender, e) => { if (DataSettings.DatabaseIsInstalled()) { Localizer localizer = e.Context.Resolve().Get; - userProperty.SetValue(e.Instance, localizer, null); + fastProperty.SetValue(e.Instance, localizer); } }; } @@ -1016,16 +1020,16 @@ public IEnumerable RegistrationsFor( var ts = service as TypedService; if (ts != null && typeof(ISettings).IsAssignableFrom(ts.ServiceType)) { - //var buildMethod = BuildMethod.MakeGenericMethod(ts.ServiceType); - //yield return (IComponentRegistration)buildMethod.Invoke(null, null); - - // Perf with Fasterflect - yield return (IComponentRegistration)Fasterflect.TryInvokeWithValuesExtensions.TryCallMethodWithValues( - typeof(SettingsSource), - null, - "BuildRegistration", - new Type[] { ts.ServiceType }, - BindingFlags.Static | BindingFlags.NonPublic); + var buildMethod = BuildMethod.MakeGenericMethod(ts.ServiceType); + yield return (IComponentRegistration)buildMethod.Invoke(null, null); + + //// Perf with Fasterflect + //yield return (IComponentRegistration)Fasterflect.TryInvokeWithValuesExtensions.TryCallMethodWithValues( + // typeof(SettingsSource), + // null, + // "BuildRegistration", + // new Type[] { ts.ServiceType }, + // BindingFlags.Static | BindingFlags.NonPublic); } } From e910adace963cef2360e75a6afd79f170186de7b Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sun, 8 Nov 2015 04:43:14 +0100 Subject: [PATCH 184/874] (Perf) Replaced Fasterflect with custom reflection utilities (much faster & less mem usage) --- .../ComponentModel/SerializationUtils.cs | 9 +- .../Extensions/DictionaryExtensions.cs | 3 +- .../Extensions/TypeExtensions.cs | 58 ------------ .../Infrastructure/ComparableObject.cs | 7 +- .../Infrastructure/DateRange.cs | 70 --------------- .../SmartStore.Core/Infrastructure/Error.cs | 2 - .../Infrastructure/ValueObject.cs | 90 ------------------- .../SmartStore.Core/SmartStore.Core.csproj | 7 +- .../Utilities/CollectionHelper.cs | 21 +---- .../Utilities/DictionaryConverter.cs | 48 +++++----- .../Utilities/Reflection/FastProperty.cs | 4 +- .../SmartStore.Core/Utilities/TypeHelper.cs | 11 --- src/Libraries/SmartStore.Core/packages.config | 1 - src/Libraries/SmartStore.Data/app.config | 12 +-- .../Configuration/SettingService.cs | 17 ++-- .../ExportImport/DataSegmenter.cs | 17 +++- .../Localization/LocalizationExtentions.cs | 11 ++- .../SmartStore.Services.csproj | 4 - .../SmartStore.Services/packages.config | 1 - src/Plugins/SmartStore.Clickatell/web.config | 2 +- src/Plugins/SmartStore.DevTools/Web.config | 2 +- .../SmartStore.DiscountRules/web.config | 2 +- .../SmartStore.FacebookAuth/web.config | 2 +- .../SmartStore.GoogleAnalytics/web.config | 2 +- .../web.config | 2 +- .../SmartStore.OfflinePayment/web.config | 2 +- src/Plugins/SmartStore.Shipping/web.config | 2 +- .../SmartStore.ShippingByWeight/web.config | 2 +- src/Plugins/SmartStore.Tax/web.config | 2 +- src/Plugins/SmartStore.WebApi/web.config | 2 +- .../Settings/StoreDependingSettingHelper.cs | 10 +-- .../SmartStore.Web.Framework.csproj | 4 - .../UI/Components/ComponentBuilder.cs | 2 +- .../WebApi/WebApiEntityController.cs | 19 ++-- .../SmartStore.Web.Framework/packages.config | 1 - .../SmartStore.Web/Administration/Web.config | 2 +- src/Tools/SmartStore.Packager/App.config | 18 ++-- 37 files changed, 113 insertions(+), 358 deletions(-) delete mode 100644 src/Libraries/SmartStore.Core/Infrastructure/DateRange.cs delete mode 100644 src/Libraries/SmartStore.Core/Infrastructure/ValueObject.cs diff --git a/src/Libraries/SmartStore.Core/ComponentModel/SerializationUtils.cs b/src/Libraries/SmartStore.Core/ComponentModel/SerializationUtils.cs index 263e516ef5..26d6f8b079 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/SerializationUtils.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/SerializationUtils.cs @@ -35,7 +35,6 @@ using System.IO; using System.Text; //using System.Reflection; -using Fasterflect; using System.Xml; using System.Xml.Serialization; @@ -419,17 +418,17 @@ public static object DeSerializeObject(byte[] buffer, Type objectType, bool thro /// public static string ObjectToString(object instance, string separator, ObjectToStringTypes type) { - var fi = instance.GetType().Fields(); + var fi = instance.GetType().GetFields(); string output = string.Empty; if (type == ObjectToStringTypes.Properties || type == ObjectToStringTypes.PropertiesAndFields) { - foreach (var property in instance.GetType().Properties()) + foreach (var property in instance.GetType().GetProperties()) { try { - output += property.Name + ":" + instance.GetPropertyValue(property.Name).ToString() + separator; + output += property.Name + ":" + property.GetValue(instance, null).ToString() + separator; } catch { @@ -444,7 +443,7 @@ public static string ObjectToString(object instance, string separator, ObjectToS { try { - output = output + field.Name + ": " + instance.GetFieldValue(field.Name).ToString() + separator; + output = output + field.Name + ": " + field.GetValue(instance).ToString() + separator; } catch { diff --git a/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs index 8cfb8c1142..d0dde45343 100644 --- a/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/DictionaryExtensions.cs @@ -5,6 +5,7 @@ using System.Web.Routing; using System.Globalization; using System.Dynamic; +using SmartStore.Utilities; namespace SmartStore { @@ -34,7 +35,7 @@ public static void Merge(this IDictionary instance, string key, public static void Merge(this IDictionary instance, object values, bool replaceExisting = true) { - instance.Merge(new RouteValueDictionary(values), replaceExisting); + instance.Merge(CollectionHelper.ObjectToDictionary(values), replaceExisting); } public static void Merge(this IDictionary instance, IDictionary from, bool replaceExisting = true) diff --git a/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs index d2ca8d9df9..007022ae53 100644 --- a/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs @@ -4,7 +4,6 @@ using System.Reflection; using System.Collections; using System.Diagnostics; -using Fasterflect; namespace SmartStore { @@ -393,63 +392,6 @@ public static bool IsGenericDictionary(this Type type) // return false; //} - /// - /// Gets the member's value on the object. - /// - /// The member. - /// The target object. - /// The member's value on the object. - public static object GetValue(this MemberInfo member, object target) - { - Guard.ArgumentNotNull(member, "member"); - Guard.ArgumentNotNull(target, "target"); - - var type = target.GetType(); - - switch (member.MemberType) - { - case MemberTypes.Field: - return target.GetFieldValue(member.Name); - //return ((FieldInfo)member).GetValue(target); - case MemberTypes.Property: - return target.GetPropertyValue(member.Name); - default: - throw new ArgumentException("MemberInfo '{0}' is not of type FieldInfo or PropertyInfo".FormatInvariant(member.Name), "member"); - } - } - - /// - /// Sets the member's value on the target object. - /// - /// The member. - /// The target. - /// The value. - public static void SetValue(this MemberInfo member, object target, object value) - { - Guard.ArgumentNotNull(member, "member"); - Guard.ArgumentNotNull(target, "target"); - - switch (member.MemberType) - { - case MemberTypes.Field: - target.SetFieldValue(member.Name, value); - break; - //return ((FieldInfo)member).GetValue(target); - case MemberTypes.Property: - try - { - target.SetPropertyValue(member.Name, value); - } - catch (TargetParameterCountException e) - { - throw new ArgumentException("PropertyInfo '{0}' has index parameters".FormatInvariant(member.Name), "member", e); - } - break; - default: - throw new ArgumentException("MemberInfo '{0}' is not of type FieldInfo or PropertyInfo".FormatInvariant(member.Name), "member"); - } - } - /// /// Gets the underlying type of a type. /// diff --git a/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs b/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs index 7a0c3d9dbb..32b4a6a1e2 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs @@ -4,7 +4,6 @@ using System.Linq.Expressions; using System.Reflection; using System.ComponentModel; -using Fasterflect; namespace SmartStore { @@ -67,7 +66,7 @@ public override int GetHashCode() foreach (var pi in signatureProperties) { - object value = this.GetPropertyValue(pi.Name); // pi.GetValue(this); + object value = pi.GetValue(this); if (value != null) hashCode = (hashCode * HashMultiplier) ^ value.GetHashCode(); @@ -102,8 +101,8 @@ protected virtual bool HasSameSignatureAs(ComparableObject compareTo) foreach (var pi in signatureProperties) { - object thisValue = this.GetPropertyValue(pi.Name); - object thatValue = compareTo.GetPropertyValue(pi.Name); + object thisValue = pi.GetValue(this); + object thatValue = pi.GetValue(compareTo); if (thisValue == null && thatValue == null) continue; diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DateRange.cs b/src/Libraries/SmartStore.Core/Infrastructure/DateRange.cs deleted file mode 100644 index bb331223be..0000000000 --- a/src/Libraries/SmartStore.Core/Infrastructure/DateRange.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace SmartStore -{ - - public class DateRange : ValueObject, ICloneable - { - - public DateRange() - { - } - - public DateRange(DateTime to) - : this(default(DateTime), to) - { - } - - public DateRange(DateTime from, DateTime to) - { - DateFrom = from; - DateTo = to; - } - - [ObjectSignature] - public DateTime? DateFrom { get; set; } - - [ObjectSignature] - public DateTime? DateTo { get; set; } - - public bool IsDefined - { - get { return DateFrom.HasValue || DateTo.HasValue; } - } - - //public override int GetHashCode() - //{ - // return SystemUtil.GetHashCode(this.DateFrom, this.DateTo); - //} - - //public override bool Equals(object obj) - //{ - // if (ReferenceEquals(null, obj)) return false; - // if (ReferenceEquals(this, obj)) return true; - - // DateRange other = (DateRange)obj; - // return this.DateFrom.Equals(other.DateFrom) && this.DateTo.Equals(other.DateTo); - //} - - #region ICloneable Members - - public DateRange Clone() - { - return new DateRange() - { - DateFrom = this.DateFrom, - DateTo = this.DateTo - }; - } - - object ICloneable.Clone() - { - return this.Clone(); - } - - #endregion - - } - -} diff --git a/src/Libraries/SmartStore.Core/Infrastructure/Error.cs b/src/Libraries/SmartStore.Core/Infrastructure/Error.cs index d8752d8169..a83cc11464 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/Error.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/Error.cs @@ -2,8 +2,6 @@ using System.Globalization; using System.Diagnostics; -using Fasterflect; - namespace SmartStore { public static class Error diff --git a/src/Libraries/SmartStore.Core/Infrastructure/ValueObject.cs b/src/Libraries/SmartStore.Core/Infrastructure/ValueObject.cs deleted file mode 100644 index 066c47f810..0000000000 --- a/src/Libraries/SmartStore.Core/Infrastructure/ValueObject.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Reflection; -using Fasterflect; - -namespace SmartStore -{ - - /// - /// Base class for complex value objects, which do not have - /// identifiers. - /// - /// Type of the entity, which is the value obect. - public abstract class ValueObject : ComparableObject> - where T : ValueObject - { - - /// - /// Registers all properties of the subclass as signature properties. - /// - protected void RegisterProperties() - { - RegisterProperties((pd) => true); - } - - /// - /// Registers any properties of the subclass matching - /// the given as signature properties. - /// - protected void RegisterProperties(Func filter) - { - foreach (var pi in this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)) - { - if (filter(pi)) - RegisterSignatureProperty(pi); - } - } - - public override bool Equals(object obj) - { - return base.Equals(obj); - } - - public override int GetHashCode() - { - return base.GetHashCode(); - } - - public override string ToString() - { - var sb = new StringBuilder(); - var properties = GetSignatureProperties(); - int propsCount = properties.Count(); - - int i = 1; - - foreach (var pi in properties) - { - object value = this.GetPropertyValue(pi.Name); - if (value == null) - continue; - - sb.Append(pi.Name + ": " + value.ToString()); - if (i < propsCount) - sb.Append(", "); - - i++; - } - - return sb.ToString(); - } - - public static bool operator ==(ValueObject x, ValueObject y) - { - if ((object)x == null) - return (object)y == null; - - return x.Equals(y); - } - - public static bool operator !=(ValueObject x, ValueObject y) - { - return !(x == y); - } - - } - -} diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index 7d39d16e05..8dc531e3be 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -77,10 +77,6 @@ False ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll - - False - ..\..\packages\fasterflect.2.1.3\lib\net40\Fasterflect.dll - True ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll @@ -441,7 +437,6 @@ - @@ -451,7 +446,6 @@ - @@ -599,6 +593,7 @@ + diff --git a/src/Libraries/SmartStore.Core/Utilities/CollectionHelper.cs b/src/Libraries/SmartStore.Core/Utilities/CollectionHelper.cs index 2497ef9349..f5dcfae895 100644 --- a/src/Libraries/SmartStore.Core/Utilities/CollectionHelper.cs +++ b/src/Libraries/SmartStore.Core/Utilities/CollectionHelper.cs @@ -6,10 +6,7 @@ using System.Text; using System.Collections; using System.Globalization; -using System.Collections.Specialized; -using System.ComponentModel; - -using Fasterflect; +using SmartStore.Utilities.Reflection; namespace SmartStore.Utilities { @@ -221,24 +218,12 @@ public static IDictionary ObjectToDictionary(object obj) Type t = obj.GetType(); - return t.GetProperties(BindingFlags.Instance | BindingFlags.Public) + return FastProperty.GetProperties(t).Values .ToDictionary(k => k.Name.Replace("_", "-"), - v => obj.GetPropertyValue(v.Name), + v => v.GetValue(obj), StringComparer.OrdinalIgnoreCase); } - public static NameValueCollection ObjectToNameValueCollection(object obj) - { - var result = new NameValueCollection(StringComparer.OrdinalIgnoreCase); - foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(obj)) - { - object value = descriptor.GetValue(obj); - result.Add(descriptor.Name, value.ToString()); - } - - return result; - } - /// /// Converts the type to an /// compatible object. diff --git a/src/Libraries/SmartStore.Core/Utilities/DictionaryConverter.cs b/src/Libraries/SmartStore.Core/Utilities/DictionaryConverter.cs index bcb4706ad6..34f4e74782 100644 --- a/src/Libraries/SmartStore.Core/Utilities/DictionaryConverter.cs +++ b/src/Libraries/SmartStore.Core/Utilities/DictionaryConverter.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Generic; using System.Linq; -using System.ComponentModel; using System.Text; using System.Collections; using System.Reflection; using System.Runtime.Serialization; -using Fasterflect; +using SmartStore.Utilities.Reflection; namespace SmartStore.Utilities { @@ -93,7 +92,7 @@ public static object CreateAndPopulate(Type targetType, IDictionary targetType); - var target = targetType.CreateInstance(); //Activator.CreateInstance(targetType); + var target = Activator.CreateInstance(targetType); Populate(source, target, out problems); @@ -141,15 +140,14 @@ public static void Populate(IDictionary source, object target, o if (source != null) { - // TODO: Metadaten aus einem TypeCache ziehen zwecks Performance! - foreach (var pi in t.GetProperties(BindingFlags.Instance | BindingFlags.Public)) + foreach (var fastProp in FastProperty.GetProperties(t).Values) { - object value; + var pi = fastProp.Property; if (!pi.PropertyType.IsPredefinedSimpleType() && source.TryGetValue(pi.Name, out value) && value is IDictionary) { - var nestedValue = target.GetPropertyValue(pi.Name); + var nestedValue = fastProp.GetValue(target); ICollection nestedProblems; populated = populated.Concat(new object[] { target }).ToArray(); @@ -159,17 +157,17 @@ public static void Populate(IDictionary source, object target, o { problems.AddRange(nestedProblems); } - WriteToProperty(target, pi, nestedValue, problems); + WriteToProperty(target, fastProp, nestedValue, problems); } else if (pi.PropertyType.IsArray && !source.ContainsKey(pi.Name)) { - WriteToProperty(target, pi, RetrieveArrayValues(pi, source, problems), problems); + WriteToProperty(target, fastProp, RetrieveArrayValues(pi, source, problems), problems); } else { if (source.TryGetValue(pi.Name, out value)) { - WriteToProperty(target, pi, value, problems); + WriteToProperty(target, fastProp, value, problems); } } } @@ -182,21 +180,19 @@ private static object RetrieveArrayValues(PropertyInfo arrayProp, IDictionary).MakeGenericType(elemType)); - var elements = (IList)typeof(List<>).MakeGenericType(elemType).CreateInstance(); + var elements = (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(elemType)); - // TODO: Metadaten aus einem TypeCache ziehen zwecks Performance! - var properties = elemType.GetProperties(BindingFlags.Public | BindingFlags.Instance); + var properties = FastProperty.GetProperties(elemType); while (anyValuesFound) { object curElement = null; anyValuesFound = false; // false until proven otherwise - foreach (var pi in properties) + foreach (var prop in properties.Values) { //var key = string.Format("_{0}{1}_{2}", idx, arrayProp.Name, pd.Name); - var key = string.Format("{0}[{1}].{2}", arrayProp.Name, idx, pi.Name); + var key = string.Format("{0}[{1}].{2}", arrayProp.Name, idx, prop.Name); object value; if (source.TryGetValue(key, out value)) @@ -205,11 +201,11 @@ private static object RetrieveArrayValues(PropertyInfo arrayProp, IDictionary problems) + private static void SetPropFromValue(object value, object item, FastProperty prop, ICollection problems) { - WriteToProperty(item, pi, value, problems); + WriteToProperty(item, prop, value, problems); } - private static void WriteToProperty(object item, PropertyInfo pi, object value, ICollection problems) + private static void WriteToProperty(object item, FastProperty prop, object value, ICollection problems) { - if (!pi.CanWrite) + var pi = prop.Property; + + if (!pi.CanWrite) return; try @@ -246,8 +244,7 @@ private static void WriteToProperty(object item, PropertyInfo pi, object value, if (pi.PropertyType.IsAssignableFrom(value.GetType())) { - //pi.SetValue(item, value); - item.SetPropertyValue(pi.Name, value); + prop.SetValue(item, value); return; } @@ -256,8 +253,7 @@ private static void WriteToProperty(object item, PropertyInfo pi, object value, destType = pi.PropertyType.GetGenericArguments()[0]; } - //pi.SetValue(item, value.Convert(destType)); - item.SetPropertyValue(pi.Name, value.Convert(destType)); + prop.SetValue(item, value.Convert(destType)); } } catch (Exception ex) diff --git a/src/Libraries/SmartStore.Core/Utilities/Reflection/FastProperty.cs b/src/Libraries/SmartStore.Core/Utilities/Reflection/FastProperty.cs index 27498b5576..e72bbafcd9 100644 --- a/src/Libraries/SmartStore.Core/Utilities/Reflection/FastProperty.cs +++ b/src/Libraries/SmartStore.Core/Utilities/Reflection/FastProperty.cs @@ -355,9 +355,9 @@ public static IDictionary ObjectToDictionary(object value) if (value != null) { - foreach (var helper in GetProperties(value).Values) + foreach (var prop in GetProperties(value).Values) { - dictionary[helper.Name] = helper.GetValue(value); + dictionary[prop.Name] = prop.GetValue(value); } } diff --git a/src/Libraries/SmartStore.Core/Utilities/TypeHelper.cs b/src/Libraries/SmartStore.Core/Utilities/TypeHelper.cs index 34dbf27b18..482b1ed9bd 100644 --- a/src/Libraries/SmartStore.Core/Utilities/TypeHelper.cs +++ b/src/Libraries/SmartStore.Core/Utilities/TypeHelper.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Globalization; using System.IO; -using Fasterflect; namespace SmartStore.Utilities { @@ -332,16 +331,6 @@ public static void RegisterTypeConverter() where TC : TypeConverter TypeDescriptor.AddAttributes(typeof(T), new TypeConverterAttribute(typeof(TC))); } - public static T CreateInstance(params object[] parameters) where T : class - { - return (T)typeof(T).CreateInstance(parameters); - } - - public static object CreateInstance(Type type, params object[] parameters) - { - return type.CreateInstance(parameters); - } - } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Core/packages.config b/src/Libraries/SmartStore.Core/packages.config index 923c1183a2..78f9872f99 100644 --- a/src/Libraries/SmartStore.Core/packages.config +++ b/src/Libraries/SmartStore.Core/packages.config @@ -3,7 +3,6 @@ - diff --git a/src/Libraries/SmartStore.Data/app.config b/src/Libraries/SmartStore.Data/app.config index 4f592fbcd8..7808ca6f28 100644 --- a/src/Libraries/SmartStore.Data/app.config +++ b/src/Libraries/SmartStore.Data/app.config @@ -1,15 +1,15 @@ - + - - + + - - + + - + diff --git a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs index c439ee40b5..ae83128abb 100644 --- a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs +++ b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs @@ -9,7 +9,7 @@ using SmartStore.Core.Domain.Configuration; using SmartStore.Core.Infrastructure; using SmartStore.Core.Events; -using Fasterflect; +//using Fasterflect; using System.Linq.Expressions; using System.Reflection; using SmartStore.Core.Plugins; @@ -413,18 +413,20 @@ public virtual void SetSetting(string key, T value, int storeId = 0, bool cle /* We do not clear cache after each setting update. * This behavior can increase performance because cached settings will not be cleared * and loaded from database after each update */ - foreach (var prop in typeof(T).GetProperties()) + foreach (var prop in FastProperty.GetProperties(typeof(T)).Values) { + var pi = prop.Property; + // get properties we can read and write to - if (!prop.CanRead || !prop.CanWrite) + if (!pi.CanRead || !pi.CanWrite) continue; - if (!CommonHelper.GetTypeConverter(prop.PropertyType).CanConvertFrom(typeof(string))) + if (!CommonHelper.GetTypeConverter(pi.PropertyType).CanConvertFrom(typeof(string))) continue; string key = typeof(T).Name + "." + prop.Name; //Duck typing is not supported in C#. That's why we're using dynamic type - dynamic value = settings.TryGetPropertyValue(prop.Name); + dynamic value = prop.GetValue(settings); SetSetting(key, value ?? "", storeId, false); } @@ -462,8 +464,9 @@ public virtual void SaveSetting(T settings, } string key = typeof(T).Name + "." + propInfo.Name; - //Duck typing is not supported in C#. That's why we're using dynamic type - dynamic value = settings.TryGetPropertyValue(propInfo.Name); + // Duck typing is not supported in C#. That's why we're using dynamic type + var fastProp = FastProperty.GetProperty(settings.GetType(), propInfo.Name, true); + dynamic value = fastProp.GetValue(settings); SetSetting(key, value ?? "", storeId, false); } diff --git a/src/Libraries/SmartStore.Services/ExportImport/DataSegmenter.cs b/src/Libraries/SmartStore.Services/ExportImport/DataSegmenter.cs index 303f12a2c5..6bb3693cd1 100644 --- a/src/Libraries/SmartStore.Services/ExportImport/DataSegmenter.cs +++ b/src/Libraries/SmartStore.Services/ExportImport/DataSegmenter.cs @@ -5,10 +5,10 @@ using System.Linq.Expressions; using System.Reflection; using System.Text; -using Fasterflect; using OfficeOpenXml; using SmartStore.Core; using SmartStore.Core.Data; +using SmartStore.Utilities.Reflection; namespace SmartStore.Services.ExportImport { @@ -277,6 +277,12 @@ public int Position get { return _position; } } + public object Fastproperty + { + get; + private set; + } + public TProp GetValue(string columnName) { object value; @@ -298,6 +304,8 @@ public TProp GetValue(string columnName) try { + var fastProp = FastProperty.GetProperty(typeof(T), propName, true); + object value; if (this.TryGetValue(propName, out value)) { @@ -319,7 +327,9 @@ public TProp GetValue(string columnName) converted = value.Convert(); } } - return target.TrySetPropertyValue(propName, converted); + + fastProp.SetValue(target, converted); + return true; } else { @@ -328,7 +338,8 @@ public TProp GetValue(string columnName) { // ...but the entity is new. In this case // set the default value if given. - return target.TrySetPropertyValue(propName, defaultValue); + fastProp.SetValue(target, defaultValue); + return true; } } } diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizationExtentions.cs b/src/Libraries/SmartStore.Services/Localization/LocalizationExtentions.cs index b73e7660ed..0256ef1ac8 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizationExtentions.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizationExtentions.cs @@ -5,12 +5,13 @@ using SmartStore.Core.Domain.Localization; using SmartStore.Core.Infrastructure; using SmartStore.Core.Plugins; -using Fasterflect; +//using Fasterflect; using System.Xml; using SmartStore.Core.Data; using SmartStore.Utilities; using System.Collections.Concurrent; using SmartStore.Core.ComponentModel; +using SmartStore.Utilities.Reflection; namespace SmartStore.Services.Localization { @@ -324,7 +325,13 @@ public static string GetLocalizedValue(this PluginDescriptor descriptor, ILocali string result = localizationService.GetResource(resourceName, languageId, false, "", true); if (String.IsNullOrEmpty(result) && returnDefaultValue) - result = descriptor.TryGetPropertyValue(propertyName) as string; + { + var fastProp = FastProperty.GetProperty(descriptor.GetType(), propertyName); + if (fastProp != null) + { + result = fastProp.GetValue(descriptor) as string; + } + } return result; } diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index f63d565765..76cfeb1040 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -80,10 +80,6 @@ False ..\..\packages\EPPlus.4.0.3\lib\net20\EPPlus.dll - - False - ..\..\packages\fasterflect.2.1.3\lib\net40\Fasterflect.dll - False ..\..\packages\ImageResizer.3.4.2\lib\ImageResizer.dll diff --git a/src/Libraries/SmartStore.Services/packages.config b/src/Libraries/SmartStore.Services/packages.config index 2419041a2c..2efca85a84 100644 --- a/src/Libraries/SmartStore.Services/packages.config +++ b/src/Libraries/SmartStore.Services/packages.config @@ -4,7 +4,6 @@ - diff --git a/src/Plugins/SmartStore.Clickatell/web.config b/src/Plugins/SmartStore.Clickatell/web.config index 46b8ba77d4..deaeafe833 100644 --- a/src/Plugins/SmartStore.Clickatell/web.config +++ b/src/Plugins/SmartStore.Clickatell/web.config @@ -106,7 +106,7 @@ - + diff --git a/src/Plugins/SmartStore.DevTools/Web.config b/src/Plugins/SmartStore.DevTools/Web.config index e0593e522a..58d0f26ce4 100644 --- a/src/Plugins/SmartStore.DevTools/Web.config +++ b/src/Plugins/SmartStore.DevTools/Web.config @@ -114,7 +114,7 @@ - + diff --git a/src/Plugins/SmartStore.DiscountRules/web.config b/src/Plugins/SmartStore.DiscountRules/web.config index 46b8ba77d4..deaeafe833 100644 --- a/src/Plugins/SmartStore.DiscountRules/web.config +++ b/src/Plugins/SmartStore.DiscountRules/web.config @@ -106,7 +106,7 @@ - + diff --git a/src/Plugins/SmartStore.FacebookAuth/web.config b/src/Plugins/SmartStore.FacebookAuth/web.config index 10ad5c862a..b294e19325 100644 --- a/src/Plugins/SmartStore.FacebookAuth/web.config +++ b/src/Plugins/SmartStore.FacebookAuth/web.config @@ -110,7 +110,7 @@ - + diff --git a/src/Plugins/SmartStore.GoogleAnalytics/web.config b/src/Plugins/SmartStore.GoogleAnalytics/web.config index ad003b3e26..d4330046b2 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/web.config +++ b/src/Plugins/SmartStore.GoogleAnalytics/web.config @@ -105,7 +105,7 @@ - + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/web.config b/src/Plugins/SmartStore.GoogleMerchantCenter/web.config index 46b8ba77d4..deaeafe833 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/web.config +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/web.config @@ -106,7 +106,7 @@ - + diff --git a/src/Plugins/SmartStore.OfflinePayment/web.config b/src/Plugins/SmartStore.OfflinePayment/web.config index 46b8ba77d4..deaeafe833 100644 --- a/src/Plugins/SmartStore.OfflinePayment/web.config +++ b/src/Plugins/SmartStore.OfflinePayment/web.config @@ -106,7 +106,7 @@ - + diff --git a/src/Plugins/SmartStore.Shipping/web.config b/src/Plugins/SmartStore.Shipping/web.config index ad003b3e26..d4330046b2 100644 --- a/src/Plugins/SmartStore.Shipping/web.config +++ b/src/Plugins/SmartStore.Shipping/web.config @@ -105,7 +105,7 @@ - + diff --git a/src/Plugins/SmartStore.ShippingByWeight/web.config b/src/Plugins/SmartStore.ShippingByWeight/web.config index ad003b3e26..d4330046b2 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/web.config +++ b/src/Plugins/SmartStore.ShippingByWeight/web.config @@ -105,7 +105,7 @@ - + diff --git a/src/Plugins/SmartStore.Tax/web.config b/src/Plugins/SmartStore.Tax/web.config index 46b8ba77d4..deaeafe833 100644 --- a/src/Plugins/SmartStore.Tax/web.config +++ b/src/Plugins/SmartStore.Tax/web.config @@ -106,7 +106,7 @@ - + diff --git a/src/Plugins/SmartStore.WebApi/web.config b/src/Plugins/SmartStore.WebApi/web.config index 0219bc2ade..8c7ae2efce 100644 --- a/src/Plugins/SmartStore.WebApi/web.config +++ b/src/Plugins/SmartStore.WebApi/web.config @@ -78,7 +78,7 @@ - + diff --git a/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs b/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs index 802fea0798..7f0f2e0096 100644 --- a/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs +++ b/src/Presentation/SmartStore.Web.Framework/Settings/StoreDependingSettingHelper.cs @@ -2,11 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Web.Mvc; -using Fasterflect; using SmartStore.Core.Infrastructure; using SmartStore.Services.Configuration; using SmartStore.Services.Localization; using SmartStore.Web.Framework.Localization; +using SmartStore.Utilities.Reflection; namespace SmartStore.Web.Framework.Settings { @@ -139,7 +139,7 @@ public void GetOverrideKeysLocalized(object settings, object model, int storeId, public void UpdateSettings(object settings, FormCollection form, int storeId, ISettingService settingService) { var settingName = settings.GetType().Name; - var properties = settings.GetType().GetProperties(); + var properties = FastProperty.GetProperties(settings.GetType()).Values; foreach (var prop in properties) { @@ -148,7 +148,7 @@ public void UpdateSettings(object settings, FormCollection form, int storeId, IS if (storeId == 0 || IsOverrideChecked(key, form)) { - dynamic value = settings.TryGetPropertyValue(name); + dynamic value = prop.GetValue(settings); settingService.SetSetting(key, value == null ? "" : value, storeId, false); } else if (storeId > 0) @@ -161,7 +161,7 @@ public void UpdateSettings(object settings, FormCollection form, int storeId, IS public void UpdateLocalizedSettings(object settings, FormCollection form, int storeId, ISettingService settingService, ILocalizedModelLocal localized) { var settingName = settings.GetType().Name; - var properties = localized.GetType().GetProperties(); + var properties = FastProperty.GetProperties(localized.GetType()).Values; foreach (var prop in properties) { @@ -170,7 +170,7 @@ public void UpdateLocalizedSettings(object settings, FormCollection form, int st if (storeId == 0 || IsOverrideChecked(key, form)) { - dynamic value = settings.TryGetPropertyValue(name); + dynamic value = prop.GetValue(settings); settingService.SetSetting(key, value == null ? "" : value, storeId, false); } else if (storeId > 0) diff --git a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj index dba5ac1aaf..b70e4658a1 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj +++ b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj @@ -101,10 +101,6 @@ False ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.SqlServer.dll - - False - ..\..\packages\fasterflect.2.1.3\lib\net40\Fasterflect.dll - False ..\..\packages\FluentValidation.5.0.0.1\lib\Net40\FluentValidation.dll diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentBuilder.cs b/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentBuilder.cs index e315ebacd9..b45a50dfae 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentBuilder.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Components/ComponentBuilder.cs @@ -88,7 +88,7 @@ public TBuilder WithRenderer(Type rendererType) { Guard.ArgumentNotNull(() => rendererType); Guard.Implements>(rendererType); - var renderer = TypeHelper.CreateInstance(rendererType) as ComponentRenderer; + var renderer = Activator.CreateInstance(rendererType) as ComponentRenderer; if (renderer != null) { this.Renderer = renderer; diff --git a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs index 4354426a9f..3f48b374d2 100644 --- a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs +++ b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs @@ -16,6 +16,7 @@ using SmartStore.Services.Directory; using SmartStore.Services.Localization; using System.Collections.Generic; +using SmartStore.Utilities.Reflection; namespace SmartStore.Web.Framework.WebApi { @@ -77,7 +78,7 @@ protected virtual internal HttpResponseMessage UnmappedGetProperty(ODataPath oda if (entity == null) return Request.CreateErrorResponse(HttpStatusCode.NotFound, WebApiGlobal.Error.EntityNotFound.FormatInvariant(key)); - PropertyInfo pi = null; + FastProperty prop = null; string propertyName = null; var lastSegment = odataPath.Segments.Last(); var propertySegment = (lastSegment as PropertyAccessPathSegment); @@ -88,14 +89,14 @@ protected virtual internal HttpResponseMessage UnmappedGetProperty(ODataPath oda propertyName = propertySegment.PropertyName; if (propertyName.HasValue()) - pi = entity.GetType().GetProperty(propertyName); + prop = FastProperty.GetProperty(entity.GetType(), propertyName); - if (pi == null) + if (prop == null) return UnmappedGetProperty(entity, propertyName ?? ""); - var propertyValue = pi.GetValue(entity, null); + var propertyValue = prop.GetValue(entity); - return Request.CreateResponse(HttpStatusCode.OK, pi.PropertyType, propertyValue); + return Request.CreateResponse(HttpStatusCode.OK, prop.Property.PropertyType, propertyValue); } protected virtual internal HttpResponseMessage UnmappedGetProperty(TEntity entity, string propertyName) @@ -465,17 +466,17 @@ protected internal virtual TEntity FulfillPropertiesOn(TEntity entity) if (propertyName.HasValue() && queryValue.HasValue()) { - var pi = entity.GetType().GetProperty(propertyName); - if (pi != null) + var prop = FastProperty.GetProperty(entity.GetType(), propertyName); + if (prop != null) { - var propertyValue = pi.GetValue(entity, null); + var propertyValue = prop.GetValue(entity); if (propertyValue == null) { object value = FulfillPropertyOn(entity, propertyName, queryValue); if (value != null) // there's no requirement to set a property value of null { - pi.SetValue(entity, value); + prop.SetValue(entity, value); } } } diff --git a/src/Presentation/SmartStore.Web.Framework/packages.config b/src/Presentation/SmartStore.Web.Framework/packages.config index 2f40cf1830..1688ceb9e7 100644 --- a/src/Presentation/SmartStore.Web.Framework/packages.config +++ b/src/Presentation/SmartStore.Web.Framework/packages.config @@ -9,7 +9,6 @@ - diff --git a/src/Presentation/SmartStore.Web/Administration/Web.config b/src/Presentation/SmartStore.Web/Administration/Web.config index 7d3a217dd0..82ce6dbca5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Web.config +++ b/src/Presentation/SmartStore.Web/Administration/Web.config @@ -100,7 +100,7 @@ - + diff --git a/src/Tools/SmartStore.Packager/App.config b/src/Tools/SmartStore.Packager/App.config index 30622f85af..59552e8d6b 100644 --- a/src/Tools/SmartStore.Packager/App.config +++ b/src/Tools/SmartStore.Packager/App.config @@ -1,32 +1,32 @@ - + -
    +
    - + - + - + - - + + - - + + From adbce646e1586e37330c0e0178b23c9b7605e1a1 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 8 Nov 2015 11:40:45 +0100 Subject: [PATCH 185/874] ExportXmlHelper creates own XmlWriter --- .../ExportTask/ExportProfileTask.cs | 2 + .../DataExchange/ExportXmlHelper.cs | 39 ++- .../Providers/CategoryXmlExportProvider.cs | 49 +-- .../Providers/CustomerXmlExportProvider.cs | 27 +- .../ManufacturerXmlExportProvider.cs | 49 +-- .../Providers/OrderXmlExportProvider.cs | 308 +++++++++--------- .../Providers/ProductXmlExportProvider.cs | 27 +- .../Controllers/ExportController.cs | 3 +- 8 files changed, 238 insertions(+), 266 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 86c909fc27..1a52e264ea 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -132,6 +132,8 @@ private void InitDependencies(TaskExecutionContext context) _urlRecordService = context.Resolve>(); _genericAttributeService = context.Resolve>(); _subscriptionRepository = context.Resolve>>(); + + T = NullLocalizer.Instance; } public Localizer T { get; set; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs index 8191f0d76e..e5837578d6 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs @@ -1,20 +1,53 @@ using System; using System.Globalization; +using System.IO; +using System.Text; using System.Xml; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.DataExchange; namespace SmartStore.Services.DataExchange { - public class ExportXmlHelper + public class ExportXmlHelper : IDisposable { private XmlWriter _writer; private CultureInfo _culture; + private bool _doNotDispose; - public ExportXmlHelper(XmlWriter writer, CultureInfo culture) + public ExportXmlHelper(XmlWriter writer, bool doNotDispose = false, CultureInfo culture = null) { _writer = writer; - _culture = culture; + _doNotDispose = doNotDispose; + _culture = (culture == null ? CultureInfo.InvariantCulture : culture); + } + public ExportXmlHelper(Stream stream, XmlWriterSettings settings = null, CultureInfo culture = null) + { + if (settings == null) + { + settings = new XmlWriterSettings + { + Encoding = Encoding.UTF8, + CheckCharacters = false, + Indent = true, + IndentChars = "\t" + }; + } + + _writer = XmlWriter.Create(stream, settings); + _culture = (culture == null ? CultureInfo.InvariantCulture : culture); + } + + public XmlWriter Writer + { + get { return _writer; } + } + + public void Dispose() + { + if (_writer != null && !_doNotDispose) + { + _writer.Dispose(); + } } public void WriteLocalized(dynamic parentNode) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs index e473a28ad2..2b74231730 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs @@ -1,8 +1,5 @@ using System; -using System.Globalization; using System.IO; -using System.Text; -using System.Xml; using SmartStore.Core; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Logging; @@ -35,26 +32,16 @@ public override string FileExtension public override void Execute(IExportExecuteContext context) { - var settings = new XmlWriterSettings - { - Encoding = Encoding.UTF8, - CheckCharacters = false, - Indent = true, - IndentChars = "\t" - }; - var path = context.FilePath; context.Log.Information("Creating file " + path); using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var writer = XmlWriter.Create(stream, settings)) + using (var helper = new ExportXmlHelper(stream)) { - var xmlHelper = new ExportXmlHelper(writer, CultureInfo.InvariantCulture); - - writer.WriteStartDocument(); - writer.WriteStartElement("Categories"); - writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); + helper.Writer.WriteStartDocument(); + helper.Writer.WriteStartElement("Categories"); + helper.Writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); while (context.Abort == ExportAbortion.None && context.Segmenter.ReadNextSegment()) { @@ -65,24 +52,24 @@ public override void Execute(IExportExecuteContext context) if (context.Abort != ExportAbortion.None) break; - writer.WriteStartElement("Category"); + helper.Writer.WriteStartElement("Category"); try { - xmlHelper.WriteCategory(category, null); + helper.WriteCategory(category, null); - writer.WriteStartElement("ProductCategories"); + helper.Writer.WriteStartElement("ProductCategories"); foreach (dynamic productCategory in category.ProductCategories) { - writer.WriteStartElement("ProductCategory"); - writer.Write("Id", ((int)productCategory.Id).ToString()); - writer.Write("ProductId", ((int)productCategory.ProductId).ToString()); - writer.Write("DisplayOrder", ((int)productCategory.DisplayOrder).ToString()); - writer.Write("IsFeaturedProduct", ((bool)productCategory.IsFeaturedProduct).ToString()); - writer.Write("CategoryId", ((int)productCategory.CategoryId).ToString()); - writer.WriteEndElement(); // ProductCategory + helper.Writer.WriteStartElement("ProductCategory"); + helper.Writer.Write("Id", ((int)productCategory.Id).ToString()); + helper.Writer.Write("ProductId", ((int)productCategory.ProductId).ToString()); + helper.Writer.Write("DisplayOrder", ((int)productCategory.DisplayOrder).ToString()); + helper.Writer.Write("IsFeaturedProduct", ((bool)productCategory.IsFeaturedProduct).ToString()); + helper.Writer.Write("CategoryId", ((int)productCategory.CategoryId).ToString()); + helper.Writer.WriteEndElement(); // ProductCategory } - writer.WriteEndElement(); // ProductCategories + helper.Writer.WriteEndElement(); // ProductCategories ++context.RecordsSucceeded; } @@ -91,12 +78,12 @@ public override void Execute(IExportExecuteContext context) context.RecordException(exc, (int)category.Id); } - writer.WriteEndElement(); // Category + helper.Writer.WriteEndElement(); // Category } } - writer.WriteEndElement(); // Categories - writer.WriteEndDocument(); + helper.Writer.WriteEndElement(); // Categories + helper.Writer.WriteEndDocument(); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs index 93d9c4462b..4103b412a5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs @@ -1,8 +1,5 @@ using System; -using System.Globalization; using System.IO; -using System.Text; -using System.Xml; using SmartStore.Core; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Logging; @@ -35,26 +32,16 @@ public override string FileExtension public override void Execute(IExportExecuteContext context) { - var settings = new XmlWriterSettings - { - Encoding = Encoding.UTF8, - CheckCharacters = false, - Indent = true, - IndentChars = "\t" - }; - var path = context.FilePath; context.Log.Information("Creating file " + path); using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var writer = XmlWriter.Create(stream, settings)) + using (var helper = new ExportXmlHelper(stream)) { - var xmlHelper = new ExportXmlHelper(writer, CultureInfo.InvariantCulture); - - writer.WriteStartDocument(); - writer.WriteStartElement("Customers"); - writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); + helper.Writer.WriteStartDocument(); + helper.Writer.WriteStartElement("Customers"); + helper.Writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); while (context.Abort == ExportAbortion.None && context.Segmenter.ReadNextSegment()) { @@ -67,7 +54,7 @@ public override void Execute(IExportExecuteContext context) try { - xmlHelper.WriteCustomer(customer, "Customer"); + helper.WriteCustomer(customer, "Customer"); ++context.RecordsSucceeded; } @@ -78,8 +65,8 @@ public override void Execute(IExportExecuteContext context) } } - writer.WriteEndElement(); // Customers - writer.WriteEndDocument(); + helper.Writer.WriteEndElement(); // Customers + helper.Writer.WriteEndDocument(); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs index 8d26a9ab4e..3c5d6afa53 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs @@ -1,8 +1,5 @@ using System; -using System.Globalization; using System.IO; -using System.Text; -using System.Xml; using SmartStore.Core; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Logging; @@ -35,26 +32,16 @@ public override string FileExtension public override void Execute(IExportExecuteContext context) { - var settings = new XmlWriterSettings - { - Encoding = Encoding.UTF8, - CheckCharacters = false, - Indent = true, - IndentChars = "\t" - }; - var path = context.FilePath; context.Log.Information("Creating file " + path); using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var writer = XmlWriter.Create(stream, settings)) + using (var helper = new ExportXmlHelper(stream)) { - var xmlHelper = new ExportXmlHelper(writer, CultureInfo.InvariantCulture); - - writer.WriteStartDocument(); - writer.WriteStartElement("Manufacturers"); - writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); + helper.Writer.WriteStartDocument(); + helper.Writer.WriteStartElement("Manufacturers"); + helper.Writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); while (context.Abort == ExportAbortion.None && context.Segmenter.ReadNextSegment()) { @@ -65,24 +52,24 @@ public override void Execute(IExportExecuteContext context) if (context.Abort != ExportAbortion.None) break; - writer.WriteStartElement("Manufacturer"); + helper.Writer.WriteStartElement("Manufacturer"); try { - xmlHelper.WriteManufacturer(manufacturer, null); + helper.WriteManufacturer(manufacturer, null); - writer.WriteStartElement("ProductManufacturers"); + helper.Writer.WriteStartElement("ProductManufacturers"); foreach (dynamic productManu in manufacturer.ProductManufacturers) { - writer.WriteStartElement("ProductManufacturer"); - writer.Write("Id", ((int)productManu.Id).ToString()); - writer.Write("ProductId", ((int)productManu.ProductId).ToString()); - writer.Write("DisplayOrder", ((int)productManu.DisplayOrder).ToString()); - writer.Write("IsFeaturedProduct", ((bool)productManu.IsFeaturedProduct).ToString()); - writer.Write("ManufacturerId", ((int)productManu.ManufacturerId).ToString()); - writer.WriteEndElement(); // ProductManufacturer + helper.Writer.WriteStartElement("ProductManufacturer"); + helper.Writer.Write("Id", ((int)productManu.Id).ToString()); + helper.Writer.Write("ProductId", ((int)productManu.ProductId).ToString()); + helper.Writer.Write("DisplayOrder", ((int)productManu.DisplayOrder).ToString()); + helper.Writer.Write("IsFeaturedProduct", ((bool)productManu.IsFeaturedProduct).ToString()); + helper.Writer.Write("ManufacturerId", ((int)productManu.ManufacturerId).ToString()); + helper.Writer.WriteEndElement(); // ProductManufacturer } - writer.WriteEndElement(); // ProductManufacturers + helper.Writer.WriteEndElement(); // ProductManufacturers ++context.RecordsSucceeded; } @@ -91,12 +78,12 @@ public override void Execute(IExportExecuteContext context) context.RecordException(exc, (int)manufacturer.Id); } - writer.WriteEndElement(); // Manufacturer + helper.Writer.WriteEndElement(); // Manufacturer } } - writer.WriteEndElement(); // Manufacturers - writer.WriteEndDocument(); + helper.Writer.WriteEndElement(); // Manufacturers + helper.Writer.WriteEndDocument(); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs index 15d347847f..470155603a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs @@ -1,8 +1,6 @@ using System; using System.Globalization; using System.IO; -using System.Text; -using System.Xml; using SmartStore.Core; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Logging; @@ -35,27 +33,17 @@ public override string FileExtension public override void Execute(IExportExecuteContext context) { - var settings = new XmlWriterSettings - { - Encoding = Encoding.UTF8, - CheckCharacters = false, - Indent = true, - IndentChars = "\t" - }; - var path = context.FilePath; var invariantCulture = CultureInfo.InvariantCulture; context.Log.Information("Creating file " + path); using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var writer = XmlWriter.Create(stream, settings)) + using (var helper = new ExportXmlHelper(stream)) { - var xmlHelper = new ExportXmlHelper(writer, invariantCulture); - - writer.WriteStartDocument(); - writer.WriteStartElement("Orders"); - writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); + helper.Writer.WriteStartDocument(); + helper.Writer.WriteStartElement("Orders"); + helper.Writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); while (context.Abort == ExportAbortion.None && context.Segmenter.ReadNextSegment()) { @@ -66,7 +54,7 @@ public override void Execute(IExportExecuteContext context) if (context.Abort != ExportAbortion.None) break; - writer.WriteStartElement("Order"); + helper.Writer.WriteStartElement("Order"); try { @@ -75,170 +63,170 @@ public override void Execute(IExportExecuteContext context) DateTime? paidDateUtc = order.PaidDateUtc; int? rewardPointsRemaining = order.RewardPointsRemaining; - writer.Write("Id", ((int)order.Id).ToString()); - writer.Write("OrderNumber", (string)order.OrderNumber); - writer.Write("OrderGuid", ((Guid)order.OrderGuid).ToString()); - writer.Write("StoreId", ((int)order.StoreId).ToString()); - writer.Write("CustomerId", ((int)order.CustomerId).ToString()); - writer.Write("BillingAddressId", ((int)order.BillingAddressId).ToString()); - writer.Write("ShippingAddressId", shippingAddressId.HasValue ? shippingAddressId.Value.ToString() : ""); - writer.Write("OrderStatusId", ((int)order.OrderStatusId).ToString()); - writer.Write("ShippingStatusId", ((int)order.ShippingStatusId).ToString()); - writer.Write("PaymentStatusId", ((int)order.PaymentStatusId).ToString()); - writer.Write("PaymentMethodSystemName", (string)order.PaymentMethodSystemName); - writer.Write("CustomerCurrencyCode", (string)order.CustomerCurrencyCode); - writer.Write("CurrencyRate", ((decimal)order.CurrencyRate).ToString(invariantCulture)); - writer.Write("CustomerTaxDisplayTypeId", ((int)order.CustomerTaxDisplayTypeId).ToString()); - writer.Write("VatNumber", (string)order.VatNumber); - writer.Write("OrderSubtotalInclTax", ((decimal)order.OrderSubtotalInclTax).ToString(invariantCulture)); - writer.Write("OrderSubtotalExclTax", ((decimal)order.OrderSubtotalExclTax).ToString(invariantCulture)); - writer.Write("OrderSubTotalDiscountInclTax", ((decimal)order.OrderSubTotalDiscountInclTax).ToString(invariantCulture)); - writer.Write("OrderSubTotalDiscountExclTax", ((decimal)order.OrderSubTotalDiscountExclTax).ToString(invariantCulture)); - writer.Write("OrderShippingInclTax", ((decimal)order.OrderShippingInclTax).ToString(invariantCulture)); - writer.Write("OrderShippingExclTax", ((decimal)order.OrderShippingExclTax).ToString(invariantCulture)); - writer.Write("OrderShippingTaxRate", ((decimal)order.OrderShippingTaxRate).ToString(invariantCulture)); - writer.Write("PaymentMethodAdditionalFeeInclTax", ((decimal)order.PaymentMethodAdditionalFeeInclTax).ToString(invariantCulture)); - writer.Write("PaymentMethodAdditionalFeeExclTax", ((decimal)order.PaymentMethodAdditionalFeeExclTax).ToString(invariantCulture)); - writer.Write("PaymentMethodAdditionalFeeTaxRate", ((decimal)order.PaymentMethodAdditionalFeeTaxRate).ToString(invariantCulture)); - writer.Write("TaxRates", (string)order.TaxRates); - writer.Write("OrderTax", ((decimal)order.OrderTax).ToString(invariantCulture)); - writer.Write("OrderDiscount", ((decimal)order.OrderDiscount).ToString(invariantCulture)); - writer.Write("OrderTotal", ((decimal)order.OrderTotal).ToString(invariantCulture)); - writer.Write("RefundedAmount", ((decimal)order.RefundedAmount).ToString(invariantCulture)); - writer.Write("RewardPointsWereAdded", ((bool)order.RewardPointsWereAdded).ToString()); - writer.Write("CheckoutAttributeDescription", (string)order.CheckoutAttributeDescription); - writer.Write("CheckoutAttributesXml", (string)order.CheckoutAttributesXml); - writer.Write("CustomerLanguageId", ((int)order.CustomerLanguageId).ToString()); - writer.Write("AffiliateId", ((int)order.AffiliateId).ToString()); - writer.Write("CustomerIp", (string)order.CustomerIp); - writer.Write("AllowStoringCreditCardNumber", ((bool)order.AllowStoringCreditCardNumber).ToString()); - writer.Write("CardType", (string)order.CardType); - writer.Write("CardName", (string)order.CardName); - writer.Write("CardNumber", (string)order.CardNumber); - writer.Write("MaskedCreditCardNumber", (string)order.MaskedCreditCardNumber); - writer.Write("CardCvv2", (string)order.CardCvv2); - writer.Write("CardExpirationMonth", (string)order.CardExpirationMonth); - writer.Write("CardExpirationYear", (string)order.CardExpirationYear); - writer.Write("AllowStoringDirectDebit", ((bool)order.AllowStoringDirectDebit).ToString()); - writer.Write("DirectDebitAccountHolder", (string)order.DirectDebitAccountHolder); - writer.Write("DirectDebitAccountNumber", (string)order.DirectDebitAccountNumber); - writer.Write("DirectDebitBankCode", (string)order.DirectDebitBankCode); - writer.Write("DirectDebitBankName", (string)order.DirectDebitBankName); - writer.Write("DirectDebitBIC", (string)order.DirectDebitBIC); - writer.Write("DirectDebitCountry", (string)order.DirectDebitCountry); - writer.Write("DirectDebitIban", (string)order.DirectDebitIban); - writer.Write("CustomerOrderComment", (string)order.CustomerOrderComment); - writer.Write("AuthorizationTransactionId", (string)order.AuthorizationTransactionId); - writer.Write("AuthorizationTransactionCode", (string)order.AuthorizationTransactionCode); - writer.Write("AuthorizationTransactionResult", (string)order.AuthorizationTransactionResult); - writer.Write("CaptureTransactionId", (string)order.CaptureTransactionId); - writer.Write("CaptureTransactionResult", (string)order.CaptureTransactionResult); - writer.Write("SubscriptionTransactionId", (string)order.SubscriptionTransactionId); - writer.Write("PurchaseOrderNumber", (string)order.PurchaseOrderNumber); - writer.Write("PaidDateUtc", paidDateUtc.HasValue ? paidDateUtc.Value.ToString(invariantCulture) : ""); - writer.Write("ShippingMethod", (string)order.ShippingMethod); - writer.Write("ShippingRateComputationMethodSystemName", (string)order.ShippingRateComputationMethodSystemName); - writer.Write("Deleted", ((bool)order.Deleted).ToString()); - writer.Write("CreatedOnUtc", ((DateTime)order.CreatedOnUtc).ToString(invariantCulture)); - writer.Write("UpdatedOnUtc", ((DateTime)order.UpdatedOnUtc).ToString(invariantCulture)); - writer.Write("RewardPointsRemaining", rewardPointsRemaining.HasValue ? rewardPointsRemaining.Value.ToString() : ""); - writer.Write("HasNewPaymentNotification", ((bool)order.HasNewPaymentNotification).ToString()); - writer.Write("OrderStatus", (string)order.OrderStatus); - writer.Write("PaymentStatus", (string)order.PaymentStatus); - writer.Write("ShippingStatus", (string)order.ShippingStatus); - - xmlHelper.WriteCustomer(order.Customer, "Customer"); - - xmlHelper.WriteAddress(order.BillingAddress, "BillingAddress"); - xmlHelper.WriteAddress(order.ShippingAddress, "ShippingAddress"); + helper.Writer.Write("Id", ((int)order.Id).ToString()); + helper.Writer.Write("OrderNumber", (string)order.OrderNumber); + helper.Writer.Write("OrderGuid", ((Guid)order.OrderGuid).ToString()); + helper.Writer.Write("StoreId", ((int)order.StoreId).ToString()); + helper.Writer.Write("CustomerId", ((int)order.CustomerId).ToString()); + helper.Writer.Write("BillingAddressId", ((int)order.BillingAddressId).ToString()); + helper.Writer.Write("ShippingAddressId", shippingAddressId.HasValue ? shippingAddressId.Value.ToString() : ""); + helper.Writer.Write("OrderStatusId", ((int)order.OrderStatusId).ToString()); + helper.Writer.Write("ShippingStatusId", ((int)order.ShippingStatusId).ToString()); + helper.Writer.Write("PaymentStatusId", ((int)order.PaymentStatusId).ToString()); + helper.Writer.Write("PaymentMethodSystemName", (string)order.PaymentMethodSystemName); + helper.Writer.Write("CustomerCurrencyCode", (string)order.CustomerCurrencyCode); + helper.Writer.Write("CurrencyRate", ((decimal)order.CurrencyRate).ToString(invariantCulture)); + helper.Writer.Write("CustomerTaxDisplayTypeId", ((int)order.CustomerTaxDisplayTypeId).ToString()); + helper.Writer.Write("VatNumber", (string)order.VatNumber); + helper.Writer.Write("OrderSubtotalInclTax", ((decimal)order.OrderSubtotalInclTax).ToString(invariantCulture)); + helper.Writer.Write("OrderSubtotalExclTax", ((decimal)order.OrderSubtotalExclTax).ToString(invariantCulture)); + helper.Writer.Write("OrderSubTotalDiscountInclTax", ((decimal)order.OrderSubTotalDiscountInclTax).ToString(invariantCulture)); + helper.Writer.Write("OrderSubTotalDiscountExclTax", ((decimal)order.OrderSubTotalDiscountExclTax).ToString(invariantCulture)); + helper.Writer.Write("OrderShippingInclTax", ((decimal)order.OrderShippingInclTax).ToString(invariantCulture)); + helper.Writer.Write("OrderShippingExclTax", ((decimal)order.OrderShippingExclTax).ToString(invariantCulture)); + helper.Writer.Write("OrderShippingTaxRate", ((decimal)order.OrderShippingTaxRate).ToString(invariantCulture)); + helper.Writer.Write("PaymentMethodAdditionalFeeInclTax", ((decimal)order.PaymentMethodAdditionalFeeInclTax).ToString(invariantCulture)); + helper.Writer.Write("PaymentMethodAdditionalFeeExclTax", ((decimal)order.PaymentMethodAdditionalFeeExclTax).ToString(invariantCulture)); + helper.Writer.Write("PaymentMethodAdditionalFeeTaxRate", ((decimal)order.PaymentMethodAdditionalFeeTaxRate).ToString(invariantCulture)); + helper.Writer.Write("TaxRates", (string)order.TaxRates); + helper.Writer.Write("OrderTax", ((decimal)order.OrderTax).ToString(invariantCulture)); + helper.Writer.Write("OrderDiscount", ((decimal)order.OrderDiscount).ToString(invariantCulture)); + helper.Writer.Write("OrderTotal", ((decimal)order.OrderTotal).ToString(invariantCulture)); + helper.Writer.Write("RefundedAmount", ((decimal)order.RefundedAmount).ToString(invariantCulture)); + helper.Writer.Write("RewardPointsWereAdded", ((bool)order.RewardPointsWereAdded).ToString()); + helper.Writer.Write("CheckoutAttributeDescription", (string)order.CheckoutAttributeDescription); + helper.Writer.Write("CheckoutAttributesXml", (string)order.CheckoutAttributesXml); + helper.Writer.Write("CustomerLanguageId", ((int)order.CustomerLanguageId).ToString()); + helper.Writer.Write("AffiliateId", ((int)order.AffiliateId).ToString()); + helper.Writer.Write("CustomerIp", (string)order.CustomerIp); + helper.Writer.Write("AllowStoringCreditCardNumber", ((bool)order.AllowStoringCreditCardNumber).ToString()); + helper.Writer.Write("CardType", (string)order.CardType); + helper.Writer.Write("CardName", (string)order.CardName); + helper.Writer.Write("CardNumber", (string)order.CardNumber); + helper.Writer.Write("MaskedCreditCardNumber", (string)order.MaskedCreditCardNumber); + helper.Writer.Write("CardCvv2", (string)order.CardCvv2); + helper.Writer.Write("CardExpirationMonth", (string)order.CardExpirationMonth); + helper.Writer.Write("CardExpirationYear", (string)order.CardExpirationYear); + helper.Writer.Write("AllowStoringDirectDebit", ((bool)order.AllowStoringDirectDebit).ToString()); + helper.Writer.Write("DirectDebitAccountHolder", (string)order.DirectDebitAccountHolder); + helper.Writer.Write("DirectDebitAccountNumber", (string)order.DirectDebitAccountNumber); + helper.Writer.Write("DirectDebitBankCode", (string)order.DirectDebitBankCode); + helper.Writer.Write("DirectDebitBankName", (string)order.DirectDebitBankName); + helper.Writer.Write("DirectDebitBIC", (string)order.DirectDebitBIC); + helper.Writer.Write("DirectDebitCountry", (string)order.DirectDebitCountry); + helper.Writer.Write("DirectDebitIban", (string)order.DirectDebitIban); + helper.Writer.Write("CustomerOrderComment", (string)order.CustomerOrderComment); + helper.Writer.Write("AuthorizationTransactionId", (string)order.AuthorizationTransactionId); + helper.Writer.Write("AuthorizationTransactionCode", (string)order.AuthorizationTransactionCode); + helper.Writer.Write("AuthorizationTransactionResult", (string)order.AuthorizationTransactionResult); + helper.Writer.Write("CaptureTransactionId", (string)order.CaptureTransactionId); + helper.Writer.Write("CaptureTransactionResult", (string)order.CaptureTransactionResult); + helper.Writer.Write("SubscriptionTransactionId", (string)order.SubscriptionTransactionId); + helper.Writer.Write("PurchaseOrderNumber", (string)order.PurchaseOrderNumber); + helper.Writer.Write("PaidDateUtc", paidDateUtc.HasValue ? paidDateUtc.Value.ToString(invariantCulture) : ""); + helper.Writer.Write("ShippingMethod", (string)order.ShippingMethod); + helper.Writer.Write("ShippingRateComputationMethodSystemName", (string)order.ShippingRateComputationMethodSystemName); + helper.Writer.Write("Deleted", ((bool)order.Deleted).ToString()); + helper.Writer.Write("CreatedOnUtc", ((DateTime)order.CreatedOnUtc).ToString(invariantCulture)); + helper.Writer.Write("UpdatedOnUtc", ((DateTime)order.UpdatedOnUtc).ToString(invariantCulture)); + helper.Writer.Write("RewardPointsRemaining", rewardPointsRemaining.HasValue ? rewardPointsRemaining.Value.ToString() : ""); + helper.Writer.Write("HasNewPaymentNotification", ((bool)order.HasNewPaymentNotification).ToString()); + helper.Writer.Write("OrderStatus", (string)order.OrderStatus); + helper.Writer.Write("PaymentStatus", (string)order.PaymentStatus); + helper.Writer.Write("ShippingStatus", (string)order.ShippingStatus); + + helper.WriteCustomer(order.Customer, "Customer"); + + helper.WriteAddress(order.BillingAddress, "BillingAddress"); + helper.WriteAddress(order.ShippingAddress, "ShippingAddress"); if (store != null) { - writer.WriteStartElement("Store"); - writer.Write("Id", ((int)store.Id).ToString()); - writer.Write("Name", (string)store.Name); - writer.Write("Url", (string)store.Url); - writer.Write("SslEnabled", ((bool)store.SslEnabled).ToString()); - writer.Write("SecureUrl", (string)store.SecureUrl); - writer.Write("Hosts", (string)store.Hosts); - writer.Write("LogoPictureId", ((int)store.LogoPictureId).ToString()); - writer.Write("DisplayOrder", ((int)store.DisplayOrder).ToString()); - writer.Write("HtmlBodyId", (string)store.HtmlBodyId); - writer.Write("ContentDeliveryNetwork", (string)store.ContentDeliveryNetwork); - writer.Write("PrimaryStoreCurrencyId", ((int)store.PrimaryStoreCurrencyId).ToString()); - writer.Write("PrimaryExchangeRateCurrencyId", ((int)store.PrimaryExchangeRateCurrencyId).ToString()); - - xmlHelper.WriteCurrency(store.PrimaryStoreCurrency, "PrimaryStoreCurrency"); - xmlHelper.WriteCurrency(store.PrimaryExchangeRateCurrency, "PrimaryExchangeRateCurrency"); - - writer.WriteEndElement(); // Store + helper.Writer.WriteStartElement("Store"); + helper.Writer.Write("Id", ((int)store.Id).ToString()); + helper.Writer.Write("Name", (string)store.Name); + helper.Writer.Write("Url", (string)store.Url); + helper.Writer.Write("SslEnabled", ((bool)store.SslEnabled).ToString()); + helper.Writer.Write("SecureUrl", (string)store.SecureUrl); + helper.Writer.Write("Hosts", (string)store.Hosts); + helper.Writer.Write("LogoPictureId", ((int)store.LogoPictureId).ToString()); + helper.Writer.Write("DisplayOrder", ((int)store.DisplayOrder).ToString()); + helper.Writer.Write("HtmlBodyId", (string)store.HtmlBodyId); + helper.Writer.Write("ContentDeliveryNetwork", (string)store.ContentDeliveryNetwork); + helper.Writer.Write("PrimaryStoreCurrencyId", ((int)store.PrimaryStoreCurrencyId).ToString()); + helper.Writer.Write("PrimaryExchangeRateCurrencyId", ((int)store.PrimaryExchangeRateCurrencyId).ToString()); + + helper.WriteCurrency(store.PrimaryStoreCurrency, "PrimaryStoreCurrency"); + helper.WriteCurrency(store.PrimaryExchangeRateCurrency, "PrimaryExchangeRateCurrency"); + + helper.Writer.WriteEndElement(); // Store } - writer.WriteStartElement("OrderItems"); + helper.Writer.WriteStartElement("OrderItems"); foreach (dynamic orderItem in order.OrderItems) { int? licenseDownloadId = orderItem.LicenseDownloadId; decimal? itemWeight = orderItem.ItemWeight; - writer.WriteStartElement("OrderItem"); - writer.Write("Id", ((int)orderItem.Id).ToString()); - writer.Write("OrderItemGuid", ((Guid)orderItem.OrderItemGuid).ToString()); - writer.Write("OrderId", ((int)orderItem.OrderId).ToString()); - writer.Write("ProductId", ((int)orderItem.ProductId).ToString()); - writer.Write("Quantity", ((int)orderItem.Quantity).ToString()); - writer.Write("UnitPriceInclTax", ((decimal)orderItem.UnitPriceInclTax).ToString(invariantCulture)); - writer.Write("UnitPriceExclTax", ((decimal)orderItem.UnitPriceExclTax).ToString(invariantCulture)); - writer.Write("PriceInclTax", ((decimal)orderItem.PriceInclTax).ToString(invariantCulture)); - writer.Write("PriceExclTax", ((decimal)orderItem.PriceExclTax).ToString(invariantCulture)); - writer.Write("TaxRate", ((decimal)orderItem.TaxRate).ToString(invariantCulture)); - writer.Write("DiscountAmountInclTax", ((decimal)orderItem.DiscountAmountInclTax).ToString(invariantCulture)); - writer.Write("DiscountAmountExclTax", ((decimal)orderItem.DiscountAmountExclTax).ToString(invariantCulture)); - writer.Write("AttributeDescription", (string)orderItem.AttributeDescription); - writer.Write("AttributesXml", (string)orderItem.AttributesXml); - writer.Write("DownloadCount", ((int)orderItem.DownloadCount).ToString()); - writer.Write("IsDownloadActivated", ((bool)orderItem.IsDownloadActivated).ToString()); - writer.Write("LicenseDownloadId", licenseDownloadId.HasValue ? licenseDownloadId.Value.ToString() : ""); - writer.Write("ItemWeight", itemWeight.HasValue ? itemWeight.Value.ToString(invariantCulture) : ""); - writer.Write("BundleData", (string)orderItem.BundleData); - writer.Write("ProductCost", ((decimal)orderItem.ProductCost).ToString(invariantCulture)); - - xmlHelper.WriteProduct(orderItem.Product, "Product"); - - writer.WriteEndElement(); // OrderItem + helper.Writer.WriteStartElement("OrderItem"); + helper.Writer.Write("Id", ((int)orderItem.Id).ToString()); + helper.Writer.Write("OrderItemGuid", ((Guid)orderItem.OrderItemGuid).ToString()); + helper.Writer.Write("OrderId", ((int)orderItem.OrderId).ToString()); + helper.Writer.Write("ProductId", ((int)orderItem.ProductId).ToString()); + helper.Writer.Write("Quantity", ((int)orderItem.Quantity).ToString()); + helper.Writer.Write("UnitPriceInclTax", ((decimal)orderItem.UnitPriceInclTax).ToString(invariantCulture)); + helper.Writer.Write("UnitPriceExclTax", ((decimal)orderItem.UnitPriceExclTax).ToString(invariantCulture)); + helper.Writer.Write("PriceInclTax", ((decimal)orderItem.PriceInclTax).ToString(invariantCulture)); + helper.Writer.Write("PriceExclTax", ((decimal)orderItem.PriceExclTax).ToString(invariantCulture)); + helper.Writer.Write("TaxRate", ((decimal)orderItem.TaxRate).ToString(invariantCulture)); + helper.Writer.Write("DiscountAmountInclTax", ((decimal)orderItem.DiscountAmountInclTax).ToString(invariantCulture)); + helper.Writer.Write("DiscountAmountExclTax", ((decimal)orderItem.DiscountAmountExclTax).ToString(invariantCulture)); + helper.Writer.Write("AttributeDescription", (string)orderItem.AttributeDescription); + helper.Writer.Write("AttributesXml", (string)orderItem.AttributesXml); + helper.Writer.Write("DownloadCount", ((int)orderItem.DownloadCount).ToString()); + helper.Writer.Write("IsDownloadActivated", ((bool)orderItem.IsDownloadActivated).ToString()); + helper.Writer.Write("LicenseDownloadId", licenseDownloadId.HasValue ? licenseDownloadId.Value.ToString() : ""); + helper.Writer.Write("ItemWeight", itemWeight.HasValue ? itemWeight.Value.ToString(invariantCulture) : ""); + helper.Writer.Write("BundleData", (string)orderItem.BundleData); + helper.Writer.Write("ProductCost", ((decimal)orderItem.ProductCost).ToString(invariantCulture)); + + helper.WriteProduct(orderItem.Product, "Product"); + + helper.Writer.WriteEndElement(); // OrderItem } - writer.WriteEndElement(); // OrderItems + helper.Writer.WriteEndElement(); // OrderItems - writer.WriteStartElement("Shipments"); + helper.Writer.WriteStartElement("Shipments"); foreach (dynamic shipment in order.Shipments) { decimal? totalWeight = shipment.TotalWeight; DateTime? shippedDateUtc = shipment.ShippedDateUtc; DateTime? deliveryDateUtc = shipment.DeliveryDateUtc; - writer.WriteStartElement("Shipment"); - writer.Write("Id", ((int)shipment.Id).ToString()); - writer.Write("OrderId", ((int)shipment.OrderId).ToString()); - writer.Write("TrackingNumber", (string)shipment.TrackingNumber); - writer.Write("TotalWeight", totalWeight.HasValue ? totalWeight.Value.ToString(invariantCulture) : ""); - writer.Write("ShippedDateUtc", shippedDateUtc.HasValue ? shippedDateUtc.Value.ToString(invariantCulture) : ""); - writer.Write("DeliveryDateUtc", deliveryDateUtc.HasValue ? deliveryDateUtc.Value.ToString(invariantCulture) : ""); - writer.Write("CreatedOnUtc", ((DateTime)shipment.CreatedOnUtc).ToString(invariantCulture)); + helper.Writer.WriteStartElement("Shipment"); + helper.Writer.Write("Id", ((int)shipment.Id).ToString()); + helper.Writer.Write("OrderId", ((int)shipment.OrderId).ToString()); + helper.Writer.Write("TrackingNumber", (string)shipment.TrackingNumber); + helper.Writer.Write("TotalWeight", totalWeight.HasValue ? totalWeight.Value.ToString(invariantCulture) : ""); + helper.Writer.Write("ShippedDateUtc", shippedDateUtc.HasValue ? shippedDateUtc.Value.ToString(invariantCulture) : ""); + helper.Writer.Write("DeliveryDateUtc", deliveryDateUtc.HasValue ? deliveryDateUtc.Value.ToString(invariantCulture) : ""); + helper.Writer.Write("CreatedOnUtc", ((DateTime)shipment.CreatedOnUtc).ToString(invariantCulture)); - writer.WriteStartElement("ShipmentItems"); + helper.Writer.WriteStartElement("ShipmentItems"); foreach (dynamic shipmentItem in shipment.ShipmentItems) { - writer.WriteStartElement("ShipmentItem"); - writer.Write("Id", ((int)shipmentItem.Id).ToString()); - writer.Write("ShipmentId", ((int)shipmentItem.ShipmentId).ToString()); - writer.Write("OrderItemId", ((int)shipmentItem.OrderItemId).ToString()); - writer.Write("Quantity", ((int)shipmentItem.Quantity).ToString()); - writer.WriteEndElement(); // ShipmentItem + helper.Writer.WriteStartElement("ShipmentItem"); + helper.Writer.Write("Id", ((int)shipmentItem.Id).ToString()); + helper.Writer.Write("ShipmentId", ((int)shipmentItem.ShipmentId).ToString()); + helper.Writer.Write("OrderItemId", ((int)shipmentItem.OrderItemId).ToString()); + helper.Writer.Write("Quantity", ((int)shipmentItem.Quantity).ToString()); + helper.Writer.WriteEndElement(); // ShipmentItem } - writer.WriteEndElement(); // ShipmentItems + helper.Writer.WriteEndElement(); // ShipmentItems - writer.WriteEndElement(); // Shipment + helper.Writer.WriteEndElement(); // Shipment } - writer.WriteEndElement(); // Shipments + helper.Writer.WriteEndElement(); // Shipments ++context.RecordsSucceeded; } @@ -247,12 +235,12 @@ public override void Execute(IExportExecuteContext context) context.RecordException(exc, (int)order.Id); } - writer.WriteEndElement(); // Order + helper.Writer.WriteEndElement(); // Order } } - writer.WriteEndElement(); // Orders - writer.WriteEndDocument(); + helper.Writer.WriteEndElement(); // Orders + helper.Writer.WriteEndDocument(); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs index e4154f4804..a3480bdb3a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs @@ -1,8 +1,5 @@ using System; -using System.Globalization; using System.IO; -using System.Text; -using System.Xml; using SmartStore.Core; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Logging; @@ -35,26 +32,16 @@ public override string FileExtension public override void Execute(IExportExecuteContext context) { - var settings = new XmlWriterSettings - { - Encoding = Encoding.UTF8, - CheckCharacters = false, - Indent = true, - IndentChars = "\t" - }; - var path = context.FilePath; context.Log.Information("Creating file " + path); using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var writer = XmlWriter.Create(stream, settings)) + using (var helper = new ExportXmlHelper(stream)) { - var xmlHelper = new ExportXmlHelper(writer, CultureInfo.InvariantCulture); - - writer.WriteStartDocument(); - writer.WriteStartElement("Products"); - writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); + helper.Writer.WriteStartDocument(); + helper.Writer.WriteStartElement("Products"); + helper.Writer.WriteAttributeString("Version", SmartStoreVersion.CurrentVersion); while (context.Abort == ExportAbortion.None && context.Segmenter.ReadNextSegment()) { @@ -67,7 +54,7 @@ public override void Execute(IExportExecuteContext context) try { - xmlHelper.WriteProduct(product, "Product"); + helper.WriteProduct(product, "Product"); ++context.RecordsSucceeded; } @@ -78,8 +65,8 @@ public override void Execute(IExportExecuteContext context) } } - writer.WriteEndElement(); // Products - writer.WriteEndDocument(); + helper.Writer.WriteEndElement(); // Products + helper.Writer.WriteEndDocument(); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 984f3641a1..29c4c040b9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Net.Mime; +using System.Text; using System.Web; using System.Web.Mvc; using Autofac; @@ -878,7 +879,7 @@ public ActionResult DownloadLogFile(int id) var path = profile.GetExportLogFilePath(); var stream = new FileStream(path, FileMode.Open); - var result = new FileStreamResult(stream, MediaTypeNames.Text.Plain); + var result = new FileStreamResult(stream, "text/plain; charset=utf-8"); result.FileDownloadName = profile.Name.ToValidFileName() + "-log.txt"; return result; From 451dd5e9460a20fc8e9e010bcf9bce4a510748a2 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 8 Nov 2015 18:58:53 +0100 Subject: [PATCH 186/874] Export framework rather than provider should create export data files --- .../Extensions/XmlWriterExtensions.cs | 10 -- .../DataExchange/ExportExecuteContext.cs | 54 ++++++-- .../ExportTask/ExportProfileTask.cs | 122 +++++++++++++----- .../DataExchange/ExportXmlHelper.cs | 16 ++- .../Providers/CategoryXmlExportProvider.cs | 9 +- .../Providers/CustomerXlsxExportProvider.cs | 8 +- .../Providers/CustomerXmlExportProvider.cs | 9 +- .../ManufacturerXmlExportProvider.cs | 9 +- .../Providers/OrderXlsxExportProvider.cs | 9 +- .../Providers/OrderXmlExportProvider.cs | 8 +- .../Providers/ProductXlsxExportProvider.cs | 9 +- .../Providers/ProductXmlExportProvider.cs | 9 +- .../Providers/SubscriberCsvExportProvider.cs | 8 +- .../Providers/GmcXmlExportProvider.cs | 14 +- 14 files changed, 153 insertions(+), 141 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Extensions/XmlWriterExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/XmlWriterExtensions.cs index 5ead943146..ca4e1c2c71 100644 --- a/src/Libraries/SmartStore.Core/Extensions/XmlWriterExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/XmlWriterExtensions.cs @@ -21,16 +21,6 @@ public static void WriteCData(this XmlWriter writer, string name, string value, } } - public static void WriteNode(this XmlWriter writer, string name, Action content) - { - if (name.HasValue() && content != null) - { - writer.WriteStartElement(name); - content(); - writer.WriteEndElement(); - } - } - /// /// Created a simple or CData node element /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs index 17ae53a8bf..d4a6286570 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Logging; @@ -50,14 +51,25 @@ public interface IExportExecuteContext /// - /// The maximum allowed file name length + /// Identifier of current data stream. Can be null. /// - int MaxFileNameLength { get; } + string DataStreamId { get; set; } /// - /// The path of the export content folder + /// Stream used to write data to /// - string Folder { get; } + Stream DataStream { get; } + + /// + /// List with extra data streams required by provider + /// + List ExtraDataStreams { get; set; } + + + /// + /// The maximum allowed file name length + /// + int MaxFileNameLength { get; } /// /// The name of the current export file @@ -65,9 +77,9 @@ public interface IExportExecuteContext string FileName { get; } /// - /// The path of the current export file + /// The path of the export content folder /// - string FilePath { get; } + string Folder { get; } /// @@ -114,6 +126,25 @@ public interface IExportExecuteContext } + public class ExportExtraStreams + { + /// + /// Your Id to identify this stream within a list of streams + /// + public string Id { get; set; } + + /// + /// Stream used to write data to + /// + public Stream DataStream { get; internal set; } + + /// + /// The name of the file to be created + /// + public string FileName { get; set; } + } + + public class ExportExecuteContext : IExportExecuteContext { private DataExportResult _result; @@ -125,7 +156,7 @@ internal ExportExecuteContext(DataExportResult result, CancellationToken cancell _result = result; _cancellation = cancellation; Folder = folder; - + ExtraDataStreams = new List(); CustomProperties = new Dictionary(); } @@ -159,12 +190,13 @@ public bool IsMaxFailures get { return RecordsFailed > 11; } } - public int MaxFileNameLength { get; internal set; } + public string DataStreamId { get; set; } + public Stream DataStream { get; internal set; } + public List ExtraDataStreams { get; set; } - public string Folder { get; private set; } + public int MaxFileNameLength { get; internal set; } public string FileName { get; internal set; } - public string FileExtension { get; internal set; } - public string FilePath { get; internal set; } + public string Folder { get; private set; } public bool HasPublicDeployment { get; internal set; } public string PublicFolderPath { get; internal set; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 1a52e264ea..8218acffb0 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -133,7 +133,7 @@ private void InitDependencies(TaskExecutionContext context) _genericAttributeService = context.Resolve>(); _subscriptionRepository = context.Resolve>>(); - T = NullLocalizer.Instance; + T = NullLocalizer.Instance; // TODO: resolve } public Localizer T { get; set; } @@ -316,6 +316,61 @@ private IExportDataSegmenterProvider CreateSegmenter(ExportProfileTaskContext ct return ctx.ExecuteContext.Segmenter as IExportDataSegmenterProvider; } + private bool CallProvider(ExportProfileTaskContext ctx, string streamId, string method, string path) + { + if (method != "Execute" && method != "OnExecuted") + throw new SmartException("Unknown export method {0}".FormatInvariant(method.NaIfEmpty())); + + try + { + ctx.ExecuteContext.DataStreamId = streamId; + + using (ctx.ExecuteContext.DataStream = new MemoryStream()) + { + if (method == "Execute") + { + ctx.Provider.Value.Execute(ctx.ExecuteContext); + } + else if (method == "OnExecuted") + { + ctx.Provider.Value.OnExecuted(ctx.ExecuteContext); + } + + if (ctx.IsFileBasedExport && path.HasValue()) + { + if (!ctx.ExecuteContext.DataStream.CanSeek) + ctx.Log.Warning("Data stream seems to be closed!"); + + ctx.ExecuteContext.DataStream.Seek(0, SeekOrigin.Begin); + + using (_rwLock.GetWriteLock()) + using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + { + ctx.Log.Information("Creating file " + path); + + ctx.ExecuteContext.DataStream.CopyTo(fileStream); + } + } + } + } + catch (Exception exc) + { + ctx.ExecuteContext.Abort = ExportAbortion.Hard; + ctx.Log.Error("The provider failed at the {0} method: {1}".FormatInvariant(method, exc.ToAllMessages()), exc); + ctx.Result.LastError = exc.ToString(); + } + finally + { + if (ctx.ExecuteContext.DataStream != null) + { + ctx.ExecuteContext.DataStream.Dispose(); + ctx.ExecuteContext.DataStream = null; + } + } + + return (ctx.ExecuteContext.Abort != ExportAbortion.Hard); + } + private void PrepareProductDescription(ExportProfileTaskContext ctx, dynamic expando, Product product) { try @@ -2465,12 +2520,12 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) ctx.ExecuteContext.MaxFileNameLength = _dataExchangeSettings.Value.MaxFileNameLength; - ctx.ExecuteContext.FileExtension = (ctx.Provider.Value.FileExtension.HasValue() ? ctx.Provider.Value.FileExtension.ToLower().EnsureStartsWith(".") : ""); - ctx.ExecuteContext.HasPublicDeployment = ctx.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.Provider.Value.FileExtension.HasValue() ? ctx.Provider.Value.FileExtension.ToLower().EnsureStartsWith(".") : ""); + using (var segmenter = CreateSegmenter(ctx)) { @@ -2489,42 +2544,32 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) segmenter.RecordPerSegmentCount = 0; ctx.ExecuteContext.RecordsSucceeded = 0; - try + string path = null; + + if (ctx.IsFileBasedExport) { - if (ctx.IsFileBasedExport) - { - var resolvedPattern = ctx.Profile.ResolveFileNamePattern(ctx.Store, ++fileIndex, ctx.ExecuteContext.MaxFileNameLength); + var resolvedPattern = ctx.Profile.ResolveFileNamePattern(ctx.Store, ++fileIndex, ctx.ExecuteContext.MaxFileNameLength); - ctx.ExecuteContext.FileName = resolvedPattern + ctx.ExecuteContext.FileExtension; - ctx.ExecuteContext.FilePath = Path.Combine(ctx.ExecuteContext.Folder, ctx.ExecuteContext.FileName); + 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 (ctx.ExecuteContext.HasPublicDeployment) + ctx.ExecuteContext.PublicFileUrl = ctx.Store.Url.EnsureEndsWith("/") + PublicFolder.EnsureEndsWith("/") + ctx.ExecuteContext.FileName; + } - ctx.Provider.Value.Execute(ctx.ExecuteContext); + if (CallProvider(ctx, null, "Execute", path)) + { + ctx.Log.Information("Provider reports {0} successful exported record(s)".FormatInvariant(ctx.ExecuteContext.RecordsSucceeded)); - // create info for deployment list in profile edit - if (File.Exists(ctx.ExecuteContext.FilePath)) - { - ctx.Result.Files.Add(new DataExportResult.ExportFileInfo - { - StoreId = ctx.Store.Id, - FileName = ctx.ExecuteContext.FileName - }); - } - } - else + // create info for deployment list in profile edit + if (ctx.IsFileBasedExport) { - ctx.Provider.Value.Execute(ctx.ExecuteContext); + ctx.Result.Files.Add(new DataExportResult.ExportFileInfo + { + StoreId = ctx.Store.Id, + FileName = ctx.ExecuteContext.FileName + }); } - - ctx.Log.Information("Provider reports {0} successful exported record(s)".FormatInvariant(ctx.ExecuteContext.RecordsSucceeded)); - } - catch (Exception exc) - { - ctx.ExecuteContext.Abort = ExportAbortion.Hard; - ctx.Log.Error("The provider failed to execute the export: " + exc.ToAllMessages(), exc); - ctx.Result.LastError = exc.ToString(); } if (ctx.ExecuteContext.IsMaxFailures) @@ -2536,7 +2581,17 @@ private void ExportCoreInner(ExportProfileTaskContext ctx, Store store) if (ctx.ExecuteContext.Abort != ExportAbortion.Hard) { - ctx.Provider.Value.OnExecuted(ctx.ExecuteContext); + if (ctx.ExecuteContext.ExtraDataStreams.Count == 0) + ctx.ExecuteContext.ExtraDataStreams.Add(new ExportExtraStreams()); + + ctx.ExecuteContext.ExtraDataStreams.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(); } } } @@ -2550,7 +2605,6 @@ private void ExportCoreOuter(ExportProfileTaskContext ctx) FileSystemHelper.ClearDirectory(ctx.FolderContent, false); FileSystemHelper.Delete(ctx.ZipPath); - using (_rwLock.GetWriteLock()) using (var logger = new TraceLogger(ctx.LogPath)) { try diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs index e5837578d6..d58c724eac 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs @@ -24,7 +24,18 @@ public ExportXmlHelper(Stream stream, XmlWriterSettings settings = null, Culture { if (settings == null) { - settings = new XmlWriterSettings + settings = DefaultSettings; + } + + _writer = XmlWriter.Create(stream, settings); + _culture = (culture == null ? CultureInfo.InvariantCulture : culture); + } + + public static XmlWriterSettings DefaultSettings + { + get + { + return new XmlWriterSettings { Encoding = Encoding.UTF8, CheckCharacters = false, @@ -32,9 +43,6 @@ public ExportXmlHelper(Stream stream, XmlWriterSettings settings = null, Culture IndentChars = "\t" }; } - - _writer = XmlWriter.Create(stream, settings); - _culture = (culture == null ? CultureInfo.InvariantCulture : culture); } public XmlWriter Writer diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs index 2b74231730..1aecac8667 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs @@ -1,8 +1,6 @@ using System; -using System.IO; using SmartStore.Core; using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Logging; using SmartStore.Core.Plugins; namespace SmartStore.Services.DataExchange.Providers @@ -32,12 +30,7 @@ public override string FileExtension public override void Execute(IExportExecuteContext context) { - var path = context.FilePath; - - context.Log.Information("Creating file " + path); - - using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var helper = new ExportXmlHelper(stream)) + using (var helper = new ExportXmlHelper(context.DataStream)) { helper.Writer.WriteStartDocument(); helper.Writer.WriteStartElement("Categories"); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXlsxExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXlsxExportProvider.cs index 4ab09bd5c5..72bda50969 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXlsxExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXlsxExportProvider.cs @@ -1,12 +1,10 @@ using System; using System.Drawing; using System.Globalization; -using System.IO; using OfficeOpenXml; using OfficeOpenXml.Style; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Customers; @@ -110,12 +108,8 @@ public override string FileExtension public override void Execute(IExportExecuteContext context) { var invariantCulture = CultureInfo.InvariantCulture; - var path = context.FilePath; - context.Log.Information("Creating file " + path); - - using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var xlPackage = new ExcelPackage(stream)) + using (var xlPackage = new ExcelPackage(context.DataStream)) { // uncomment this line if you want the XML written out to the outputDir //xlPackage.DebugMode = true; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs index 4103b412a5..4963380787 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs @@ -1,8 +1,6 @@ using System; -using System.IO; using SmartStore.Core; using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Logging; using SmartStore.Core.Plugins; namespace SmartStore.Services.DataExchange.Providers @@ -32,12 +30,7 @@ public override string FileExtension public override void Execute(IExportExecuteContext context) { - var path = context.FilePath; - - context.Log.Information("Creating file " + path); - - using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var helper = new ExportXmlHelper(stream)) + using (var helper = new ExportXmlHelper(context.DataStream)) { helper.Writer.WriteStartDocument(); helper.Writer.WriteStartElement("Customers"); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs index 3c5d6afa53..af672aeed8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs @@ -1,8 +1,6 @@ using System; -using System.IO; using SmartStore.Core; using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Logging; using SmartStore.Core.Plugins; namespace SmartStore.Services.DataExchange.Providers @@ -32,12 +30,7 @@ public override string FileExtension public override void Execute(IExportExecuteContext context) { - var path = context.FilePath; - - context.Log.Information("Creating file " + path); - - using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var helper = new ExportXmlHelper(stream)) + using (var helper = new ExportXmlHelper(context.DataStream)) { helper.Writer.WriteStartDocument(); helper.Writer.WriteStartElement("Manufacturers"); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXlsxExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXlsxExportProvider.cs index ae6ce4271d..0520409914 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXlsxExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXlsxExportProvider.cs @@ -1,10 +1,8 @@ using System; using System.Drawing; -using System.IO; using OfficeOpenXml; using OfficeOpenXml.Style; using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Logging; using SmartStore.Core.Plugins; namespace SmartStore.Services.DataExchange.Providers @@ -108,12 +106,7 @@ public override string FileExtension public override void Execute(IExportExecuteContext context) { - var path = context.FilePath; - - context.Log.Information("Creating file " + path); - - using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var xlPackage = new ExcelPackage(stream)) + using (var xlPackage = new ExcelPackage(context.DataStream)) { // uncomment this line if you want the XML written out to the outputDir //xlPackage.DebugMode = true; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs index 470155603a..366dcd15d0 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs @@ -1,9 +1,7 @@ using System; using System.Globalization; -using System.IO; using SmartStore.Core; using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Logging; using SmartStore.Core.Plugins; namespace SmartStore.Services.DataExchange.Providers @@ -33,13 +31,9 @@ public override string FileExtension public override void Execute(IExportExecuteContext context) { - var path = context.FilePath; var invariantCulture = CultureInfo.InvariantCulture; - context.Log.Information("Creating file " + path); - - using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var helper = new ExportXmlHelper(stream)) + using (var helper = new ExportXmlHelper(context.DataStream)) { helper.Writer.WriteStartDocument(); helper.Writer.WriteStartElement("Orders"); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXlsxExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXlsxExportProvider.cs index 3da13558c0..d2e877b7f8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXlsxExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXlsxExportProvider.cs @@ -1,14 +1,12 @@ using System; using System.Collections.Generic; using System.Drawing; -using System.IO; using System.Linq; using OfficeOpenXml; using OfficeOpenXml.Style; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Localization; -using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Catalog; using SmartStore.Services.Localization; @@ -177,12 +175,7 @@ public override string FileExtension public override void Execute(IExportExecuteContext context) { - var path = context.FilePath; - - context.Log.Information("Creating file " + path); - - using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var xlPackage = new ExcelPackage(stream)) + using (var xlPackage = new ExcelPackage(context.DataStream)) { // uncomment this line if you want the XML written out to the outputDir //xlPackage.DebugMode = true; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs index a3480bdb3a..63d5dc4122 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs @@ -1,8 +1,6 @@ using System; -using System.IO; using SmartStore.Core; using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Logging; using SmartStore.Core.Plugins; namespace SmartStore.Services.DataExchange.Providers @@ -32,12 +30,7 @@ public override string FileExtension public override void Execute(IExportExecuteContext context) { - var path = context.FilePath; - - context.Log.Information("Creating file " + path); - - using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var helper = new ExportXmlHelper(stream)) + using (var helper = new ExportXmlHelper(context.DataStream)) { helper.Writer.WriteStartDocument(); helper.Writer.WriteStartElement("Products"); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/SubscriberCsvExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/SubscriberCsvExportProvider.cs index 2a7e299b51..be904fa6c7 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/SubscriberCsvExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/SubscriberCsvExportProvider.cs @@ -2,7 +2,6 @@ using System.IO; using System.Text; using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Logging; using SmartStore.Core.Plugins; namespace SmartStore.Services.DataExchange.Providers @@ -32,12 +31,7 @@ public override string FileExtension public override void Execute(IExportExecuteContext context) { - var path = context.FilePath; - - context.Log.Information("Creating file " + path); - - using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var writer = new StreamWriter(stream, Encoding.UTF8)) + using (var writer = new StreamWriter(context.DataStream, Encoding.UTF8, 1024, true)) { while (context.Abort == ExportAbortion.None && context.Segmenter.ReadNextSegment()) { diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index b1a68d4591..a58b4b98b8 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -1,7 +1,5 @@ using System; -using System.IO; using System.Linq; -using System.Text; using System.Xml; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.DataExchange; @@ -157,13 +155,6 @@ public override string FileExtension public override void Execute(IExportExecuteContext context) { - var settings = new XmlWriterSettings - { - Encoding = Encoding.UTF8, - CheckCharacters = false - }; - - var path = context.FilePath; dynamic currency = context.Currency; string measureWeightSystemKey = ""; @@ -174,10 +165,7 @@ public override void Execute(IExportExecuteContext context) var config = (context.ConfigurationData as ProfileConfigurationModel) ?? new ProfileConfigurationModel(); - context.Log.Information("Creating file " + path); - - using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) - using (var writer = XmlWriter.Create(stream, settings)) + using (var writer = XmlWriter.Create(context.DataStream, ExportXmlHelper.DefaultSettings)) { writer.WriteStartDocument(); writer.WriteStartElement("rss"); From 738052a5be9af329f02181d7d2d9fdbd3839ce76 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 8 Nov 2015 19:47:39 +0100 Subject: [PATCH 187/874] ExportFeature becomes an enum of flags --- .../Domain/DataExchange/ExportEnums.cs | 32 +++++++++++-------- .../Plugins/Providers/ProviderMetadata.cs | 4 +-- .../DataExchange/ExportExtensions.cs | 16 ---------- .../DataExchange/ExportFeaturesAttribute.cs | 7 +--- .../DataExchange/ExportProfileService.cs | 4 +-- .../ExportTask/ExportProfileTask.cs | 18 +++++------ .../ExportTask/ExportProfileTaskContext.cs | 9 ++---- .../Providers/GmcXmlExportProvider.cs | 18 +++++------ .../DependencyRegistrar.cs | 8 ++--- .../Controllers/ExportController.cs | 2 +- .../Models/DataExchange/ExportProfileModel.cs | 2 +- .../Views/Export/_Tab.Projection.cshtml | 14 ++++---- 12 files changed, 56 insertions(+), 78 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs index b7f041fed5..90cb107353 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs @@ -1,4 +1,5 @@  +using System; namespace SmartStore.Core.Domain.DataExchange { /// @@ -59,64 +60,67 @@ public enum ExportAttributeValueMerging } /// - /// Controls data processing and projection items supported by export provider + /// Controls data processing and projection items supported by an export provider /// - public enum ExportFeatures + [Flags] + public enum ExportFeature { + None = 0, + /// /// Whether to automatically create a file based public deployment when an export profile is created /// - CreatesInitialPublicDeployment = 0, + CreatesInitialPublicDeployment = 1, /// /// Whether to offer option to include\exclude grouped products /// - CanOmitGroupedProducts, + CanOmitGroupedProducts = 1 << 2, /// /// Whether to offer option to export attribute combinations as products /// - CanProjectAttributeCombinations, + CanProjectAttributeCombinations = 1 << 3, /// /// Whether to offer further options to manipulate the product description /// - CanProjectDescription, + CanProjectDescription = 1 << 4, /// /// Whether to offer option to enter a brand fallback /// - OffersBrandFallback, + OffersBrandFallback = 1 << 5, /// /// Whether to offer option to set a picture size and to get the URL of the main image /// - CanIncludeMainPicture, + CanIncludeMainPicture = 1 << 6, /// /// Whether to use SKU as manufacturer part number if MPN is empty /// - UsesSkuAsMpnFallback, - + UsesSkuAsMpnFallback = 1 << 7, + /// /// Whether to offer option to enter a shipping time fallback /// - OffersShippingTimeFallback, + OffersShippingTimeFallback = 1 << 8, /// /// Whether to offer option to enter a shipping costs fallback and a free shipping threshold /// - OffersShippingCostsFallback, + OffersShippingCostsFallback = 1 << 9, /// /// Whether to get the calculated old product price /// - UsesOldPrice, + UsesOldPrice = 1 << 10, /// /// Whether to get the calculated special and regular price (ignoring special offers) /// - UsesSpecialPrice + UsesSpecialPrice = 1 << 11 } /// diff --git a/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs b/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs index 31d98942d4..05ef601ecc 100644 --- a/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs +++ b/src/Libraries/SmartStore.Core/Plugins/Providers/ProviderMetadata.cs @@ -74,9 +74,9 @@ public class ProviderMetadata public bool IsHidden { get; set; } /// - /// Gets or sets an array of values that reflects what export data processing is supported by a provider + /// Gets or sets flags that reflects what features of export data processing is supported by a provider /// - public ExportFeatures[] ExportSupport { get; set; } + public ExportFeature ExportFeature { get; set; } /// /// Gets or sets an array of widget system names, which depend on the current provider diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs index ae5b8ff6ce..a7a8e5c5b9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs @@ -1,10 +1,8 @@ using System; using System.IO; -using System.Linq; using System.Web; using System.Web.Caching; using SmartStore.Core.Domain; -using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Plugins; using SmartStore.Services.Localization; @@ -24,20 +22,6 @@ public static bool IsValid(this Provider provider) return provider != null; } - /// - /// Returns a value indicating whether the export provider supports a projection type - /// - /// Export provider - /// The feature to check - /// true provider supports type, false provider does not support type. - public static bool Supports(this Provider provider, ExportFeatures feature) - { - if (provider != null) - return provider.Metadata.ExportSupport.Contains(feature); - - return false; - } - /// /// Gets the localized friendly name or the system name as fallback /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportFeaturesAttribute.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportFeaturesAttribute.cs index d29f5641d3..7f803715f4 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportFeaturesAttribute.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportFeaturesAttribute.cs @@ -10,11 +10,6 @@ namespace SmartStore.Services.DataExchange [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] public sealed class ExportFeaturesAttribute : Attribute { - public ExportFeaturesAttribute(params ExportFeatures[] features) - { - Features = features; - } - - public ExportFeatures[] Features { get; set; } + public ExportFeature Features { get; set; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs index dac3287770..95302de285 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs @@ -116,7 +116,7 @@ public virtual ExportProfile InsertExportProfile(Provider provi RemoveCriticalCharacters = true, CriticalCharacters = "¼,½,¾", PriceType = PriceDisplayType.PreSelectedPrice, - NoGroupedProducts = (provider.Supports(ExportFeatures.CanOmitGroupedProducts) ? true : false) + NoGroupedProducts = (provider.Metadata.ExportFeature.HasFlag(ExportFeature.CanOmitGroupedProducts) ? true : false) }; var filter = new ExportFilter @@ -150,7 +150,7 @@ public virtual ExportProfile InsertExportProfile(Provider provi { if (cloneProfile == null) { - if (provider.Supports(ExportFeatures.CreatesInitialPublicDeployment)) + if (provider.Metadata.ExportFeature.HasFlag(ExportFeature.CreatesInitialPublicDeployment)) { profile.Deployments.Add(new ExportDeployment { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 8218acffb0..b796715059 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -1539,7 +1539,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro var productTemplate = ctx.ProductTemplates.FirstOrDefault(x => x.Key == product.ProductTemplateId); var pictureSize = _mediaSettings.Value.ProductDetailsPictureSize; - if (ctx.SupportedFeatures[ExportFeatures.CanIncludeMainPicture] && ctx.Projection.PictureSize > 0) + if (ctx.Supports(ExportFeature.CanIncludeMainPicture) && ctx.Projection.PictureSize > 0) pictureSize = ctx.Projection.PictureSize; var perfLoadId = (ctx.IsPreview ? 0 : product.Id); // perf preview (it's a compromise) @@ -1745,12 +1745,12 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro #region more attribute controlled data - if (ctx.Supports(ExportFeatures.CanProjectDescription)) + if (ctx.Supports(ExportFeature.CanProjectDescription)) { PrepareProductDescription(ctx, expando, product); } - if (ctx.Supports(ExportFeatures.OffersBrandFallback)) + if (ctx.Supports(ExportFeature.OffersBrandFallback)) { string brand = null; var productManus = ctx.ProductExportContext.ProductManufacturers.Load(perfLoadId); @@ -1764,7 +1764,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro expando._Brand = brand; } - if (ctx.Supports(ExportFeatures.CanIncludeMainPicture)) + if (ctx.Supports(ExportFeature.CanIncludeMainPicture)) { if (productPictures != null && productPictures.Any()) expando._MainPictureUrl = _pictureService.Value.GetPictureUrl(productPictures.First().Picture, ctx.Projection.PictureSize, storeLocation: ctx.Store.Url); @@ -1806,18 +1806,18 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro // navigation properties GetDeliveryTimeAndQuantityUnit(ctx, exp, product.DeliveryTimeId, product.QuantityUnitId); - if (ctx.Supports(ExportFeatures.UsesSkuAsMpnFallback) && product.ManufacturerPartNumber.IsEmpty()) + if (ctx.Supports(ExportFeature.UsesSkuAsMpnFallback) && product.ManufacturerPartNumber.IsEmpty()) { exp.ManufacturerPartNumber = product.Sku; } - if (ctx.Supports(ExportFeatures.OffersShippingTimeFallback)) + if (ctx.Supports(ExportFeature.OffersShippingTimeFallback)) { dynamic deliveryTime = exp.DeliveryTime; exp._ShippingTime = (deliveryTime == null ? ctx.Projection.ShippingTime : deliveryTime.Name); } - if (ctx.Supports(ExportFeatures.OffersShippingCostsFallback)) + if (ctx.Supports(ExportFeature.OffersShippingCostsFallback)) { exp._FreeShippingThreshold = ctx.Projection.FreeShippingThreshold; @@ -1827,7 +1827,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro exp._ShippingCosts = ctx.Projection.ShippingCosts; } - if (ctx.Supports(ExportFeatures.UsesOldPrice)) + if (ctx.Supports(ExportFeature.UsesOldPrice)) { if (product.OldPrice != decimal.Zero && product.OldPrice != (decimal)exp.Price && !(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)) { @@ -1847,7 +1847,7 @@ private List ConvertToExpando(ExportProfileTaskContext ctx, Product pro } } - if (ctx.Supports(ExportFeatures.UsesSpecialPrice)) + if (ctx.Supports(ExportFeature.UsesSpecialPrice)) { exp._SpecialPrice = null; exp._RegularPrice = null; // price if a special price would not exist diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs index 8c1286b5d6..ae14972e42 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTaskContext.cs @@ -41,10 +41,6 @@ public ExportProfileTaskContext( EntityIdsSelected = selectedIds.SplitSafe(",").Select(x => x.ToInt()).ToList(); PreviewData = previewData; - SupportedFeatures = Enum.GetValues(typeof(ExportFeatures)) - .Cast() - .ToDictionary(x => x, x => Provider.Supports(x)); - FolderContent = FileSystemHelper.TempDir(@"Profile\Export\{0}\Content".FormatInvariant(profile.FolderName)); FolderRoot = System.IO.Directory.GetParent(FolderContent).FullName; @@ -84,10 +80,9 @@ public bool IsPreview public ExportProfile Profile { get; private set; } public Provider Provider { get; private set; } - public Dictionary SupportedFeatures { get; private set; } - public bool Supports(ExportFeatures feature) + public bool Supports(ExportFeature feature) { - return (!IsPreview && SupportedFeatures[feature]); + return (!IsPreview && Provider.Metadata.ExportFeature.HasFlag(feature)); } public ExportFilter Filter { get; private set; } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index a58b4b98b8..852f8eb430 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -17,15 +17,15 @@ namespace SmartStore.GoogleMerchantCenter.Providers [SystemName("Feeds.GoogleMerchantCenterProductXml")] [FriendlyName("Google Merchant Center XML product feed")] [DisplayOrder(1)] - [ExportFeatures( - ExportFeatures.CreatesInitialPublicDeployment, - ExportFeatures.CanOmitGroupedProducts, - ExportFeatures.CanProjectAttributeCombinations, - ExportFeatures.CanProjectDescription, - ExportFeatures.UsesSkuAsMpnFallback, - ExportFeatures.OffersBrandFallback, - ExportFeatures.CanIncludeMainPicture, - ExportFeatures.UsesSpecialPrice)] + [ExportFeatures(Features = + ExportFeature.CreatesInitialPublicDeployment | + ExportFeature.CanOmitGroupedProducts | + ExportFeature.CanProjectAttributeCombinations | + ExportFeature.CanProjectDescription | + ExportFeature.UsesSkuAsMpnFallback | + ExportFeature.OffersBrandFallback | + ExportFeature.CanIncludeMainPicture | + ExportFeature.UsesSpecialPrice)] public class GmcXmlExportProvider : ExportProviderBase { private const string _googleNamespace = "http://base.google.com/ns/1.0"; diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index 1daa71bd03..2b11924761 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -768,7 +768,7 @@ protected override void Load(ContainerBuilder builder) var isConfigurable = typeof(IConfigurable).IsAssignableFrom(type); var isEditable = typeof(IUserEditable).IsAssignableFrom(type); var isHidden = GetIsHidden(type); - var exportSupport = GetExportFeatures(type); + var exportFeature = GetExportFeature(type); var registration = builder.RegisterType(type).Named(systemName).InstancePerRequest().PropertiesAutowired(PropertyWiringOptions.None); registration.WithMetadata(m => @@ -785,7 +785,7 @@ protected override void Load(ContainerBuilder builder) m.For(em => em.IsConfigurable, isConfigurable); m.For(em => em.IsEditable, isEditable); m.For(em => em.IsHidden, isHidden); - m.For(em => em.ExportSupport, exportSupport); + m.For(em => em.ExportFeature, exportFeature); }); // register specific provider type @@ -863,7 +863,7 @@ private bool GetIsHidden(Type type) return false; } - private ExportFeatures[] GetExportFeatures(Type type) + private ExportFeature GetExportFeature(Type type) { var attr = type.GetAttribute(false); @@ -872,7 +872,7 @@ private ExportFeatures[] GetExportFeatures(Type type) return attr.Features; } - return new ExportFeatures[0]; + return ExportFeature.None; } private Tuple GetFriendlyName(Type type, PluginDescriptor descriptor) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 29c4c040b9..8690b4507c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -279,7 +279,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile if (provider != null) { - model.Provider.Supporting = provider.Metadata.ExportSupport; + model.Provider.Feature = provider.Metadata.ExportFeature; if (model.Provider.EntityType == ExportEntityType.Product) { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 3ebf4350d9..ee44a44113 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -94,7 +94,7 @@ public class ProviderModel public Type ConfigDataType { get; set; } public object ConfigData { get; set; } - public ExportFeatures[] Supporting { get; set; } + public ExportFeature Feature { get; set; } [SmartResourceDisplayName("Common.Image")] public string ThumbnailUrl { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml index 4815fba951..3d02c0a230 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Projection.cshtml @@ -42,7 +42,7 @@ @if (Model.Provider.EntityType == ExportEntityType.Product) { - if (Model.Provider.Supporting.Contains(ExportFeatures.CanProjectAttributeCombinations)) + if (Model.Provider.Feature.HasFlag(ExportFeature.CanProjectAttributeCombinations)) {
    @@ -71,7 +71,7 @@
    @@ -110,7 +110,7 @@
    @@ -123,7 +123,7 @@
    @@ -145,7 +145,7 @@
    @@ -158,7 +158,7 @@
    @@ -171,7 +171,7 @@
    From 6d1e4ad7a7e7f9e1c6cb5723b7c1d292c1e231ad Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 9 Nov 2015 11:01:35 +0100 Subject: [PATCH 188/874] Fixed issues due to incomplete renaming --- .../FeedGoogleMerchantCenter/Configure.cshtml | 2 +- .../ProfileConfiguration.cshtml | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml index 8d01b7baeb..6312648112 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml @@ -22,7 +22,7 @@ @Html.Raw(@T("Plugins.Feed.Froogle.AdminInstruction"))
    - @Html.Action("InfoProfile", "Export", new { systemName = ProductExportXmlProvider.SystemName, returnUrl = Request.RawUrl, area = "admin" }) + @Html.Action("InfoProfile", "Export", new { systemName = GmcXmlExportProvider.SystemName, returnUrl = Request.RawUrl, area = "admin" })
    diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml index 57b1c7b693..edf7fecb7e 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProfileConfiguration.cshtml @@ -39,9 +39,10 @@ @Html.SmartLabelFor(m => m.Condition)
    - @Html.DropDownList("Condition", new List { + @Html.DropDownList("Condition", new List + { new SelectListItem { Text = T("Common.Auto"), Value = "" }, - new SelectListItem { Text = T("Common.Unspecified"), Value = ProductExportXmlProvider.Unspecified }, + new SelectListItem { Text = T("Common.Unspecified"), Value = GmcXmlExportProvider.Unspecified }, new SelectListItem { Text = T("Plugins.Feed.Froogle.ConditionNew"), Value = "new" }, new SelectListItem { Text = T("Plugins.Feed.Froogle.ConditionUsed"), Value = "used" }, new SelectListItem { Text = T("Plugins.Feed.Froogle.ConditionRefurbished"), Value = "refurbished" } @@ -54,9 +55,10 @@ @Html.SmartLabelFor(m => m.Availability) - @Html.DropDownList("Availability", new List { + @Html.DropDownList("Availability", new List + { new SelectListItem { Text = T("Common.Auto"), Value = "" }, - new SelectListItem { Text = T("Common.Unspecified"), Value = ProductExportXmlProvider.Unspecified }, + new SelectListItem { Text = T("Common.Unspecified"), Value = GmcXmlExportProvider.Unspecified }, new SelectListItem { Text = T("Plugins.Feed.Froogle.AvailabilityInStock"), Value = "in stock" }, new SelectListItem { Text = T("Plugins.Feed.Froogle.AvailabilityOutOfStock"), Value = "out of stock" }, new SelectListItem { Text = T("Plugins.Feed.Froogle.AvailabilityPreorder"), Value = "preorder" } @@ -71,7 +73,7 @@ @Html.DropDownList("Gender", new List { new SelectListItem { Text = T("Common.Auto"), Value = "" }, - new SelectListItem { Text = T("Common.Unspecified"), Value = ProductExportXmlProvider.Unspecified }, + new SelectListItem { Text = T("Common.Unspecified"), Value = GmcXmlExportProvider.Unspecified }, new SelectListItem { Text = T("Plugins.Feed.Froogle.GenderMale"), Value = "male" }, new SelectListItem { Text = T("Plugins.Feed.Froogle.GenderFemale"), Value = "female" }, new SelectListItem { Text = T("Plugins.Feed.Froogle.GenderUnisex"), Value = "unisex" } @@ -86,7 +88,7 @@ @Html.DropDownList("AgeGroup", new List { new SelectListItem { Text = T("Common.Auto"), Value = "" }, - new SelectListItem { Text = T("Common.Unspecified"), Value = ProductExportXmlProvider.Unspecified }, + new SelectListItem { Text = T("Common.Unspecified"), Value = GmcXmlExportProvider.Unspecified }, new SelectListItem { Text = T("Plugins.Feed.Froogle.AgeGroupAdult"), Value = "adult" }, new SelectListItem { Text = T("Plugins.Feed.Froogle.AgeGroupKids"), Value = "kids" } }) From d75613c885bf853b4e647b0e3f9ce8cf1aaad01d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 9 Nov 2015 12:50:37 +0100 Subject: [PATCH 189/874] Improved pattern resolving of export file names --- .../201509021536425_ExportFramework1.cs | 4 +- .../201509150931528_ExportFramework2.cs | 5 ++ .../DataExchange/ExportExtensions.cs | 33 ++++++--- .../DataExchange/ExportProfileService.cs | 2 +- .../Controllers/ExportController.cs | 1 + .../Models/DataExchange/ExportProfileModel.cs | 1 + .../Views/Export/_Tab.Deployment.cshtml | 70 ++++++++++++++++++- 7 files changed, 103 insertions(+), 13 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs index d8e9b8ab9d..ed09b5e8d3 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs @@ -100,8 +100,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Legt den Namen des Ordners fest, in den die Daten exportiert werden."); builder.AddOrUpdate("Admin.DataExchange.Export.FolderAndFileName.Validate", - "Please enter a valid folder and file name. Example for file names: %Misc.FileNumber%-%ExportProfile.Id%-gmc-%Store.Name%", - "Bitte einen gltigen Ordner- und Dateinamen eingeben. Beispiel fr Dateinamen: %Misc.FileNumber%-%ExportProfile.Id%-gmc-%Store.Name%"); + "Please enter a valid folder and file name. Example for file names: %File.Index%-%Profile.Id%-gmc-%Store.Name%", + "Bitte einen gltigen Ordner- und Dateinamen eingeben. Beispiel fr Dateinamen: %File.Index%-%Profile.Id%-gmc-%Store.Name%"); builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.CreateZip", diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index 174f2d1f44..ccc5a52b37 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -41,6 +41,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Common.ShowAll", "Show all", "Alle anzeigen"); builder.AddOrUpdate("Admin.Common.Selected", "Selected", "Ausgewhlte"); builder.AddOrUpdate("Admin.Common.Entity", "Entity", "Entitt"); + builder.AddOrUpdate("Admin.Common.Placeholder", "Placeholder", "Platzhalter"); builder.AddOrUpdate("Admin.Common.FilesDeleted", @@ -108,6 +109,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Pro Sprache darf nur ein aktiver SEO Name festgelegt werden."); + builder.AddOrUpdate("Admin.DataExchange.Export.FileNamePatternDescriptions", + "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", + "ID des Exportprofils;Ordername des Exportprofils;SEO Name des Exportprofils;Shop ID;SEO Name des Shops;Mit 1 beginnender Dateiindex;Zufallszahl;UTC Zeitstempel"); + builder.AddOrUpdate("Admin.DataExchange.Export.NotPreviewCompatible", "This option is not taken into account in the preview.", "Diese Option wird in der Vorschau nicht bercksichtigt."); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs index a7a8e5c5b9..8a0cdc959d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs @@ -1,5 +1,7 @@ using System; +using System.Globalization; using System.IO; +using System.Text; using System.Web; using System.Web.Caching; using SmartStore.Core.Domain; @@ -64,18 +66,31 @@ public static string GetExportLogFilePath(this ExportProfile profile) /// /// Export profile /// Store - /// File number + /// One based file index /// The maximum length of the file name /// Resolved file name pattern - public static string ResolveFileNamePattern(this ExportProfile profile, Store store, int fileNumber, int maxFileNameLength) + public static string ResolveFileNamePattern(this ExportProfile profile, Store store, int fileIndex, int maxFileNameLength) { - var result = profile.FileNamePattern - .Replace("%ExportProfile.Id%", profile.Id.ToString()) - .Replace("%ExportProfile.SeoName%", SeoHelper.GetSeName(profile.Name, true, false).Replace("/", "").Replace("-", "")) - .Replace("%ExportProfile.FolderName%", profile.FolderName) - .Replace("%Store.Id%", store.Id.ToString()) - .Replace("%Store.SeoName%", profile.PerStore ? SeoHelper.GetSeName(store.Name, true, false) : "allstores") - .Replace("%Misc.FileNumber%", fileNumber.ToString("D4")) + var sb = new StringBuilder(profile.FileNamePattern); + + sb.Replace("%Profile.Id%", profile.Id.ToString()); + sb.Replace("%Profile.FolderName%", profile.FolderName); + sb.Replace("%Store.Id%", store.Id.ToString()); + sb.Replace("%File.Index%", fileIndex.ToString("D4")); + + if (profile.FileNamePattern.Contains("%Profile.SeoName%")) + sb.Replace("%Profile.SeoName%", SeoHelper.GetSeName(profile.Name, true, false).Replace("/", "").Replace("-", "")); + + if (profile.FileNamePattern.Contains("%Store.SeoName%")) + sb.Replace("%Store.SeoName%", profile.PerStore ? SeoHelper.GetSeName(store.Name, true, false) : "allstores"); + + if (profile.FileNamePattern.Contains("%Random.Number%")) + sb.Replace("%Random.Number%", CommonHelper.GenerateRandomInteger().ToString()); + + if (profile.FileNamePattern.Contains("%Timestamp%")) + sb.Replace("%Timestamp%", DateTime.UtcNow.ToString("s", CultureInfo.InvariantCulture)); + + var result = sb.ToString() .ToValidFileName("") .Truncate(maxFileNameLength); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs index 95302de285..465b97a3c2 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs @@ -18,7 +18,7 @@ namespace SmartStore.Services.DataExchange { public partial class ExportProfileService : IExportProfileService { - private const string _defaultFileNamePattern = "%Store.Id%-%ExportProfile.Id%-%Misc.FileNumber%-%ExportProfile.SeoName%"; + private const string _defaultFileNamePattern = "%Store.Id%-%Profile.Id%-%File.Index%-%Profile.SeoName%"; private readonly IRepository _exportProfileRepository; private readonly IRepository _exportDeploymentRepository; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 8690b4507c..0aa83791c2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -113,6 +113,7 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile model.IsTaskEnabled = profile.ScheduleTask.Enabled; model.LogFileExists = System.IO.File.Exists(profile.GetExportLogFilePath()); model.HasActiveProvider = (provider != null); + model.FileNamePatternDescriptions = T("Admin.DataExchange.Export.FileNamePatternDescriptions").Text.SplitSafe(";"); model.Provider = new ExportProfileModel.ProviderModel(); model.Provider.ThumbnailUrl = GetThumbnailUrl(provider); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index ee44a44113..a863d55d3f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -19,6 +19,7 @@ public partial class ExportProfileModel : EntityModelBase public string UnspecifiedString { get; set; } public bool LogFileExists { get; set; } public bool HasActiveProvider { get; set; } + public string[] FileNamePatternDescriptions { get; set; } [SmartResourceDisplayName("Admin.DataExchange.Export.Name")] public string Name { get; set; } 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 3e8d295898..17bfd46a84 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml @@ -23,6 +23,16 @@ @Html.TextBoxFor(x => x.FileNamePattern, new { @class = "input-large" }) @Html.ValidationMessageFor(x => x.FileNamePattern) + +
    +
    + @T("Admin.Common.Show")... +
    + @FileNamePatternDescription() +
    +
    + +
    @@ -71,7 +71,7 @@
    @@ -110,7 +110,7 @@
    @@ -123,7 +123,7 @@
    @@ -145,7 +145,7 @@
    @@ -158,7 +158,7 @@
    @@ -171,7 +171,7 @@
    From c951a1d29ae8f9521365078a6b98dd073dbc0f86 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 11 Nov 2015 12:55:53 +0100 Subject: [PATCH 204/874] Further IDataExporter implementation --- .../DataExchange/IDataExporter.cs | 29 +- .../DataExchange/Internal/DataExportTask.cs | 12 +- .../DataExchange/Internal/DataExporter.cs | 917 +++++++++++++++++- .../Internal/DataExporterContext.cs | 218 +++++ .../Internal/DynamicEntityHelper.cs | 130 +-- .../SmartStore.Services.csproj | 1 + 6 files changed, 1191 insertions(+), 116 deletions(-) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs index 82820238d8..4a96e5c515 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs @@ -6,7 +6,8 @@ namespace SmartStore.Services.DataExchange { - public delegate void ProgressSetter(int value, int maximum, string message); + public delegate void ProgressValueSetter(int value, int maximum, string message); + public delegate void ProgressMessageSetter(string message); public interface IDataExporter { @@ -23,24 +24,31 @@ public interface IDataExporter public class DataExportRequest { - private readonly static ProgressSetter _voidProgressSetter = DataExportRequest.SetProgress; + private readonly static ProgressValueSetter _voidProgressValueSetter = DataExportRequest.SetProgress; + private readonly static ProgressMessageSetter _voidProgressMessageSetter = DataExportRequest.SetProgress; - public DataExportRequest(ExportProfile profile) + public DataExportRequest(ExportProfile profile, Provider provider) { Guard.ArgumentNotNull(() => profile); + Guard.ArgumentNotNull(() => provider); - CustomData = new Dictionary(StringComparer.OrdinalIgnoreCase); - ProgressSetter = _voidProgressSetter; Profile = profile; - } + Provider = provider; + + ProgressValueSetter = _voidProgressValueSetter; + ProgressMessageSetter = _voidProgressMessageSetter; + + CustomData = new Dictionary(StringComparer.OrdinalIgnoreCase); + } public ExportProfile Profile { get; private set; } - public Provider Provider { get; set; } + public Provider Provider { get; private set; } public IEnumerable EntitiesToExport { get; set; } - public ProgressSetter ProgressSetter { get; set; } + public ProgressValueSetter ProgressValueSetter { get; set; } + public ProgressMessageSetter ProgressMessageSetter { get; set; } public IDictionary CustomData { get; private set; } @@ -49,5 +57,10 @@ private static void SetProgress(int val, int max, string msg) { // do nothing } + + private static void SetProgress(string msg) + { + // do nothing + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs index ea3c676587..b1e3d31f54 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs @@ -37,10 +37,16 @@ public void Execute(TaskExecutionContext ctx) throw new SmartException(T("Admin.Common.ProviderNotLoaded", profile.ProviderSystemName.NaIfEmpty())); // build export request - var request = new DataExportRequest(profile); - request.ProgressSetter = delegate(int val, int max, string msg) + var request = new DataExportRequest(profile, provider); + + request.ProgressValueSetter = delegate(int val, int max, string msg) + { + ctx.SetProgress(val, max, msg, true); + }; + + request.ProgressMessageSetter = delegate(string msg) { - ctx.SetProgress(val, max, msg); + ctx.SetProgress(null, msg, true); }; if (selectedEntityIds.HasValue()) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs index c0c44e0304..391b210db6 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs @@ -1,21 +1,38 @@ using System; using System.Collections.Generic; +using System.IO; +using System.IO.Compression; using System.Linq; +using System.Text; using System.Threading; using System.Web; +using SmartStore.Core; +using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Messages; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Email; using SmartStore.Core.Localization; +using SmartStore.Core.Logging; using SmartStore.Services.Catalog; +using SmartStore.Services.Common; +using SmartStore.Services.Customers; using SmartStore.Services.Directory; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Media; +using SmartStore.Services.Messages; +using SmartStore.Services.Orders; using SmartStore.Services.Seo; +using SmartStore.Services.Shipping; using SmartStore.Services.Tasks; using SmartStore.Services.Tax; +using SmartStore.Utilities; +using SmartStore.Utilities.Threading; namespace SmartStore.Services.DataExchange.Internal { @@ -23,57 +40,121 @@ public partial class DataExporter : IDataExporter { private static readonly ReaderWriterLockSlim _rwLock = new ReaderWriterLockSlim(); + #region Dependencies + private readonly ICommonServices _services; - private readonly IUrlRecordService _urlRecordService; - private readonly ILocalizedEntityService _localizedEntityService; - private readonly IPictureService _pictureService; - private readonly IPriceCalculationService _priceCalculationService; - private readonly ICurrencyService _currencyService; - private readonly ITaxService _taxService; - private readonly IPriceFormatter _priceFormatter; - private readonly ICategoryService _categoryService; - private readonly IProductAttributeParser _productAttributeParser; - private readonly IProductService _productService; - private readonly IDateTimeHelper _dateTimeHelper; - - private MediaSettings _mediaSettings; + private readonly Lazy _priceFormatter; + private readonly Lazy _dateTimeHelper; + private readonly Lazy _exportProfileService; + private readonly Lazy _localizedEntityService; + private readonly Lazy _languageService; + private readonly Lazy _urlRecordService; + private readonly Lazy _pictureService; + private readonly Lazy _priceCalculationService; + private readonly Lazy _currencyService; + private readonly Lazy _taxService; + private readonly Lazy _categoryService; + private readonly Lazy _productAttributeParser; + private readonly Lazy _productAttributeService; + private readonly Lazy _productTemplateService; + private readonly Lazy _productService; + private readonly Lazy _orderService; + private readonly Lazy _manufacturerService; + private readonly Lazy _customerService; + private readonly Lazy _addressService; + private readonly Lazy _countryService; + private readonly Lazy _shipmentService; + private readonly Lazy _genericAttributeService; + private readonly Lazy _emailAccountService; + private readonly Lazy _emailSender; + private readonly Lazy _deliveryTimeService; + private readonly Lazy _quantityUnitService; + + private readonly Lazy>_customerRepository; + private readonly Lazy> _subscriptionRepository; + + private Lazy _dataExchangeSettings; + private Lazy _mediaSettings; public DataExporter( ICommonServices services, - IUrlRecordService urlRecordService, - ILocalizedEntityService localizedEntityService, - IPictureService pictureService, - IPriceCalculationService priceCalculationService, - ICurrencyService currencyService, - ITaxService taxService, - IPriceFormatter priceFormatter, - ICategoryService categoryService, - IProductAttributeParser productAttributeParser, - IProductService productService, - IDateTimeHelper dateTimeHelper, - MediaSettings mediaSettings) + Lazy priceFormatter, + Lazy dateTimeHelper, + Lazy exportProfileService, + Lazy localizedEntityService, + Lazy languageService, + Lazy urlRecordService, + Lazy pictureService, + Lazy priceCalculationService, + Lazy currencyService, + Lazy taxService, + Lazy categoryService, + Lazy productAttributeParser, + Lazy productAttributeService, + Lazy productTemplateService, + Lazy productService, + Lazy orderService, + Lazy manufacturerService, + Lazy customerService, + Lazy addressService, + Lazy countryService, + Lazy shipmentService, + Lazy genericAttributeService, + Lazy emailAccountService, + Lazy emailSender, + Lazy deliveryTimeService, + Lazy quantityUnitService, + Lazy> customerRepository, + Lazy> subscriptionRepository, + Lazy dataExchangeSettings, + Lazy mediaSettings) { + _priceFormatter = priceFormatter; + _dateTimeHelper = dateTimeHelper; _services = services; - _urlRecordService = urlRecordService; + _exportProfileService = exportProfileService; _localizedEntityService = localizedEntityService; + _languageService = languageService; + _urlRecordService = urlRecordService; _pictureService = pictureService; _priceCalculationService = priceCalculationService; _currencyService = currencyService; _taxService = taxService; - _priceFormatter = priceFormatter; _categoryService = categoryService; _productAttributeParser = productAttributeParser; + _productAttributeService = productAttributeService; + _productTemplateService = productTemplateService; _productService = productService; - _dateTimeHelper = dateTimeHelper; + _orderService = orderService; + _manufacturerService = manufacturerService; + _customerService = customerService; + _addressService = addressService; + _countryService = countryService; + _shipmentService = shipmentService; + _genericAttributeService = genericAttributeService; + _emailAccountService = emailAccountService; + _emailSender = emailSender; + _deliveryTimeService = deliveryTimeService; + _quantityUnitService = quantityUnitService; + _customerRepository = customerRepository; + _subscriptionRepository = subscriptionRepository; + + _dataExchangeSettings = dataExchangeSettings; _mediaSettings = mediaSettings; + + T = NullLocalizer.Instance; } + public Localizer T { get; set; } + + #endregion + #region Utilities - private void SetProgress(DataExportTaskContext ctx, int loadedRecords) + private void SetProgress(DataExporterContext ctx, int loadedRecords) { - if (!ctx.IsPreview && ctx.TaskContext.ScheduleTask != null && loadedRecords > 0) + if (!ctx.IsPreview && loadedRecords > 0) { int totalRecords = ctx.RecordsPerStore.Sum(x => x.Value); @@ -84,23 +165,219 @@ private void SetProgress(DataExportTaskContext ctx, int loadedRecords) var msg = ctx.ProgressInfo.FormatInvariant(ctx.RecordCount, totalRecords); - ctx.TaskContext.SetProgress(ctx.RecordCount, totalRecords, msg, true); + ctx.Request.ProgressValueSetter.Invoke(ctx.RecordCount, totalRecords, msg); + } + } + + private void SetProgress(DataExporterContext ctx, string message) + { + if (!ctx.IsPreview && message.HasValue()) + { + ctx.Request.ProgressMessageSetter.Invoke(message); } } - private void SetProgress(DataExportTaskContext ctx, string message) + private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, int pageIndex = 0) { - if (!ctx.IsPreview && ctx.TaskContext.ScheduleTask != null && message.HasValue()) + var offset = ctx.Profile.Offset + (pageIndex * PageSize); + + var limit = (ctx.IsPreview ? PageSize : ctx.Profile.Limit); + + var recordsPerSegment = (ctx.IsPreview ? 0 : ctx.Profile.BatchSize); + + var totalCount = ctx.Profile.Offset + ctx.RecordsPerStore.First(x => x.Key == ctx.Store.Id).Value; + + switch (ctx.Provider.Value.EntityType) { - ctx.TaskContext.SetProgress(null, message, true); + case ExportEntityType.Product: + ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ( + skip => GetProducts(ctx, skip), + entities => + { + // load data behind navigation properties for current queue in one go + ctx.ProductExportContext = new ProductExportContext(entities, + 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 => _manufacturerService.Value.GetProductManufacturersByProductIds(x), + x => _productService.Value.GetProductPicturesByProductIds(x), + x => _productService.Value.GetProductTagsByProductIds(x), + x => _productService.Value.GetAppliedDiscountsByProductIds(x), + x => _productService.Value.GetProductSpecificationAttributesByProductIds(x), + x => _productService.Value.GetBundleItemsByProductIds(x, true) + ); + }, + entity => Convert(ctx, entity), + offset, PageSize, limit, recordsPerSegment, totalCount + ); + break; + + case ExportEntityType.Order: + ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ( + skip => GetOrders(ctx, skip), + entities => + { + ctx.OrderExportContext = new OrderExportContext(entities, + x => _customerService.Value.GetCustomersByIds(x), + x => _customerService.Value.GetRewardPointsHistoriesByCustomerIds(x), + x => _addressService.Value.GetAddressByIds(x), + x => _orderService.Value.GetOrderItemsByOrderIds(x), + x => _shipmentService.Value.GetShipmentsByOrderIds(x) + ); + }, + entity => Convert(ctx, entity), + offset, PageSize, limit, recordsPerSegment, totalCount + ); + break; + + case ExportEntityType.Manufacturer: + ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ( + skip => GetManufacturers(ctx, skip), + entities => + { + ctx.ManufacturerExportContext = new ManufacturerExportContext(entities, + x => _manufacturerService.Value.GetProductManufacturersByManufacturerIds(x), + x => _pictureService.Value.GetPicturesByIds(x) + ); + }, + entity => Convert(ctx, entity), + offset, PageSize, limit, recordsPerSegment, totalCount + ); + break; + + case ExportEntityType.Category: + ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ( + skip => GetCategories(ctx, skip), + entities => + { + ctx.CategoryExportContext = new CategoryExportContext(entities, + x => _categoryService.Value.GetProductCategoriesByCategoryIds(x), + x => _pictureService.Value.GetPicturesByIds(x) + ); + }, + entity => Convert(ctx, entity), + offset, PageSize, limit, recordsPerSegment, totalCount + ); + break; + + case ExportEntityType.Customer: + ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ( + skip => GetCustomers(ctx, skip), + entities => + { + ctx.CustomerExportContext = new CustomerExportContext(entities, + x => _genericAttributeService.Value.GetAttributesForEntity(x, "Customer") + ); + }, + entity => Convert(ctx, entity), + offset, PageSize, limit, recordsPerSegment, totalCount + ); + break; + + case ExportEntityType.NewsLetterSubscription: + ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ( + skip => GetNewsLetterSubscriptions(ctx, skip), + null, + entity => Convert(ctx, entity), + offset, PageSize, limit, recordsPerSegment, totalCount + ); + break; + + default: + ctx.ExecuteContext.Segmenter = null; + break; } + + return ctx.ExecuteContext.Segmenter as IExportDataSegmenterProvider; + } + + private bool CallProvider(DataExporterContext ctx, string streamId, string method, string path) + { + if (method != "Execute" && method != "OnExecuted") + throw new SmartException("Unknown export method {0}".FormatInvariant(method.NaIfEmpty())); + + try + { + ctx.ExecuteContext.DataStreamId = streamId; + + using (ctx.ExecuteContext.DataStream = new MemoryStream()) + { + if (method == "Execute") + { + ctx.Provider.Value.Execute(ctx.ExecuteContext); + } + else if (method == "OnExecuted") + { + ctx.Provider.Value.OnExecuted(ctx.ExecuteContext); + } + + if (ctx.IsFileBasedExport && path.HasValue()) + { + if (!ctx.ExecuteContext.DataStream.CanSeek) + ctx.Log.Warning("Data stream seems to be closed!"); + + ctx.ExecuteContext.DataStream.Seek(0, SeekOrigin.Begin); + + using (_rwLock.GetWriteLock()) + using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + { + ctx.Log.Information("Creating file " + path); + + ctx.ExecuteContext.DataStream.CopyTo(fileStream); + } + } + } + } + catch (Exception exc) + { + ctx.ExecuteContext.Abort = ExportAbortion.Hard; + ctx.Log.Error("The provider failed at the {0} method: {1}".FormatInvariant(method, exc.ToAllMessages()), exc); + ctx.Result.LastError = exc.ToString(); + } + finally + { + if (ctx.ExecuteContext.DataStream != null) + { + ctx.ExecuteContext.DataStream.Dispose(); + ctx.ExecuteContext.DataStream = null; + } + } + + return (ctx.ExecuteContext.Abort != ExportAbortion.Hard); + } + + private void SendCompletionEmail(DataExporterContext ctx) + { + var emailAccount = _emailAccountService.Value.GetEmailAccountById(ctx.Profile.EmailAccountId); + var smtpContext = new SmtpContext(emailAccount); + var message = new EmailMessage(); + + var storeInfo = "{0} ({1})".FormatInvariant(ctx.Store.Name, ctx.Store.Url); + + message.To.AddRange(ctx.Profile.CompletedEmailAddresses.SplitSafe(",").Where(x => x.IsEmail()).Select(x => new EmailAddress(x))); + message.From = new EmailAddress(emailAccount.Email, emailAccount.DisplayName); + + message.Subject = _services.Localization.GetResource("Admin.DataExchange.Export.CompletedEmail.Subject", ctx.Projection.LanguageId ?? 0) + .FormatInvariant(ctx.Profile.Name); + + message.Body = _services.Localization.GetResource("Admin.DataExchange.Export.CompletedEmail.Body", ctx.Projection.LanguageId ?? 0) + .FormatInvariant(storeInfo); + + _emailSender.Value.SendEmail(smtpContext, message); } #endregion #region Getting data - private IQueryable GetProductQuery(DataExportTaskContext ctx, int skip, int take) + private IQueryable GetProductQuery(DataExporterContext ctx, int skip, int take) { IQueryable query = null; @@ -134,12 +411,12 @@ private IQueryable GetProductQuery(DataExportTaskContext ctx, int skip, searchContext.CategoryIds = ctx.Filter.CategoryIds.ToList(); if (ctx.Filter.CreatedFrom.HasValue) - searchContext.CreatedFromUtc = _dateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _dateTimeHelper.CurrentTimeZone); + searchContext.CreatedFromUtc = _dateTimeHelper.Value.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _dateTimeHelper.Value.CurrentTimeZone); if (ctx.Filter.CreatedTo.HasValue) - searchContext.CreatedToUtc = _dateTimeHelper.ConvertToUtcTime(ctx.Filter.CreatedTo.Value, _dateTimeHelper.CurrentTimeZone); + searchContext.CreatedToUtc = _dateTimeHelper.Value.ConvertToUtcTime(ctx.Filter.CreatedTo.Value, _dateTimeHelper.Value.CurrentTimeZone); - query = _productService.PrepareProductSearchQuery(searchContext); + query = _productService.Value.PrepareProductSearchQuery(searchContext); query = query.OrderByDescending(x => x.CreatedOnUtc); } @@ -157,7 +434,7 @@ private IQueryable GetProductQuery(DataExportTaskContext ctx, int skip, return query; } - private List GetProducts(DataExportTaskContext ctx, int skip) + private List GetProducts(DataExporterContext ctx, int skip) { var result = new List(); @@ -182,7 +459,7 @@ private List GetProducts(DataExportTaskContext ctx, int skip) ParentGroupedProductId = product.Id }; - foreach (var associatedProduct in _productService.SearchProducts(associatedSearchContext)) + foreach (var associatedProduct in _productService.Value.SearchProducts(associatedSearchContext)) { result.Add(associatedProduct); } @@ -205,8 +482,568 @@ private List GetProducts(DataExportTaskContext ctx, int skip) return result; } + private IQueryable GetOrderQuery(DataExporterContext ctx, int skip, int take) + { + var query = _orderService.Value.GetOrders( + ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId, + ctx.Projection.CustomerId ?? 0, + ctx.Filter.CreatedFrom.HasValue ? (DateTime?)_dateTimeHelper.Value.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _dateTimeHelper.Value.CurrentTimeZone) : null, + ctx.Filter.CreatedTo.HasValue ? (DateTime?)_dateTimeHelper.Value.ConvertToUtcTime(ctx.Filter.CreatedTo.Value, _dateTimeHelper.Value.CurrentTimeZone) : null, + ctx.Filter.OrderStatusIds, + ctx.Filter.PaymentStatusIds, + ctx.Filter.ShippingStatusIds, + null, + null, + null); + + if (ctx.EntityIdsSelected.Count > 0) + query = query.Where(x => ctx.EntityIdsSelected.Contains(x.Id)); + + query = query.OrderByDescending(x => x.CreatedOnUtc); + + if (skip > 0) + query = query.Skip(skip); + + if (take != int.MaxValue) + query = query.Take(take); + + return query; + } + + private List GetOrders(DataExporterContext ctx, int skip) + { + var orders = GetOrderQuery(ctx, skip, PageSize).ToList(); + + if (ctx.Projection.OrderStatusChange != ExportOrderStatusChange.None) + { + ctx.EntityIdsLoaded = ctx.EntityIdsLoaded + .Union(orders.Select(x => x.Id)) + .Distinct() + .ToList(); + } + + try + { + SetProgress(ctx, orders.Count); + + _services.DbContext.DetachEntities(orders); + } + catch { } + + return orders; + } + + private IQueryable GetManufacturerQuery(DataExporterContext ctx, int skip, int take) + { + var showHidden = !ctx.Filter.IsPublished.HasValue; + var storeId = (ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); + + var query = _manufacturerService.Value.GetManufacturers(showHidden, storeId); + + query = query.OrderBy(x => x.DisplayOrder); + + if (skip > 0) + query = query.Skip(skip); + + if (take != int.MaxValue) + query = query.Take(take); + + return query; + } + + private List GetManufacturers(DataExporterContext ctx, int skip) + { + var manus = GetManufacturerQuery(ctx, skip, PageSize).ToList(); + + try + { + SetProgress(ctx, manus.Count); + + _services.DbContext.DetachEntities(manus); + } + catch { } + + return manus; + } + + private IQueryable GetCategoryQuery(DataExporterContext ctx, int skip, int take) + { + var showHidden = !ctx.Filter.IsPublished.HasValue; + var storeId = (ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); + + var query = _categoryService.Value.GetCategories(null, showHidden, null, true, storeId); + + query = query + .OrderBy(x => x.ParentCategoryId) + .ThenBy(x => x.DisplayOrder); + + if (skip > 0) + query = query.Skip(skip); + + if (take != int.MaxValue) + query = query.Take(take); + + return query; + } + + private List GetCategories(DataExporterContext ctx, int skip) + { + var categories = GetCategoryQuery(ctx, skip, PageSize).ToList(); + + try + { + SetProgress(ctx, categories.Count); + + _services.DbContext.DetachEntities(categories); + } + catch { } + + return categories; + } + + private IQueryable GetCustomerQuery(DataExporterContext ctx, int skip, int take) + { + var query = _customerRepository.Value.TableUntracked + .Expand(x => x.BillingAddress) + .Expand(x => x.ShippingAddress) + .Expand(x => x.Addresses.Select(y => y.Country)) + .Expand(x => x.Addresses.Select(y => y.StateProvince)) + .Expand(x => x.CustomerRoles) + .Where(x => !x.Deleted); + + if (ctx.EntityIdsSelected.Count > 0) + { + query = query.Where(x => ctx.EntityIdsSelected.Contains(x.Id)); + } + + query = query.OrderByDescending(x => x.CreatedOnUtc); + + if (skip > 0) + query = query.Skip(skip); + + if (take != int.MaxValue) + query = query.Take(take); + + return query; + } + + private List GetCustomers(DataExporterContext ctx, int skip) + { + var customers = GetCustomerQuery(ctx, skip, PageSize).ToList(); + + try + { + SetProgress(ctx, customers.Count); + + _services.DbContext.DetachEntities(customers); + } + catch { } + + return customers; + } + + private IQueryable GetNewsLetterSubscriptionQuery(DataExporterContext ctx, int skip, int take) + { + var storeId = (ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); + + var query = _subscriptionRepository.Value.TableUntracked; + + if (storeId > 0) + { + query = query.Where(x => x.StoreId == storeId); + } + + query = query + .OrderBy(x => x.StoreId) + .ThenBy(x => x.Email); + + if (skip > 0) + query = query.Skip(skip); + + if (take != int.MaxValue) + query = query.Take(take); + + return query; + } + + private List GetNewsLetterSubscriptions(DataExporterContext ctx, int skip) + { + var subscriptions = GetNewsLetterSubscriptionQuery(ctx, skip, PageSize).ToList(); + + try + { + SetProgress(ctx, subscriptions.Count); + + _services.DbContext.DetachEntities(subscriptions); + } + catch { } + + return subscriptions; + } + #endregion + private List Init(DataExporterContext ctx, int? totalRecords = null) + { + // Init base things that are even required for preview. Init all other things (regular export) in ExportCoreOuter. + List result = null; + + if (ctx.Projection.CurrencyId.HasValue) + ctx.ContextCurrency = _currencyService.Value.GetCurrencyById(ctx.Projection.CurrencyId.Value); + else + ctx.ContextCurrency = _services.WorkContext.WorkingCurrency; + + if (ctx.Projection.CustomerId.HasValue) + ctx.ContextCustomer = _customerService.Value.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); + else + ctx.ContextLanguage = _services.WorkContext.WorkingLanguage; + + ctx.Stores = _services.StoreService.GetAllStores().ToDictionary(x => x.Id, x => x); + ctx.Languages = _languageService.Value.GetAllLanguages(true).ToDictionary(x => x.Id, x => x); + + if (!ctx.IsPreview && ctx.Profile.PerStore) + { + result = new List(ctx.Stores.Values.Where(x => x.Id == ctx.Filter.StoreId || ctx.Filter.StoreId == 0)); + } + else + { + int? storeId = (ctx.Filter.StoreId == 0 ? ctx.Projection.StoreId : ctx.Filter.StoreId); + + ctx.Store = ctx.Stores.Values.FirstOrDefault(x => x.Id == (storeId ?? _services.StoreContext.CurrentStore.Id)); + + result = new List { ctx.Store }; + } + + // get total records for progress + foreach (var store in result) + { + ctx.Store = store; + + int totalCount = 0; + + if (totalRecords.HasValue) + { + totalCount = totalRecords.Value; // speed up preview by not counting total at each page + } + else + { + switch (ctx.Provider.Value.EntityType) + { + case ExportEntityType.Product: + totalCount = GetProductQuery(ctx, ctx.Profile.Offset, int.MaxValue).Count(); + break; + case ExportEntityType.Order: + totalCount = GetOrderQuery(ctx, ctx.Profile.Offset, int.MaxValue).Count(); + break; + case ExportEntityType.Manufacturer: + totalCount = GetManufacturerQuery(ctx, ctx.Profile.Offset, int.MaxValue).Count(); + break; + case ExportEntityType.Category: + totalCount = GetCategoryQuery(ctx, ctx.Profile.Offset, int.MaxValue).Count(); + break; + case ExportEntityType.Customer: + totalCount = GetCustomerQuery(ctx, ctx.Profile.Offset, int.MaxValue).Count(); + break; + case ExportEntityType.NewsLetterSubscription: + totalCount = GetNewsLetterSubscriptionQuery(ctx, ctx.Profile.Offset, int.MaxValue).Count(); + break; + } + } + + ctx.RecordsPerStore.Add(store.Id, totalCount); + } + + return result; + } + + private void ExportCoreInner(DataExporterContext ctx, Store store) + { + if (ctx.ExecuteContext.Abort != ExportAbortion.None) + return; + + int fileIndex = 0; + + ctx.Store = store; + + { + var logHead = new StringBuilder(); + logHead.AppendLine(); + logHead.AppendLine(new string('-', 40)); + logHead.AppendLine("SmartStore.NET:\t\tv." + SmartStoreVersion.CurrentFullVersion); + logHead.Append("Export profile:\t\t" + ctx.Profile.Name); + logHead.AppendLine(ctx.Profile.Id == 0 ? " (volatile)" : " (Id {0})".FormatInvariant(ctx.Profile.Id)); + + logHead.AppendLine("Export provider:\t{0} ({1})".FormatInvariant(ctx.Provider.Metadata.FriendlyName, ctx.Profile.ProviderSystemName)); + + var plugin = ctx.Provider.Metadata.PluginDescriptor; + logHead.Append("Plugin:\t\t\t\t"); + logHead.AppendLine(plugin == null ? "".NaIfEmpty() : "{0} ({1}) v.{2}".FormatInvariant(plugin.FriendlyName, plugin.SystemName, plugin.Version.ToString())); + + logHead.AppendLine("Entity:\t\t\t\t" + ctx.Provider.Value.EntityType.ToString()); + + var storeInfo = (ctx.Profile.PerStore ? "{0} (Id {1})".FormatInvariant(ctx.Store.Name, ctx.Store.Id) : "All stores"); + logHead.Append("Store:\t\t\t\t" + storeInfo); + + ctx.Log.Information(logHead.ToString()); + } + + ctx.ExecuteContext.Store = ToDynamic(ctx, ctx.Store); + + ctx.ExecuteContext.MaxFileNameLength = _dataExchangeSettings.Value.MaxFileNameLength; + + ctx.ExecuteContext.HasPublicDeployment = ctx.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.Provider.Value.FileExtension.HasValue() ? ctx.Provider.Value.FileExtension.ToLower().EnsureStartsWith(".") : ""); + + + using (var segmenter = CreateSegmenter(ctx)) + { + if (segmenter == null) + { + throw new SmartException("Unsupported entity type '{0}'".FormatInvariant(ctx.Provider.Value.EntityType.ToString())); + } + + if (segmenter.TotalRecords <= 0) + { + ctx.Log.Information("There are no records to export"); + } + + while (ctx.ExecuteContext.Abort == ExportAbortion.None && segmenter.HasData) + { + segmenter.RecordPerSegmentCount = 0; + ctx.ExecuteContext.RecordsSucceeded = 0; + + string path = null; + + if (ctx.IsFileBasedExport) + { + var resolvedPattern = ctx.Profile.ResolveFileNamePattern(ctx.Store, ++fileIndex, ctx.ExecuteContext.MaxFileNameLength); + + 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)) + { + ctx.Log.Information("Provider reports {0} successful exported record(s)".FormatInvariant(ctx.ExecuteContext.RecordsSucceeded)); + + // create info for deployment list in profile edit + if (ctx.IsFileBasedExport) + { + ctx.Result.Files.Add(new DataExportResult.ExportFileInfo + { + StoreId = ctx.Store.Id, + FileName = ctx.ExecuteContext.FileName + }); + } + } + + if (ctx.ExecuteContext.IsMaxFailures) + ctx.Log.Warning("Export aborted. The maximum number of failures has been reached"); + + if (ctx.CancellationToken.IsCancellationRequested) + ctx.Log.Warning("Export aborted. A cancellation has been requested"); + } + + if (ctx.ExecuteContext.Abort != ExportAbortion.Hard) + { + // always call OnExecuted + if (ctx.ExecuteContext.ExtraDataStreams.Count == 0) + ctx.ExecuteContext.ExtraDataStreams.Add(new ExportExtraStreams()); + + ctx.ExecuteContext.ExtraDataStreams.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(); + } + } + } + + private void ExportCoreOuter(DataExporterContext ctx) + { + if (ctx.Profile == null || !ctx.Profile.Enabled) + return; + + FileSystemHelper.Delete(ctx.LogPath); + FileSystemHelper.ClearDirectory(ctx.FolderContent, false); + FileSystemHelper.Delete(ctx.ZipPath); + + using (var logger = new TraceLogger(ctx.LogPath)) + { + try + { + if (!ctx.Provider.IsValid()) + { + throw new SmartException("Export aborted because the export provider is not valid"); + } + + ctx.Log = logger; + ctx.ExecuteContext.Log = logger; + ctx.ProgressInfo = T("Admin.DataExchange.Export.ProgressInfo"); + + if (ctx.Profile.ProviderConfigData.HasValue()) + { + var configInfo = ctx.Provider.Value.ConfigurationInfo; + if (configInfo != null) + { + ctx.ExecuteContext.ConfigurationData = XmlHelper.Deserialize(ctx.Profile.ProviderConfigData, configInfo.ModelType); + } + } + + // TODO: lazyLoading: false. It requires IDbContextExtensions::Load/QueryFor for properties internally mapped by EF + using (var scope = new DbContextScope(_services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, forceNoTracking: true)) + { + 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); + + if (ctx.Provider.Value.EntityType == ExportEntityType.Product) + { + var allCategories = _categoryService.Value.GetAllCategories(showHidden: true, applyNavigationFilters: false); + ctx.Categories = allCategories.ToDictionary(x => x.Id); + } + + if (ctx.Provider.Value.EntityType == ExportEntityType.Order) + { + ctx.Countries = _countryService.Value.GetAllCountries(true).ToDictionary(x => x.Id, x => x); + } + + if (ctx.Provider.Value.EntityType == ExportEntityType.Customer) + { + var subscriptionEmails = _subscriptionRepository.Value.TableUntracked + .Where(x => x.Active) + .Select(x => x.Email) + .Distinct() + .ToList(); + + ctx.NewsletterSubscriptions = new HashSet(subscriptionEmails, StringComparer.OrdinalIgnoreCase); + } + + var stores = Init(ctx); + + ctx.ExecuteContext.Language = ToDynamic(ctx, ctx.ContextLanguage); + ctx.ExecuteContext.Customer = ToDynamic(ctx, ctx.ContextCustomer); + ctx.ExecuteContext.Currency = ToDynamic(ctx, ctx.ContextCurrency); + + stores.ForEach(x => ExportCoreInner(ctx, x)); + } + + if (!ctx.IsPreview && ctx.ExecuteContext.Abort != ExportAbortion.Hard) + { + if (ctx.IsFileBasedExport) + { + if (ctx.Profile.CreateZipArchive || ctx.Profile.Deployments.Any(x => x.Enabled && x.CreateZip)) + { + ZipFile.CreateFromDirectory(ctx.FolderContent, ctx.ZipPath, CompressionLevel.Fastest, true); + } + + SetProgress(ctx, T("Common.Deployment")); + + // TODO: deployment + //foreach (var deployment in ctx.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) + //{ + // try + // { + // switch (deployment.DeploymentType) + // { + // case ExportDeploymentType.FileSystem: + // DeployFileSystem(ctx, deployment); + // break; + // case ExportDeploymentType.Email: + // DeployEmail(ctx, deployment); + // break; + // case ExportDeploymentType.Http: + // DeployHttp(ctx, deployment); + // break; + // case ExportDeploymentType.Ftp: + // DeployFtp(ctx, deployment); + // break; + // } + // } + // catch (Exception exc) + // { + // logger.Error("Deployment \"{0}\" of type {1} failed: {2}".FormatInvariant( + // deployment.Name, deployment.DeploymentType.ToString(), exc.Message), exc); + // } + //} + } + + if (ctx.Profile.EmailAccountId != 0 && ctx.Profile.CompletedEmailAddresses.HasValue()) + { + SendCompletionEmail(ctx); + } + } + } + catch (Exception exc) + { + logger.Error(exc); + ctx.Result.LastError = exc.ToString(); + } + finally + { + try + { + if (!ctx.IsPreview && ctx.Profile.Id != 0) + { + ctx.Profile.ResultInfo = XmlHelper.Serialize(ctx.Result); + + _exportProfileService.Value.UpdateExportProfile(ctx.Profile); + } + } + catch { } + + try + { + if (ctx.IsFileBasedExport && ctx.ExecuteContext.Abort != ExportAbortion.Hard && ctx.Profile.Cleanup) + { + FileSystemHelper.ClearDirectory(ctx.FolderContent, false); + } + } + catch { } + + try + { + ctx.NewsletterSubscriptions.Clear(); + ctx.ProductTemplates.Clear(); + ctx.Countries.Clear(); + ctx.Stores.Clear(); + ctx.QuantityUnits.Clear(); + ctx.DeliveryTimes.Clear(); + ctx.CategoryPathes.Clear(); + ctx.Categories.Clear(); + ctx.EntityIdsSelected.Clear(); + ctx.ProductExportContext = null; + ctx.OrderExportContext = null; + ctx.ManufacturerExportContext = null; + ctx.CategoryExportContext = null; + ctx.CustomerExportContext = null; + + ctx.ExecuteContext.CustomProperties.Clear(); + ctx.ExecuteContext.Log = null; + ctx.Log = null; + } + catch { } + } + } + + if (ctx.IsPreview || ctx.ExecuteContext.Abort == ExportAbortion.Hard) + return; + } + /// /// The name of the public export folder /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs new file mode 100644 index 0000000000..cb95e67f23 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using SmartStore.Core; +using SmartStore.Core.Domain; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Directory; +using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; +using SmartStore.Utilities; + +namespace SmartStore.Services.DataExchange.Internal +{ + internal class DataExporterContext + { + private ProductExportContext _productExportContext; + private OrderExportContext _orderExportContext; + private ManufacturerExportContext _manufacturerExportContext; + private CategoryExportContext _categoryExportContext; + private CustomerExportContext _customerExportContext; + + public DataExporterContext( + DataExportRequest request, + CancellationToken cancellationToken, + ExportProfile profile, + Provider provider, + string selectedIds = null, + Action previewData = null) + { + Request = request; + CancellationToken = cancellationToken; + Profile = profile; + Provider = provider; + Filter = XmlHelper.Deserialize(profile.Filtering); + Projection = XmlHelper.Deserialize(profile.Projection); + EntityIdsSelected = selectedIds.SplitSafe(",").Select(x => x.ToInt()).ToList(); + PreviewData = previewData; + + FolderContent = FileSystemHelper.TempDir(@"Profile\Export\{0}\Content".FormatInvariant(profile.FolderName)); + FolderRoot = System.IO.Directory.GetParent(FolderContent).FullName; + + Categories = new Dictionary(); + CategoryPathes = new Dictionary(); + Countries = new Dictionary(); + ProductTemplates = new Dictionary(); + NewsletterSubscriptions = new HashSet(); + + RecordsPerStore = new Dictionary(); + EntityIdsLoaded = new List(); + + Result = new DataExportResult + { + FileFolder = (IsFileBasedExport ? FolderContent : null) + }; + + ExecuteContext = new ExportExecuteContext(Result, CancellationToken, FolderContent); + ExecuteContext.Projection = XmlHelper.Deserialize(profile.Projection); + } + + public List EntityIdsSelected { get; private set; } + public List EntityIdsLoaded { get; set; } + + public int RecordCount { get; set; } + public Dictionary RecordsPerStore { get; set; } + public string ProgressInfo { get; set; } + public IQueryable QueryProducts { get; set; } + + public Action PreviewData { get; private set; } + public bool IsPreview + { + get { return PreviewData != null; } + } + + public DataExportRequest Request { get; private set; } + public CancellationToken CancellationToken { get; private set; } + public ExportProfile Profile { get; private set; } + public Provider Provider { get; private set; } + + public bool Supports(ExportFeatures feature) + { + return (!IsPreview && Provider.Metadata.ExportFeatures.HasFlag(feature)); + } + + public ExportFilter Filter { get; private set; } + public ExportProjection Projection { get; private set; } + public Currency ContextCurrency { get; set; } + public Customer ContextCustomer { get; set; } + public Language ContextLanguage { get; set; } + + public TraceLogger Log { get; set; } + public Store Store { get; set; } + + public string FolderRoot { get; private set; } + public string FolderContent { get; private set; } + public string ZipName + { + get { return Profile.FolderName + ".zip"; } + } + public string ZipPath + { + get { return Path.Combine(FolderRoot, ZipName); } + } + public string LogPath + { + get { return Path.Combine(FolderRoot, "log.txt"); } + } + + public bool IsFileBasedExport + { + get { return Provider == null || Provider.Value == null || Provider.Value.FileExtension.HasValue(); } + } + public string[] GetDeploymentFiles(ExportDeployment deployment) + { + if (!IsFileBasedExport) + return new string[0]; + + if (deployment.CreateZip) + return new string[] { ZipPath }; + + return System.IO.Directory.GetFiles(FolderContent, "*.*", SearchOption.AllDirectories); + } + + // data loaded once per export + public Dictionary Categories { get; set; } + public Dictionary CategoryPathes { get; set; } + public Dictionary DeliveryTimes { get; set; } + public Dictionary QuantityUnits { get; set; } + public Dictionary Stores { get; set; } + public Dictionary Languages { get; set; } + public Dictionary Countries { get; set; } + public Dictionary ProductTemplates { get; set; } + public HashSet NewsletterSubscriptions { get; set; } + + // data loaded once per page + public ProductExportContext ProductExportContext + { + get + { + return _productExportContext; + } + set + { + if (_productExportContext != null) + _productExportContext.Clear(); + + _productExportContext = value; + } + } + + public OrderExportContext OrderExportContext + { + get + { + return _orderExportContext; + } + set + { + if (_orderExportContext != null) + _orderExportContext.Clear(); + + _orderExportContext = value; + } + } + + public ManufacturerExportContext ManufacturerExportContext + { + get + { + return _manufacturerExportContext; + } + set + { + if (_manufacturerExportContext != null) + _manufacturerExportContext.Clear(); + + _manufacturerExportContext = value; + } + } + + public CategoryExportContext CategoryExportContext + { + get + { + return _categoryExportContext; + } + set + { + if (_categoryExportContext != null) + _categoryExportContext.Clear(); + + _categoryExportContext = value; + } + } + public CustomerExportContext CustomerExportContext + { + get + { + return _customerExportContext; + } + set + { + if (_customerExportContext != null) + _customerExportContext.Clear(); + + _customerExportContext = value; + } + } + + public ExportExecuteContext ExecuteContext { get; set; } + public DataExportResult Result { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntityHelper.cs index c1ebf6a7a5..e3de6b5e56 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntityHelper.cs @@ -28,7 +28,7 @@ namespace SmartStore.Services.DataExchange.Internal { public partial class DataExporter { - private void PrepareProductDescription(DataExportTaskContext ctx, dynamic dynObject, Product product) + private void PrepareProductDescription(DataExporterContext ctx, dynamic dynObject, Product product) { try { @@ -118,25 +118,25 @@ private void PrepareProductDescription(DataExportTaskContext ctx, dynamic dynObj catch { } } - private decimal? ConvertPrice(DataExportTaskContext ctx, Product product, decimal? price) + private decimal? ConvertPrice(DataExporterContext ctx, Product product, decimal? price) { if (price.HasValue) { if (ctx.Projection.ConvertNetToGrossPrices) { decimal taxRate; - price = _taxService.GetProductPrice(product, price.Value, true, ctx.ContextCustomer, out taxRate); + price = _taxService.Value.GetProductPrice(product, price.Value, true, ctx.ContextCustomer, out taxRate); } if (price != decimal.Zero) { - price = _currencyService.ConvertFromPrimaryStoreCurrency(price.Value, ctx.ContextCurrency, ctx.Store); + price = _currencyService.Value.ConvertFromPrimaryStoreCurrency(price.Value, ctx.ContextCurrency, ctx.Store); } } return price; } - private decimal CalculatePrice(DataExportTaskContext ctx, Product product, bool forAttributeCombination) + private decimal CalculatePrice(DataExporterContext ctx, Product product, bool forAttributeCombination) { decimal price = product.Price; @@ -148,22 +148,22 @@ private decimal CalculatePrice(DataExportTaskContext ctx, Product product, bool if (ctx.Projection.PriceType.Value == PriceDisplayType.LowestPrice) { bool displayFromMessage; - price = _priceCalculationService.GetLowestPrice(product, priceCalculationContext, out displayFromMessage); + price = _priceCalculationService.Value.GetLowestPrice(product, priceCalculationContext, out displayFromMessage); } else if (ctx.Projection.PriceType.Value == PriceDisplayType.PreSelectedPrice) { - price = _priceCalculationService.GetPreselectedPrice(product, priceCalculationContext); + price = _priceCalculationService.Value.GetPreselectedPrice(product, priceCalculationContext); } else if (ctx.Projection.PriceType.Value == PriceDisplayType.PriceWithoutDiscountsAndAttributes) { - price = _priceCalculationService.GetFinalPrice(product, null, ctx.ContextCustomer, decimal.Zero, false, 1, null, priceCalculationContext); + price = _priceCalculationService.Value.GetFinalPrice(product, null, ctx.ContextCustomer, decimal.Zero, false, 1, null, priceCalculationContext); } } return ConvertPrice(ctx, product, price) ?? price; } - private void MergeWithCombination(DataExportTaskContext ctx, dynamic dynObject, Product product, + private void MergeWithCombination(DataExporterContext ctx, dynamic dynObject, Product product, ICollection productAttributes, ProductVariantAttributeCombination combination) { product.MergeWithCombination(combination); @@ -172,13 +172,13 @@ private void MergeWithCombination(DataExportTaskContext ctx, dynamic dynObject, if (combination != null && ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) { - var values = _productAttributeParser.ParseProductVariantAttributeValues(combination.AttributesXml, productAttributes, ctx.Projection.LanguageId ?? 0); + var values = _productAttributeParser.Value.ParseProductVariantAttributeValues(combination.AttributesXml, productAttributes, ctx.Projection.LanguageId ?? 0); dynObject.Name = ((string)dynObject.Name).Grow(string.Join(", ", values), " "); } - dynObject._BasePriceInfo = product.GetBasePriceInfo(_services.Localization, _priceFormatter, _currencyService, _taxService, - _priceCalculationService, ctx.ContextCurrency, decimal.Zero, true); + dynObject._BasePriceInfo = product.GetBasePriceInfo(_services.Localization, _priceFormatter.Value, _currencyService.Value, _taxService.Value, + _priceCalculationService.Value, ctx.ContextCurrency, decimal.Zero, true); // navigation properties ToDeliveryTime(ctx, dynObject, product.DeliveryTimeId); @@ -212,7 +212,7 @@ private void MergeWithCombination(DataExportTaskContext ctx, dynamic dynObject, if (ctx.Projection.ConvertNetToGrossPrices) { decimal taxRate; - dynObject._OldPrice = _taxService.GetProductPrice(product, product.OldPrice, true, ctx.ContextCustomer, out taxRate); + dynObject._OldPrice = _taxService.Value.GetProductPrice(product, product.OldPrice, true, ctx.ContextCustomer, out taxRate); } else { @@ -232,7 +232,7 @@ private void MergeWithCombination(DataExportTaskContext ctx, dynamic dynObject, if (!(product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing)) { - var specialPrice = _priceCalculationService.GetSpecialPrice(product); + var specialPrice = _priceCalculationService.Value.GetSpecialPrice(product); dynObject._SpecialPrice = ConvertPrice(ctx, product, specialPrice); @@ -248,7 +248,7 @@ private void MergeWithCombination(DataExportTaskContext ctx, dynamic dynObject, } - private List GetLocalized(DataExportTaskContext ctx, T entity, params Expression>[] keySelectors) + private List GetLocalized(DataExporterContext ctx, T entity, params Expression>[] keySelectors) where T : BaseEntity, ILocalizedEntity { if (ctx.Languages.Count <= 1) @@ -266,7 +266,7 @@ private List GetLocalized(DataExportTaskContext ctx, T entity, param // add SeName if (isSlugSupported) { - var value = _urlRecordService.GetActiveSlug(entity.Id, localeKeyGroup, language.Value.Id); + var value = _urlRecordService.Value.GetActiveSlug(entity.Id, localeKeyGroup, language.Value.Id); if (value.HasValue()) { dynamic exp = new HybridExpando(); @@ -283,7 +283,7 @@ private List GetLocalized(DataExportTaskContext ctx, T entity, param var member = keySelector.Body as MemberExpression; var propInfo = member.Member as PropertyInfo; string localeKey = propInfo.Name; - var value = _localizedEntityService.GetLocalizedValue(language.Value.Id, entity.Id, localeKeyGroup, localeKey); + var value = _localizedEntityService.Value.GetLocalizedValue(language.Value.Id, entity.Id, localeKeyGroup, localeKey); // we better not export empty values. the risk is to high that they are imported and unnecessary fill databases. if (value.HasValue()) @@ -301,7 +301,7 @@ private List GetLocalized(DataExportTaskContext ctx, T entity, param return (localized.Count == 0 ? null : localized); } - private dynamic ToDynamic(DataExportTaskContext ctx, Currency currency) + private dynamic ToDynamic(DataExporterContext ctx, Currency currency) { if (currency == null) return null; @@ -314,7 +314,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, Currency currency) return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, Language language) + private dynamic ToDynamic(DataExporterContext ctx, Language language) { if (language == null) return null; @@ -323,7 +323,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, Language language) return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, Country country) + private dynamic ToDynamic(DataExporterContext ctx, Country country) { if (country == null) return null; @@ -336,7 +336,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, Country country) return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, Address address) + private dynamic ToDynamic(DataExporterContext ctx, Address address) { if (address == null) return null; @@ -362,7 +362,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, Address address) return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, RewardPointsHistory points) + private dynamic ToDynamic(DataExporterContext ctx, RewardPointsHistory points) { if (points == null) return null; @@ -372,7 +372,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, RewardPointsHistory points) return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, Customer customer) + private dynamic ToDynamic(DataExporterContext ctx, Customer customer) { if (customer == null) return null; @@ -393,7 +393,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, Customer customer) return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, Store store) + private dynamic ToDynamic(DataExporterContext ctx, Store store) { if (store == null) return null; @@ -406,7 +406,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, Store store) return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, DeliveryTime deliveryTime) + private dynamic ToDynamic(DataExporterContext ctx, DeliveryTime deliveryTime) { if (deliveryTime == null) return null; @@ -419,7 +419,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, DeliveryTime deliveryTime) return result; } - private void ToDeliveryTime(DataExportTaskContext ctx, dynamic parent, int? deliveryTimeId) + private void ToDeliveryTime(DataExporterContext ctx, dynamic parent, int? deliveryTimeId) { if (ctx.DeliveryTimes != null) { @@ -430,7 +430,7 @@ private void ToDeliveryTime(DataExportTaskContext ctx, dynamic parent, int? deli } } - private dynamic ToDynamic(DataExportTaskContext ctx, QuantityUnit quantityUnit) + private dynamic ToDynamic(DataExporterContext ctx, QuantityUnit quantityUnit) { if (quantityUnit == null) return null; @@ -447,7 +447,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, QuantityUnit quantityUnit) return result; } - private void ToQuantityUnit(DataExportTaskContext ctx, dynamic parent, int? quantityUnitId) + private void ToQuantityUnit(DataExporterContext ctx, dynamic parent, int? quantityUnitId) { if (ctx.QuantityUnits != null) { @@ -458,26 +458,26 @@ private void ToQuantityUnit(DataExportTaskContext ctx, dynamic parent, int? quan } } - private dynamic ToDynamic(DataExportTaskContext ctx, Picture picture, int thumbPictureSize, int detailsPictureSize) + private dynamic ToDynamic(DataExporterContext ctx, Picture picture, int thumbPictureSize, int detailsPictureSize) { if (picture == null) return null; dynamic result = new DynamicEntity(picture); - result._ThumbImageUrl = _pictureService.GetPictureUrl(picture, thumbPictureSize, false, ctx.Store.Url); - result._ImageUrl = _pictureService.GetPictureUrl(picture, detailsPictureSize, false, ctx.Store.Url); - result._FullSizeImageUrl = _pictureService.GetPictureUrl(picture, 0, false, ctx.Store.Url); + result._ThumbImageUrl = _pictureService.Value.GetPictureUrl(picture, thumbPictureSize, false, ctx.Store.Url); + result._ImageUrl = _pictureService.Value.GetPictureUrl(picture, detailsPictureSize, false, ctx.Store.Url); + result._FullSizeImageUrl = _pictureService.Value.GetPictureUrl(picture, 0, false, ctx.Store.Url); - var relativeUrl = _pictureService.GetPictureUrl(picture); + var relativeUrl = _pictureService.Value.GetPictureUrl(picture); result._FileName = relativeUrl.Substring(relativeUrl.LastIndexOf("/") + 1); - result._ThumbLocalPath = _pictureService.GetThumbLocalPath(picture); + result._ThumbLocalPath = _pictureService.Value.GetThumbLocalPath(picture); return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, ProductVariantAttribute pva) + private dynamic ToDynamic(DataExporterContext ctx, ProductVariantAttribute pva) { if (pva == null) return null; @@ -511,7 +511,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, ProductVariantAttribute pva return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, ProductVariantAttributeCombination pvac) + private dynamic ToDynamic(DataExporterContext ctx, ProductVariantAttributeCombination pvac) { if (pvac == null) return null; @@ -524,7 +524,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, ProductVariantAttributeComb return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, Manufacturer manufacturer) + private dynamic ToDynamic(DataExporterContext ctx, Manufacturer manufacturer) { if (manufacturer == null) return null; @@ -550,7 +550,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, Manufacturer manufacturer) return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, Category category) + private dynamic ToDynamic(DataExporterContext ctx, Category category) { if (category == null) return null; @@ -580,7 +580,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, Category category) return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, Product product) + private dynamic ToDynamic(DataExporterContext ctx, Product product) { if (product == null) return null; @@ -619,7 +619,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, Product product) return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, Order order) + private dynamic ToDynamic(DataExporterContext ctx, Order order) { if (order == null) return null; @@ -642,7 +642,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, Order order) return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, OrderItem orderItem) + private dynamic ToDynamic(DataExporterContext ctx, OrderItem orderItem) { if (orderItem == null) return null; @@ -654,7 +654,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, OrderItem orderItem) return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, Shipment shipment) + private dynamic ToDynamic(DataExporterContext ctx, Shipment shipment) { if (shipment == null) return null; @@ -673,7 +673,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, Shipment shipment) return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, Discount discount) + private dynamic ToDynamic(DataExporterContext ctx, Discount discount) { if (discount == null) return null; @@ -683,7 +683,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, Discount discount) return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, ProductSpecificationAttribute psa) + private dynamic ToDynamic(DataExporterContext ctx, ProductSpecificationAttribute psa) { if (psa == null) return null; @@ -709,7 +709,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, ProductSpecificationAttribu return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, GenericAttribute genericAttribute) + private dynamic ToDynamic(DataExporterContext ctx, GenericAttribute genericAttribute) { if (genericAttribute == null) return null; @@ -719,7 +719,7 @@ private dynamic ToDynamic(DataExportTaskContext ctx, GenericAttribute genericAtt return result; } - private dynamic ToDynamic(DataExportTaskContext ctx, NewsLetterSubscription subscription) + private dynamic ToDynamic(DataExporterContext ctx, NewsLetterSubscription subscription) { if (subscription == null) return null; @@ -730,13 +730,13 @@ private dynamic ToDynamic(DataExportTaskContext ctx, NewsLetterSubscription subs } - private List Convert(DataExportTaskContext ctx, Product product) + private List Convert(DataExporterContext ctx, Product product) { var result = new List(); var languageId = (ctx.Projection.LanguageId ?? 0); var productTemplate = ctx.ProductTemplates.FirstOrDefault(x => x.Key == product.ProductTemplateId); - var pictureSize = _mediaSettings.ProductDetailsPictureSize; + var pictureSize = _mediaSettings.Value.ProductDetailsPictureSize; if (ctx.Supports(ExportFeatures.CanIncludeMainPicture) && ctx.Projection.PictureSize > 0) pictureSize = ctx.Projection.PictureSize; @@ -762,12 +762,12 @@ private List Convert(DataExportTaskContext ctx, Product product) if (ctx.Categories.Count > 0 && ctx.CategoryPathes.Count > 0) { - dynObject._CategoryPath = _categoryService.GetCategoryPath( + dynObject._CategoryPath = _categoryService.Value.GetCategoryPath( product, null, x => ctx.CategoryPathes.ContainsKey(x) ? ctx.CategoryPathes[x] : null, (id, value) => ctx.CategoryPathes[id] = value, - x => ctx.Categories.ContainsKey(x) ? ctx.Categories[x] : _categoryService.GetCategoryById(x), + x => ctx.Categories.ContainsKey(x) ? ctx.Categories[x] : _categoryService.Value.GetCategoryById(x), productCategories.OrderBy(x => x.DisplayOrder).FirstOrDefault() ); } @@ -782,7 +782,7 @@ private List Convert(DataExportTaskContext ctx, Product product) { dynamic dyn = new DynamicEntity(x); - dyn.Picture = ToDynamic(ctx, x.Picture, _mediaSettings.ProductThumbPictureSize, pictureSize); + dyn.Picture = ToDynamic(ctx, x.Picture, _mediaSettings.Value.ProductThumbPictureSize, pictureSize); return dyn; }) @@ -797,7 +797,7 @@ private List Convert(DataExportTaskContext ctx, Product product) dyn.Manufacturer = ToDynamic(ctx, x.Manufacturer); if (x.Manufacturer != null && x.Manufacturer.PictureId.HasValue) - dyn.Manufacturer.Picture = ToDynamic(ctx, x.Manufacturer.Picture, _mediaSettings.ManufacturerThumbPictureSize, _mediaSettings.ManufacturerThumbPictureSize); + dyn.Manufacturer.Picture = ToDynamic(ctx, x.Manufacturer.Picture, _mediaSettings.Value.ManufacturerThumbPictureSize, _mediaSettings.Value.ManufacturerThumbPictureSize); else dyn.Manufacturer.Picture = null; @@ -814,7 +814,7 @@ private List Convert(DataExportTaskContext ctx, Product product) dyn.Category = ToDynamic(ctx, x.Category); if (x.Category != null && x.Category.PictureId.HasValue) - dyn.Category.Picture = ToDynamic(ctx, x.Category.Picture, _mediaSettings.CategoryThumbPictureSize, _mediaSettings.CategoryThumbPictureSize); + dyn.Category.Picture = ToDynamic(ctx, x.Category.Picture, _mediaSettings.Value.CategoryThumbPictureSize, _mediaSettings.Value.CategoryThumbPictureSize); if (dynObject._CategoryName == null) dynObject._CategoryName = (string)dyn.Category.Name; @@ -839,7 +839,7 @@ private List Convert(DataExportTaskContext ctx, Product product) var assignedPicture = productPictures.FirstOrDefault(y => y.PictureId == pictureId); if (assignedPicture != null && assignedPicture.Picture != null) { - assignedPictures.Add(ToDynamic(ctx, assignedPicture.Picture, _mediaSettings.ProductThumbPictureSize, pictureSize)); + assignedPictures.Add(ToDynamic(ctx, assignedPicture.Picture, _mediaSettings.Value.ProductThumbPictureSize, pictureSize)); } } @@ -934,9 +934,9 @@ private List Convert(DataExportTaskContext ctx, Product product) if (ctx.Supports(ExportFeatures.CanIncludeMainPicture)) { if (productPictures != null && productPictures.Any()) - dynObject._MainPictureUrl = _pictureService.GetPictureUrl(productPictures.First().Picture, ctx.Projection.PictureSize, storeLocation: ctx.Store.Url); + dynObject._MainPictureUrl = _pictureService.Value.GetPictureUrl(productPictures.First().Picture, ctx.Projection.PictureSize, storeLocation: ctx.Store.Url); else - dynObject._MainPictureUrl = _pictureService.GetDefaultPictureUrl(ctx.Projection.PictureSize, storeLocation: ctx.Store.Url); + dynObject._MainPictureUrl = _pictureService.Value.GetDefaultPictureUrl(ctx.Projection.PictureSize, storeLocation: ctx.Store.Url); } #endregion @@ -975,7 +975,7 @@ private List Convert(DataExportTaskContext ctx, Product product) return result; } - private List Convert(DataExportTaskContext ctx, Order order) + private List Convert(DataExporterContext ctx, Order order) { var result = new List(); @@ -1025,8 +1025,8 @@ private List Convert(DataExportTaskContext ctx, Order order) dyn.Product._ProductTemplateViewPath = (productTemplate.Value == null ? "" : productTemplate.Value.ViewPath); - dyn.Product._BasePriceInfo = e.Product.GetBasePriceInfo(_services.Localization, _priceFormatter, _currencyService, _taxService, - _priceCalculationService, ctx.ContextCurrency, decimal.Zero, true); + dyn.Product._BasePriceInfo = e.Product.GetBasePriceInfo(_services.Localization, _priceFormatter.Value, _currencyService.Value, _taxService.Value, + _priceCalculationService.Value, ctx.ContextCurrency, decimal.Zero, true); ToDeliveryTime(ctx, dyn.Product, e.Product.DeliveryTimeId); ToQuantityUnit(ctx, dyn.Product, e.Product.QuantityUnitId); @@ -1044,7 +1044,7 @@ private List Convert(DataExportTaskContext ctx, Order order) return result; } - private List Convert(DataExportTaskContext ctx, Manufacturer manufacturer) + private List Convert(DataExporterContext ctx, Manufacturer manufacturer) { var result = new List(); @@ -1057,7 +1057,7 @@ private List Convert(DataExportTaskContext ctx, Manufacturer manufactur var pictures = ctx.ManufacturerExportContext.Pictures.Load(manufacturer.PictureId.Value); if (pictures.Count > 0) - dynObject.Picture = ToDynamic(ctx, pictures.First(), _mediaSettings.ManufacturerThumbPictureSize, _mediaSettings.ManufacturerThumbPictureSize); + dynObject.Picture = ToDynamic(ctx, pictures.First(), _mediaSettings.Value.ManufacturerThumbPictureSize, _mediaSettings.Value.ManufacturerThumbPictureSize); } dynObject.ProductManufacturers = productManufacturers @@ -1075,7 +1075,7 @@ private List Convert(DataExportTaskContext ctx, Manufacturer manufactur return result; } - private List Convert(DataExportTaskContext ctx, Category category) + private List Convert(DataExporterContext ctx, Category category) { var result = new List(); @@ -1088,7 +1088,7 @@ private List Convert(DataExportTaskContext ctx, Category category) var pictures = ctx.CategoryExportContext.Pictures.Load(category.PictureId.Value); if (pictures.Count > 0) - dynObject.Picture = ToDynamic(ctx, pictures.First(), _mediaSettings.CategoryThumbPictureSize, _mediaSettings.CategoryThumbPictureSize); + dynObject.Picture = ToDynamic(ctx, pictures.First(), _mediaSettings.Value.CategoryThumbPictureSize, _mediaSettings.Value.CategoryThumbPictureSize); } dynObject.ProductCategories = productCategories @@ -1106,7 +1106,7 @@ private List Convert(DataExportTaskContext ctx, Category category) return result; } - private List Convert(DataExportTaskContext ctx, Customer customer) + private List Convert(DataExporterContext ctx, Customer customer) { var result = new List(); @@ -1142,7 +1142,7 @@ private List Convert(DataExportTaskContext ctx, Customer customer) return result; } - private List Convert(DataExportTaskContext ctx, NewsLetterSubscription subscription) + private List Convert(DataExporterContext ctx, NewsLetterSubscription subscription) { var result = new List(); diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 0e67aca232..83fe5c10e4 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -169,6 +169,7 @@ + From bca184d4728c09f28e475242bd2b251e6d9c66ca Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 11 Nov 2015 14:40:04 +0100 Subject: [PATCH 205/874] Further IDataExporter implementation --- .../DataExchange/Internal/DataExporter.cs | 184 ++++++++++++------ .../Internal/DataExporterContext.cs | 35 ++-- 2 files changed, 140 insertions(+), 79 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs index 391b210db6..37d64d5fbb 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs @@ -29,7 +29,6 @@ using SmartStore.Services.Orders; using SmartStore.Services.Seo; using SmartStore.Services.Shipping; -using SmartStore.Services.Tasks; using SmartStore.Services.Tax; using SmartStore.Utilities; using SmartStore.Utilities.Threading; @@ -158,8 +157,8 @@ private void SetProgress(DataExporterContext ctx, int loadedRecords) { int totalRecords = ctx.RecordsPerStore.Sum(x => x.Value); - if (ctx.Profile.Limit > 0 && totalRecords > ctx.Profile.Limit) - totalRecords = ctx.Profile.Limit; + if (ctx.Request.Profile.Limit > 0 && totalRecords > ctx.Request.Profile.Limit) + totalRecords = ctx.Request.Profile.Limit; ctx.RecordCount = Math.Min(ctx.RecordCount + loadedRecords, totalRecords); @@ -179,15 +178,15 @@ private void SetProgress(DataExporterContext ctx, string message) private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, int pageIndex = 0) { - var offset = ctx.Profile.Offset + (pageIndex * PageSize); + var offset = ctx.Request.Profile.Offset + (pageIndex * PageSize); - var limit = (ctx.IsPreview ? PageSize : ctx.Profile.Limit); + var limit = (ctx.IsPreview ? PageSize : ctx.Request.Profile.Limit); - var recordsPerSegment = (ctx.IsPreview ? 0 : ctx.Profile.BatchSize); + var recordsPerSegment = (ctx.IsPreview ? 0 : ctx.Request.Profile.BatchSize); - var totalCount = ctx.Profile.Offset + ctx.RecordsPerStore.First(x => x.Key == ctx.Store.Id).Value; + var totalCount = ctx.Request.Profile.Offset + ctx.RecordsPerStore.First(x => x.Key == ctx.Store.Id).Value; - switch (ctx.Provider.Value.EntityType) + switch (ctx.Request.Provider.Value.EntityType) { case ExportEntityType.Product: ctx.ExecuteContext.Segmenter = new ExportDataSegmenter @@ -311,11 +310,11 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho { if (method == "Execute") { - ctx.Provider.Value.Execute(ctx.ExecuteContext); + ctx.Request.Provider.Value.Execute(ctx.ExecuteContext); } else if (method == "OnExecuted") { - ctx.Provider.Value.OnExecuted(ctx.ExecuteContext); + ctx.Request.Provider.Value.OnExecuted(ctx.ExecuteContext); } if (ctx.IsFileBasedExport && path.HasValue()) @@ -355,17 +354,17 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho private void SendCompletionEmail(DataExporterContext ctx) { - var emailAccount = _emailAccountService.Value.GetEmailAccountById(ctx.Profile.EmailAccountId); + var emailAccount = _emailAccountService.Value.GetEmailAccountById(ctx.Request.Profile.EmailAccountId); var smtpContext = new SmtpContext(emailAccount); var message = new EmailMessage(); var storeInfo = "{0} ({1})".FormatInvariant(ctx.Store.Name, ctx.Store.Url); - message.To.AddRange(ctx.Profile.CompletedEmailAddresses.SplitSafe(",").Where(x => x.IsEmail()).Select(x => new EmailAddress(x))); + message.To.AddRange(ctx.Request.Profile.CompletedEmailAddresses.SplitSafe(",").Where(x => x.IsEmail()).Select(x => new EmailAddress(x))); message.From = new EmailAddress(emailAccount.Email, emailAccount.DisplayName); message.Subject = _services.Localization.GetResource("Admin.DataExchange.Export.CompletedEmail.Subject", ctx.Projection.LanguageId ?? 0) - .FormatInvariant(ctx.Profile.Name); + .FormatInvariant(ctx.Request.Profile.Name); message.Body = _services.Localization.GetResource("Admin.DataExchange.Export.CompletedEmail.Body", ctx.Projection.LanguageId ?? 0) .FormatInvariant(storeInfo); @@ -387,7 +386,7 @@ private IQueryable GetProductQuery(DataExporterContext ctx, int skip, i { OrderBy = ProductSortingEnum.CreatedOn, ProductIds = ctx.EntityIdsSelected, - StoreId = (ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId), + StoreId = (ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId), VisibleIndividuallyOnly = true, PriceMin = ctx.Filter.PriceMinimum, PriceMax = ctx.Filter.PriceMaximum, @@ -454,7 +453,7 @@ private List GetProducts(DataExporterContext ctx, int skip) { OrderBy = ProductSortingEnum.CreatedOn, PageSize = int.MaxValue, - StoreId = (ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId), + StoreId = (ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId), VisibleIndividuallyOnly = false, ParentGroupedProductId = product.Id }; @@ -485,7 +484,7 @@ private List GetProducts(DataExporterContext ctx, int skip) private IQueryable GetOrderQuery(DataExporterContext ctx, int skip, int take) { var query = _orderService.Value.GetOrders( - ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId, + ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId, ctx.Projection.CustomerId ?? 0, ctx.Filter.CreatedFrom.HasValue ? (DateTime?)_dateTimeHelper.Value.ConvertToUtcTime(ctx.Filter.CreatedFrom.Value, _dateTimeHelper.Value.CurrentTimeZone) : null, ctx.Filter.CreatedTo.HasValue ? (DateTime?)_dateTimeHelper.Value.ConvertToUtcTime(ctx.Filter.CreatedTo.Value, _dateTimeHelper.Value.CurrentTimeZone) : null, @@ -536,7 +535,7 @@ private List GetOrders(DataExporterContext ctx, int skip) private IQueryable GetManufacturerQuery(DataExporterContext ctx, int skip, int take) { var showHidden = !ctx.Filter.IsPublished.HasValue; - var storeId = (ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); + var storeId = (ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); var query = _manufacturerService.Value.GetManufacturers(showHidden, storeId); @@ -569,7 +568,7 @@ private List GetManufacturers(DataExporterContext ctx, int skip) private IQueryable GetCategoryQuery(DataExporterContext ctx, int skip, int take) { var showHidden = !ctx.Filter.IsPublished.HasValue; - var storeId = (ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); + var storeId = (ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); var query = _categoryService.Value.GetCategories(null, showHidden, null, true, storeId); @@ -644,7 +643,7 @@ private List GetCustomers(DataExporterContext ctx, int skip) private IQueryable GetNewsLetterSubscriptionQuery(DataExporterContext ctx, int skip, int take) { - var storeId = (ctx.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); + var storeId = (ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId); var query = _subscriptionRepository.Value.TableUntracked; @@ -706,7 +705,7 @@ private List Init(DataExporterContext ctx, int? totalRecords = null) ctx.Stores = _services.StoreService.GetAllStores().ToDictionary(x => x.Id, x => x); ctx.Languages = _languageService.Value.GetAllLanguages(true).ToDictionary(x => x.Id, x => x); - if (!ctx.IsPreview && ctx.Profile.PerStore) + if (!ctx.IsPreview && ctx.Request.Profile.PerStore) { result = new List(ctx.Stores.Values.Where(x => x.Id == ctx.Filter.StoreId || ctx.Filter.StoreId == 0)); } @@ -732,25 +731,25 @@ private List Init(DataExporterContext ctx, int? totalRecords = null) } else { - switch (ctx.Provider.Value.EntityType) + switch (ctx.Request.Provider.Value.EntityType) { case ExportEntityType.Product: - totalCount = GetProductQuery(ctx, ctx.Profile.Offset, int.MaxValue).Count(); + totalCount = GetProductQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue).Count(); break; case ExportEntityType.Order: - totalCount = GetOrderQuery(ctx, ctx.Profile.Offset, int.MaxValue).Count(); + totalCount = GetOrderQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue).Count(); break; case ExportEntityType.Manufacturer: - totalCount = GetManufacturerQuery(ctx, ctx.Profile.Offset, int.MaxValue).Count(); + totalCount = GetManufacturerQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue).Count(); break; case ExportEntityType.Category: - totalCount = GetCategoryQuery(ctx, ctx.Profile.Offset, int.MaxValue).Count(); + totalCount = GetCategoryQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue).Count(); break; case ExportEntityType.Customer: - totalCount = GetCustomerQuery(ctx, ctx.Profile.Offset, int.MaxValue).Count(); + totalCount = GetCustomerQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue).Count(); break; case ExportEntityType.NewsLetterSubscription: - totalCount = GetNewsLetterSubscriptionQuery(ctx, ctx.Profile.Offset, int.MaxValue).Count(); + totalCount = GetNewsLetterSubscriptionQuery(ctx, ctx.Request.Profile.Offset, int.MaxValue).Count(); break; } } @@ -775,18 +774,18 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) logHead.AppendLine(); logHead.AppendLine(new string('-', 40)); logHead.AppendLine("SmartStore.NET:\t\tv." + SmartStoreVersion.CurrentFullVersion); - logHead.Append("Export profile:\t\t" + ctx.Profile.Name); - logHead.AppendLine(ctx.Profile.Id == 0 ? " (volatile)" : " (Id {0})".FormatInvariant(ctx.Profile.Id)); + logHead.Append("Export profile:\t\t" + ctx.Request.Profile.Name); + logHead.AppendLine(ctx.Request.Profile.Id == 0 ? " (volatile)" : " (Id {0})".FormatInvariant(ctx.Request.Profile.Id)); - logHead.AppendLine("Export provider:\t{0} ({1})".FormatInvariant(ctx.Provider.Metadata.FriendlyName, ctx.Profile.ProviderSystemName)); + logHead.AppendLine("Export provider:\t{0} ({1})".FormatInvariant(ctx.Request.Provider.Metadata.FriendlyName, ctx.Request.Profile.ProviderSystemName)); - var plugin = ctx.Provider.Metadata.PluginDescriptor; + var plugin = ctx.Request.Provider.Metadata.PluginDescriptor; logHead.Append("Plugin:\t\t\t\t"); logHead.AppendLine(plugin == null ? "".NaIfEmpty() : "{0} ({1}) v.{2}".FormatInvariant(plugin.FriendlyName, plugin.SystemName, plugin.Version.ToString())); - logHead.AppendLine("Entity:\t\t\t\t" + ctx.Provider.Value.EntityType.ToString()); + logHead.AppendLine("Entity:\t\t\t\t" + ctx.Request.Provider.Value.EntityType.ToString()); - var storeInfo = (ctx.Profile.PerStore ? "{0} (Id {1})".FormatInvariant(ctx.Store.Name, ctx.Store.Id) : "All stores"); + var storeInfo = (ctx.Request.Profile.PerStore ? "{0} (Id {1})".FormatInvariant(ctx.Store.Name, ctx.Store.Id) : "All stores"); logHead.Append("Store:\t\t\t\t" + storeInfo); ctx.Log.Information(logHead.ToString()); @@ -796,18 +795,18 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) ctx.ExecuteContext.MaxFileNameLength = _dataExchangeSettings.Value.MaxFileNameLength; - ctx.ExecuteContext.HasPublicDeployment = ctx.Profile.Deployments.Any(x => x.IsPublic && x.DeploymentType == ExportDeploymentType.FileSystem); + 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.Provider.Value.FileExtension.HasValue() ? ctx.Provider.Value.FileExtension.ToLower().EnsureStartsWith(".") : ""); + var fileExtension = (ctx.Request.Provider.Value.FileExtension.HasValue() ? ctx.Request.Provider.Value.FileExtension.ToLower().EnsureStartsWith(".") : ""); using (var segmenter = CreateSegmenter(ctx)) { if (segmenter == null) { - throw new SmartException("Unsupported entity type '{0}'".FormatInvariant(ctx.Provider.Value.EntityType.ToString())); + throw new SmartException("Unsupported entity type '{0}'".FormatInvariant(ctx.Request.Provider.Value.EntityType.ToString())); } if (segmenter.TotalRecords <= 0) @@ -824,7 +823,7 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) if (ctx.IsFileBasedExport) { - var resolvedPattern = ctx.Profile.ResolveFileNamePattern(ctx.Store, ++fileIndex, ctx.ExecuteContext.MaxFileNameLength); + var resolvedPattern = ctx.Request.Profile.ResolveFileNamePattern(ctx.Store, ++fileIndex, ctx.ExecuteContext.MaxFileNameLength); ctx.ExecuteContext.FileName = resolvedPattern + fileExtension; path = Path.Combine(ctx.ExecuteContext.Folder, ctx.ExecuteContext.FileName); @@ -875,7 +874,7 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) private void ExportCoreOuter(DataExporterContext ctx) { - if (ctx.Profile == null || !ctx.Profile.Enabled) + if (ctx.Request.Profile == null || !ctx.Request.Profile.Enabled) return; FileSystemHelper.Delete(ctx.LogPath); @@ -886,7 +885,7 @@ private void ExportCoreOuter(DataExporterContext ctx) { try { - if (!ctx.Provider.IsValid()) + if (!ctx.Request.Provider.IsValid()) { throw new SmartException("Export aborted because the export provider is not valid"); } @@ -895,12 +894,12 @@ private void ExportCoreOuter(DataExporterContext ctx) ctx.ExecuteContext.Log = logger; ctx.ProgressInfo = T("Admin.DataExchange.Export.ProgressInfo"); - if (ctx.Profile.ProviderConfigData.HasValue()) + if (ctx.Request.Profile.ProviderConfigData.HasValue()) { - var configInfo = ctx.Provider.Value.ConfigurationInfo; + var configInfo = ctx.Request.Provider.Value.ConfigurationInfo; if (configInfo != null) { - ctx.ExecuteContext.ConfigurationData = XmlHelper.Deserialize(ctx.Profile.ProviderConfigData, configInfo.ModelType); + ctx.ExecuteContext.ConfigurationData = XmlHelper.Deserialize(ctx.Request.Profile.ProviderConfigData, configInfo.ModelType); } } @@ -911,18 +910,18 @@ private void ExportCoreOuter(DataExporterContext ctx) ctx.QuantityUnits = _quantityUnitService.Value.GetAllQuantityUnits().ToDictionary(x => x.Id); ctx.ProductTemplates = _productTemplateService.Value.GetAllProductTemplates().ToDictionary(x => x.Id); - if (ctx.Provider.Value.EntityType == ExportEntityType.Product) + if (ctx.Request.Provider.Value.EntityType == ExportEntityType.Product) { var allCategories = _categoryService.Value.GetAllCategories(showHidden: true, applyNavigationFilters: false); ctx.Categories = allCategories.ToDictionary(x => x.Id); } - if (ctx.Provider.Value.EntityType == ExportEntityType.Order) + if (ctx.Request.Provider.Value.EntityType == ExportEntityType.Order) { ctx.Countries = _countryService.Value.GetAllCountries(true).ToDictionary(x => x.Id, x => x); } - if (ctx.Provider.Value.EntityType == ExportEntityType.Customer) + if (ctx.Request.Provider.Value.EntityType == ExportEntityType.Customer) { var subscriptionEmails = _subscriptionRepository.Value.TableUntracked .Where(x => x.Active) @@ -946,7 +945,7 @@ private void ExportCoreOuter(DataExporterContext ctx) { if (ctx.IsFileBasedExport) { - if (ctx.Profile.CreateZipArchive || ctx.Profile.Deployments.Any(x => x.Enabled && x.CreateZip)) + if (ctx.Request.Profile.CreateZipArchive || ctx.Request.Profile.Deployments.Any(x => x.Enabled && x.CreateZip)) { ZipFile.CreateFromDirectory(ctx.FolderContent, ctx.ZipPath, CompressionLevel.Fastest, true); } @@ -954,7 +953,7 @@ private void ExportCoreOuter(DataExporterContext ctx) SetProgress(ctx, T("Common.Deployment")); // TODO: deployment - //foreach (var deployment in ctx.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) + //foreach (var deployment in ctx.Request.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) //{ // try // { @@ -982,7 +981,7 @@ private void ExportCoreOuter(DataExporterContext ctx) //} } - if (ctx.Profile.EmailAccountId != 0 && ctx.Profile.CompletedEmailAddresses.HasValue()) + if (ctx.Request.Profile.EmailAccountId != 0 && ctx.Request.Profile.CompletedEmailAddresses.HasValue()) { SendCompletionEmail(ctx); } @@ -997,18 +996,18 @@ private void ExportCoreOuter(DataExporterContext ctx) { try { - if (!ctx.IsPreview && ctx.Profile.Id != 0) + if (!ctx.IsPreview && ctx.Request.Profile.Id != 0) { - ctx.Profile.ResultInfo = XmlHelper.Serialize(ctx.Result); + ctx.Request.Profile.ResultInfo = XmlHelper.Serialize(ctx.Result); - _exportProfileService.Value.UpdateExportProfile(ctx.Profile); + _exportProfileService.Value.UpdateExportProfile(ctx.Request.Profile); } } catch { } try { - if (ctx.IsFileBasedExport && ctx.ExecuteContext.Abort != ExportAbortion.Hard && ctx.Profile.Cleanup) + if (ctx.IsFileBasedExport && ctx.ExecuteContext.Abort != ExportAbortion.Hard && ctx.Request.Profile.Cleanup) { FileSystemHelper.ClearDirectory(ctx.FolderContent, false); } @@ -1059,17 +1058,92 @@ public static int PageSize public DataExportResult Export(DataExportRequest request, CancellationToken cancellationToken) { - return null; + var ctx = new DataExporterContext(request, cancellationToken); + + ExportCoreOuter(ctx); + + if (ctx.Result != null && ctx.Result.Succeeded && ctx.Result.Files.Count > 0) + { + string prefix = null; + string suffix = null; + var extension = Path.GetExtension(ctx.Result.Files.First().FileName); + var selectedEntityCount = (request.EntitiesToExport == null ? 0 : request.EntitiesToExport.Count()); + + if (request.Provider.Value.EntityType == ExportEntityType.Product) + prefix = T("Admin.Catalog.Products"); + else if (request.Provider.Value.EntityType == ExportEntityType.Order) + prefix = T("Admin.Orders"); + else if (request.Provider.Value.EntityType == ExportEntityType.Category) + prefix = T("Admin.Catalog.Categories"); + else if (request.Provider.Value.EntityType == ExportEntityType.Manufacturer) + prefix = T("Admin.Catalog.Manufacturers"); + else if (request.Provider.Value.EntityType == ExportEntityType.Customer) + prefix = T("Admin.Customers"); + else if (request.Provider.Value.EntityType == ExportEntityType.NewsLetterSubscription) + prefix = T("Admin.Promotions.NewsLetterSubscriptions"); + else + prefix = request.Provider.Value.EntityType.ToString(); + + if (selectedEntityCount == 0) + suffix = T("Common.All"); + else + suffix = (selectedEntityCount == 1 ? request.EntitiesToExport.First().ToString() : T("Admin.Common.Selected").Text); + + ctx.Result.DownloadFileName = string.Concat(prefix, "-", suffix).ToLower().ToValidFileName() + extension; + } + + return ctx.Result; } public IList Preview(DataExportRequest request) { - return null; + var pageIndex = (request.CustomData.ContainsKey("PageIndex") ? (int)request.CustomData["PageIndex"] : 0); + var totalRecords = (request.CustomData.ContainsKey("TotalRecords") ? (int)request.CustomData["TotalRecords"] : 0); + + var result = new List(); + var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(5.0)); + + var ctx = new DataExporterContext(request, cancellation.Token, null, true); + + var unused = Init(ctx, totalRecords); + + using (var segmenter = CreateSegmenter(ctx, pageIndex)) + { + if (segmenter == null) + { + throw new SmartException("Unsupported entity type '{0}'".FormatInvariant(ctx.Request.Provider.Value.EntityType.ToString())); + } + + while (segmenter.HasData) + { + segmenter.RecordPerSegmentCount = 0; + + while (segmenter.ReadNextSegment()) + { + result.AddRange(segmenter.CurrentSegment); + } + } + } + + if (ctx.Result.LastError.HasValue()) + { + _services.Notifier.Error(ctx.Result.LastError); + } + + return result; } public long GetDataCount(DataExportRequest request) { - return 0; + var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(5.0)); + + var ctx = new DataExporterContext(request, cancellation.Token, null, true); + + var unused = Init(ctx); + + var totalCount = ctx.RecordsPerStore.First().Value; + + return totalCount; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs index cb95e67f23..2ae610ca6d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; @@ -12,7 +11,6 @@ using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Logging; -using SmartStore.Core.Plugins; using SmartStore.Utilities; namespace SmartStore.Services.DataExchange.Internal @@ -28,21 +26,17 @@ internal class DataExporterContext public DataExporterContext( DataExportRequest request, CancellationToken cancellationToken, - ExportProfile profile, - Provider provider, string selectedIds = null, - Action previewData = null) + bool isPreview = false) { Request = request; CancellationToken = cancellationToken; - Profile = profile; - Provider = provider; - Filter = XmlHelper.Deserialize(profile.Filtering); - Projection = XmlHelper.Deserialize(profile.Projection); + Filter = XmlHelper.Deserialize(request.Profile.Filtering); + Projection = XmlHelper.Deserialize(request.Profile.Projection); EntityIdsSelected = selectedIds.SplitSafe(",").Select(x => x.ToInt()).ToList(); - PreviewData = previewData; + IsPreview = isPreview; - FolderContent = FileSystemHelper.TempDir(@"Profile\Export\{0}\Content".FormatInvariant(profile.FolderName)); + FolderContent = FileSystemHelper.TempDir(@"Profile\Export\{0}\Content".FormatInvariant(request.Profile.FolderName)); FolderRoot = System.IO.Directory.GetParent(FolderContent).FullName; Categories = new Dictionary(); @@ -60,7 +54,7 @@ public DataExporterContext( }; ExecuteContext = new ExportExecuteContext(Result, CancellationToken, FolderContent); - ExecuteContext.Projection = XmlHelper.Deserialize(profile.Projection); + ExecuteContext.Projection = XmlHelper.Deserialize(request.Profile.Projection); } public List EntityIdsSelected { get; private set; } @@ -71,20 +65,13 @@ public DataExporterContext( public string ProgressInfo { get; set; } public IQueryable QueryProducts { get; set; } - public Action PreviewData { get; private set; } - public bool IsPreview - { - get { return PreviewData != null; } - } - public DataExportRequest Request { get; private set; } public CancellationToken CancellationToken { get; private set; } - public ExportProfile Profile { get; private set; } - public Provider Provider { get; private set; } + public bool IsPreview { get; private set; } public bool Supports(ExportFeatures feature) { - return (!IsPreview && Provider.Metadata.ExportFeatures.HasFlag(feature)); + return (!IsPreview && Request.Provider.Metadata.ExportFeatures.HasFlag(feature)); } public ExportFilter Filter { get; private set; } @@ -100,7 +87,7 @@ public bool Supports(ExportFeatures feature) public string FolderContent { get; private set; } public string ZipName { - get { return Profile.FolderName + ".zip"; } + get { return Request.Profile.FolderName + ".zip"; } } public string ZipPath { @@ -113,7 +100,7 @@ public string LogPath public bool IsFileBasedExport { - get { return Provider == null || Provider.Value == null || Provider.Value.FileExtension.HasValue(); } + get { return Request.Provider == null || Request.Provider.Value == null || Request.Provider.Value.FileExtension.HasValue(); } } public string[] GetDeploymentFiles(ExportDeployment deployment) { From fadd6dda9cf31a871c27289ef1f2b78951c6076a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 11 Nov 2015 17:26:08 +0100 Subject: [PATCH 206/874] ExportController using DataExporter --- .../DataExchange/ExportProfileService.cs | 3 +- .../DataExchange/IDataExporter.cs | 5 +- .../DataExchange/Internal/DataExportTask.cs | 1 + .../DataExchange/Internal/DataExporter.cs | 13 +-- .../DependencyRegistrar.cs | 4 +- .../Controllers/ExportController.cs | 87 ++++++++++--------- 6 files changed, 61 insertions(+), 52 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs index 1651171aa2..414e5c073a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs @@ -10,6 +10,7 @@ using SmartStore.Core.Events; using SmartStore.Core.Plugins; using SmartStore.Services.DataExchange.ExportTask; +using SmartStore.Services.DataExchange.Internal; using SmartStore.Services.Localization; using SmartStore.Services.Tasks; using SmartStore.Utilities; @@ -91,7 +92,7 @@ public virtual ExportProfile InsertExportProfile(Provider provi task = new ScheduleTask { CronExpression = "0 */6 * * *", // every six hours - Type = (new ExportProfileTask()).GetType().AssemblyQualifiedNameWithoutVersion(), + Type = typeof(DataExportTask).AssemblyQualifiedNameWithoutVersion(), Enabled = false, StopOnError = false, IsHidden = true diff --git a/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs index 4a96e5c515..843a15351e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs @@ -13,12 +13,9 @@ public interface IDataExporter { DataExportResult Export(DataExportRequest request, CancellationToken cancellationToken); - // Handle model conversion for grid in backend's controller IList Preview(DataExportRequest request); - // useful for decision making whether export should - // be processed sync or async - long GetDataCount(DataExportRequest request); + int GetDataCount(DataExportRequest request); } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs index b1e3d31f54..33b2eb1e2a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs @@ -4,6 +4,7 @@ namespace SmartStore.Services.DataExchange.Internal { + // note: namespace persisted in ScheduleTask.Type public partial class DataExportTask : ITask { private readonly IDataExporter _exporter; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs index 37d64d5fbb..faccb9748f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs @@ -903,8 +903,9 @@ private void ExportCoreOuter(DataExporterContext ctx) } } - // TODO: lazyLoading: false. It requires IDbContextExtensions::Load/QueryFor for properties internally mapped by EF - using (var scope = new DbContextScope(_services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, forceNoTracking: true)) + // TODO: lazyLoading: false, proxyCreation: false possible? how to identify all properties of all data levels of all entities + // that require manual resolving for now and for future? fragile, susceptible to faults (e.g. price calculation)... + using (var scope = new DbContextScope(_services.DbContext, autoDetectChanges: false, proxyCreation: true, validateOnSave: false, forceNoTracking: true)) { ctx.DeliveryTimes = _deliveryTimeService.Value.GetAllDeliveryTimes().ToDictionary(x => x.Id); ctx.QuantityUnits = _quantityUnitService.Value.GetAllQuantityUnits().ToDictionary(x => x.Id); @@ -1100,7 +1101,7 @@ public IList Preview(DataExportRequest request) var pageIndex = (request.CustomData.ContainsKey("PageIndex") ? (int)request.CustomData["PageIndex"] : 0); var totalRecords = (request.CustomData.ContainsKey("TotalRecords") ? (int)request.CustomData["TotalRecords"] : 0); - var result = new List(); + var resultData = new List(); var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(5.0)); var ctx = new DataExporterContext(request, cancellation.Token, null, true); @@ -1120,7 +1121,7 @@ public IList Preview(DataExportRequest request) while (segmenter.ReadNextSegment()) { - result.AddRange(segmenter.CurrentSegment); + resultData.AddRange(segmenter.CurrentSegment); } } } @@ -1130,10 +1131,10 @@ public IList Preview(DataExportRequest request) _services.Notifier.Error(ctx.Result.LastError); } - return result; + return resultData; } - public long GetDataCount(DataExportRequest request) + public int GetDataCount(DataExportRequest request) { var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(5.0)); diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index 7eea57921e..574f649606 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -76,6 +76,7 @@ using Module = Autofac.Module; using SmartStore.Core.Domain.DataExchange; using SmartStore.Utilities.Reflection; +using SmartStore.Services.DataExchange.Internal; namespace SmartStore.Web.Framework { @@ -223,8 +224,9 @@ public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, b builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index e53c712192..55ac7330ac 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -2,11 +2,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Net.Mime; -using System.Text; using System.Web; using System.Web.Mvc; -using Autofac; using SmartStore.Admin.Extensions; using SmartStore.Admin.Models.DataExchange; using SmartStore.Core; @@ -21,12 +18,13 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Customers; using SmartStore.Services.DataExchange; -using SmartStore.Services.DataExchange.ExportTask; +using SmartStore.Services.DataExchange.Internal; using SmartStore.Services.Directory; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; using SmartStore.Services.Messages; using SmartStore.Services.Security; +using SmartStore.Services.Tasks; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Plugins; @@ -48,9 +46,10 @@ public class ExportController : AdminControllerBase private readonly ILanguageService _languageService; private readonly ICurrencyService _currencyService; private readonly IEmailAccountService _emailAccountService; - private readonly IComponentContext _componentContext; private readonly IDateTimeHelper _dateTimeHelper; private readonly DataExchangeSettings _dataExchangeSettings; + private readonly ITaskScheduler _taskScheduler; + private readonly IDataExporter _dataExporter; public ExportController( ICommonServices services, @@ -63,9 +62,10 @@ public ExportController( ILanguageService languageService, ICurrencyService currencyService, IEmailAccountService emailAccountService, - IComponentContext componentContext, IDateTimeHelper dateTimeHelper, - DataExchangeSettings dataExchangeSettings) + DataExchangeSettings dataExchangeSettings, + ITaskScheduler taskScheduler, + IDataExporter dataExporter) { _services = services; _exportService = exportService; @@ -77,9 +77,10 @@ public ExportController( _languageService = languageService; _currencyService = currencyService; _emailAccountService = emailAccountService; - _componentContext = componentContext; _dateTimeHelper = dateTimeHelper; _dataExchangeSettings = dataExchangeSettings; + _taskScheduler = taskScheduler; + _dataExporter = dataExporter; } #region Utilities @@ -245,7 +246,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile { try { - var publicFolder = Path.Combine(HttpRuntime.AppDomainAppPath, ExportProfileTask.PublicFolder); + var publicFolder = Path.Combine(HttpRuntime.AppDomainAppPath, DataExporter.PublicFolder); var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); if (resultInfo != null && resultInfo.Files != null) @@ -261,7 +262,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile StoreId = store.Id, StoreName = store.Name, FileName = fileInfo.FileName, - FileUrl = string.Concat(store.Url.EnsureEndsWith("/"), ExportProfileTask.PublicFolder.EnsureEndsWith("/"), fileInfo.FileName) + FileUrl = string.Concat(store.Url.EnsureEndsWith("/"), DataExporter.PublicFolder.EnsureEndsWith("/"), fileInfo.FileName) }); } } @@ -722,15 +723,15 @@ public ActionResult Preview(int id) var provider = _exportService.LoadProvider(profile.ProviderSystemName); - var task = new ExportProfileTask(); - var totalRecords = task.GetRecordCount(profile, provider, _componentContext); + var request = new DataExportRequest(profile, provider); + var totalRecords = _dataExporter.GetDataCount(request); var model = new ExportPreviewModel { Id = profile.Id, Name = profile.Name, ThumbnailUrl = GetThumbnailUrl(provider), - GridPageSize = ExportProfileTask.PageSize, + GridPageSize = DataExporter.PageSize, EntityType = provider.Value.EntityType, TotalRecords = totalRecords, LogFileExists = System.IO.File.Exists(profile.GetExportLogFilePath()) @@ -751,25 +752,30 @@ public ActionResult PreviewList(GridCommand command, int id, int totalRecords) { var productModel = new List(); var orderModel = new List(); - var task = new ExportProfileTask(); - Action previewData = x => + var request = new DataExportRequest(profile, provider); + request.CustomData.Add("PageIndex", command.Page - 1); + request.CustomData.Add("TotalRecords", totalRecords); + + var data = _dataExporter.Preview(request); + + foreach (dynamic item in data) { if (provider.Value.EntityType == ExportEntityType.Product) { - var product = x.Entity as Product; + var product = item.Entity as Product; var pm = new ExportPreviewProductModel { - Id = x.Id, - ProductTypeId = x.ProductTypeId, + Id = product.Id, + ProductTypeId = product.ProductTypeId, ProductTypeName = product.GetProductTypeLabel(_services.Localization), ProductTypeLabelHint = product.ProductTypeLabelHint, - Name = x.Name, - Sku = x.Sku, - Price = x.Price, - Published = x.Published, - StockQuantity = x.StockQuantity, - AdminComment = x.AdminComment + Name = item.Name, + Sku = item.Sku, + Price = item.Price, + Published = product.Published, + StockQuantity = product.StockQuantity, + AdminComment = item.AdminComment }; productModel.Add(pm); @@ -778,23 +784,21 @@ public ActionResult PreviewList(GridCommand command, int id, int totalRecords) { var om = new ExportPreviewOrderModel { - Id = x.Id, - HasNewPaymentNotification = x.HasNewPaymentNotification, - OrderNumber = x.OrderNumber, - OrderStatus = x.OrderStatus, - PaymentStatus = x.PaymentStatus, - ShippingStatus = x.ShippingStatus, - CustomerEmail = x.Customer.Email, - StoreName = (x.Store == null ? "".NaIfEmpty() : x.Store.Name), - CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc), - OrderTotal = x.OrderTotal + Id = item.Id, + HasNewPaymentNotification = item.HasNewPaymentNotification, + OrderNumber = item.OrderNumber, + OrderStatus = item.OrderStatus, + PaymentStatus = item.PaymentStatus, + ShippingStatus = item.ShippingStatus, + CustomerEmail = item.Customer.Email, + StoreName = (item.Store == null ? "".NaIfEmpty() : item.Store.Name), + CreatedOn = _dateTimeHelper.ConvertToUserTime(item.CreatedOnUtc, DateTimeKind.Utc), + OrderTotal = item.OrderTotal }; orderModel.Add(om); } - }; - - task.Preview(profile, provider, _componentContext, command.Page - 1, totalRecords, previewData); + } var normalizedTotal = totalRecords; @@ -831,12 +835,15 @@ public ActionResult Execute(int id, string selectedIds, bool exportAll) if (profile == null) return RedirectToAction("List"); + // TODO: profile.CacheSelectedEntityIds(selectedIds); - var returnUrl = Url.Action("List", "Export", new { area = "admin" }); + _taskScheduler.RunSingleTask(profile.SchedulingTaskId); - return RedirectToAction("RunJob", "ScheduleTask", new { area = "admin", id = profile.SchedulingTaskId, returnUrl = returnUrl }); - } + NotifyInfo(T("Admin.System.ScheduleTasks.RunNow.Progress")); + + return RedirectToAction("List"); + } [ChildActionOnly] public ActionResult InfoProfile(string systemName, string returnUrl) From b93c2ef966a51d88071244126eb4a3ff37c848ea Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 Nov 2015 17:44:44 +0100 Subject: [PATCH 207/874] Fix #808: Removed C# 6 features to remain compatible with Visual Studio 2013 --- .../Extensions/StringExtensions.cs | 2 +- .../Extensions/TypeExtensions.cs | 1 - .../Infrastructure/AppDomainTypeFinder.cs | 2 +- .../DependencyManagement/ContainerManager.cs | 70 +++++++--- .../Utilities/Reflection/FastActivator.cs | 127 +++++++++++++++++- .../Utilities/Reflection/FastProperty.cs | 49 +++---- .../SmartStore.Core.Tests/PerformanceTests.cs | 90 ++++++++----- .../Customer/RegisterValidatorTests.cs | 2 +- 8 files changed, 261 insertions(+), 82 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs index 1fca421bad..4a7f28e209 100644 --- a/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs @@ -511,7 +511,7 @@ public static string EncodeJsString(this string value) [DebuggerStepThrough] public static string EncodeJsString(this string value, char delimiter, bool appendDelimiters) { - StringBuilder sb = new StringBuilder(value?.Length ?? 16); + StringBuilder sb = new StringBuilder(value != null ? value.Length : 16); using (StringWriter w = new StringWriter(sb, CultureInfo.InvariantCulture)) { EncodeJsString(w, value, delimiter, appendDelimiters); diff --git a/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs index 981d71d9f8..987938eab3 100644 --- a/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/TypeExtensions.cs @@ -7,7 +7,6 @@ namespace SmartStore { - public static class TypeExtensions { private static Type[] s_predefinedTypes; diff --git a/src/Libraries/SmartStore.Core/Infrastructure/AppDomainTypeFinder.cs b/src/Libraries/SmartStore.Core/Infrastructure/AppDomainTypeFinder.cs index 8e2e1b1498..7c5169afac 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/AppDomainTypeFinder.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/AppDomainTypeFinder.cs @@ -21,7 +21,7 @@ public class AppDomainTypeFinder : ITypeFinder private static object s_lock = new object(); - private string _assemblySkipLoadingPattern = @"^System|^mscorlib|^Microsoft|^CppCodeProvider|^VJSharpCodeProvider|^WebDev|^Nuget|^Castle|^Iesi|^log4net|^Autofac|^AutoMapper|^EntityFramework|^EPPlus|^Fasterflect|^nunit|^TestDriven|^MbUnit|^Rhino|^QuickGraph|^TestFu|^Telerik|^Antlr3|^Recaptcha|^FluentValidation|^ImageResizer|^itextsharp|^MiniProfiler|^Newtonsoft|^Pandora|^WebGrease|^Noesis|^DotNetOpenAuth|^Facebook|^LinqToTwitter|^PerceptiveMCAPI|^CookComputing|^GCheckout|^Mono\.Math|^Org\.Mentalis|^App_Web|^BundleTransformer|^ClearScript|^JavaScriptEngineSwitcher|^MsieJavaScriptEngine|^Glimpse|^Ionic|^App_GlobalResources|^AjaxMin|^MaxMind|^NReco|^OffAmazonPayments|^UAParser"; + private string _assemblySkipLoadingPattern = @"^System|^mscorlib|^Microsoft|^CppCodeProvider|^VJSharpCodeProvider|^WebDev|^Nuget|^Castle|^Iesi|^log4net|^Autofac|^AutoMapper|^EntityFramework|^EPPlus|^nunit|^TestDriven|^MbUnit|^Rhino|^QuickGraph|^TestFu|^Telerik|^Antlr3|^Recaptcha|^FluentValidation|^ImageResizer|^itextsharp|^MiniProfiler|^Newtonsoft|^Pandora|^WebGrease|^Noesis|^DotNetOpenAuth|^Facebook|^LinqToTwitter|^PerceptiveMCAPI|^CookComputing|^GCheckout|^Mono\.Math|^Org\.Mentalis|^App_Web|^BundleTransformer|^ClearScript|^JavaScriptEngineSwitcher|^MsieJavaScriptEngine|^Glimpse|^Ionic|^App_GlobalResources|^AjaxMin|^MaxMind|^NReco|^OffAmazonPayments|^UAParser"; private string _assemblyRestrictToLoadingPattern = ".*"; private readonly IDictionary _assemblyMatchTable = new Dictionary(); diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs index f1abc97e26..c8c8bca9b3 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs @@ -1,15 +1,19 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Autofac; using Autofac.Builder; using SmartStore.Core.Caching; +using SmartStore.Utilities.Reflection; namespace SmartStore.Core.Infrastructure.DependencyManagement { public class ContainerManager { private readonly IContainer _container; + private readonly ConcurrentDictionary _cachedActivators = new ConcurrentDictionary(); public ContainerManager(IContainer container) { @@ -61,30 +65,60 @@ public T ResolveUnregistered(ILifetimeScope scope = null) where T : class public object ResolveUnregistered(Type type, ILifetimeScope scope = null) { - var constructors = type.GetConstructors(); + var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance); foreach (var constructor in constructors) { - try - { - var parameters = constructor.GetParameters(); - var parameterInstances = new List(); - foreach (var parameter in parameters) - { - var service = Resolve(parameter.ParameterType, scope); - if (service == null) - throw new SmartException("Unkown dependency"); - parameterInstances.Add(service); - } - return Activator.CreateInstance(type, parameterInstances.ToArray()); - } - catch - { - - } + object instance; + if (TryInvokeConstructor(constructor, out instance, scope)) + { + return instance; + } } + throw new SmartException("No contructor was found that had all the dependencies satisfied."); } + private FastActivator GetActivatorForUnregisteredService(Type serviceType) + { + FastActivator activator; + if (!_cachedActivators.TryGetValue(serviceType, out activator)) + { + + } + + return activator; + } + + private bool TryInvokeConstructor(ConstructorInfo constructor, out object instance, ILifetimeScope scope = null) + { + instance = null; + + try + { + var parameters = constructor.GetParameters(); + var parameterInstances = new List(); + + foreach (var parameter in parameters) + { + var service = Resolve(parameter.ParameterType, scope); + if (service == null) + { + return false; + } + + parameterInstances.Add(service); + } + + instance = constructor.Invoke(parameterInstances.ToArray()); + + return instance != null; + } + catch + { + return false; + } + } + public bool TryResolve(Type serviceType, ILifetimeScope scope, out object instance) { return (scope ?? Scope()).TryResolve(serviceType, out instance); diff --git a/src/Libraries/SmartStore.Core/Utilities/Reflection/FastActivator.cs b/src/Libraries/SmartStore.Core/Utilities/Reflection/FastActivator.cs index 88de6542d6..970ddd6ec1 100644 --- a/src/Libraries/SmartStore.Core/Utilities/Reflection/FastActivator.cs +++ b/src/Libraries/SmartStore.Core/Utilities/Reflection/FastActivator.cs @@ -5,12 +5,13 @@ using System.Reflection; using System.Threading.Tasks; using System.Linq.Expressions; +using System.Collections.Concurrent; namespace SmartStore.Utilities.Reflection { public class FastActivator { - private Action _invoker; + private static readonly ConcurrentDictionary _activatorsCache = new ConcurrentDictionary(); public FastActivator(ConstructorInfo constructorInfo) { @@ -23,12 +24,12 @@ public FastActivator(ConstructorInfo constructorInfo) /// /// Gets the backing . /// - public ConstructorInfo Constructor { get; } + public ConstructorInfo Constructor { get; private set; } /// /// Gets the constructor invoker. /// - public Func Invoker { get; } + public Func Invoker { get; private set; } /// /// Creates an instance of the type using the specified parameters. @@ -70,5 +71,125 @@ public static Func MakeFastInvoker(ConstructorInfo constructor return lambda.Compile(); } + + #region Static + + /// + /// Creates and caches fast constructor invokers + /// + /// The type to extract fast constructor invokers for + /// A cached array of all public instance constructors from the given type. + /// The parameterless default constructor is always excluded from the list of activators + public static FastActivator[] GetActivators(Type type) + { + return GetActivatorsCore(type); + } + + private static FastActivator[] GetActivatorsCore(Type type) + { + FastActivator[] activators; + if (!_activatorsCache.TryGetValue(type, out activators)) + { + var candidates = GetCandidateConstructors(type); + activators = candidates.Select(c => new FastActivator(c)).ToArray(); + _activatorsCache.TryAdd(type, activators); + } + + return activators; + } + + /// + /// Creates an instance of the specified type using the constructor that best matches the specified parameters. + /// + /// The type of object to create. + /// + /// An array of arguments that match in number, order, and type the parameters of the constructor to invoke. + /// If args is an empty array or null, the constructor that takes no parameters (the default constructor) is invoked. + /// + /// A reference to the newly created object. + public static T CreateInstance(params object[] args) + { + return (T)CreateInstance(typeof(T), args); + } + + /// + /// Creates an instance of the specified type using the constructor that best matches the specified parameters. + /// + /// The type of object to create. + /// + /// An array of arguments that match in number, order, and type the parameters of the constructor to invoke. + /// If args is an empty array or null, the constructor that takes no parameters (the default constructor) is invoked. + /// + /// A reference to the newly created object. + public static object CreateInstance(Type type, params object[] args) + { + Guard.ArgumentNotNull(() => type); + + if (args == null || args.Length == 0) + { + // don't struggle with FastActivator: native reflection is really fast with default constructor! + return Activator.CreateInstance(type); + } + + var activators = GetActivatorsCore(type); + var matchingActivator = FindMatchingActivatorCore(activators, type, args); + + if (matchingActivator == null) + { + throw new ArgumentException("No matching contructor was found for the given arguments.", "args"); + } + + return matchingActivator.Activate(args); + } + + public static FastActivator FindMatchingActivator(Type type, params object[] args) + { + var activators = GetActivatorsCore(type); + var matchingActivator = FindMatchingActivatorCore(activators, type, args); + + return matchingActivator; + } + + private static FastActivator FindMatchingActivatorCore(FastActivator[] activators, Type type, object[] args) + { + if (activators.Length == 0) + { + return null; + } + + if (activators.Length == 1) + { + // this seems to be bad design, but it's on purpose for performance reasons. + // In nearly ALL cases there is only one constructor. + return activators[0]; + } + + var argTypes = args.Select(x => x.GetType()).ToArray(); + var constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.CreateInstance | BindingFlags.Public, null, argTypes, null); + + if (constructor != null) + { + var matchingActivator = activators.FirstOrDefault(a => a.Constructor == constructor); + return matchingActivator; + } + + return null; + } + + private static IEnumerable GetCandidateConstructors(Type type) + { + var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); + return constructors.Where(c => c.GetParameters().Length > 0); + } + + private static void CheckIsValidType(Type type) + { + if (type.IsAbstract || type.IsInterface) + { + throw new ArgumentException("The type to create activators for must be concrete. Type: {0}".FormatInvariant(type.ToString()), "type"); + } + } + + #endregion } } diff --git a/src/Libraries/SmartStore.Core/Utilities/Reflection/FastProperty.cs b/src/Libraries/SmartStore.Core/Utilities/Reflection/FastProperty.cs index fd26b61225..65a726248f 100644 --- a/src/Libraries/SmartStore.Core/Utilities/Reflection/FastProperty.cs +++ b/src/Libraries/SmartStore.Core/Utilities/Reflection/FastProperty.cs @@ -16,17 +16,17 @@ public class FastProperty // Delegate type for a by-ref property getter private delegate TValue ByRefFunc(ref TDeclaringType arg); - private static readonly MethodInfo CallPropertyGetterOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyGetter)); - private static readonly MethodInfo CallPropertyGetterByReferenceOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertyGetterByReference)); - private static readonly MethodInfo CallNullSafePropertyGetterOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod(nameof(CallNullSafePropertyGetter)); - private static readonly MethodInfo CallNullSafePropertyGetterByReferenceOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod(nameof(CallNullSafePropertyGetterByReference)); - private static readonly MethodInfo CallPropertySetterOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod(nameof(CallPropertySetter)); + private static readonly MethodInfo CallPropertyGetterOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod("CallPropertyGetter"); + private static readonly MethodInfo CallPropertyGetterByReferenceOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod("CallPropertyGetterByReference"); + private static readonly MethodInfo CallNullSafePropertyGetterOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod("CallNullSafePropertyGetter"); + private static readonly MethodInfo CallNullSafePropertyGetterByReferenceOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod("CallNullSafePropertyGetterByReference"); + private static readonly MethodInfo CallPropertySetterOpenGenericMethod = typeof(FastProperty).GetTypeInfo().GetDeclaredMethod("CallPropertySetter"); - private static readonly ConcurrentDictionary SinglePropertiesCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary _singlePropertiesCache = new ConcurrentDictionary(); // Using an array rather than IEnumerable, as target will be called on the hot path numerous times. - private static readonly ConcurrentDictionary> PropertiesCache = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary> VisiblePropertiesCache = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> _propertiesCache = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> _visiblePropertiesCache = new ConcurrentDictionary>(); private Action _valueSetter; @@ -36,10 +36,7 @@ public class FastProperty /// public FastProperty(PropertyInfo property) { - if (property == null) - { - throw new ArgumentNullException(nameof(property)); - } + Guard.ArgumentNotNull(() => property); Property = property; Name = property.Name; @@ -49,7 +46,7 @@ public FastProperty(PropertyInfo property) /// /// Gets the backing . /// - public PropertyInfo Property { get; } + public PropertyInfo Property { get; private set; } /// /// Gets (or sets in derived types) the property name. @@ -59,7 +56,7 @@ public FastProperty(PropertyInfo property) /// /// Gets the property value getter. /// - public Func ValueGetter { get; } + public Func ValueGetter { get; private set; } /// /// Gets the property value setter. @@ -119,7 +116,7 @@ public static IReadOnlyDictionary GetProperties(object ins /// public static IReadOnlyDictionary GetProperties(Type type) { - return (IReadOnlyDictionary)GetProperties(type, CreateInstance, PropertiesCache); + return (IReadOnlyDictionary)GetProperties(type, CreateInstance, _propertiesCache); } /// @@ -138,7 +135,7 @@ public static IReadOnlyDictionary GetProperties(Type type) /// public static IReadOnlyDictionary GetVisibleProperties(object instance) { - return (IReadOnlyDictionary)GetVisibleProperties(instance.GetType(), CreateInstance, PropertiesCache, VisiblePropertiesCache); + return (IReadOnlyDictionary)GetVisibleProperties(instance.GetType(), CreateInstance, _propertiesCache, _visiblePropertiesCache); } /// @@ -157,7 +154,7 @@ public static IReadOnlyDictionary GetVisibleProperties(obj /// public static IReadOnlyDictionary GetVisibleProperties(Type type) { - return (IReadOnlyDictionary)GetVisibleProperties(type, CreateInstance, PropertiesCache, VisiblePropertiesCache); + return (IReadOnlyDictionary)GetVisibleProperties(type, CreateInstance, _propertiesCache, _visiblePropertiesCache); } public static FastProperty GetProperty(Type type, string propertyName, bool cacheAllProperties = false) @@ -172,21 +169,21 @@ public static FastProperty GetProperty(Type type, string propertyName, bool cach return fastProperty; } - if (PropertiesCache.TryGetValue(type, out allProperties)) + if (_propertiesCache.TryGetValue(type, out allProperties)) { allProperties.TryGetValue(propertyName, out fastProperty); return fastProperty; } var key = new PropertyKey(type, propertyName); - if (!SinglePropertiesCache.TryGetValue(key, out fastProperty)) + if (!_singlePropertiesCache.TryGetValue(key, out fastProperty)) { - var pi = GetPropertyCandidates(type).Where(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); + var pi = GetCandidateProperties(type).Where(p => p.Name.Equals(propertyName, StringComparison.OrdinalIgnoreCase)).FirstOrDefault(); //var pi = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); if (pi != null) { fastProperty = CreateInstance(pi); - SinglePropertiesCache.TryAdd(key, fastProperty); + _singlePropertiesCache.TryAdd(key, fastProperty); } } @@ -483,7 +480,11 @@ protected static IDictionary GetVisibleProperties( break; } - currentTypeInfo = currentTypeInfo.BaseType?.GetTypeInfo(); + if (currentTypeInfo.BaseType != null) + { + currentTypeInfo = currentTypeInfo.BaseType.GetTypeInfo(); + } + } if (!ignoreProperty) @@ -509,7 +510,7 @@ protected static IDictionary GetProperties( IDictionary fastProperties; if (!cache.TryGetValue(type, out fastProperties)) { - var candidates = GetPropertyCandidates(type); + var candidates = GetCandidateProperties(type); fastProperties = candidates.Select(p => createPropertyHelper(p)).ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); cache.TryAdd(type, fastProperties); } @@ -517,7 +518,7 @@ protected static IDictionary GetProperties( return fastProperties; } - private static IEnumerable GetPropertyCandidates(Type type) + private static IEnumerable GetCandidateProperties(Type type) { // We avoid loading indexed properties using the Where statement. var properties = type.GetRuntimeProperties().Where(IsCandidateProperty); diff --git a/src/Tests/SmartStore.Core.Tests/PerformanceTests.cs b/src/Tests/SmartStore.Core.Tests/PerformanceTests.cs index ee3e85d923..e0b542d3be 100644 --- a/src/Tests/SmartStore.Core.Tests/PerformanceTests.cs +++ b/src/Tests/SmartStore.Core.Tests/PerformanceTests.cs @@ -1,36 +1,60 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Reflection; -using NUnit.Framework; -using SmartStore.Core.Domain.Catalog; -using SmartStore.Tests; -using SmartStore.Utilities.Reflection; - -namespace SmartStore.Core.Tests -{ - [TestFixture] - public class PerformanceTests - { - [Test] - public void InstantiatePerfTest() - { - int cycles = 500000; - - Chronometer.Measure(cycles, "Create Product NATIVE", i => new Product()); - Chronometer.Measure(cycles, "Create Product Reflection", i => Activator.CreateInstance(typeof(Product))); - - var list = new List(); - - Chronometer.Measure(cycles, "Create List NATIVE", i => new List(list)); - Chronometer.Measure(cycles, "Create List Reflection", i => Activator.CreateInstance(typeof(List), list)); - - var ctor = typeof(List).GetConstructor(new Type[] { typeof(List) }); - var activator = new FastActivator(ctor); - Chronometer.Measure(cycles, "Create List FASTACTIVATOR", i => activator.Activate(list) ); - } - } -} +//using System; +//using System.Collections.Generic; +//using System.Diagnostics; +//using System.Reflection; +//using NUnit.Framework; +//using SmartStore.Core.Domain.Catalog; +//using SmartStore.Tests; +//using SmartStore.Utilities.Reflection; + +//namespace SmartStore.Core.Tests +//{ +// [TestFixture] +// public class PerformanceTests +// { +// [Test] +// public void InstantiatePerfTest() +// { +// int cycles = 500000; + +// Chronometer.Measure(cycles, "Create Product NATIVE", i => new Product()); +// Chronometer.Measure(cycles, "Create Product Reflection", i => Activator.CreateInstance(typeof(Product))); + +// var list = new List(); + +// Chronometer.Measure(cycles, "Create List NATIVE", i => new TestClass(list)); +// Chronometer.Measure(cycles, "Create List Reflection", i => Activator.CreateInstance(typeof(TestClass), list)); +// Chronometer.Measure(cycles, "Create List FASTACTIVATOR.CreateInstance()", i => FastActivator.CreateInstance(typeof(TestClass), list) ); + +// var ctor = typeof(TestClass).GetConstructor(new Type[] { typeof(List) }); +// //var activator = new FastActivator(ctor); +// var activator = FastActivator.FindMatchingActivator(typeof(TestClass), list); +// Chronometer.Measure(cycles, "Create List FASTACTIVATOR.Activate()", i => activator.Activate(list)); + +// Chronometer.Measure(cycles, "Create List CTOR.Invoke()", i => ctor.Invoke(new object[] { list })); +// } +// } + +// public class TestClass +// { +// //public TestClass() +// //{ +// //} +// public TestClass(IEnumerable param1) +// { +// } +// //public TestClass(int param1) +// //{ +// //} +// //public TestClass(IEnumerable param1, int param2) +// //{ +// //} +// //public TestClass(IEnumerable param1, int param2, string param3) +// //{ +// //} +// } + +//} diff --git a/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Customer/RegisterValidatorTests.cs b/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Customer/RegisterValidatorTests.cs index 8998a635ef..71787a6fae 100644 --- a/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Customer/RegisterValidatorTests.cs +++ b/src/Tests/SmartStore.Web.MVC.Tests/Public/Validators/Customer/RegisterValidatorTests.cs @@ -12,7 +12,7 @@ public class RegisterValidatorTests : BaseValidatorTests { private RegisterValidator _validator; private CustomerSettings _customerSettings; - private TaxSettings _taxSettings; + private TaxSettings _taxSettings = new TaxSettings(); [SetUp] public new void Setup() From dd0162d9c9f389210522411cc33041c7e1dbb99e Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 Nov 2015 18:55:06 +0100 Subject: [PATCH 208/874] * (Perf) ContainerManager.ResolveUnregistered() now much faster * (Perf) FluentValidator instances are now request scoped, not transient --- .../DependencyManagement/ContainerManager.cs | 63 +++++----- .../Utilities/Reflection/FastActivator.cs | 40 +++--- .../Security/PermissionService.cs | 2 +- .../Validators/SmartValidatorFactory.cs | 27 +++-- .../SmartStore.Core.Tests/PerformanceTests.cs | 114 +++++++++--------- 5 files changed, 136 insertions(+), 110 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs index c8c8bca9b3..d0effe4bee 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs @@ -65,53 +65,60 @@ public T ResolveUnregistered(ILifetimeScope scope = null) where T : class public object ResolveUnregistered(Type type, ILifetimeScope scope = null) { - var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance); - foreach (var constructor in constructors) - { - object instance; - if (TryInvokeConstructor(constructor, out instance, scope)) + FastActivator activator; + object[] parameterInstances = null; + + if (!_cachedActivators.TryGetValue(type, out activator)) + { + var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.CreateInstance | BindingFlags.Instance); + foreach (var constructor in constructors) { - return instance; + var parameterTypes = constructor.GetParameters().Select(p => p.ParameterType).ToArray(); + if (TryResolveAll(parameterTypes, out parameterInstances, scope)) + { + activator = new FastActivator(constructor); + _cachedActivators.TryAdd(type, activator); + break; + } } - } - - throw new SmartException("No contructor was found that had all the dependencies satisfied."); - } + } - private FastActivator GetActivatorForUnregisteredService(Type serviceType) - { - FastActivator activator; - if (!_cachedActivators.TryGetValue(serviceType, out activator)) + if (activator != null) { - + if (parameterInstances == null) + { + TryResolveAll(activator.ParameterTypes, out parameterInstances, scope); + } + if (parameterInstances != null) + { + return activator.Activate(parameterInstances); + } } - return activator; - } + throw new SmartException("No contructor was found that had all the dependencies satisfied."); + } - private bool TryInvokeConstructor(ConstructorInfo constructor, out object instance, ILifetimeScope scope = null) + private bool TryResolveAll(Type[] types, out object[] instances, ILifetimeScope scope = null) { - instance = null; + instances = null; try { - var parameters = constructor.GetParameters(); - var parameterInstances = new List(); - - foreach (var parameter in parameters) + var instances2 = new object[types.Length]; + + for (int i = 0; i < types.Length; i++) { - var service = Resolve(parameter.ParameterType, scope); + var service = Resolve(types[i], scope); if (service == null) { return false; } - parameterInstances.Add(service); + instances2[i] = service; } - instance = constructor.Invoke(parameterInstances.ToArray()); - - return instance != null; + instances = instances2; + return true; } catch { diff --git a/src/Libraries/SmartStore.Core/Utilities/Reflection/FastActivator.cs b/src/Libraries/SmartStore.Core/Utilities/Reflection/FastActivator.cs index 970ddd6ec1..3f95ddeee4 100644 --- a/src/Libraries/SmartStore.Core/Utilities/Reflection/FastActivator.cs +++ b/src/Libraries/SmartStore.Core/Utilities/Reflection/FastActivator.cs @@ -19,13 +19,19 @@ public FastActivator(ConstructorInfo constructorInfo) Constructor = constructorInfo; Invoker = MakeFastInvoker(constructorInfo); + ParameterTypes = constructorInfo.GetParameters().Select(p => p.ParameterType).ToArray(); } /// - /// Gets the backing . + /// Gets the backing . /// public ConstructorInfo Constructor { get; private set; } + /// + /// Gets the parameter types from the backing + /// + public Type[] ParameterTypes { get; private set; } + /// /// Gets the constructor invoker. /// @@ -47,27 +53,29 @@ public object Activate(params object[] parameters) /// a fast invoker. public static Func MakeFastInvoker(ConstructorInfo constructorInfo) { - // parameters to execute - var parametersParameter = Expression.Parameter(typeof(object[]), "parameters"); + var paramsInfo = constructorInfo.GetParameters(); - // build parameter list - var parameterExpressions = new List(); - var paramInfos = constructorInfo.GetParameters(); - for (int i = 0; i < paramInfos.Length; i++) + var parametersExpression = Expression.Parameter(typeof(object[]), "args"); + var argumentsExpression = new Expression[paramsInfo.Length]; + + for (int paramIndex = 0; paramIndex < paramsInfo.Length; paramIndex++) { - var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i)); - var valueCast = Expression.Convert(valueObj, paramInfos[i].ParameterType); + var indexExpression = Expression.Constant(paramIndex); + var parameterType = paramsInfo[paramIndex].ParameterType; - parameterExpressions.Add(valueCast); - } + var parameterIndexExpression = Expression.ArrayIndex(parametersExpression, indexExpression); + var convertExpression = Expression.Convert(parameterIndexExpression, parameterType); + argumentsExpression[paramIndex] = convertExpression; - // new T((T0)parameters[0], (T1)parameters[1], ...) - var instanceCreate = Expression.New(constructorInfo, parameterExpressions); + if (!parameterType.GetTypeInfo().IsValueType) + continue; - // (object)new T((T0)parameters[0], (T1)parameters[1], ...) - var instanceCreateCast = Expression.Convert(instanceCreate, typeof(object)); + var nullConditionExpression = Expression.Equal(parameterIndexExpression, Expression.Constant(null)); + argumentsExpression[paramIndex] = Expression.Condition(nullConditionExpression, Expression.Default(parameterType), convertExpression); + } - var lambda = Expression.Lambda>(instanceCreateCast, parametersParameter); + var newExpression = Expression.New(constructorInfo, argumentsExpression); + var lambda = Expression.Lambda>(newExpression, parametersExpression); return lambda.Compile(); } diff --git a/src/Libraries/SmartStore.Services/Security/PermissionService.cs b/src/Libraries/SmartStore.Services/Security/PermissionService.cs index a4c8bd1803..3ea2815466 100644 --- a/src/Libraries/SmartStore.Services/Security/PermissionService.cs +++ b/src/Libraries/SmartStore.Services/Security/PermissionService.cs @@ -207,7 +207,7 @@ public virtual void InstallPermissions(IPermissionProvider permissionProvider) if (customerRole == null) { //new role (save it) - customerRole = new CustomerRole() + customerRole = new CustomerRole { Name = defaultPermission.CustomerRoleSystemName, Active = true, diff --git a/src/Presentation/SmartStore.Web.Framework/Validators/SmartValidatorFactory.cs b/src/Presentation/SmartStore.Web.Framework/Validators/SmartValidatorFactory.cs index fd72c748ed..7a1eea0a51 100644 --- a/src/Presentation/SmartStore.Web.Framework/Validators/SmartValidatorFactory.cs +++ b/src/Presentation/SmartStore.Web.Framework/Validators/SmartValidatorFactory.cs @@ -1,28 +1,39 @@ using System; using FluentValidation; using FluentValidation.Attributes; +using SmartStore.Core.Caching; using SmartStore.Core.Infrastructure; namespace SmartStore.Web.Framework.Validators { public class SmartValidatorFactory : AttributedValidatorFactory { - //private readonly InstanceCache _cache = new InstanceCache(); public override IValidator GetValidator(Type type) { if (type != null) { - var attribute = (ValidatorAttribute)Attribute.GetCustomAttribute(type, typeof(ValidatorAttribute)); + var attribute = (ValidatorAttribute)Attribute.GetCustomAttribute(type, typeof(ValidatorAttribute)); if ((attribute != null) && (attribute.ValidatorType != null)) { - //validators can depend on some customer specific settings (such as working language) - //that's why we do not cache validators - //var instance = _cache.GetOrCreateInstance(attribute.ValidatorType, - // x => EngineContext.Current.ContainerManager.ResolveUnregistered(x)); - var instance = EngineContext.Current.ContainerManager.ResolveUnregistered(attribute.ValidatorType); - return instance as IValidator; + var container = EngineContext.Current.ContainerManager; + + // Validators can depend on some scoped dependencies settings (such as working language), + // that's why we do not cache validators in a singleton cache. + var requestCache = container.Resolve(); + + string cacheKey = "FluentValidator.{0}".FormatInvariant(attribute.ValidatorType.ToString()); + var result = requestCache.Get(cacheKey, () => + { + System.Diagnostics.Debug.WriteLine("VAL: " + type.Name); + + var instance = EngineContext.Current.ContainerManager.ResolveUnregistered(attribute.ValidatorType); + return instance as IValidator; + }); + + return result; } } + return null; } diff --git a/src/Tests/SmartStore.Core.Tests/PerformanceTests.cs b/src/Tests/SmartStore.Core.Tests/PerformanceTests.cs index e0b542d3be..1f1330805e 100644 --- a/src/Tests/SmartStore.Core.Tests/PerformanceTests.cs +++ b/src/Tests/SmartStore.Core.Tests/PerformanceTests.cs @@ -1,60 +1,60 @@ -//using System; -//using System.Collections.Generic; -//using System.Diagnostics; -//using System.Reflection; -//using NUnit.Framework; -//using SmartStore.Core.Domain.Catalog; -//using SmartStore.Tests; -//using SmartStore.Utilities.Reflection; - -//namespace SmartStore.Core.Tests -//{ -// [TestFixture] -// public class PerformanceTests -// { -// [Test] -// public void InstantiatePerfTest() -// { -// int cycles = 500000; - -// Chronometer.Measure(cycles, "Create Product NATIVE", i => new Product()); -// Chronometer.Measure(cycles, "Create Product Reflection", i => Activator.CreateInstance(typeof(Product))); - -// var list = new List(); - -// Chronometer.Measure(cycles, "Create List NATIVE", i => new TestClass(list)); -// Chronometer.Measure(cycles, "Create List Reflection", i => Activator.CreateInstance(typeof(TestClass), list)); -// Chronometer.Measure(cycles, "Create List FASTACTIVATOR.CreateInstance()", i => FastActivator.CreateInstance(typeof(TestClass), list) ); - -// var ctor = typeof(TestClass).GetConstructor(new Type[] { typeof(List) }); -// //var activator = new FastActivator(ctor); -// var activator = FastActivator.FindMatchingActivator(typeof(TestClass), list); -// Chronometer.Measure(cycles, "Create List FASTACTIVATOR.Activate()", i => activator.Activate(list)); - -// Chronometer.Measure(cycles, "Create List CTOR.Invoke()", i => ctor.Invoke(new object[] { list })); -// } -// } - -// public class TestClass -// { -// //public TestClass() -// //{ -// //} -// public TestClass(IEnumerable param1) -// { -// } -// //public TestClass(int param1) -// //{ -// //} -// //public TestClass(IEnumerable param1, int param2) -// //{ -// //} -// //public TestClass(IEnumerable param1, int param2, string param3) -// //{ -// //} -// } - -//} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using NUnit.Framework; +using SmartStore.Core.Domain.Catalog; +using SmartStore.Tests; +using SmartStore.Utilities.Reflection; + +namespace SmartStore.Core.Tests +{ + [TestFixture] + public class PerformanceTests + { + [Test] + public void InstantiatePerfTest() + { + int cycles = 500000; + + Chronometer.Measure(cycles, "Create Product NATIVE", i => new Product()); + Chronometer.Measure(cycles, "Create Product Reflection", i => Activator.CreateInstance(typeof(Product))); + + var list = new List(); + + Chronometer.Measure(cycles, "Create List NATIVE", i => new TestClass(list)); + Chronometer.Measure(cycles, "Create List Reflection", i => Activator.CreateInstance(typeof(TestClass), list)); + Chronometer.Measure(cycles, "Create List FASTACTIVATOR.CreateInstance()", i => FastActivator.CreateInstance(typeof(TestClass), list) ); + + var ctor = typeof(TestClass).GetConstructor(new Type[] { typeof(List) }); + //var activator = new FastActivator(ctor); + var activator = FastActivator.FindMatchingActivator(typeof(TestClass), list); + Chronometer.Measure(cycles, "Create List FASTACTIVATOR.Activate()", i => activator.Activate(list)); + + Chronometer.Measure(cycles, "Create List CTOR.Invoke()", i => ctor.Invoke(new object[] { list })); + } + } + + public class TestClass + { + public TestClass() + { + } + public TestClass(IEnumerable param1) + { + } + public TestClass(int param1) + { + } + public TestClass(IEnumerable param1, int param2) + { + } + public TestClass(IEnumerable param1, int param2, string param3) + { + } + } + +} From e178bf51efcb3636833c6e7376dbfb9068936bbe Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 11 Nov 2015 19:51:50 +0100 Subject: [PATCH 209/874] Export of selected entities using task parameter instead of HTTP runtime cache --- .../Collections/Querystring.cs | 11 ++++---- .../201509150931528_ExportFramework2.cs | 4 +++ .../DataExchange/ExportExtensions.cs | 28 ------------------- .../ExportTask/ExportProfileTask.cs | 2 +- .../DataExchange/Internal/DataExportTask.cs | 11 ++------ .../DataExchange/Internal/DataExporter.cs | 12 ++++++-- .../Internal/DataExporterContext.cs | 3 +- .../Tasks/TaskScheduler.cs | 2 +- .../Controllers/ExportController.cs | 13 ++++++--- .../Administration/Views/Export/List.cshtml | 1 - .../Views/Export/Preview.cshtml | 8 ++++-- 11 files changed, 40 insertions(+), 55 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Collections/Querystring.cs b/src/Libraries/SmartStore.Core/Collections/Querystring.cs index 76606ef24d..e430e4ccb2 100644 --- a/src/Libraries/SmartStore.Core/Collections/Querystring.cs +++ b/src/Libraries/SmartStore.Core/Collections/Querystring.cs @@ -45,22 +45,23 @@ public static string ExtractQuerystring(string s) /// /// the string to parse /// the QueryString object - public QueryString FillFromString(string s) + public QueryString FillFromString(string s, bool urlDecode = false) { base.Clear(); if (string.IsNullOrEmpty(s)) { return this; } + foreach (string keyValuePair in ExtractQuerystring(s).Split('&')) { if (string.IsNullOrEmpty(keyValuePair)) { continue; } + string[] split = keyValuePair.Split('='); - base.Add(split[0], - split.Length == 2 ? split[1] : ""); + base.Add(split[0], split.Length == 2 ? (urlDecode ? HttpUtility.UrlDecode(split[1]) : split[1]) : ""); } return this; } @@ -73,7 +74,7 @@ public QueryString FromCurrent() { if (HttpContext.Current != null) { - return FillFromString(HttpContext.Current.Request.QueryString.ToString()); + return FillFromString(HttpContext.Current.Request.QueryString.ToString(), true); } base.Clear(); return this; @@ -182,7 +183,7 @@ public override string ToString() { if (!string.IsNullOrEmpty(base.Keys[i])) { - foreach (string val in base[base.Keys[i]].Split(',')) + foreach (string val in base[base.Keys[i]].EmptyNull().Split(',')) { builder.Append((builder.Length == 0) ? "?" : "&").Append( HttpUtility.UrlEncode(base.Keys[i])).Append("=").Append(val); diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index 819cb147ae..b32ed81653 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -56,6 +56,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Cannot load the provider {0}.", "Der Provider {0} konnte nicht geladen werden."); + builder.AddOrUpdate("Admin.Common.NoEntriesSelected", + "No entries have been selected.", + "Es wurden keine Eintrge ausgewhlt."); + builder.AddOrUpdate("ActivityLog.EditPaymentMethod", "Edited payment method '{0}' ({1})", "Zahlungsart '{0}' ({1}) bearbeitet"); diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs index 8a0cdc959d..cf86f0d411 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs @@ -2,8 +2,6 @@ using System.Globalization; using System.IO; using System.Text; -using System.Web; -using System.Web.Caching; using SmartStore.Core.Domain; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Plugins; @@ -96,31 +94,5 @@ public static string ResolveFileNamePattern(this ExportProfile profile, Store st return result; } - - /// - /// Gets the cache key for selected entity identifiers - /// - /// Export profile - /// Cache key for selected entity identifiers - public static string GetSelectedEntityIdsCacheKey(this ExportProfile profile) - { - // do not use profile.Id because it can be 0 - return "ExportProfile_SelectedEntityIds_" + profile.ProviderSystemName; - } - - /// - /// Caches selected entity identifiers to be considered during following export - /// - /// Export profile - /// Comma separated entity identifiers - public static void CacheSelectedEntityIds(this ExportProfile profile, string selectedIds) - { - var selectedIdsCacheKey = profile.GetSelectedEntityIdsCacheKey(); - - if (selectedIds.HasValue()) - HttpRuntime.Cache.Add(selectedIdsCacheKey, selectedIds, null, DateTime.UtcNow.AddMinutes(5), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null); - else - HttpRuntime.Cache.Remove(selectedIdsCacheKey); - } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs index 3bc601bedf..95f276a993 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportTask/ExportProfileTask.cs @@ -2872,7 +2872,7 @@ public void Execute(TaskExecutionContext taskContext) var profileId = taskContext.ScheduleTask.Alias.ToInt(); var profile = _exportProfileService.Value.GetExportProfileById(profileId); - var selectedIdsCacheKey = profile.GetSelectedEntityIdsCacheKey(); + var selectedIdsCacheKey = "ExportProfile_SelectedEntityIds_" + profile.ProviderSystemName; var selectedEntityIds = HttpRuntime.Cache[selectedIdsCacheKey] as string; var provider = _exportProfileService.Value.LoadProvider(profile.ProviderSystemName); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs index 33b2eb1e2a..ab5d8e40c5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs @@ -27,11 +27,6 @@ public void Execute(TaskExecutionContext ctx) var profileId = ctx.ScheduleTask.Alias.ToInt(); var profile = _exportProfileService.GetExportProfileById(profileId); - // TODO: find a better way to transmit selected entity ids (e.g. new TaskExecutionContext.Parameters property) - var selectedIdsCacheKey = profile.GetSelectedEntityIdsCacheKey(); - var selectedEntityIds = HttpRuntime.Cache[selectedIdsCacheKey] as string; - HttpRuntime.Cache.Remove(selectedIdsCacheKey); - // load provider var provider = _exportProfileService.LoadProvider(profile.ProviderSystemName); if (provider == null) @@ -50,10 +45,10 @@ public void Execute(TaskExecutionContext ctx) ctx.SetProgress(null, msg, true); }; - if (selectedEntityIds.HasValue()) + if (ctx.Parameters.ContainsKey("SelectedIds")) { - request.EntitiesToExport = selectedEntityIds.ToIntArray(); - } + request.EntitiesToExport = ctx.Parameters["SelectedIds"].ToIntArray(); + } // process! _exporter.Export(request, ctx.CancellationToken); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs index faccb9748f..cc263c1b5b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs @@ -1061,6 +1061,11 @@ public DataExportResult Export(DataExportRequest request, CancellationToken canc { var ctx = new DataExporterContext(request, cancellationToken); + if (request.EntitiesToExport != null) + { + ctx.EntityIdsSelected.AddRange(request.EntitiesToExport); + } + ExportCoreOuter(ctx); if (ctx.Result != null && ctx.Result.Succeeded && ctx.Result.Files.Count > 0) @@ -1068,7 +1073,6 @@ public DataExportResult Export(DataExportRequest request, CancellationToken canc string prefix = null; string suffix = null; var extension = Path.GetExtension(ctx.Result.Files.First().FileName); - var selectedEntityCount = (request.EntitiesToExport == null ? 0 : request.EntitiesToExport.Count()); if (request.Provider.Value.EntityType == ExportEntityType.Product) prefix = T("Admin.Catalog.Products"); @@ -1085,6 +1089,8 @@ public DataExportResult Export(DataExportRequest request, CancellationToken canc else prefix = request.Provider.Value.EntityType.ToString(); + var selectedEntityCount = (request.EntitiesToExport == null ? 0 : request.EntitiesToExport.Count()); + if (selectedEntityCount == 0) suffix = T("Common.All"); else @@ -1104,7 +1110,7 @@ public IList Preview(DataExportRequest request) var resultData = new List(); var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(5.0)); - var ctx = new DataExporterContext(request, cancellation.Token, null, true); + var ctx = new DataExporterContext(request, cancellation.Token, true); var unused = Init(ctx, totalRecords); @@ -1138,7 +1144,7 @@ public int GetDataCount(DataExportRequest request) { var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(5.0)); - var ctx = new DataExporterContext(request, cancellation.Token, null, true); + var ctx = new DataExporterContext(request, cancellation.Token, true); var unused = Init(ctx); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs index 2ae610ca6d..b12b3a1bdb 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs @@ -26,19 +26,18 @@ internal class DataExporterContext public DataExporterContext( DataExportRequest request, CancellationToken cancellationToken, - string selectedIds = null, bool isPreview = false) { Request = request; CancellationToken = cancellationToken; Filter = XmlHelper.Deserialize(request.Profile.Filtering); Projection = XmlHelper.Deserialize(request.Profile.Projection); - EntityIdsSelected = selectedIds.SplitSafe(",").Select(x => x.ToInt()).ToList(); IsPreview = isPreview; FolderContent = FileSystemHelper.TempDir(@"Profile\Export\{0}\Content".FormatInvariant(request.Profile.FolderName)); FolderRoot = System.IO.Directory.GetParent(FolderContent).FullName; + EntityIdsSelected = new List(); Categories = new Dictionary(); CategoryPathes = new Dictionary(); Countries = new Dictionary(); diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs index 3ff2e5e04f..22cecb7046 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs @@ -113,7 +113,7 @@ public void RunSingleTask(int scheduleTaskId, IDictionary taskPa query = qs.ToString(); } - CallEndpoint(_baseUrl + "/Execute/{0}{1}".FormatInvariant(scheduleTaskId, query)); + CallEndpoint("{0}/Execute/{1}{2}".FormatInvariant(_baseUrl, scheduleTaskId, query)); } private void Elapsed(object sender, System.Timers.ElapsedEventArgs e) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 55ac7330ac..518e66169c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -826,7 +826,7 @@ public ActionResult PreviewList(GridCommand command, int id, int totalRecords) } [HttpPost] - public ActionResult Execute(int id, string selectedIds, bool exportAll) + public ActionResult Execute(int id, string selectedIds) { if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); @@ -835,10 +835,15 @@ public ActionResult Execute(int id, string selectedIds, bool exportAll) if (profile == null) return RedirectToAction("List"); - // TODO: - profile.CacheSelectedEntityIds(selectedIds); + Dictionary taskParams = null; - _taskScheduler.RunSingleTask(profile.SchedulingTaskId); + if (selectedIds.HasValue()) + { + taskParams = new Dictionary(); + taskParams.Add("SelectedIds", selectedIds); + } + + _taskScheduler.RunSingleTask(profile.SchedulingTaskId, taskParams); NotifyInfo(T("Admin.System.ScheduleTasks.RunNow.Progress")); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 689da7f9a4..740e0447ce 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -18,7 +18,6 @@
    -
    @if(Model.Count > 0) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml index b5e527976d..2bca030bc9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml @@ -16,7 +16,6 @@
    - @if (Model.LogFileExists) { @@ -42,8 +41,13 @@ e.preventDefault(); var form = $('#ProfileExportForm'), exportAll = $(this).attr('name') === 'ExportAll'; + + if (!exportAll && selectedIds.length === 0) { + EventBroker.publish("message", { title: '@T("Admin.Common.NoEntriesSelected")', type: 'warning' }); + return false; + } + form.find('[name=SelectedIds]').val(exportAll ? '' : selectedIds.join(',')); - form.find('[name=ExportAll]').val(exportAll.toString()); form.submit(); return false; }); From bde240e9c660249616febc6d46fd603dd8caea62 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 Nov 2015 23:12:06 +0100 Subject: [PATCH 210/874] (Perf) more refactoring --- .../Extensions/MiscExtensions.cs | 6 +- .../Infrastructure/ComparableObject.cs | 118 ++++----- .../DependencyManagement/ContainerManager.cs | 2 +- .../SmartStore.Core/SmartStore.Core.csproj | 1 + .../Utilities/HashCodeCombiner.cs | 90 +++++++ .../Utilities/Reflection/FastActivator.cs | 12 +- .../Extensions/DataReaderExtensions.cs | 223 ++++++++++-------- .../Extensions/SqlDataReaderExtensions.cs | 30 --- .../SmartStore.Data/SmartStore.Data.csproj | 1 - .../SmartStore.Core.Tests/PerformanceTests.cs | 31 ++- 10 files changed, 296 insertions(+), 218 deletions(-) create mode 100644 src/Libraries/SmartStore.Core/Utilities/HashCodeCombiner.cs delete mode 100644 src/Libraries/SmartStore.Data/Extensions/SqlDataReaderExtensions.cs diff --git a/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs index 865d5f8ed4..c59cb07b21 100644 --- a/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/MiscExtensions.cs @@ -11,12 +11,12 @@ namespace SmartStore { public static class MiscExtensions { - public static void Dump(this Exception exc) + public static void Dump(this Exception exception) { try { - exc.StackTrace.Dump(); - exc.Message.Dump(); + exception.StackTrace.Dump(); + exception.Message.Dump(); } catch { } } diff --git a/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs b/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs index ed24bd71f4..7291139dd8 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/ComparableObject.cs @@ -4,6 +4,9 @@ using System.Linq.Expressions; using System.Reflection; using System.ComponentModel; +using SmartStore.Utilities.Reflection; +using SmartStore.Utilities; +using System.Collections.Concurrent; namespace SmartStore { @@ -13,26 +16,9 @@ namespace SmartStore [Serializable] public abstract class ComparableObject { - /// - /// To help ensure hashcode uniqueness, a carefully selected random number multiplier - /// is used within the calculation. Goodrich and Tamassia's Data Structures and - /// Algorithms in Java asserts that 31, 33, 37, 39 and 41 will produce the fewest number - /// of collissions. See http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/ - /// for more information. - /// - protected const int HashMultiplier = 31; - - private readonly List _extraSignatureProperties = new List(); + private readonly HashSet _extraSignatureProperties = new HashSet(StringComparer.OrdinalIgnoreCase); - /// - /// This static member caches the domain signature properties to avoid looking them up for - /// each instance of the same type. - /// - /// A description of the ThreadStatic attribute may be found at - /// http://www.dotnetjunkies.com/WebLog/chris.taylor/archive/2005/08/18/132026.aspx - /// - [ThreadStatic] - private static IDictionary> s_signatureProperties; + private static readonly ConcurrentDictionary _signaturePropertyNames = new ConcurrentDictionary(); public override bool Equals(object obj) { @@ -58,21 +44,23 @@ public override int GetHashCode() var signatureProperties = GetSignatureProperties(); Type t = this.GetType(); - // It's possible for two objects to return the same hash code based on - // identically valued properties, even if they're of two different types, - // so we include the object's type in the hash calculation - int hashCode = t.GetHashCode(); + var combiner = HashCodeCombiner.Start(); - foreach (var pi in signatureProperties) + // It's possible for two objects to return the same hash code based on + // identically valued properties, even if they're of two different types, + // so we include the object's type in the hash calculation + combiner.Add(t.GetHashCode()); + + foreach (var prop in signatureProperties) { - object value = pi.GetValue(this); + object value = prop.GetValue(this); if (value != null) - hashCode = (hashCode * HashMultiplier) ^ value.GetHashCode(); + combiner.Add(value.GetHashCode()); } if (signatureProperties.Any()) - return hashCode; + return combiner.CombinedHash; // If no properties were flagged as being part of the signature of the object, // then simply return the hashcode of the base object as the hashcode. @@ -121,56 +109,46 @@ protected virtual bool HasSameSignatureAs(ComparableObject compareTo) /// /// - public IEnumerable GetSignatureProperties() + public IEnumerable GetSignatureProperties() { - IEnumerable properties; - - // Init the signaturePropertiesDictionary here due to reasons described at - // http://blogs.msdn.com/jfoscoding/archive/2006/07/18/670497.aspx - if (s_signatureProperties == null) - s_signatureProperties = new Dictionary>(); - - var t = GetType(); - - if (s_signatureProperties.TryGetValue(t, out properties)) - return properties; - - return (s_signatureProperties[t] = GetSignaturePropertiesCore()); + var type = GetType(); + var propertyNames = GetSignaturePropertyNamesCore(); + + foreach (var name in propertyNames) + { + var fastProperty = FastProperty.GetProperty(type, name); + if (fastProperty != null) + { + yield return fastProperty; + } + } } /// /// Enforces the template method pattern to have child objects determine which specific /// properties should and should not be included in the object signature comparison. /// - protected virtual IEnumerable GetSignaturePropertiesCore() + protected virtual string[] GetSignaturePropertyNamesCore() { - Type t = this.GetType(); - //var properties = TypeDescriptor.GetProvider(t).GetTypeDescriptor(t) - // .GetPropertiesWith(); + Type type = this.GetType(); + string[] names; - //if (_extraSignatureProperties.Count > 0) - //{ - // properties = properties.Union(_extraSignatureProperties); - //} + if (!_signaturePropertyNames.TryGetValue(type, out names)) + { + names = type.GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => Attribute.IsDefined(p, typeof(ObjectSignatureAttribute), true)) + .Select(p => p.Name) + .ToArray(); - //return new PropertyDescriptorCollection(properties.ToArray(), true); + _signaturePropertyNames.TryAdd(type, names); + } - var properties = t.GetProperties() - .Where(p => Attribute.IsDefined(p, typeof(ObjectSignatureAttribute), true)); + if (_extraSignatureProperties.Count == 0) + { + return names; + } - return properties.Union(_extraSignatureProperties).ToList(); - } - - /// - /// Adds an extra property to the type specific signature properties list. - /// - /// The property to add. - /// Both lists are unioned, so - /// that no duplicates can occur within the global descriptor collection. - protected void RegisterSignatureProperty(PropertyInfo propertyInfo) - { - Guard.ArgumentNotNull(() => propertyInfo); - _extraSignatureProperties.Add(propertyInfo); + return names.Union(_extraSignatureProperties).ToArray(); } /// @@ -183,13 +161,7 @@ protected void RegisterSignatureProperty(string propertyName) { Guard.ArgumentNotEmpty(() => propertyName); - Type t = GetType(); - - var pi = t.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance); - if (pi == null) - throw Error.Argument("propertyName", "Could not find property '{0}' on type '{1}'.", propertyName, t); - - RegisterSignatureProperty(pi); + _extraSignatureProperties.Add(propertyName); } } @@ -212,7 +184,7 @@ protected void RegisterSignatureProperty(Expression> expression) { Guard.ArgumentNotNull(() => expression); - base.RegisterSignatureProperty(expression.ExtractPropertyInfo()); + base.RegisterSignatureProperty(expression.ExtractPropertyInfo().Name); } public virtual bool Equals(T other) diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs index d0effe4bee..6e314c8f0c 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs @@ -70,7 +70,7 @@ public object ResolveUnregistered(Type type, ILifetimeScope scope = null) if (!_cachedActivators.TryGetValue(type, out activator)) { - var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.CreateInstance | BindingFlags.Instance); + var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); foreach (var constructor in constructors) { var parameterTypes = constructor.GetParameters().Select(p => p.ParameterType).ToArray(); diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index e4bb819bdb..8019c3d8d6 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -558,6 +558,7 @@ + diff --git a/src/Libraries/SmartStore.Core/Utilities/HashCodeCombiner.cs b/src/Libraries/SmartStore.Core/Utilities/HashCodeCombiner.cs new file mode 100644 index 0000000000..b69d640a05 --- /dev/null +++ b/src/Libraries/SmartStore.Core/Utilities/HashCodeCombiner.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; + +/* + Copied over from Microsoft.Framework.Internal +*/ + +namespace SmartStore.Utilities +{ + internal struct HashCodeCombiner + { + private long _combinedHash64; + + public int CombinedHash + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return _combinedHash64.GetHashCode(); } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private HashCodeCombiner(long seed) + { + _combinedHash64 = seed; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public HashCodeCombiner Add(IEnumerable e) + { + if (e == null) + { + Add(0); + } + else + { + var count = 0; + foreach (object o in e) + { + Add(o); + count++; + } + Add(count); + } + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator int (HashCodeCombiner self) + { + return self.CombinedHash; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public HashCodeCombiner Add(int i) + { + _combinedHash64 = ((_combinedHash64 << 5) + _combinedHash64) ^ i; + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public HashCodeCombiner Add(string s) + { + var hashCode = (s != null) ? s.GetHashCode() : 0; + return Add(hashCode); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public HashCodeCombiner Add(object o) + { + var hashCode = (o != null) ? o.GetHashCode() : 0; + return Add(hashCode); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public HashCodeCombiner Add(TValue value, IEqualityComparer comparer) + { + var hashCode = value != null ? comparer.GetHashCode(value) : 0; + return Add(hashCode); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static HashCodeCombiner Start() + { + return new HashCodeCombiner(0x1505L); + } + } +} diff --git a/src/Libraries/SmartStore.Core/Utilities/Reflection/FastActivator.cs b/src/Libraries/SmartStore.Core/Utilities/Reflection/FastActivator.cs index 3f95ddeee4..ce0ea4b9a2 100644 --- a/src/Libraries/SmartStore.Core/Utilities/Reflection/FastActivator.cs +++ b/src/Libraries/SmartStore.Core/Utilities/Reflection/FastActivator.cs @@ -131,7 +131,7 @@ public static T CreateInstance(params object[] args) /// A reference to the newly created object. public static object CreateInstance(Type type, params object[] args) { - Guard.ArgumentNotNull(() => type); + Guard.ArgumentNotNull(type, "type"); if (args == null || args.Length == 0) { @@ -164,8 +164,8 @@ private static FastActivator FindMatchingActivatorCore(FastActivator[] activator { return null; } - - if (activators.Length == 1) + + if (activators.Length == 1) { // this seems to be bad design, but it's on purpose for performance reasons. // In nearly ALL cases there is only one constructor. @@ -173,7 +173,11 @@ private static FastActivator FindMatchingActivatorCore(FastActivator[] activator } var argTypes = args.Select(x => x.GetType()).ToArray(); - var constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.CreateInstance | BindingFlags.Public, null, argTypes, null); + var constructor = type.GetConstructor( + BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, + null, + argTypes, + null); if (constructor != null) { diff --git a/src/Libraries/SmartStore.Data/Extensions/DataReaderExtensions.cs b/src/Libraries/SmartStore.Data/Extensions/DataReaderExtensions.cs index 9581cf4ce4..edd6e3dba4 100644 --- a/src/Libraries/SmartStore.Data/Extensions/DataReaderExtensions.cs +++ b/src/Libraries/SmartStore.Data/Extensions/DataReaderExtensions.cs @@ -4,112 +4,141 @@ using System.Collections.Generic; using System.Data; using System.Reflection; +using System.Linq; +using SmartStore.Utilities.Reflection; +using System.Dynamic; namespace SmartStore.Data { public static class DataReaderExtensions { - /// - /// Creates a list of a given type from all the rows in a DataReader. - /// - /// Note this method uses Reflection so this isn't a high performance - /// operation, but it can be useful for generic data reader to entity - /// conversions on the fly and with anonymous types. - /// - /// - /// An open DataReader that's in position to read - /// Optional - comma delimited list of fields that you don't want to update - /// - /// Optional - Cached PropertyInfo dictionary that holds property info data for this object. - /// Can be used for caching hte PropertyInfo structure for multiple operations to speed up - /// translation. If not passed automatically created. - /// - /// - public static List DataReaderToObjectList(this IDataReader reader, string fieldsToSkip = null, Dictionary piList = null) - where TType : new() - { - if (reader == null) - return null; - - var items = new List(); - - // Create lookup list of property info objects - if (piList == null) - { - piList = new Dictionary(); - var props = typeof(TType).GetProperties(BindingFlags.Instance | BindingFlags.Public); - foreach (var prop in props) - piList.Add(prop.Name.ToLower(), prop); - } + public static object GetValue(this IDataReader reader, string columnName) + { + try + { + if (reader != null && !reader.IsClosed && columnName.HasValue()) + { + int ordinal = reader.GetOrdinal(columnName); + return reader.GetValue(ordinal); + } + } + catch (Exception ex) + { + ex.Dump(); + } + + return null; + } + + public static IEnumerable MapSequence(this IDataReader reader, params string[] fieldsToSkip) + where T : new() + { + Guard.ArgumentNotNull(() => reader); + while (reader.Read()) + { + yield return reader.Map(fieldsToSkip); + } + } + + public static IEnumerable MapSequence(this IDataReader reader, params string[] fieldsToSkip) + { + Guard.ArgumentNotNull(() => reader); + while (reader.Read()) - { - var inst = new TType(); - DataReaderToObject(reader, inst, fieldsToSkip, piList); - items.Add(inst); - } + { + yield return reader.Map(fieldsToSkip); + } + } - return items; - } - - /// - /// Populates the properties of an object from a single DataReader row using - /// Reflection by matching the DataReader fields to a public property on - /// the object passed in. Unmatched properties are left unchanged. - /// - /// You need to pass in a data reader located on the active row you want - /// to serialize. - /// - /// This routine works best for matching pure data entities and should - /// be used only in low volume environments where retrieval speed is not - /// critical due to its use of Reflection. - /// - /// Instance of the DataReader to read data from. Should be located on the correct record (Read() should have been called on it before calling this method) - /// Instance of the object to populate properties on - /// Optional - A comma delimited list of object properties that should not be updated - /// Optional - Cached PropertyInfo dictionary that holds property info data for this object - public static void DataReaderToObject(this IDataReader reader, object instance, string fieldsToSkip = null, Dictionary piList = null) - { - if (reader.IsClosed) - throw new InvalidOperationException("Data reader cannot be used because it's already closed"); - - if (string.IsNullOrEmpty(fieldsToSkip)) - fieldsToSkip = string.Empty; - else - fieldsToSkip = "," + fieldsToSkip + ","; - - fieldsToSkip = fieldsToSkip.ToLower(); - - // create a dictionary of properties to look up - // we can pass this in so we can cache the list once - // for a list operation - if (piList == null) - { - piList = new Dictionary(); - var props = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); - foreach (var prop in props) - piList.Add(prop.Name.ToLower(), prop); - } + /// + /// Populates an object from a single DataReader row using + /// fast reflection routines by matching the DataReader fields to a public property on + /// the object passed in. Unmatched properties are left unchanged. + /// + /// You need to pass in a data reader located on the active row you want + /// to serialize. + /// + /// Instance of the DataReader to read data from. Should be located on the correct record (Read() should have been called on it before calling this method) + /// An array of reader field names to ignore + public static T Map(this IDataReader reader, params string[] fieldsToSkip) + where T : new() + { + if (reader.IsClosed) + throw new InvalidOperationException("Data reader cannot be used because it's already closed"); + + var instance = new T(); + MapObject(reader, instance, fieldsToSkip); + return instance; + } + + public static dynamic Map(this IDataReader reader, params string[] fieldsToSkip) + { + if (reader.IsClosed) + throw new InvalidOperationException("Data reader cannot be used because it's already closed"); + + dynamic instance = new ExpandoObject(); + MapDictionary(reader, instance, fieldsToSkip); + return instance; + } + + public static void Map(this IDataReader reader, object instance, params string[] fieldsToSkip) + { + Guard.ArgumentNotNull(instance, "instance"); - for (int index = 0; index < reader.FieldCount; index++) - { - string name = reader.GetName(index).ToLower(); - if (piList.ContainsKey(name)) - { - var prop = piList[name]; - - if (fieldsToSkip.Contains("," + name + ",")) - continue; - - if ((prop != null) && prop.CanWrite) - { - var val = reader.GetValue(index); - prop.SetValue(instance, (val == DBNull.Value) ? null : val, null); - } - } + if (reader.IsClosed) + throw new InvalidOperationException("Data reader cannot be used because it's already closed"); + + var dict = instance as IDictionary; + + if (dict != null) + { + MapDictionary(reader, dict, fieldsToSkip); } + else + { + MapObject(reader, instance, fieldsToSkip); + } + } + + private static void MapObject(IDataReader reader, object instance, params string[] fieldsToSkip) + { + var fastProperties = FastProperty.GetProperties(instance); + + if (fastProperties.Count == 0) + return; + + for (int i = 0; i < reader.FieldCount; i++) + { + string name = reader.GetName(i); + if (fastProperties.ContainsKey(name)) + { + var fastProp = fastProperties[name]; - return; - } - } + if (fieldsToSkip.Contains(name)) + continue; + + if ((fastProp != null) && fastProp.Property.CanWrite) + { + var dbValue = reader.GetValue(i); + fastProp.SetValue(instance, (dbValue == DBNull.Value) ? null : dbValue); + } + } + } + } + + private static void MapDictionary(IDataReader reader, IDictionary instance, params string[] fieldsToSkip) + { + for (int i = 0; i < reader.FieldCount; i++) + { + string name = reader.GetName(i); + + if (fieldsToSkip.Contains(name)) + continue; + + var dbValue = reader.GetValue(i); + instance[name] = (dbValue == DBNull.Value) ? null : dbValue; + } + } + } } diff --git a/src/Libraries/SmartStore.Data/Extensions/SqlDataReaderExtensions.cs b/src/Libraries/SmartStore.Data/Extensions/SqlDataReaderExtensions.cs deleted file mode 100644 index becda17803..0000000000 --- a/src/Libraries/SmartStore.Data/Extensions/SqlDataReaderExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data.SqlClient; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SmartStore -{ - - public static class SqlDataReaderExtensions - { - public static object GetValue(this SqlDataReader reader, string columnName) - { - try - { - if (reader != null && columnName.HasValue()) - { - int ordinal = reader.GetOrdinal(columnName); - return reader.GetValue(ordinal); - } - } - catch (Exception exc) - { - exc.Dump(); - } - return null; - } - } -} diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index 8764a6a9ed..5c46f0934d 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -375,7 +375,6 @@ - diff --git a/src/Tests/SmartStore.Core.Tests/PerformanceTests.cs b/src/Tests/SmartStore.Core.Tests/PerformanceTests.cs index 1f1330805e..36e99e743f 100644 --- a/src/Tests/SmartStore.Core.Tests/PerformanceTests.cs +++ b/src/Tests/SmartStore.Core.Tests/PerformanceTests.cs @@ -19,6 +19,7 @@ public void InstantiatePerfTest() Chronometer.Measure(cycles, "Create Product NATIVE", i => new Product()); Chronometer.Measure(cycles, "Create Product Reflection", i => Activator.CreateInstance(typeof(Product))); + Chronometer.Measure(cycles, "Create Product FASTACTIVATOR", i => FastActivator.CreateInstance(typeof(Product))); var list = new List(); @@ -43,15 +44,27 @@ public TestClass() public TestClass(IEnumerable param1) { } - public TestClass(int param1) - { - } - public TestClass(IEnumerable param1, int param2) - { - } - public TestClass(IEnumerable param1, int param2, string param3) - { - } + //public TestClass(int param1) + //{ + //} + //public TestClass(IEnumerable param1, int param2) + //{ + //} + //public TestClass(IEnumerable param1, int param2, string param3) + //{ + //} + //public TestClass(DateTime param1) + //{ + //} + //public TestClass(double param1) + //{ + //} + //public TestClass(decimal param1) + //{ + //} + //public TestClass(long param1) + //{ + //} } } From 14b775db26a66e9c292a5a3d93bb6aa1cd528c26 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 Nov 2015 23:20:01 +0100 Subject: [PATCH 211/874] Updated AutoMapper to version 4.1.1 --- .../SmartStore.WebApi/SmartStore.WebApi.csproj | 9 +++------ src/Plugins/SmartStore.WebApi/packages.config | 2 +- .../SmartStore.Web.Framework.csproj | 11 +++-------- .../SmartStore.Web.Framework/packages.config | 2 +- .../Administration/SmartStore.Admin.csproj | 11 +++-------- .../SmartStore.Web/Administration/Web.config | 4 ++++ .../SmartStore.Web/Administration/packages.config | 2 +- .../SmartStore.Web.MVC.Tests.csproj | 11 +++-------- src/Tests/SmartStore.Web.MVC.Tests/packages.config | 2 +- 9 files changed, 20 insertions(+), 34 deletions(-) diff --git a/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj b/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj index 20e3fc318c..f5e2443c24 100644 --- a/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj +++ b/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj @@ -87,11 +87,9 @@ ..\..\packages\Autofac.WebApi.3.1.0\lib\net40\Autofac.Integration.WebApi.dll - - ..\..\packages\AutoMapper.3.2.1\lib\net40\AutoMapper.dll - - - ..\..\packages\AutoMapper.3.2.1\lib\net40\AutoMapper.Net4.dll + + ..\..\packages\AutoMapper.4.1.1\lib\net45\AutoMapper.dll + True ..\..\packages\EntityFramework.6.1.3\lib\net45\EntityFramework.dll @@ -324,7 +322,6 @@ - - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + diff --git a/src/Plugins/SmartStore.DevTools/Web.config b/src/Plugins/SmartStore.DevTools/Web.config index b3e042e7ec..e4010c8209 100644 --- a/src/Plugins/SmartStore.DevTools/Web.config +++ b/src/Plugins/SmartStore.DevTools/Web.config @@ -1,135 +1,135 @@ - + -
    +
    - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - + - \ No newline at end of file + diff --git a/src/Plugins/SmartStore.DiscountRules/web.config b/src/Plugins/SmartStore.DiscountRules/web.config index 3766af5f74..c6048447e5 100644 --- a/src/Plugins/SmartStore.DiscountRules/web.config +++ b/src/Plugins/SmartStore.DiscountRules/web.config @@ -1,117 +1,117 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + diff --git a/src/Plugins/SmartStore.FacebookAuth/web.config b/src/Plugins/SmartStore.FacebookAuth/web.config index e0c21864c3..3a843a4ce5 100644 --- a/src/Plugins/SmartStore.FacebookAuth/web.config +++ b/src/Plugins/SmartStore.FacebookAuth/web.config @@ -1,121 +1,121 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + diff --git a/src/Plugins/SmartStore.GoogleAnalytics/web.config b/src/Plugins/SmartStore.GoogleAnalytics/web.config index 8e255a8d61..06e0d9a92a 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/web.config +++ b/src/Plugins/SmartStore.GoogleAnalytics/web.config @@ -1,116 +1,116 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/web.config b/src/Plugins/SmartStore.GoogleMerchantCenter/web.config index 3766af5f74..c6048447e5 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/web.config +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/web.config @@ -1,117 +1,117 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + diff --git a/src/Plugins/SmartStore.OfflinePayment/web.config b/src/Plugins/SmartStore.OfflinePayment/web.config index 3766af5f74..c6048447e5 100644 --- a/src/Plugins/SmartStore.OfflinePayment/web.config +++ b/src/Plugins/SmartStore.OfflinePayment/web.config @@ -1,117 +1,117 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + diff --git a/src/Plugins/SmartStore.PayPal/web.config b/src/Plugins/SmartStore.PayPal/web.config index 76f7e23429..92a2529d51 100644 --- a/src/Plugins/SmartStore.PayPal/web.config +++ b/src/Plugins/SmartStore.PayPal/web.config @@ -1,11 +1,11 @@ - + -
    +
    - + @@ -14,128 +14,128 @@ - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + diff --git a/src/Plugins/SmartStore.Shipping/web.config b/src/Plugins/SmartStore.Shipping/web.config index 8e255a8d61..06e0d9a92a 100644 --- a/src/Plugins/SmartStore.Shipping/web.config +++ b/src/Plugins/SmartStore.Shipping/web.config @@ -1,116 +1,116 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + diff --git a/src/Plugins/SmartStore.ShippingByWeight/web.config b/src/Plugins/SmartStore.ShippingByWeight/web.config index 8e255a8d61..06e0d9a92a 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/web.config +++ b/src/Plugins/SmartStore.ShippingByWeight/web.config @@ -1,116 +1,116 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + diff --git a/src/Plugins/SmartStore.Tax/web.config b/src/Plugins/SmartStore.Tax/web.config index 3766af5f74..c6048447e5 100644 --- a/src/Plugins/SmartStore.Tax/web.config +++ b/src/Plugins/SmartStore.Tax/web.config @@ -1,117 +1,117 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - \ No newline at end of file + diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs index 3a44246262..8d6e7b5986 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs @@ -828,6 +828,9 @@ public ActionResult MaintenanceDeleteImageCache() _imageCache.Value.DeleteCachedImages(); + // get rid of cached image metadata + _cache("static").Clear(); + return RedirectToAction("Maintenance"); } From 6a008e6474066afd56c291119db2b9d8fa097785 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 Nov 2015 00:15:14 +0100 Subject: [PATCH 216/874] Updated MsieJavaScriptEngine to latest version --- src/Plugins/SmartStore.AmazonPay/web.config | 132 ++++++++--------- src/Plugins/SmartStore.Clickatell/web.config | 116 +++++++-------- src/Plugins/SmartStore.DevTools/Web.config | 130 ++++++++--------- .../SmartStore.DiscountRules/web.config | 116 +++++++-------- .../SmartStore.FacebookAuth/web.config | 120 ++++++++-------- .../SmartStore.GoogleAnalytics/web.config | 116 +++++++-------- .../web.config | 116 +++++++-------- .../SmartStore.OfflinePayment/web.config | 116 +++++++-------- src/Plugins/SmartStore.PayPal/web.config | 136 +++++++++--------- src/Plugins/SmartStore.Shipping/web.config | 116 +++++++-------- .../SmartStore.ShippingByWeight/web.config | 116 +++++++-------- src/Plugins/SmartStore.Tax/web.config | 116 +++++++-------- src/Plugins/SmartStore.WebApi/web.config | 2 +- .../SmartStore.Web.Framework.csproj | 6 +- .../SmartStore.Web.Framework/app.config | 4 +- .../SmartStore.Web.Framework/packages.config | 2 +- .../SmartStore.Web/Administration/Web.config | 2 +- .../SmartStore.Web/SmartStore.Web.csproj | 16 ++- src/Presentation/SmartStore.Web/Web.config | 6 +- .../SmartStore.Web/packages.config | 6 +- src/Tests/SmartStore.Web.MVC.Tests/App.config | 6 +- 21 files changed, 757 insertions(+), 739 deletions(-) diff --git a/src/Plugins/SmartStore.AmazonPay/web.config b/src/Plugins/SmartStore.AmazonPay/web.config index 56c636965f..0e57dde0c1 100644 --- a/src/Plugins/SmartStore.AmazonPay/web.config +++ b/src/Plugins/SmartStore.AmazonPay/web.config @@ -1,127 +1,131 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + diff --git a/src/Plugins/SmartStore.Clickatell/web.config b/src/Plugins/SmartStore.Clickatell/web.config index c6048447e5..3e1b370f5d 100644 --- a/src/Plugins/SmartStore.Clickatell/web.config +++ b/src/Plugins/SmartStore.Clickatell/web.config @@ -1,116 +1,116 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.DevTools/Web.config b/src/Plugins/SmartStore.DevTools/Web.config index e4010c8209..e1ac213bd8 100644 --- a/src/Plugins/SmartStore.DevTools/Web.config +++ b/src/Plugins/SmartStore.DevTools/Web.config @@ -1,135 +1,135 @@ - + -
    +
    - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - + diff --git a/src/Plugins/SmartStore.DiscountRules/web.config b/src/Plugins/SmartStore.DiscountRules/web.config index c6048447e5..3e1b370f5d 100644 --- a/src/Plugins/SmartStore.DiscountRules/web.config +++ b/src/Plugins/SmartStore.DiscountRules/web.config @@ -1,116 +1,116 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.FacebookAuth/web.config b/src/Plugins/SmartStore.FacebookAuth/web.config index 3a843a4ce5..6b304e839a 100644 --- a/src/Plugins/SmartStore.FacebookAuth/web.config +++ b/src/Plugins/SmartStore.FacebookAuth/web.config @@ -1,120 +1,120 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.GoogleAnalytics/web.config b/src/Plugins/SmartStore.GoogleAnalytics/web.config index 06e0d9a92a..32347fc005 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/web.config +++ b/src/Plugins/SmartStore.GoogleAnalytics/web.config @@ -1,115 +1,115 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/web.config b/src/Plugins/SmartStore.GoogleMerchantCenter/web.config index c6048447e5..3e1b370f5d 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/web.config +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/web.config @@ -1,116 +1,116 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.OfflinePayment/web.config b/src/Plugins/SmartStore.OfflinePayment/web.config index c6048447e5..3e1b370f5d 100644 --- a/src/Plugins/SmartStore.OfflinePayment/web.config +++ b/src/Plugins/SmartStore.OfflinePayment/web.config @@ -1,116 +1,116 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.PayPal/web.config b/src/Plugins/SmartStore.PayPal/web.config index 92a2529d51..d9badc0f86 100644 --- a/src/Plugins/SmartStore.PayPal/web.config +++ b/src/Plugins/SmartStore.PayPal/web.config @@ -1,11 +1,11 @@ - + -
    +
    - + @@ -14,127 +14,131 @@ - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + diff --git a/src/Plugins/SmartStore.Shipping/web.config b/src/Plugins/SmartStore.Shipping/web.config index 06e0d9a92a..32347fc005 100644 --- a/src/Plugins/SmartStore.Shipping/web.config +++ b/src/Plugins/SmartStore.Shipping/web.config @@ -1,115 +1,115 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.ShippingByWeight/web.config b/src/Plugins/SmartStore.ShippingByWeight/web.config index 06e0d9a92a..32347fc005 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/web.config +++ b/src/Plugins/SmartStore.ShippingByWeight/web.config @@ -1,115 +1,115 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.Tax/web.config b/src/Plugins/SmartStore.Tax/web.config index c6048447e5..3e1b370f5d 100644 --- a/src/Plugins/SmartStore.Tax/web.config +++ b/src/Plugins/SmartStore.Tax/web.config @@ -1,116 +1,116 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.WebApi/web.config b/src/Plugins/SmartStore.WebApi/web.config index b6bf71d919..71013386a2 100644 --- a/src/Plugins/SmartStore.WebApi/web.config +++ b/src/Plugins/SmartStore.WebApi/web.config @@ -46,7 +46,7 @@ - + diff --git a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj index e4907bffb2..1c59563458 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj +++ b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj @@ -102,9 +102,9 @@ ..\..\packages\FluentValidation.5.6.2.0\lib\Net45\FluentValidation.dll True - - False - ..\..\packages\JavaScriptEngineSwitcher.Core.1.1.3\lib\net40\JavaScriptEngineSwitcher.Core.dll + + ..\..\packages\JavaScriptEngineSwitcher.Core.1.2.4\lib\net40\JavaScriptEngineSwitcher.Core.dll + True False diff --git a/src/Presentation/SmartStore.Web.Framework/app.config b/src/Presentation/SmartStore.Web.Framework/app.config index f31d89f1ed..5d4773d412 100644 --- a/src/Presentation/SmartStore.Web.Framework/app.config +++ b/src/Presentation/SmartStore.Web.Framework/app.config @@ -16,7 +16,7 @@ - + @@ -52,4 +52,4 @@ - + diff --git a/src/Presentation/SmartStore.Web.Framework/packages.config b/src/Presentation/SmartStore.Web.Framework/packages.config index 64af87c9ed..d105ec9f25 100644 --- a/src/Presentation/SmartStore.Web.Framework/packages.config +++ b/src/Presentation/SmartStore.Web.Framework/packages.config @@ -10,7 +10,7 @@ - + diff --git a/src/Presentation/SmartStore.Web/Administration/Web.config b/src/Presentation/SmartStore.Web/Administration/Web.config index 7f2608b189..82720de3ee 100644 --- a/src/Presentation/SmartStore.Web/Administration/Web.config +++ b/src/Presentation/SmartStore.Web/Administration/Web.config @@ -92,7 +92,7 @@ - + diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index aea5811809..2d2216c935 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -114,12 +114,13 @@ ..\..\packages\FluentValidation.MVC5.5.6.2.0\lib\Net45\FluentValidation.Mvc.dll True - - False - ..\..\packages\JavaScriptEngineSwitcher.Core.1.1.3\lib\net40\JavaScriptEngineSwitcher.Core.dll + + ..\..\packages\JavaScriptEngineSwitcher.Core.1.2.4\lib\net40\JavaScriptEngineSwitcher.Core.dll + True - - ..\..\packages\JavaScriptEngineSwitcher.Msie.1.1.4\lib\net40\JavaScriptEngineSwitcher.Msie.dll + + ..\..\packages\JavaScriptEngineSwitcher.Msie.1.2.11\lib\net40\JavaScriptEngineSwitcher.Msie.dll + True False @@ -129,8 +130,9 @@ True ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\..\packages\MsieJavaScriptEngine.1.4.2\lib\net40\MsieJavaScriptEngine.dll + + ..\..\packages\MsieJavaScriptEngine.1.5.6\lib\net40\MsieJavaScriptEngine.dll + True False diff --git a/src/Presentation/SmartStore.Web/Web.config b/src/Presentation/SmartStore.Web/Web.config index 61b4ad427c..72900c3413 100644 --- a/src/Presentation/SmartStore.Web/Web.config +++ b/src/Presentation/SmartStore.Web/Web.config @@ -256,7 +256,7 @@ - + @@ -306,6 +306,10 @@ + + + + diff --git a/src/Presentation/SmartStore.Web/packages.config b/src/Presentation/SmartStore.Web/packages.config index 555ecc8763..46414d814b 100644 --- a/src/Presentation/SmartStore.Web/packages.config +++ b/src/Presentation/SmartStore.Web/packages.config @@ -11,8 +11,8 @@ - - + + @@ -26,7 +26,7 @@ - + diff --git a/src/Tests/SmartStore.Web.MVC.Tests/App.config b/src/Tests/SmartStore.Web.MVC.Tests/App.config index 0d3c92bfdd..47411d9ac8 100644 --- a/src/Tests/SmartStore.Web.MVC.Tests/App.config +++ b/src/Tests/SmartStore.Web.MVC.Tests/App.config @@ -43,7 +43,7 @@ - + @@ -73,6 +73,10 @@ + + + + From 48cc39feaea772e6a39f689540a4148af9f39389 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 12 Nov 2015 12:05:37 +0100 Subject: [PATCH 217/874] Further IDataExporter implementation --- .../DataExchange/IDataExporter.cs | 9 ++++- .../DataExchange/Internal/DataExportTask.cs | 7 +++- .../DataExchange/Internal/DataExporter.cs | 37 +++++++++---------- .../Internal/DataExporterContext.cs | 4 -- .../Controllers/ExportController.cs | 4 +- 5 files changed, 30 insertions(+), 31 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs index 843a15351e..b39c61fcf5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using SmartStore.Core.Domain; +using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Plugins; namespace SmartStore.Services.DataExchange @@ -13,7 +15,7 @@ public interface IDataExporter { DataExportResult Export(DataExportRequest request, CancellationToken cancellationToken); - IList Preview(DataExportRequest request); + IList Preview(DataExportRequest request, int pageIndex, int? totalRecords = null); int GetDataCount(DataExportRequest request); } @@ -35,6 +37,7 @@ public DataExportRequest(ExportProfile profile, Provider provid ProgressValueSetter = _voidProgressValueSetter; ProgressMessageSetter = _voidProgressMessageSetter; + EntitiesToExport = new List(); CustomData = new Dictionary(StringComparer.OrdinalIgnoreCase); } @@ -42,13 +45,15 @@ public DataExportRequest(ExportProfile profile, Provider provid public Provider Provider { get; private set; } - public IEnumerable EntitiesToExport { get; set; } + public IList EntitiesToExport { get; set; } public ProgressValueSetter ProgressValueSetter { get; set; } public ProgressMessageSetter ProgressMessageSetter { get; set; } public IDictionary CustomData { get; private set; } + public IQueryable ProductQuery { get; set; } + private static void SetProgress(int val, int max, string msg) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs index ab5d8e40c5..54ed0768e6 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs @@ -1,4 +1,4 @@ -using System.Web; +using System.Linq; using SmartStore.Core.Localization; using SmartStore.Services.Tasks; @@ -47,7 +47,10 @@ public void Execute(TaskExecutionContext ctx) if (ctx.Parameters.ContainsKey("SelectedIds")) { - request.EntitiesToExport = ctx.Parameters["SelectedIds"].ToIntArray(); + request.EntitiesToExport = ctx.Parameters["SelectedIds"] + .SplitSafe(",") + .Select(x => x.ToInt()) + .ToList(); } // process! diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs index cc263c1b5b..43cf433f7a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs @@ -380,12 +380,12 @@ private IQueryable GetProductQuery(DataExporterContext ctx, int skip, i { IQueryable query = null; - if (ctx.QueryProducts == null) + if (ctx.Request.ProductQuery == null) { var searchContext = new ProductSearchContext { OrderBy = ProductSortingEnum.CreatedOn, - ProductIds = ctx.EntityIdsSelected, + ProductIds = ctx.Request.EntitiesToExport, StoreId = (ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId), VisibleIndividuallyOnly = true, PriceMin = ctx.Filter.PriceMinimum, @@ -421,7 +421,7 @@ private IQueryable GetProductQuery(DataExporterContext ctx, int skip, i } else { - query = ctx.QueryProducts; + query = ctx.Request.ProductQuery; } if (skip > 0) @@ -495,8 +495,8 @@ private IQueryable GetOrderQuery(DataExporterContext ctx, int skip, int t null, null); - if (ctx.EntityIdsSelected.Count > 0) - query = query.Where(x => ctx.EntityIdsSelected.Contains(x.Id)); + if (ctx.Request.EntitiesToExport.Count > 0) + query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); query = query.OrderByDescending(x => x.CreatedOnUtc); @@ -610,10 +610,8 @@ private IQueryable GetCustomerQuery(DataExporterContext ctx, int skip, .Expand(x => x.CustomerRoles) .Where(x => !x.Deleted); - if (ctx.EntityIdsSelected.Count > 0) - { - query = query.Where(x => ctx.EntityIdsSelected.Contains(x.Id)); - } + if (ctx.Request.EntitiesToExport.Count > 0) + query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); query = query.OrderByDescending(x => x.CreatedOnUtc); @@ -890,6 +888,11 @@ private void ExportCoreOuter(DataExporterContext ctx) throw new SmartException("Export aborted because the export provider is not valid"); } + foreach (var item in ctx.Request.CustomData) + { + ctx.ExecuteContext.CustomProperties.Add(item.Key, item.Value); + } + ctx.Log = logger; ctx.ExecuteContext.Log = logger; ctx.ProgressInfo = T("Admin.DataExchange.Export.ProgressInfo"); @@ -1025,13 +1028,15 @@ private void ExportCoreOuter(DataExporterContext ctx) ctx.DeliveryTimes.Clear(); ctx.CategoryPathes.Clear(); ctx.Categories.Clear(); - ctx.EntityIdsSelected.Clear(); ctx.ProductExportContext = null; ctx.OrderExportContext = null; ctx.ManufacturerExportContext = null; ctx.CategoryExportContext = null; ctx.CustomerExportContext = null; + ctx.Request.EntitiesToExport.Clear(); + ctx.Request.CustomData.Clear(); + ctx.ExecuteContext.CustomProperties.Clear(); ctx.ExecuteContext.Log = null; ctx.Log = null; @@ -1061,11 +1066,6 @@ public DataExportResult Export(DataExportRequest request, CancellationToken canc { var ctx = new DataExporterContext(request, cancellationToken); - if (request.EntitiesToExport != null) - { - ctx.EntityIdsSelected.AddRange(request.EntitiesToExport); - } - ExportCoreOuter(ctx); if (ctx.Result != null && ctx.Result.Succeeded && ctx.Result.Files.Count > 0) @@ -1089,7 +1089,7 @@ public DataExportResult Export(DataExportRequest request, CancellationToken canc else prefix = request.Provider.Value.EntityType.ToString(); - var selectedEntityCount = (request.EntitiesToExport == null ? 0 : request.EntitiesToExport.Count()); + var selectedEntityCount = (request.EntitiesToExport == null ? 0 : request.EntitiesToExport.Count); if (selectedEntityCount == 0) suffix = T("Common.All"); @@ -1102,11 +1102,8 @@ public DataExportResult Export(DataExportRequest request, CancellationToken canc return ctx.Result; } - public IList Preview(DataExportRequest request) + public IList Preview(DataExportRequest request, int pageIndex, int? totalRecords = null) { - var pageIndex = (request.CustomData.ContainsKey("PageIndex") ? (int)request.CustomData["PageIndex"] : 0); - var totalRecords = (request.CustomData.ContainsKey("TotalRecords") ? (int)request.CustomData["TotalRecords"] : 0); - var resultData = new List(); var cancellation = new CancellationTokenSource(TimeSpan.FromMinutes(5.0)); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs index b12b3a1bdb..23da17e069 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs @@ -1,6 +1,5 @@ using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading; using SmartStore.Core; using SmartStore.Core.Domain; @@ -37,7 +36,6 @@ public DataExporterContext( FolderContent = FileSystemHelper.TempDir(@"Profile\Export\{0}\Content".FormatInvariant(request.Profile.FolderName)); FolderRoot = System.IO.Directory.GetParent(FolderContent).FullName; - EntityIdsSelected = new List(); Categories = new Dictionary(); CategoryPathes = new Dictionary(); Countries = new Dictionary(); @@ -56,13 +54,11 @@ public DataExporterContext( ExecuteContext.Projection = XmlHelper.Deserialize(request.Profile.Projection); } - public List EntityIdsSelected { get; private set; } public List EntityIdsLoaded { get; set; } public int RecordCount { get; set; } public Dictionary RecordsPerStore { get; set; } public string ProgressInfo { get; set; } - public IQueryable QueryProducts { get; set; } public DataExportRequest Request { get; private set; } public CancellationToken CancellationToken { get; private set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 518e66169c..354da99fe9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -754,10 +754,8 @@ public ActionResult PreviewList(GridCommand command, int id, int totalRecords) var orderModel = new List(); var request = new DataExportRequest(profile, provider); - request.CustomData.Add("PageIndex", command.Page - 1); - request.CustomData.Add("TotalRecords", totalRecords); - var data = _dataExporter.Preview(request); + var data = _dataExporter.Preview(request, command.Page - 1, totalRecords); foreach (dynamic item in data) { From f685b80399f4584fccecd17c19af37756278ebe5 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 12 Nov 2015 17:16:49 +0100 Subject: [PATCH 218/874] Creating system export profiles --- .../Domain/DataExchange/ExportProfile.cs | 10 + .../Mapping/DataExchange/ExportProfileMap.cs | 1 + ...1511121139009_ExportFramework3.Designer.cs | 29 +++ .../201511121139009_ExportFramework3.cs | 193 ++++++++++++++++++ .../201511121139009_ExportFramework3.resx | 126 ++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 7 + .../{Internal => }/DataExportTask.cs | 2 +- .../{Internal => }/DataExporter.cs | 3 +- .../{Internal => }/DynamicEntityHelper.cs | 3 +- .../DataExchange/ExportProfileService.cs | 8 +- .../Providers/CategoryXmlExportProvider.cs | 3 +- .../Providers/CustomerXlsxExportProvider.cs | 3 +- .../Providers/CustomerXmlExportProvider.cs | 3 +- .../ManufacturerXmlExportProvider.cs | 3 +- .../Providers/OrderXlsxExportProvider.cs | 3 +- .../Providers/OrderXmlExportProvider.cs | 3 +- .../Providers/ProductXlsxExportProvider.cs | 3 +- .../Providers/ProductXmlExportProvider.cs | 3 +- .../Providers/SubscriberCsvExportProvider.cs | 3 +- .../SmartStore.Services.csproj | 6 +- .../Localization/resources.en-us.xml | 2 +- .../Controllers/ExportController.cs | 2 + .../Models/DataExchange/ExportProfileModel.cs | 6 + .../Administration/Views/Export/Edit.cshtml | 25 ++- .../Administration/Views/Export/List.cshtml | 12 ++ 25 files changed, 432 insertions(+), 30 deletions(-) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.resx rename src/Libraries/SmartStore.Services/DataExchange/{Internal => }/DataExportTask.cs (96%) rename src/Libraries/SmartStore.Services/DataExchange/{Internal => }/DataExporter.cs (99%) rename src/Libraries/SmartStore.Services/DataExchange/{Internal => }/DynamicEntityHelper.cs (99%) diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs index ea98e0838b..b49d8d76d1 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportProfile.cs @@ -32,11 +32,21 @@ public ExportProfile() ///
    public string FileNamePattern { get; set; } + /// + /// The system name of the profile + /// + public string SystemName { get; set; } + /// /// The system name of the export provider /// public string ProviderSystemName { get; set; } + /// + /// Whether the profile is an unremovable system profile + /// + public bool IsSystemProfile { get; set; } + /// /// Whether the export profile is enabled /// diff --git a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs index d54bf034e3..6e5e6758fe 100644 --- a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportProfileMap.cs @@ -12,6 +12,7 @@ public ExportProfileMap() this.Property(x => x.Name).IsRequired().HasMaxLength(100); this.Property(x => x.FolderName).IsRequired().HasMaxLength(100); + this.Property(x => x.SystemName).HasMaxLength(400); this.Property(x => x.ProviderSystemName).IsRequired().HasMaxLength(4000); this.Property(x => x.Filtering).IsMaxLength(); this.Property(x => x.Projection).IsMaxLength(); diff --git a/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.Designer.cs new file mode 100644 index 0000000000..634dd864d5 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.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 ExportFramework3 : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(ExportFramework3)); + + string IMigrationMetadata.Id + { + get { return "201511121139009_ExportFramework3"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs b/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs new file mode 100644 index 0000000000..34a97d19bc --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs @@ -0,0 +1,193 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using Setup; + using Core.Domain.Tasks; + using System.Collections.Generic; + using Core.Domain; + using System.Linq; + using Utilities; + + public partial class ExportFramework3 : DbMigration, ILocaleResourcesProvider, IDataSeeder + { + public override void Up() + { + AddColumn("dbo.ExportProfile", "SystemName", c => c.String(maxLength: 400)); + AddColumn("dbo.ExportProfile", "IsSystemProfile", c => c.Boolean(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.ExportProfile", "IsSystemProfile"); + DropColumn("dbo.ExportProfile", "SystemName"); + } + + public bool RollbackOnFailure + { + get { return false; } + } + + public void Seed(SmartObjectContext context) + { + context.MigrateLocaleResources(MigrateLocaleResources); + + var systemProfilesInfos = new List + { + new SystemProfileInfo { SystemName = "SmartStoreCategoryXml", ProviderSystemName = "Exports.SmartStoreCategoryXml", Name = "Category XML Export" }, + new SystemProfileInfo { SystemName = "SmartStoreCustomerXlsx", ProviderSystemName = "Exports.SmartStoreCustomerXlsx", Name = "Customer Excel Export" }, + new SystemProfileInfo { SystemName = "SmartStoreCustomerXml", ProviderSystemName = "Exports.SmartStoreCustomerXml", Name = "Customer XML Export" }, + new SystemProfileInfo { SystemName = "SmartStoreManufacturerXml", ProviderSystemName = "Exports.SmartStoreManufacturerXml", Name = "Manufacturer XML Export" }, + new SystemProfileInfo { SystemName = "SmartStoreOrderXlsx", ProviderSystemName = "Exports.SmartStoreOrderXlsx", Name = "Order Excel Export" }, + new SystemProfileInfo { SystemName = "SmartStoreOrderXml", ProviderSystemName = "Exports.SmartStoreOrderXml", Name = "Order XML Export" }, + new SystemProfileInfo { SystemName = "SmartStoreProductXlsx", ProviderSystemName = "Exports.SmartStoreProductXlsx", Name = "Product Excel Export" }, + new SystemProfileInfo { SystemName = "SmartStoreProductXml", ProviderSystemName = "Exports.SmartStoreProductXml", Name = "Product XML Export" }, + new SystemProfileInfo { SystemName = "SmartStoreNewsSubscriptionCsv", ProviderSystemName = "Exports.SmartStoreNewsSubscriptionCsv", Name = "Newsletter Subscribers CSV Export" } + }; + + var tasks = context.Set(); + var profiles = context.Set(); + + foreach (var profileInfo in systemProfilesInfos) + { + var profile = profiles.FirstOrDefault(x => x.IsSystemProfile && x.SystemName == profileInfo.SystemName && x.ProviderSystemName == profileInfo.ProviderSystemName); + + if (profile != null) + continue; + + var task = new ScheduleTask + { + CronExpression = "0 */6 * * *", // every six hours + Type = "SmartStore.Services.DataExchange.DataExportTask, SmartStore.Services", + Enabled = false, + StopOnError = false, + IsHidden = true + }; + + task.Name = string.Concat(profileInfo.Name, " task"); + + task = tasks.Add(task); + context.SaveChanges(); + + var seoName = SeoHelper.GetSeName(profileInfo.Name, true, false).Replace("/", "").Replace("-", ""); + + profile = new ExportProfile + { + IsSystemProfile = true, + Name = profileInfo.Name, + SystemName = profileInfo.SystemName, + ProviderSystemName = profileInfo.ProviderSystemName, + FolderName = string.Concat("sm-", seoName.Replace("export", "").ToValidPath().Truncate(50)), + FileNamePattern = "%Store.Id%-%Profile.Id%-%File.Index%-%Profile.SeoName%", + Enabled = true, + PerStore = false, + CreateZipArchive = false, + Cleanup = false, + SchedulingTaskId = task.Id + }; + + profile = profiles.Add(profile); + + task.Alias = profile.Id.ToString(); + + tasks.AddOrUpdate(task); + context.SaveChanges(); + } + } + + public void MigrateLocaleResources(LocaleResourcesBuilder builder) + { + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.NewsLetterSubscription", + "Newsletter Subscribers", + "Newsletter Abonnenten"); + + + builder.AddOrUpdate("Providers.FriendlyName.Exports.SmartStoreCategoryXml", + "Category XML Export", + "Warengruppen XML Export"); + builder.AddOrUpdate("Providers.Description.Exports.SmartStoreCategoryXml", + "Allows to export category data in XML format.", + "Ermglicht den Export von Warengruppendaten im XML Format."); + + builder.AddOrUpdate("Providers.FriendlyName.Exports.SmartStoreCustomerXlsx", + "Customer Excel Export", + "Kunden Excel Export"); + builder.AddOrUpdate("Providers.Description.Exports.SmartStoreCustomerXlsx", + "Allows to export customer data in Excel format.", + "Ermglicht den Export von Kundendaten im Excel Format."); + + builder.AddOrUpdate("Providers.FriendlyName.Exports.SmartStoreCustomerXml", + "Customer XML Export", + "Kunden XML Export"); + builder.AddOrUpdate("Providers.Description.Exports.SmartStoreCustomerXml", + "Allows to export customer data in XML format.", + "Ermglicht den Export von Kundendaten im XML Format."); + + builder.AddOrUpdate("Providers.FriendlyName.Exports.SmartStoreManufacturerXml", + "Manufacturer XML Export", + "Hersteller XML Export"); + builder.AddOrUpdate("Providers.Description.Exports.SmartStoreManufacturerXml", + "Allows to export manufacturer data in XML format.", + "Ermglicht den Export von Herstellerdaten im XML Format."); + + builder.AddOrUpdate("Providers.FriendlyName.Exports.SmartStoreOrderXlsx", + "Order Excel Export", + "Auftrags Excel Export"); + builder.AddOrUpdate("Providers.Description.Exports.SmartStoreOrderXlsx", + "Allows to export order data in Excel format.", + "Ermglicht den Export von Auftragsdaten im Excel Format."); + + builder.AddOrUpdate("Providers.FriendlyName.Exports.SmartStoreOrderXml", + "Order XML Export", + "Auftrags XML Export"); + builder.AddOrUpdate("Providers.Description.Exports.SmartStoreOrderXml", + "Allows to export order data in XML format.", + "Ermglicht den Export von Auftragsdaten im XML Format."); + + builder.AddOrUpdate("Providers.FriendlyName.Exports.SmartStoreProductXlsx", + "Product Excel Export", + "Produkt Excel Export"); + builder.AddOrUpdate("Providers.Description.Exports.SmartStoreProductXlsx", + "Allows to export product data in Excel format.", + "Ermglicht den Export von Produktdaten im Excel Format."); + + builder.AddOrUpdate("Providers.FriendlyName.Exports.SmartStoreProductXml", + "Product XML Export", + "Produkt XML Export"); + builder.AddOrUpdate("Providers.Description.Exports.SmartStoreProductXml", + "Allows to export product data in XML format.", + "Ermglicht den Export von Produktdaten im XML Format."); + + builder.AddOrUpdate("Providers.FriendlyName.Exports.SmartStoreNewsSubscriptionCsv", + "Newsletter Subscribers CSV Export", + "Newsletter Abonnenten CSV Export"); + builder.AddOrUpdate("Providers.Description.Exports.SmartStoreNewsSubscriptionCsv", + "Allows to export newsletter subscriber data in CSV format.", + "Ermglicht den Export von Newsletter Abonnentendaten im CSV Format."); + + + builder.AddOrUpdate("Admin.DataExchange.Export.SystemName", + "System name of profile", + "Systemname des Profils", + "The system name of the export profile.", + "Der Systemname des Exportprofils."); + + builder.AddOrUpdate("Admin.DataExchange.Export.IsSystemProfile", + "System profile", + "Systemprofil", + "Indicates whether the export profile is a system profile. System profiles cannot be removed.", + "Gibt an, ob es sich bei dem Exportprofil um eine Systemprofil handelt. Systemprofile knnen nicht entfernt werden."); + + builder.AddOrUpdate("Admin.DataExchange.Export.CannotDeleteSystemProfile", + "Cannot delete a system export profile.", + "Ein System-Exportprofil kann nicht gelscht werden."); + } + } + + + internal class SystemProfileInfo + { + public string SystemName { get; set; } + public string ProviderSystemName { get; set; } + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.resx b/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.resx new file mode 100644 index 0000000000..1ed5a1d758 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.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 8764a6a9ed..b328653e66 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -366,6 +366,10 @@ 201509150931528_ExportFramework2.cs + + + 201511121139009_ExportFramework3.cs + @@ -687,6 +691,9 @@ 201509150931528_ExportFramework2.cs + + 201511121139009_ExportFramework3.cs + diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs b/src/Libraries/SmartStore.Services/DataExchange/DataExportTask.cs similarity index 96% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs rename to src/Libraries/SmartStore.Services/DataExchange/DataExportTask.cs index 54ed0768e6..0622be6566 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExportTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DataExportTask.cs @@ -2,7 +2,7 @@ using SmartStore.Core.Localization; using SmartStore.Services.Tasks; -namespace SmartStore.Services.DataExchange.Internal +namespace SmartStore.Services.DataExchange { // note: namespace persisted in ScheduleTask.Type public partial class DataExportTask : ITask diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs similarity index 99% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs rename to src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs index 43cf433f7a..b409401da6 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs @@ -21,6 +21,7 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; +using SmartStore.Services.DataExchange.Internal; using SmartStore.Services.Directory; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; @@ -33,7 +34,7 @@ using SmartStore.Utilities; using SmartStore.Utilities.Threading; -namespace SmartStore.Services.DataExchange.Internal +namespace SmartStore.Services.DataExchange { public partial class DataExporter : IDataExporter { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs similarity index 99% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntityHelper.cs rename to src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs index e3de6b5e56..73cb0ae431 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs @@ -21,10 +21,11 @@ using SmartStore.Core.Domain.Stores; using SmartStore.Core.Html; using SmartStore.Services.Catalog; +using SmartStore.Services.DataExchange.Internal; using SmartStore.Services.Localization; using SmartStore.Services.Seo; -namespace SmartStore.Services.DataExchange.Internal +namespace SmartStore.Services.DataExchange { public partial class DataExporter { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs index 414e5c073a..f2860ed481 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs @@ -104,7 +104,7 @@ public virtual ExportProfile InsertExportProfile(Provider provi task.LastEndUtc = task.LastStartUtc = task.LastSuccessUtc = null; } - task.Name = string.Concat(name, " export task"); + task.Name = string.Concat(name, " task"); _scheduleTaskService.InsertTask(task); @@ -196,6 +196,9 @@ public virtual void DeleteExportProfile(ExportProfile profile) if (profile == null) throw new ArgumentNullException("profile"); + if (profile.IsSystemProfile) + throw new SmartException(_localizationService.GetResource("Admin.DataExchange.Export.CannotDeleteSystemProfile")); + int scheduleTaskId = profile.SchedulingTaskId; var folder = profile.GetExportFolder(); @@ -224,7 +227,8 @@ from x in _exportProfileRepository.Table.Expand(x => x.ScheduleTask) } query = query - .OrderBy(x => x.ProviderSystemName) + .OrderBy(x => x.IsSystemProfile) + .ThenBy(x => x.ProviderSystemName) .ThenBy(x => x.Name); return query; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs index 401b9a165c..8733ac6a1e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/CategoryXmlExportProvider.cs @@ -10,8 +10,7 @@ namespace SmartStore.Services.DataExchange.Providers /// Exports XML formatted category data to a file /// [SystemName("Exports.SmartStoreCategoryXml")] - [FriendlyName("SmartStore XML category export")] - [IsHidden(true)] + [FriendlyName("Category XML Export")] public class CategoryXmlExportProvider : ExportProviderBase { public static string SystemName diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXlsxExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXlsxExportProvider.cs index 133ea6e3a7..75437762ee 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXlsxExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXlsxExportProvider.cs @@ -14,8 +14,7 @@ namespace SmartStore.Services.DataExchange.Providers /// Exports Excel formatted customer data to a file /// [SystemName("Exports.SmartStoreCustomerXlsx")] - [FriendlyName("SmartStore Excel customer export")] - [IsHidden(true)] + [FriendlyName("Customer Excel Export")] public class CustomerXlsxExportProvider : ExportProviderBase { private string[] Properties diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs index 7b7611fb2a..a5f7a56048 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/CustomerXmlExportProvider.cs @@ -9,8 +9,7 @@ namespace SmartStore.Services.DataExchange.Providers /// Exports XML formatted customer data to a file /// [SystemName("Exports.SmartStoreCustomerXml")] - [FriendlyName("SmartStore XML customer export")] - [IsHidden(true)] + [FriendlyName("Customer XML Export")] public class CustomerXmlExportProvider : ExportProviderBase { public static string SystemName diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs index 74eb5551a7..e4bb40c427 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/ManufacturerXmlExportProvider.cs @@ -10,8 +10,7 @@ namespace SmartStore.Services.DataExchange.Providers /// Exports XML formatted manufacturer data to a file /// [SystemName("Exports.SmartStoreManufacturerXml")] - [FriendlyName("SmartStore XML manufacturer export")] - [IsHidden(true)] + [FriendlyName("Manufacturer XML Export")] public class ManufacturerXmlExportProvider : ExportProviderBase { public static string SystemName diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXlsxExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXlsxExportProvider.cs index b36b4286d9..f6b34744d0 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXlsxExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXlsxExportProvider.cs @@ -12,8 +12,7 @@ namespace SmartStore.Services.DataExchange.Providers /// Exports Excel formatted order data to a file /// [SystemName("Exports.SmartStoreOrderXlsx")] - [FriendlyName("SmartStore Excel order export")] - [IsHidden(true)] + [FriendlyName("Order Excel Export")] public class OrderXlsxExportProvider : ExportProviderBase { private string[] Properties diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs index 6a878ddb84..85069df675 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/OrderXmlExportProvider.cs @@ -13,8 +13,7 @@ namespace SmartStore.Services.DataExchange.Providers /// Exports XML formatted order data to a file /// [SystemName("Exports.SmartStoreOrderXml")] - [FriendlyName("SmartStore XML order export")] - [IsHidden(true)] + [FriendlyName("Order XML Export")] public class OrderXmlExportProvider : ExportProviderBase { public static string SystemName diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXlsxExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXlsxExportProvider.cs index e908fa8001..284388bbb0 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXlsxExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXlsxExportProvider.cs @@ -18,8 +18,7 @@ namespace SmartStore.Services.DataExchange.Providers /// Exports Excel formatted product data to a file /// [SystemName("Exports.SmartStoreProductXlsx")] - [FriendlyName("SmartStore Excel product export")] - [IsHidden(true)] + [FriendlyName("Product Excel Export")] public class ProductXlsxExportProvider : ExportProviderBase { private readonly ILanguageService _languageService; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs index 4c2f86ccfd..a5f88db4b4 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/ProductXmlExportProvider.cs @@ -9,8 +9,7 @@ namespace SmartStore.Services.DataExchange.Providers /// Exports XML formatted product data to a file /// [SystemName("Exports.SmartStoreProductXml")] - [FriendlyName("SmartStore XML product export")] - [IsHidden(true)] + [FriendlyName("Product XML Export")] public class ProductXmlExportProvider : ExportProviderBase { public static string SystemName diff --git a/src/Libraries/SmartStore.Services/DataExchange/Providers/SubscriberCsvExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Providers/SubscriberCsvExportProvider.cs index bf8b192421..ff2873ee92 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Providers/SubscriberCsvExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Providers/SubscriberCsvExportProvider.cs @@ -11,8 +11,7 @@ namespace SmartStore.Services.DataExchange.Providers /// Exports CSV formatted newsletter subscription data to a file /// [SystemName("Exports.SmartStoreNewsSubscriptionCsv")] - [FriendlyName("SmartStore CSV newsletter subscription export")] - [IsHidden(true)] + [FriendlyName("Newsletter Subscribers CSV Export")] public class SubscriberCsvExportProvider : ExportProviderBase { public static string SystemName diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 83fe5c10e4..ec4af99cbd 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -168,9 +168,9 @@ - + - + @@ -198,7 +198,7 @@ - + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml index 8e7528f68e..a80721be22 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml @@ -9,7 +9,7 @@ Google Merchant Center XML feed - Allows you to export product data in the XML feed format of Google Merchant Center (GMC). + Allows to export product data in XML feed format of Google Merchant Center (GMC). diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 354da99fe9..f15c4fe3a0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -104,6 +104,8 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile { model.Id = profile.Id; model.Name = profile.Name; + model.SystemName = profile.SystemName; + model.IsSystemProfile = profile.IsSystemProfile; model.ProviderSystemName = profile.ProviderSystemName; model.FolderName = profile.FolderName; model.FileNamePattern = profile.FileNamePattern; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 493636f238..7ea59f0921 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -24,6 +24,12 @@ public partial class ExportProfileModel : EntityModelBase [SmartResourceDisplayName("Admin.DataExchange.Export.Name")] public string Name { get; set; } + [SmartResourceDisplayName("Admin.DataExchange.Export.SystemName")] + public string SystemName { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.IsSystemProfile")] + public bool IsSystemProfile { get; set; } + [SmartResourceDisplayName("Admin.DataExchange.Export.ProviderSystemName")] public string ProviderSystemName { get; set; } public List AvailableProviders { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 55bac26fec..ecf22fd744 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -35,9 +35,12 @@ - + @if (!Model.IsSystemProfile) + { + + } @@ -73,6 +76,22 @@ @Html.ValidationMessageFor(x => x.Name)
    + @Html.SmartLabelFor(x => x.SystemName) + +
    @(Model.SystemName.NaIfEmpty())
    +
    + @Html.SmartLabelFor(x => x.IsSystemProfile) + + @T(Model.IsSystemProfile ? "Admin.Common.Yes" : "Admin.Common.No") +
    @Html.SmartLabelFor(x => x.Enabled) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 740e0447ce..6448df05a3 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -31,6 +31,7 @@ @T("Common.Enabled") @T("Admin.DataExchange.Export.EntityType") @T("Admin.DataExchange.Export.FileExtension")@T("Admin.DataExchange.Export.IsSystemProfile") @T("Admin.System.ScheduleTasks.LastStart") @T("Admin.System.ScheduleTasks.NextRun") @T("Admin.Common.Actions") @profile.Name + @if (profile.SystemName.HasValue()) + { +
    @profile.SystemName
    + }
    @if (profile.Provider.ConfigurationUrl.HasValue()) { @profile.Provider.FriendlyName.NaIfEmpty() } + else + { + @profile.Provider.FriendlyName.NaIfEmpty() + }
    @profile.ProviderSystemName
    @@ -62,6 +71,9 @@ @Html.IconForFileExtension(profile.Provider.FileExtension, true) + @Html.SymbolForBool(profile.IsSystemProfile) +
    @Html.Partial("~/Administration/Views/ScheduleTask/_LastRun.cshtml", profile.TaskModel) From dda09131ef478a0a10b7e4d7381438723df69204 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 Nov 2015 20:20:53 +0100 Subject: [PATCH 219/874] Cleaned, renamed and (re)moved a bunch of extension methods --- .../Extensions/CollectionExtensions.cs | 4 - .../Extensions/DateTimeExtensions.cs | 1 - .../Extensions/EnumerableExtensions.cs | 2 +- .../Extensions/HtmlTextWriterExtensions.cs | 7 +- .../Extensions/HttpExtensions.cs | 3 +- .../Extensions/MiscExtensions.cs | 29 +- .../Extensions/StringExtensions.cs | 1 - .../Utilities/Reflection/FastProperty.cs | 10 +- .../Extensions/MiscExtensions.cs | 43 +- .../Extensions/DateTimeExtensions.cs | 154 ++++++ .../Extensions/Extensions.cs | 455 ------------------ .../Extensions/HtmlSelectListExtensions.cs | 66 +++ .../Extensions/RouteExtensions.cs | 9 +- .../Extensions/TelerikExtensions.cs | 233 +++++++++ .../Plugins/PluginExtensions.cs | 32 ++ .../SmartStore.Web.Framework.csproj | 5 +- 16 files changed, 509 insertions(+), 545 deletions(-) create mode 100644 src/Presentation/SmartStore.Web.Framework/Extensions/DateTimeExtensions.cs delete mode 100644 src/Presentation/SmartStore.Web.Framework/Extensions/Extensions.cs create mode 100644 src/Presentation/SmartStore.Web.Framework/Extensions/HtmlSelectListExtensions.cs create mode 100644 src/Presentation/SmartStore.Web.Framework/Extensions/TelerikExtensions.cs create mode 100644 src/Presentation/SmartStore.Web.Framework/Plugins/PluginExtensions.cs diff --git a/src/Libraries/SmartStore.Core/Extensions/CollectionExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/CollectionExtensions.cs index e5ab2c829a..b6fec3780a 100644 --- a/src/Libraries/SmartStore.Core/Extensions/CollectionExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/CollectionExtensions.cs @@ -7,10 +7,8 @@ namespace SmartStore { - public static class CollectionExtensions { - public static void AddRange(this ICollection initial, IEnumerable other) { if (other == null) @@ -31,7 +29,5 @@ public static bool IsNullOrEmpty(this ICollection source) { return (source == null || source.Count == 0); } - } - } diff --git a/src/Libraries/SmartStore.Core/Extensions/DateTimeExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/DateTimeExtensions.cs index fc0cf3eacb..6ec7fc1cf9 100644 --- a/src/Libraries/SmartStore.Core/Extensions/DateTimeExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/DateTimeExtensions.cs @@ -5,7 +5,6 @@ namespace SmartStore { - public static class DateTimeExtensions { private static readonly DateTime MinDate = new DateTime(1900, 1, 1); diff --git a/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs index 34b4babed5..bae0a18392 100644 --- a/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/EnumerableExtensions.cs @@ -170,7 +170,7 @@ public static void AddRange(this NameValueCollection initial, NameValueCollectio /// /// Builds an URL query string /// - /// Namer value collection + /// Name value collection /// Encoding type. Can be null. /// Whether to encode keys and values /// The query string without leading a question mark diff --git a/src/Libraries/SmartStore.Core/Extensions/HtmlTextWriterExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/HtmlTextWriterExtensions.cs index 26edc0d0e3..27b16ca907 100644 --- a/src/Libraries/SmartStore.Core/Extensions/HtmlTextWriterExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/HtmlTextWriterExtensions.cs @@ -4,11 +4,9 @@ using System.Web.UI; namespace SmartStore -{ - +{ public static class HtmlTextWriterExtensions { - public static void AddAttributes(this HtmlTextWriter writer, IDictionary attributes) { if (attributes.Any>()) @@ -20,8 +18,5 @@ public static void AddAttributes(this HtmlTextWriter writer, IDictionary 0; - } - - public static string GetDataType(this DataTable dt, string columnName) - { - dt.DefaultView.RowFilter = "ColumnName='" + columnName + "'"; - return dt.Rows[0]["DataType"].ToString(); - } - - public static int CountExecute(this OleDbConnection conn, string sqlCount) - { - using (OleDbCommand cmd = new OleDbCommand(sqlCount, conn)) - { - return (int)cmd.ExecuteScalar(); - } - } - public static object SafeConvert(this TypeConverter converter, string value) { try @@ -79,6 +59,7 @@ public static object SafeConvert(this TypeConverter converter, string value) { exc.Dump(); } + return null; } @@ -128,14 +109,6 @@ public static T GetMergedDataValue(this IMergedData mergedData, string key, T return defaultValue; } - public static bool IsRouteEqual(this RouteData routeData, string controller, string action) - { - if (routeData == null) - return false; - - return routeData.GetRequiredString("controller").IsCaseInsensitiveEqual(controller) && routeData.GetRequiredString("action").IsCaseInsensitiveEqual(action); - } - /// /// Append grow if string builder is empty. Append delimiter and grow otherwise. /// diff --git a/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs index 4a7f28e209..10a5fcc557 100644 --- a/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/StringExtensions.cs @@ -11,7 +11,6 @@ namespace SmartStore { - public static class StringExtensions { public const string CarriageReturnLineFeed = "\r\n"; diff --git a/src/Libraries/SmartStore.Core/Utilities/Reflection/FastProperty.cs b/src/Libraries/SmartStore.Core/Utilities/Reflection/FastProperty.cs index 65a726248f..ada0e55b7a 100644 --- a/src/Libraries/SmartStore.Core/Utilities/Reflection/FastProperty.cs +++ b/src/Libraries/SmartStore.Core/Utilities/Reflection/FastProperty.cs @@ -31,7 +31,7 @@ public class FastProperty private Action _valueSetter; /// - /// Initializes a fast . + /// Initializes a . /// This constructor does not cache the helper. For caching, use . /// public FastProperty(PropertyInfo property) @@ -100,7 +100,7 @@ public void SetValue(object instance, object value) /// underlying type. /// /// the instance to extract property accessors for. - /// a cached array of all public property getters from the underlying type of target instance. + /// A cached array of all public property getters from the underlying type of target instance. /// public static IReadOnlyDictionary GetProperties(object instance) { @@ -111,8 +111,8 @@ public static IReadOnlyDictionary GetProperties(object ins /// Creates and caches fast property helpers that expose getters for every public get property on the /// specified type. /// - /// the type to extract property accessors for. - /// a cached array of all public property getters from the type of target instance. + /// The type to extract property accessors for. + /// A cached array of all public property getters from the type of target instance. /// public static IReadOnlyDictionary GetProperties(Type type) { @@ -337,7 +337,7 @@ public static Action MakeFastPropertySetter(PropertyInfo propert /// is returned. /// /// - /// The implementation of PropertyHelper will cache the property accessors per-type. This is + /// The implementation of FastProperty will cache the property accessors per-type. This is /// faster when the the same type is used multiple times with ObjectToDictionary. /// public static IDictionary ObjectToDictionary(object value, Func keySelector = null) diff --git a/src/Libraries/SmartStore.Data/Extensions/MiscExtensions.cs b/src/Libraries/SmartStore.Data/Extensions/MiscExtensions.cs index ba396e84fe..70efa11f18 100644 --- a/src/Libraries/SmartStore.Data/Extensions/MiscExtensions.cs +++ b/src/Libraries/SmartStore.Data/Extensions/MiscExtensions.cs @@ -1,49 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Data.OleDb; -using System.Data.SqlClient; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Linq; namespace SmartStore { - public static class MiscExtensions { - public static bool IsDatabaseAvailable(this SqlConnectionStringBuilder sb) - { - bool res = false; - try - { - using (SqlConnection conn = new SqlConnection(sb.ToString())) - { - conn.Open(); - res = (conn.State == System.Data.ConnectionState.Open); - conn.Close(); - } - } - catch (Exception) - { - } - return res; - } - - public static T GetValue(this OleDbDataReader reader, string name, T defaultValue = default(T)) + public static bool IsEntityFrameworkProvider(this IQueryProvider provider) { - try - { - object value; - if (reader != null && name.HasValue() && (value = reader[name]) != null && value != DBNull.Value) - { - return (T)Convert.ChangeType(value, typeof(T)); - } - } - catch (Exception exc) - { - exc.Dump(); - } - return defaultValue; + return provider.GetType().FullName == "System.Data.Objects.ELinq.ObjectQueryProvider"; } } diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/DateTimeExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/DateTimeExtensions.cs new file mode 100644 index 0000000000..3277e60cb6 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/DateTimeExtensions.cs @@ -0,0 +1,154 @@ +using System; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.Localization; +using SmartStore.Services.Helpers; +using SmartStore.Web.Framework.Localization; + +namespace SmartStore.Web.Framework +{ + public static class DateTimeExtensions + { + /// + /// Relative formatting of DateTime (e.g. 2 hours ago, a month ago) + /// + /// Source (UTC format) + /// Formatted date and time string + public static string RelativeFormat(this DateTime source) + { + return RelativeFormat(source, string.Empty); + } + + /// + /// Relative formatting of DateTime (e.g. 2 hours ago, a month ago) + /// + /// Source (UTC format) + /// Default format string (in case relative formatting is not applied) + /// Formatted date and time string + public static string RelativeFormat(this DateTime source, string defaultFormat) + { + return RelativeFormat(source, false, defaultFormat); + } + + /// + /// Relative formatting of DateTime (e.g. 2 hours ago, a month ago) + /// + /// Source (UTC format) + /// A value indicating whether we should convet DateTime instance to user local time (in case relative formatting is not applied) + /// Default format string (in case relative formatting is not applied) + /// Formatted date and time string + public static string RelativeFormat(this DateTime source, bool convertToUserTime, string defaultFormat) + { + string result = ""; + Localizer T = EngineContext.Current.Resolve().Get; + + var ts = new TimeSpan(DateTime.UtcNow.Ticks - source.Ticks); + double delta = ts.TotalSeconds; + + if (delta > 0) + { + if (delta < 60) // 60 (seconds) + { + result = ts.Seconds == 1 ? T("Time.OneSecondAgo") : T("Time.SecondsAgo", ts.Seconds); + } + else if (delta < 120) //2 (minutes) * 60 (seconds) + { + result = T("Time.OneMinuteAgo"); + } + else if (delta < 2700) // 45 (minutes) * 60 (seconds) + { + result = String.Format(T("Time.MinutesAgo"), ts.Minutes); + } + else if (delta < 5400) // 90 (minutes) * 60 (seconds) + { + result = T("Time.OneHourAgo"); + } + else if (delta < 86400) // 24 (hours) * 60 (minutes) * 60 (seconds) + { + int hours = ts.Hours; + if (hours == 1) + hours = 2; + result = T("Time.HoursAgo", hours); + } + else if (delta < 172800) // 48 (hours) * 60 (minutes) * 60 (seconds) + { + result = T("Time.Yesterday"); + } + else if (delta < 2592000) // 30 (days) * 24 (hours) * 60 (minutes) * 60 (seconds) + { + result = String.Format(T("Time.DaysAgo"), ts.Days); + } + else if (delta < 31104000) // 12 (months) * 30 (days) * 24 (hours) * 60 (minutes) * 60 (seconds) + { + int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30)); + result = months <= 1 ? T("Time.OneMonthAgo") : T("Time.MonthsAgo", months); + } + else + { + int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365)); + result = years <= 1 ? T("Time.OneYearAgo") : T("Time.YearsAgo", years); + } + } + else + { + DateTime tmp1 = source; + if (convertToUserTime) + { + tmp1 = EngineContext.Current.Resolve().ConvertToUserTime(tmp1, DateTimeKind.Utc); + } + //default formatting + if (!String.IsNullOrEmpty(defaultFormat)) + { + result = tmp1.ToString(defaultFormat); + } + else + { + result = tmp1.ToString(); + } + } + return result; + } + + public static string Prettify(this TimeSpan ts) + { + Localizer T = EngineContext.Current.Resolve().Get; + double seconds = ts.TotalSeconds; + + try + { + int secsTemp = Convert.ToInt32(seconds); + string label = T("Time.SecondsAbbr"); + int remainder = 0; + string remainderLabel = ""; + + if (secsTemp > 59) + { + remainder = secsTemp % 60; + secsTemp /= 60; + label = T("Time.MinutesAbbr"); + remainderLabel = T("Time.SecondsAbbr"); + } + + if (secsTemp > 59) + { + remainder = secsTemp % 60; + secsTemp /= 60; + label = (secsTemp == 1) ? T("Time.HourAbbr") : T("Time.HoursAbbr"); + remainderLabel = T("Time.MinutesAbbr"); + } + + if (remainder == 0) + { + return string.Format("{0:#,##0.#} {1}", secsTemp, label); + } + else + { + return string.Format("{0:#,##0} {1} {2} {3}", secsTemp, label, remainder, remainderLabel); + } + } + catch + { + return "(-)"; + } + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/Extensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/Extensions.cs deleted file mode 100644 index 504cc92ab6..0000000000 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/Extensions.cs +++ /dev/null @@ -1,455 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Web.Mvc; -using SmartStore.Core; -using SmartStore.Core.Domain.Stores; -using SmartStore.Core.Infrastructure; -using SmartStore.Core.Localization; -using SmartStore.Core.Plugins; -using SmartStore.Services.Configuration; -using SmartStore.Services.Helpers; -using SmartStore.Services.Localization; -using SmartStore.Web.Framework.Localization; -using Telerik.Web.Mvc; -using Telerik.Web.Mvc.Extensions; -using Telerik.Web.Mvc.UI.Fluent; - -namespace SmartStore.Web.Framework -{ - - [Serializable] - public class GridStateInfo - { - public GridState State { get; set; } - public string Path { get; set; } - } - - - public static class Extensions - { - - public static GridBuilder PreserveGridState(this GridBuilder builder) where T : class - { - var grid = builder.ToComponent(); - - if (!grid.DataBinding.Ajax.Enabled) - return builder; - - if (grid.Id.IsEmpty()) - throw new SmartException("A grid with preservable state must have a valid Id or Name"); - - var urlHelper = new UrlHelper(grid.ViewContext.RequestContext); - - var gridId = "GridState." + grid.Id + "__" + grid.ViewContext.RouteData.GenerateRouteIdentifier(); - - grid.AppendCssClass("grid-preservestate"); - grid.HtmlAttributes.Add("data-statepreserver-href", urlHelper.Action("SetGridState", "Common", new { area = "admin" })); - grid.HtmlAttributes.Add("data-statepreserver-key", gridId); - - // Try restore state from a previous request - var info = (GridStateInfo)grid.ViewContext.TempData[gridId]; - - if (info == null) - return builder; - - var state = info.State; - var command = GridCommand.Parse(state.Page, state.Size, state.OrderBy, state.GroupBy, state.Filter); - - if (grid.Paging.Enabled) - { - var pathChanged = !info.Path.Equals(grid.ViewContext.HttpContext.Request.RawUrl, StringComparison.OrdinalIgnoreCase); - if (!pathChanged) - { - if (command.PageSize > 0) - grid.Paging.PageSize = command.PageSize; - if (command.Page > 0) - grid.Paging.CurrentPage = command.Page; - } - } - - if (grid.Sorting.Enabled) - { - foreach (var sort in command.SortDescriptors) - { - var existingSort = grid.Sorting.OrderBy.FirstOrDefault(x => x.Member.IsCaseInsensitiveEqual(sort.Member)); - if (existingSort != null) - { - grid.Sorting.OrderBy.Remove(existingSort); - } - grid.Sorting.OrderBy.Add(sort); - } - } - - if (grid.Grouping.Enabled) - { - foreach (var group in command.GroupDescriptors) - { - var existingGroup = grid.Grouping.Groups.FirstOrDefault(x => x.Member.IsCaseInsensitiveEqual(group.Member)); - if (existingGroup != null) - { - grid.Grouping.Groups.Remove(existingGroup); - } - grid.Grouping.Groups.Add(group); - } - } - - if (grid.Filtering.Enabled) - { - foreach (var filter in command.FilterDescriptors) - { - var compositeFilter = filter as CompositeFilterDescriptor; - if (compositeFilter == null) - { - compositeFilter = new CompositeFilterDescriptor { LogicalOperator = FilterCompositionLogicalOperator.And }; - compositeFilter.FilterDescriptors.Add(filter); - } - grid.Filtering.Filters.Add(compositeFilter); - } - } - - // persist again for the next request - grid.ViewContext.TempData[gridId] = info; - - return builder; - } - - public static IEnumerable ForCommand(this IEnumerable current, GridCommand command) - { - var queryable = current.AsQueryable() as IQueryable; - if (command.FilterDescriptors.Any()) - { - queryable = queryable.Where(command.FilterDescriptors.AsEnumerable()).AsQueryable() as IQueryable; - } - - IList temporarySortDescriptors = new List(); - - if (!command.SortDescriptors.Any() && queryable.Provider.IsEntityFrameworkProvider()) - { - // The Entity Framework provider demands OrderBy before calling Skip. - SortDescriptor sortDescriptor = new SortDescriptor - { - Member = GetFirstSortableProperty(queryable.ElementType) - }; - command.SortDescriptors.Add(sortDescriptor); - temporarySortDescriptors.Add(sortDescriptor); - } - - if (command.GroupDescriptors.Any()) - { - command.GroupDescriptors.Reverse().Each(groupDescriptor => - { - SortDescriptor sortDescriptor = new SortDescriptor - { - Member = groupDescriptor.Member, - SortDirection = groupDescriptor.SortDirection - }; - - command.SortDescriptors.Insert(0, sortDescriptor); - temporarySortDescriptors.Add(sortDescriptor); - }); - } - - if (command.SortDescriptors.Any()) - { - queryable = queryable.Sort(command.SortDescriptors); - } - - return queryable as IQueryable; - } - - public static IEnumerable PagedForCommand(this IEnumerable current, GridCommand command) - { - return current.Skip((command.Page - 1) * command.PageSize).Take(command.PageSize); - } - - public static bool IsEntityFrameworkProvider(this IQueryProvider provider) - { - return provider.GetType().FullName == "System.Data.Objects.ELinq.ObjectQueryProvider"; - } - - private static string GetFirstSortableProperty(Type type) - { - PropertyInfo firstSortableProperty = type.GetProperties().Where(property => property.PropertyType.IsPredefinedSimpleType()).FirstOrDefault(); - - if (firstSortableProperty == null) - { - throw new NotSupportedException("Cannot find property to sort by."); - } - - return firstSortableProperty.Name; - } - - public static GridBoundColumnBuilder Centered(this GridBoundColumnBuilder columnBuilder) where T:class - { - return columnBuilder.HtmlAttributes(new { align = "center" }).HeaderHtmlAttributes(new { style = "text-align:center;" }); - } - - public static GridTemplateColumnBuilder Centered(this GridTemplateColumnBuilder columnBuilder) where T : class - { - return columnBuilder.HtmlAttributes(new { align = "center" }).HeaderHtmlAttributes(new { style = "text-align:center;" }); - } - - public static GridBoundColumnBuilder RightAlign(this GridBoundColumnBuilder columnBuilder) where T : class - { - return columnBuilder.HtmlAttributes(new { style = "text-align:right;" }).HeaderHtmlAttributes(new { style = "text-align:right;" }); - } - - public static GridTemplateColumnBuilder RightAlign(this GridTemplateColumnBuilder columnBuilder) where T : class - { - return columnBuilder.HtmlAttributes(new { style = "text-align:right;" }).HeaderHtmlAttributes(new { style = "text-align:right;" }); - } - - public static SelectList ToSelectList(this TEnum enumObj, bool markCurrentAsSelected = true) where TEnum : struct - { - if (!typeof(TEnum).IsEnum) - throw new ArgumentException("An Enumeration type is required.", "enumObj"); - - var localizationService = EngineContext.Current.Resolve(); - var workContext = EngineContext.Current.Resolve(); - - var values = from TEnum enumValue in Enum.GetValues(typeof(TEnum)) - select new { ID = Convert.ToInt32(enumValue), Name = enumValue.GetLocalizedEnum(localizationService, workContext) }; - object selectedValue = null; - if (markCurrentAsSelected) - selectedValue = Convert.ToInt32(enumObj); - return new SelectList(values, "ID", "Name", selectedValue); - } - - - public static string GetValueFromAppliedFilter(this IFilterDescriptor filter, string valueName, FilterOperator? filterOperator = null) - { - if (filter is CompositeFilterDescriptor) - { - foreach (IFilterDescriptor childFilter in ((CompositeFilterDescriptor)filter).FilterDescriptors) - { - var val1 = GetValueFromAppliedFilter(childFilter, valueName, filterOperator); - if (!String.IsNullOrEmpty(val1)) - return val1; - } - } - else - { - var filterDescriptor = (FilterDescriptor)filter; - if (filterDescriptor != null && - filterDescriptor.Member.Equals(valueName, StringComparison.InvariantCultureIgnoreCase)) - { - if (!filterOperator.HasValue || filterDescriptor.Operator == filterOperator.Value) - return Convert.ToString(filterDescriptor.Value); - } - } - - return ""; - } - - public static string GetValueFromAppliedFilters(this IList filters, string valueName, FilterOperator? filterOperator = null) - { - foreach (var filter in filters) - { - var val1 = GetValueFromAppliedFilter(filter, valueName, filterOperator); - if (!String.IsNullOrEmpty(val1)) - return val1; - } - return ""; - } - - /// - /// Relative formatting of DateTime (e.g. 2 hours ago, a month ago) - /// - /// Source (UTC format) - /// Formatted date and time string - public static string RelativeFormat(this DateTime source) - { - return RelativeFormat(source, string.Empty); - } - - /// - /// Relative formatting of DateTime (e.g. 2 hours ago, a month ago) - /// - /// Source (UTC format) - /// Default format string (in case relative formatting is not applied) - /// Formatted date and time string - public static string RelativeFormat(this DateTime source, string defaultFormat) - { - return RelativeFormat(source, false, defaultFormat); - } - - /// - /// Relative formatting of DateTime (e.g. 2 hours ago, a month ago) - /// - /// Source (UTC format) - /// A value indicating whether we should convet DateTime instance to user local time (in case relative formatting is not applied) - /// Default format string (in case relative formatting is not applied) - /// Formatted date and time string - public static string RelativeFormat(this DateTime source, bool convertToUserTime, string defaultFormat) - { - string result = ""; - Localizer T = EngineContext.Current.Resolve().Get; - - var ts = new TimeSpan(DateTime.UtcNow.Ticks - source.Ticks); - double delta = ts.TotalSeconds; - - if (delta > 0) - { - if (delta < 60) // 60 (seconds) - { - result = ts.Seconds == 1 ? T("Time.OneSecondAgo") : T("Time.SecondsAgo", ts.Seconds); - } - else if (delta < 120) //2 (minutes) * 60 (seconds) - { - result = T("Time.OneMinuteAgo"); - } - else if (delta < 2700) // 45 (minutes) * 60 (seconds) - { - result = String.Format(T("Time.MinutesAgo"), ts.Minutes); - } - else if (delta < 5400) // 90 (minutes) * 60 (seconds) - { - result = T("Time.OneHourAgo"); - } - else if (delta < 86400) // 24 (hours) * 60 (minutes) * 60 (seconds) - { - int hours = ts.Hours; - if (hours == 1) - hours = 2; - result = T("Time.HoursAgo", hours); - } - else if (delta < 172800) // 48 (hours) * 60 (minutes) * 60 (seconds) - { - result = T("Time.Yesterday"); - } - else if (delta < 2592000) // 30 (days) * 24 (hours) * 60 (minutes) * 60 (seconds) - { - result = String.Format(T("Time.DaysAgo"), ts.Days); - } - else if (delta < 31104000) // 12 (months) * 30 (days) * 24 (hours) * 60 (minutes) * 60 (seconds) - { - int months = Convert.ToInt32(Math.Floor((double)ts.Days / 30)); - result = months <= 1 ? T("Time.OneMonthAgo") : T("Time.MonthsAgo", months); - } - else - { - int years = Convert.ToInt32(Math.Floor((double)ts.Days / 365)); - result = years <= 1 ? T("Time.OneYearAgo") : T("Time.YearsAgo", years); - } - } - else - { - DateTime tmp1 = source; - if (convertToUserTime) - { - tmp1 = EngineContext.Current.Resolve().ConvertToUserTime(tmp1, DateTimeKind.Utc); - } - //default formatting - if (!String.IsNullOrEmpty(defaultFormat)) - { - result = tmp1.ToString(defaultFormat); - } - else - { - result = tmp1.ToString(); - } - } - return result; - } - - public static string Prettify(this TimeSpan ts) - { - Localizer T = EngineContext.Current.Resolve().Get; - double seconds = ts.TotalSeconds; - - try - { - int secsTemp = Convert.ToInt32(seconds); - string label = T("Time.SecondsAbbr"); - int remainder = 0; - string remainderLabel = ""; - - if (secsTemp > 59) - { - remainder = secsTemp % 60; - secsTemp /= 60; - label = T("Time.MinutesAbbr"); - remainderLabel = T("Time.SecondsAbbr"); - } - - if (secsTemp > 59) - { - remainder = secsTemp % 60; - secsTemp /= 60; - label = (secsTemp == 1) ? T("Time.HourAbbr") : T("Time.HoursAbbr"); - remainderLabel = T("Time.MinutesAbbr"); - } - - if (remainder == 0) - { - return string.Format("{0:#,##0.#} {1}", secsTemp, label); - } - else - { - return string.Format("{0:#,##0} {1} {2} {3}", secsTemp, label, remainder, remainderLabel); - } - } - catch - { - return "(-)"; - } - } - - /// - /// Get a list of all stores - /// - /// codehint: sm-add - public static IList ToSelectListItems(this IEnumerable stores) - { - var lst = new List(); - - foreach (var store in stores) - { - lst.Add(new SelectListItem - { - Text = store.Name, - Value = store.Id.ToString() - }); - } - return lst; - } - - public static void SelectValue(this List lst, string value, string defaultValue = null) - { - if (lst != null) - { - var itm = lst.FirstOrDefault(i => i.Value.IsCaseInsensitiveEqual(value)); - - if (itm == null && defaultValue != null) - itm = lst.FirstOrDefault(i => i.Value.IsCaseInsensitiveEqual(defaultValue)); - - if (itm != null) - itm.Selected = true; - } - } - - /// - /// Determines whether a plugin is installed and activated for a particular store. - /// - public static bool IsPluginReady(this IPluginFinder pluginFinder, ISettingService settingService, string systemName, int storeId) - { - try - { - var pluginDescriptor = pluginFinder.GetPluginDescriptorBySystemName(systemName); - - if (pluginDescriptor != null && pluginDescriptor.Installed) - { - if (storeId == 0 || settingService.GetSettingByKey(pluginDescriptor.GetSettingKey("LimitedToStores")).ToIntArrayContains(storeId, true)) - return true; - } - } - catch (Exception exc) - { - exc.Dump(); - } - return false; - } - } -} diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlSelectListExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlSelectListExtensions.cs new file mode 100644 index 0000000000..33455155a2 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/HtmlSelectListExtensions.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web.Mvc; +using SmartStore.Core; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Infrastructure; +using SmartStore.Services.Localization; + +namespace SmartStore.Web.Framework +{ + public static class HtmlSelectListExtensions + { + public static SelectList ToSelectList(this TEnum enumObj, bool markCurrentAsSelected = true) where TEnum : struct + { + if (!typeof(TEnum).IsEnum) + throw new ArgumentException("An Enumeration type is required.", "enumObj"); + + var localizationService = EngineContext.Current.Resolve(); + var workContext = EngineContext.Current.Resolve(); + + var values = from TEnum enumValue in Enum.GetValues(typeof(TEnum)) + select new { ID = Convert.ToInt32(enumValue), Name = enumValue.GetLocalizedEnum(localizationService, workContext) }; + + object selectedValue = null; + if (markCurrentAsSelected) + selectedValue = Convert.ToInt32(enumObj); + + return new SelectList(values, "ID", "Name", selectedValue); + } + + /// + /// Get a list of all stores + /// + public static IList ToSelectListItems(this IEnumerable stores) + { + var lst = new List(); + + foreach (var store in stores) + { + lst.Add(new SelectListItem + { + Text = store.Name, + Value = store.Id.ToString() + }); + } + return lst; + } + + public static void SelectValue(this List lst, string value, string defaultValue = null) + { + if (lst != null) + { + var itm = lst.FirstOrDefault(i => i.Value.IsCaseInsensitiveEqual(value)); + + if (itm == null && defaultValue != null) + itm = lst.FirstOrDefault(i => i.Value.IsCaseInsensitiveEqual(defaultValue)); + + if (itm != null) + itm.Selected = true; + } + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/RouteExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/RouteExtensions.cs index bfbffe4c03..f6cfa11a01 100644 --- a/src/Presentation/SmartStore.Web.Framework/Extensions/RouteExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/RouteExtensions.cs @@ -11,7 +11,6 @@ namespace SmartStore { public static class RouteExtensions { - public static string GetAreaName(this RouteData routeData) { object obj2; @@ -49,5 +48,13 @@ public static string GenerateRouteIdentifier(this RouteData routeData) return "{0}{1}.{2}".FormatInvariant(area.HasValue() ? area + "." : "", controller, action); } + public static bool IsRouteEqual(this RouteData routeData, string controller, string action) + { + if (routeData == null) + return false; + + return routeData.GetRequiredString("controller").IsCaseInsensitiveEqual(controller) && routeData.GetRequiredString("action").IsCaseInsensitiveEqual(action); + } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/Extensions/TelerikExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Extensions/TelerikExtensions.cs new file mode 100644 index 0000000000..08cd2d8951 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Extensions/TelerikExtensions.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Web.Mvc; +using SmartStore.Core; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Infrastructure; +using SmartStore.Core.Localization; +using SmartStore.Core.Plugins; +using SmartStore.Services.Configuration; +using SmartStore.Services.Helpers; +using SmartStore.Services.Localization; +using SmartStore.Web.Framework.Localization; +using Telerik.Web.Mvc; +using Telerik.Web.Mvc.Extensions; +using Telerik.Web.Mvc.UI.Fluent; + +namespace SmartStore.Web.Framework +{ + [Serializable] + public class GridStateInfo + { + public GridState State { get; set; } + public string Path { get; set; } + } + + public static class TelerikExtensions + { + public static GridBuilder PreserveGridState(this GridBuilder builder) where T : class + { + var grid = builder.ToComponent(); + + if (!grid.DataBinding.Ajax.Enabled) + return builder; + + if (grid.Id.IsEmpty()) + throw new SmartException("A grid with preservable state must have a valid Id or Name"); + + var urlHelper = new UrlHelper(grid.ViewContext.RequestContext); + + var gridId = "GridState." + grid.Id + "__" + grid.ViewContext.RouteData.GenerateRouteIdentifier(); + + grid.AppendCssClass("grid-preservestate"); + grid.HtmlAttributes.Add("data-statepreserver-href", urlHelper.Action("SetGridState", "Common", new { area = "admin" })); + grid.HtmlAttributes.Add("data-statepreserver-key", gridId); + + // Try restore state from a previous request + var info = (GridStateInfo)grid.ViewContext.TempData[gridId]; + + if (info == null) + return builder; + + var state = info.State; + var command = GridCommand.Parse(state.Page, state.Size, state.OrderBy, state.GroupBy, state.Filter); + + if (grid.Paging.Enabled) + { + var pathChanged = !info.Path.Equals(grid.ViewContext.HttpContext.Request.RawUrl, StringComparison.OrdinalIgnoreCase); + if (!pathChanged) + { + if (command.PageSize > 0) + grid.Paging.PageSize = command.PageSize; + if (command.Page > 0) + grid.Paging.CurrentPage = command.Page; + } + } + + if (grid.Sorting.Enabled) + { + foreach (var sort in command.SortDescriptors) + { + var existingSort = grid.Sorting.OrderBy.FirstOrDefault(x => x.Member.IsCaseInsensitiveEqual(sort.Member)); + if (existingSort != null) + { + grid.Sorting.OrderBy.Remove(existingSort); + } + grid.Sorting.OrderBy.Add(sort); + } + } + + if (grid.Grouping.Enabled) + { + foreach (var group in command.GroupDescriptors) + { + var existingGroup = grid.Grouping.Groups.FirstOrDefault(x => x.Member.IsCaseInsensitiveEqual(group.Member)); + if (existingGroup != null) + { + grid.Grouping.Groups.Remove(existingGroup); + } + grid.Grouping.Groups.Add(group); + } + } + + if (grid.Filtering.Enabled) + { + foreach (var filter in command.FilterDescriptors) + { + var compositeFilter = filter as CompositeFilterDescriptor; + if (compositeFilter == null) + { + compositeFilter = new CompositeFilterDescriptor { LogicalOperator = FilterCompositionLogicalOperator.And }; + compositeFilter.FilterDescriptors.Add(filter); + } + grid.Filtering.Filters.Add(compositeFilter); + } + } + + // persist again for the next request + grid.ViewContext.TempData[gridId] = info; + + return builder; + } + + public static IEnumerable ForCommand(this IEnumerable current, GridCommand command) + { + var queryable = current.AsQueryable() as IQueryable; + if (command.FilterDescriptors.Any()) + { + queryable = queryable.Where(command.FilterDescriptors.AsEnumerable()).AsQueryable() as IQueryable; + } + + IList temporarySortDescriptors = new List(); + + if (!command.SortDescriptors.Any() && queryable.Provider.IsEntityFrameworkProvider()) + { + // The Entity Framework provider demands OrderBy before calling Skip. + SortDescriptor sortDescriptor = new SortDescriptor + { + Member = GetFirstSortableProperty(queryable.ElementType) + }; + command.SortDescriptors.Add(sortDescriptor); + temporarySortDescriptors.Add(sortDescriptor); + } + + if (command.GroupDescriptors.Any()) + { + command.GroupDescriptors.Reverse().Each(groupDescriptor => + { + SortDescriptor sortDescriptor = new SortDescriptor + { + Member = groupDescriptor.Member, + SortDirection = groupDescriptor.SortDirection + }; + + command.SortDescriptors.Insert(0, sortDescriptor); + temporarySortDescriptors.Add(sortDescriptor); + }); + } + + if (command.SortDescriptors.Any()) + { + queryable = queryable.Sort(command.SortDescriptors); + } + + return queryable as IQueryable; + } + + public static IEnumerable PagedForCommand(this IEnumerable current, GridCommand command) + { + return current.Skip((command.Page - 1) * command.PageSize).Take(command.PageSize); + } + + + public static GridBoundColumnBuilder Centered(this GridBoundColumnBuilder columnBuilder) where T : class + { + return columnBuilder.HtmlAttributes(new { align = "center" }).HeaderHtmlAttributes(new { style = "text-align:center;" }); + } + + public static GridTemplateColumnBuilder Centered(this GridTemplateColumnBuilder columnBuilder) where T : class + { + return columnBuilder.HtmlAttributes(new { align = "center" }).HeaderHtmlAttributes(new { style = "text-align:center;" }); + } + + public static GridBoundColumnBuilder RightAlign(this GridBoundColumnBuilder columnBuilder) where T : class + { + return columnBuilder.HtmlAttributes(new { style = "text-align:right;" }).HeaderHtmlAttributes(new { style = "text-align:right;" }); + } + + public static GridTemplateColumnBuilder RightAlign(this GridTemplateColumnBuilder columnBuilder) where T : class + { + return columnBuilder.HtmlAttributes(new { style = "text-align:right;" }).HeaderHtmlAttributes(new { style = "text-align:right;" }); + } + + public static string GetValueFromAppliedFilter(this IFilterDescriptor filter, string valueName, FilterOperator? filterOperator = null) + { + if (filter is CompositeFilterDescriptor) + { + foreach (IFilterDescriptor childFilter in ((CompositeFilterDescriptor)filter).FilterDescriptors) + { + var val1 = GetValueFromAppliedFilter(childFilter, valueName, filterOperator); + if (!String.IsNullOrEmpty(val1)) + return val1; + } + } + else + { + var filterDescriptor = (FilterDescriptor)filter; + if (filterDescriptor != null && + filterDescriptor.Member.Equals(valueName, StringComparison.InvariantCultureIgnoreCase)) + { + if (!filterOperator.HasValue || filterDescriptor.Operator == filterOperator.Value) + return Convert.ToString(filterDescriptor.Value); + } + } + + return ""; + } + + public static string GetValueFromAppliedFilters(this IList filters, string valueName, FilterOperator? filterOperator = null) + { + foreach (var filter in filters) + { + var val1 = GetValueFromAppliedFilter(filter, valueName, filterOperator); + if (!String.IsNullOrEmpty(val1)) + return val1; + } + return ""; + } + + private static string GetFirstSortableProperty(Type type) + { + PropertyInfo firstSortableProperty = type.GetProperties().Where(property => property.PropertyType.IsPredefinedSimpleType()).FirstOrDefault(); + + if (firstSortableProperty == null) + { + throw new NotSupportedException("Cannot find property to sort by."); + } + + return firstSortableProperty.Name; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/Plugins/PluginExtensions.cs b/src/Presentation/SmartStore.Web.Framework/Plugins/PluginExtensions.cs new file mode 100644 index 0000000000..0925a59f65 --- /dev/null +++ b/src/Presentation/SmartStore.Web.Framework/Plugins/PluginExtensions.cs @@ -0,0 +1,32 @@ +using System; +using SmartStore.Core.Plugins; +using SmartStore.Services.Configuration; + +namespace SmartStore.Web.Framework +{ + public static class PluginExtensions + { + /// + /// Determines whether a plugin is installed and activated for a particular store. + /// + public static bool IsPluginReady(this IPluginFinder pluginFinder, ISettingService settingService, string systemName, int storeId) + { + try + { + var pluginDescriptor = pluginFinder.GetPluginDescriptorBySystemName(systemName); + + if (pluginDescriptor != null && pluginDescriptor.Installed) + { + if (storeId == 0 || settingService.GetSettingByKey(pluginDescriptor.GetSettingKey("LimitedToStores")).ToIntArrayContains(storeId, true)) + return true; + } + } + catch (Exception exc) + { + exc.Dump(); + } + + return false; + } + } +} diff --git a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj index 1c59563458..2ae06313d2 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj +++ b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj @@ -232,12 +232,14 @@ + + @@ -248,6 +250,7 @@ + @@ -308,7 +311,7 @@ - + From ef722954594c39f1f9aa7c99ea8515ca7607ee4c Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 Nov 2015 20:52:29 +0100 Subject: [PATCH 220/874] Export: Added RowExportingEvent --- .../DataExchange/DataExporter.cs | 15 +++--- .../DataExchange/DynamicEntityHelper.cs | 54 +++++++++++++++++-- .../DataExchange/Events/RowExportingEvent.cs | 17 ++++++ .../SmartStore.Services.csproj | 1 + 4 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs index b409401da6..b09801ff15 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs @@ -1074,21 +1074,22 @@ public DataExportResult Export(DataExportRequest request, CancellationToken canc string prefix = null; string suffix = null; var extension = Path.GetExtension(ctx.Result.Files.First().FileName); + var provider = request.Provider.Value; - if (request.Provider.Value.EntityType == ExportEntityType.Product) + if (provider.EntityType == ExportEntityType.Product) prefix = T("Admin.Catalog.Products"); - else if (request.Provider.Value.EntityType == ExportEntityType.Order) + else if (provider.EntityType == ExportEntityType.Order) prefix = T("Admin.Orders"); - else if (request.Provider.Value.EntityType == ExportEntityType.Category) + else if (provider.EntityType == ExportEntityType.Category) prefix = T("Admin.Catalog.Categories"); - else if (request.Provider.Value.EntityType == ExportEntityType.Manufacturer) + else if (provider.EntityType == ExportEntityType.Manufacturer) prefix = T("Admin.Catalog.Manufacturers"); - else if (request.Provider.Value.EntityType == ExportEntityType.Customer) + else if (provider.EntityType == ExportEntityType.Customer) prefix = T("Admin.Customers"); - else if (request.Provider.Value.EntityType == ExportEntityType.NewsLetterSubscription) + else if (provider.EntityType == ExportEntityType.NewsLetterSubscription) prefix = T("Admin.Promotions.NewsLetterSubscriptions"); else - prefix = request.Provider.Value.EntityType.ToString(); + prefix = provider.EntityType.ToString(); var selectedEntityCount = (request.EntitiesToExport == null ? 0 : request.EntitiesToExport.Count); diff --git a/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs index 73cb0ae431..7faca463f9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs @@ -21,6 +21,7 @@ using SmartStore.Core.Domain.Stores; using SmartStore.Core.Html; using SmartStore.Services.Catalog; +using SmartStore.Services.DataExchange.Events; using SmartStore.Services.DataExchange.Internal; using SmartStore.Services.Localization; using SmartStore.Services.Seo; @@ -973,6 +974,14 @@ private List Convert(DataExporterContext ctx, Product product) result.Add(dynObject); } + _services.EventPublisher.Publish(new RowExportingEvent + { + Row = dynObject, + EntityType = ExportEntityType.Product, + ExportRequest = ctx.Request, + ExecuteContext = ctx.ExecuteContext + }); + return result; } @@ -1042,6 +1051,14 @@ private List Convert(DataExporterContext ctx, Order order) result.Add(dynObject); + _services.EventPublisher.Publish(new RowExportingEvent + { + Row = dynObject, + EntityType = ExportEntityType.Order, + ExportRequest = ctx.Request, + ExecuteContext = ctx.ExecuteContext + }); + return result; } @@ -1073,13 +1090,21 @@ private List Convert(DataExporterContext ctx, Manufacturer manufacturer result.Add(dynObject); + _services.EventPublisher.Publish(new RowExportingEvent + { + Row = dynObject, + EntityType = ExportEntityType.Manufacturer, + ExportRequest = ctx.Request, + ExecuteContext = ctx.ExecuteContext + }); + return result; } private List Convert(DataExporterContext ctx, Category category) { var result = new List(); - + var productCategories = ctx.CategoryExportContext.ProductCategories.Load(category.Id); dynamic dynObject = ToDynamic(ctx, category); @@ -1104,6 +1129,14 @@ private List Convert(DataExporterContext ctx, Category category) result.Add(dynObject); + _services.EventPublisher.Publish(new RowExportingEvent + { + Row = dynObject, + EntityType = ExportEntityType.Category, + ExportRequest = ctx.Request, + ExecuteContext = ctx.ExecuteContext + }); + return result; } @@ -1140,16 +1173,31 @@ private List Convert(DataExporterContext ctx, Customer customer) result.Add(dynObject); + _services.EventPublisher.Publish(new RowExportingEvent + { + Row = dynObject, + EntityType = ExportEntityType.Customer, + ExportRequest = ctx.Request, + ExecuteContext = ctx.ExecuteContext + }); + return result; } private List Convert(DataExporterContext ctx, NewsLetterSubscription subscription) { var result = new List(); - dynamic dynObject = ToDynamic(ctx, subscription); - result.Add(dynObject); + + _services.EventPublisher.Publish(new RowExportingEvent + { + Row = dynObject, + EntityType = ExportEntityType.NewsLetterSubscription, + ExportRequest = ctx.Request, + ExecuteContext = ctx.ExecuteContext + }); + return result; } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs b/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs new file mode 100644 index 0000000000..31922ae3b9 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SmartStore.Core.Domain.DataExchange; + +namespace SmartStore.Services.DataExchange.Events +{ + 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; } + } +} diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index ec4af99cbd..0c121d3896 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -166,6 +166,7 @@ + From c468baf706b86c3965f424f16c140f3b97ddb438 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 Nov 2015 21:06:22 +0100 Subject: [PATCH 221/874] Export: some experimenting with RowExportingEvent --- .../DataExchange/Events/RowExportingEvent.cs | 4 +++ .../SmartStore.GoogleMerchantCenter/Events.cs | 32 +++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs b/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs index 31922ae3b9..0b1dbcc017 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs @@ -7,6 +7,10 @@ namespace SmartStore.Services.DataExchange.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. + public class RowExportingEvent { public dynamic Row { get; internal set; } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs index 5ca8aeb5dc..4d0019134f 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs @@ -5,12 +5,16 @@ using SmartStore.GoogleMerchantCenter.Services; using SmartStore.Web.Framework.Events; using SmartStore.Web.Framework.Mvc; +using SmartStore.Services.DataExchange.Events; +using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Catalog; namespace SmartStore.GoogleMerchantCenter { public class Events : IConsumer, - IConsumer + IConsumer/*, + IConsumer*/ { private readonly IGoogleFeedService _googleService; @@ -18,7 +22,7 @@ public Events(IGoogleFeedService googleService) { this._googleService = googleService; } - + public void HandleEvent(TabStripCreated eventMessage) { if (eventMessage.TabStripName == "product-edit") @@ -33,6 +37,30 @@ public void HandleEvent(TabStripCreated eventMessage) } } + //public void HandleEvent(RowExportingEvent eventMessage) + //{ + // if (eventMessage.EntityType != ExportEntityType.Product) + // return; + + // var row = eventMessage.Row; + // var product = eventMessage.Row.Entity as Product; + + // if (product == null) + // return; + + // var gmc = _googleService.GetGoogleProductRecord(product.Id); + // if (gmc == null) + // return; + + // row["_GMC_AgeGroup"] = gmc.AgeGroup; + // row["_GMC_Color"] = gmc.Color; + // row["_GMC_Gender"] = gmc.Gender; + // row["_GMC_Size"] = gmc.Size; + // row["_GMC_Taxonomy"] = gmc.Taxonomy; + // row["_GMC_Material"] = gmc.Material; + // row["_GMC_Pattern"] = gmc.Pattern; + //} + public void HandleEvent(ModelBoundEvent eventMessage) { if (!eventMessage.BoundModel.CustomProperties.ContainsKey("GMC")) From 5f74aecb89db44a7eee2d3884b1838d35d691c2c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 13 Nov 2015 12:46:05 +0100 Subject: [PATCH 222/874] Export preview for all supported entity types --- .../201511121139009_ExportFramework3.cs | 1 + .../DataExchange/DataExporter.cs | 11 +- .../DataExchange/DynamicEntityHelper.cs | 21 ++ .../Controllers/ExportController.cs | 138 +++++++-- .../Models/DataExchange/ExportPreviewModel.cs | 78 +++++ .../Administration/Views/Category/List.cshtml | 9 +- .../Views/Export/Preview.cshtml | 286 +++++++++++++----- .../Views/Manufacturer/List.cshtml | 58 ++-- 8 files changed, 468 insertions(+), 134 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs b/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs index 34a97d19bc..132d36fee6 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs @@ -86,6 +86,7 @@ public void Seed(SmartObjectContext context) }; profile = profiles.Add(profile); + context.SaveChanges(); task.Alias = profile.Id.ToString(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs index b09801ff15..d839d3d839 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs @@ -540,6 +540,9 @@ private IQueryable GetManufacturerQuery(DataExporterContext ctx, i var query = _manufacturerService.Value.GetManufacturers(showHidden, storeId); + if (ctx.Request.EntitiesToExport.Count > 0) + query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + query = query.OrderBy(x => x.DisplayOrder); if (skip > 0) @@ -573,6 +576,9 @@ private IQueryable GetCategoryQuery(DataExporterContext ctx, int skip, var query = _categoryService.Value.GetCategories(null, showHidden, null, true, storeId); + if (ctx.Request.EntitiesToExport.Count > 0) + query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); + query = query .OrderBy(x => x.ParentCategoryId) .ThenBy(x => x.DisplayOrder); @@ -647,9 +653,10 @@ private IQueryable GetNewsLetterSubscriptionQuery(DataEx var query = _subscriptionRepository.Value.TableUntracked; if (storeId > 0) - { query = query.Where(x => x.StoreId == storeId); - } + + if (ctx.Request.EntitiesToExport.Count > 0) + query = query.Where(x => ctx.Request.EntitiesToExport.Contains(x.Id)); query = query .OrderBy(x => x.StoreId) diff --git a/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs index 7faca463f9..cb964a59f8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs @@ -392,6 +392,8 @@ private dynamic ToDynamic(DataExporterContext ctx, Customer customer) result._GenericAttributes = null; result._HasNewsletterSubscription = false; + result._FullName = null; + return result; } @@ -1171,6 +1173,24 @@ private List Convert(DataExporterContext ctx, Customer customer) dynObject._HasNewsletterSubscription = ctx.NewsletterSubscriptions.Contains(customer.Email, StringComparer.CurrentCultureIgnoreCase); + var attrFirstName = genericAttributes.FirstOrDefault(x => x.Key == SystemCustomerAttributeNames.FirstName); + var attrLastName = genericAttributes.FirstOrDefault(x => x.Key == SystemCustomerAttributeNames.LastName); + + string firstName = (attrFirstName == null ? "" : attrFirstName.Value); + string lastName = (attrLastName == null ? "" : attrLastName.Value); + + if (firstName.IsEmpty() && lastName.IsEmpty()) + { + var address = customer.Addresses.FirstOrDefault(); + if (address != null) + { + firstName = address.FirstName; + lastName = address.LastName; + } + } + + dynObject._FullName = firstName.Grow(lastName, " "); + result.Add(dynObject); _services.EventPublisher.Publish(new RowExportingEvent @@ -1198,6 +1218,7 @@ private List Convert(DataExporterContext ctx, NewsLetterSubscription su ExecuteContext = ctx.ExecuteContext }); + return result; } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index f15c4fe3a0..c0e200ccc8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -9,10 +9,13 @@ using SmartStore.Core; using SmartStore.Core.Domain; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Messages; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Domain.Stores; using SmartStore.Core.Plugins; using SmartStore.Services; using SmartStore.Services.Catalog; @@ -50,6 +53,7 @@ public class ExportController : AdminControllerBase private readonly DataExchangeSettings _dataExchangeSettings; private readonly ITaskScheduler _taskScheduler; private readonly IDataExporter _dataExporter; + private readonly Lazy _customerSettings; public ExportController( ICommonServices services, @@ -65,7 +69,8 @@ public ExportController( IDateTimeHelper dateTimeHelper, DataExchangeSettings dataExchangeSettings, ITaskScheduler taskScheduler, - IDataExporter dataExporter) + IDataExporter dataExporter, + Lazy customerSettings) { _services = services; _exportService = exportService; @@ -81,6 +86,7 @@ public ExportController( _dataExchangeSettings = dataExchangeSettings; _taskScheduler = taskScheduler; _dataExporter = dataExporter; + _customerSettings = customerSettings; } #region Utilities @@ -736,7 +742,8 @@ public ActionResult Preview(int id) GridPageSize = DataExporter.PageSize, EntityType = provider.Value.EntityType, TotalRecords = totalRecords, - LogFileExists = System.IO.File.Exists(profile.GetExportLogFilePath()) + LogFileExists = System.IO.File.Exists(profile.GetExportLogFilePath()), + UsernamesEnabled = _customerSettings.Value.UsernamesEnabled }; return View(model); @@ -754,8 +761,20 @@ public ActionResult PreviewList(GridCommand command, int id, int totalRecords) { var productModel = new List(); var orderModel = new List(); + var categoryModel = new List(); + var manuModel = new List(); + var customerModel = new List(); + var subscriberModel = new List(); + + object gridData = null; + Dictionary allCategories = null; + IList allStores = null; var request = new DataExportRequest(profile, provider); + var normalizedTotal = totalRecords; + + if (profile.Limit > 0 && normalizedTotal > profile.Limit) + normalizedTotal = profile.Limit; var data = _dataExporter.Preview(request, command.Page - 1, totalRecords); @@ -764,7 +783,8 @@ public ActionResult PreviewList(GridCommand command, int id, int totalRecords) if (provider.Value.EntityType == ExportEntityType.Product) { var product = item.Entity as Product; - var pm = new ExportPreviewProductModel + + productModel.Add(new ExportPreviewProductModel { Id = product.Id, ProductTypeId = product.ProductTypeId, @@ -776,13 +796,11 @@ public ActionResult PreviewList(GridCommand command, int id, int totalRecords) Published = product.Published, StockQuantity = product.StockQuantity, AdminComment = item.AdminComment - }; - - productModel.Add(pm); + }); } else if (provider.Value.EntityType == ExportEntityType.Order) { - var om = new ExportPreviewOrderModel + orderModel.Add(new ExportPreviewOrderModel { Id = item.Id, HasNewPaymentNotification = item.HasNewPaymentNotification, @@ -794,33 +812,105 @@ public ActionResult PreviewList(GridCommand command, int id, int totalRecords) StoreName = (item.Store == null ? "".NaIfEmpty() : item.Store.Name), CreatedOn = _dateTimeHelper.ConvertToUserTime(item.CreatedOnUtc, DateTimeKind.Utc), OrderTotal = item.OrderTotal - }; + }); + } + else if (provider.Value.EntityType == ExportEntityType.Category) + { + var category = item.Entity as Category; + + if (allCategories == null) + { + allCategories = _categoryService.GetAllCategories(showHidden: true, applyNavigationFilters: false) + .ToDictionary(x => x.Id); + } - orderModel.Add(om); + categoryModel.Add(new ExportPreviewCategoryModel + { + Id = category.Id, + Breadcrumb = category.GetCategoryBreadCrumb(_categoryService, allCategories), + FullName = item.FullName, + Alias = item.Alias, + Published = category.Published, + DisplayOrder = category.DisplayOrder, + LimitedToStores = category.LimitedToStores + }); + } + else if (provider.Value.EntityType == ExportEntityType.Manufacturer) + { + manuModel.Add(new ExportPreviewManufacturerModel + { + Id = item.Id, + Name = item.Name, + Published = item.Published, + DisplayOrder = item.DisplayOrder, + LimitedToStores = item.LimitedToStores + }); } - } + else if (provider.Value.EntityType == ExportEntityType.Customer) + { + var customer = item.Entity as Customer; + var customerRoles = item.CustomerRoles as List; + var customerRolesString = string.Join(", ", customerRoles.Select(x => x.Name)); - var normalizedTotal = totalRecords; + customerModel.Add(new ExportPreviewCustomerModel + { + Id = customer.Id, + Active = customer.Active, + CreatedOn = _dateTimeHelper.ConvertToUserTime(customer.CreatedOnUtc, DateTimeKind.Utc), + CustomerRoleNames = customerRolesString, + Email = customer.Email, + FullName = item._FullName, + LastActivityDate = _dateTimeHelper.ConvertToUserTime(customer.LastActivityDateUtc, DateTimeKind.Utc), + Username = customer.Username + }); + } + else if (provider.Value.EntityType == ExportEntityType.NewsLetterSubscription) + { + var subscription = item.Entity as NewsLetterSubscription; - if (profile.Limit > 0 && normalizedTotal > profile.Limit) - normalizedTotal = profile.Limit; + if (allStores == null) + allStores = _services.StoreService.GetAllStores(); + + var store = allStores.FirstOrDefault(x => x.Id == subscription.StoreId); + + subscriberModel.Add(new ExportPreviewNewsLetterSubscriptionModel + { + Id = subscription.Id, + Active = subscription.Active, + CreatedOn = _dateTimeHelper.ConvertToUserTime(subscription.CreatedOnUtc, DateTimeKind.Utc), + Email = subscription.Email, + StoreName = (store == null ? "".NaIfEmpty() : store.Name) + }); + } + } if (provider.Value.EntityType == ExportEntityType.Product) { - return new JsonResult - { - Data = new GridModel { Data = productModel, Total = normalizedTotal } - }; + gridData = new GridModel { Data = productModel, Total = normalizedTotal }; } - - if (provider.Value.EntityType == ExportEntityType.Order) + else if (provider.Value.EntityType == ExportEntityType.Order) { - return new JsonResult - { - Data = new GridModel { Data = orderModel, Total = normalizedTotal } - }; + gridData = new GridModel { Data = orderModel, Total = normalizedTotal }; } - } + else if (provider.Value.EntityType == ExportEntityType.Category) + { + gridData = new GridModel { Data = categoryModel, Total = normalizedTotal }; + } + else if (provider.Value.EntityType == ExportEntityType.Manufacturer) + { + gridData = new GridModel { Data = manuModel, Total = normalizedTotal }; + } + else if (provider.Value.EntityType == ExportEntityType.Customer) + { + gridData = new GridModel { Data = customerModel, Total = normalizedTotal }; + } + else if (provider.Value.EntityType == ExportEntityType.NewsLetterSubscription) + { + gridData = new GridModel { Data = subscriberModel, Total = normalizedTotal }; + } + + return new JsonResult { Data = gridData }; + } return new EmptyResult(); } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportPreviewModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportPreviewModel.cs index 4dbcde6c6a..eff05703f6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportPreviewModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportPreviewModel.cs @@ -14,6 +14,7 @@ public class ExportPreviewModel : EntityModelBase public int TotalRecords { get; set; } public ExportEntityType EntityType { get; set; } public bool LogFileExists { get; set; } + public bool UsernamesEnabled { get; set; } } public class ExportPreviewProductModel : EntityModelBase @@ -71,4 +72,81 @@ public class ExportPreviewOrderModel : EntityModelBase [SmartResourceDisplayName("Admin.Orders.Fields.OrderTotal")] public decimal OrderTotal { get; set; } } + + public class ExportPreviewCategoryModel : EntityModelBase + { + [SmartResourceDisplayName("Admin.Catalog.Products.Categories.Fields.Category")] + public string Breadcrumb { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.FullName")] + public string FullName { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.Alias")] + public string Alias { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.Published")] + public bool Published { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Categories.Fields.DisplayOrder")] + public int DisplayOrder { get; set; } + + [SmartResourceDisplayName("Admin.Common.Store.LimitedTo")] + public bool LimitedToStores { get; set; } + } + + public class ExportPreviewManufacturerModel : EntityModelBase + { + [SmartResourceDisplayName("Admin.Catalog.Manufacturers.Fields.Name")] + public string Name { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Manufacturers.Fields.Published")] + public bool Published { get; set; } + + [SmartResourceDisplayName("Admin.Catalog.Manufacturers.Fields.DisplayOrder")] + public int DisplayOrder { get; set; } + + [SmartResourceDisplayName("Admin.Common.Store.LimitedTo")] + public bool LimitedToStores { get; set; } + } + + public class ExportPreviewCustomerModel : EntityModelBase + { + public bool UsernamesEnabled { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.Username")] + public string Username { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.FullName")] + public string FullName { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.Email")] + public string Email { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.CustomerRoles")] + public string CustomerRoleNames { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.Active")] + public bool Active { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.CreatedOn")] + public DateTime CreatedOn { get; set; } + + [SmartResourceDisplayName("Admin.Customers.Customers.Fields.LastActivityDate")] + public DateTime LastActivityDate { get; set; } + } + + public class ExportPreviewNewsLetterSubscriptionModel : EntityModelBase + { + [SmartResourceDisplayName("Admin.Promotions.NewsLetterSubscriptions.Fields.Email")] + public string Email { get; set; } + + [SmartResourceDisplayName("Admin.Promotions.NewsLetterSubscriptions.Fields.Active")] + public bool Active { get; set; } + + [SmartResourceDisplayName("Admin.Promotions.NewsLetterSubscriptions.Fields.CreatedOn")] + public DateTime CreatedOn { get; set; } + + [SmartResourceDisplayName("Admin.Common.Store")] + public string StoreName { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Category/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Category/List.cshtml index 9a3b2abd9f..08d7f81a83 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Category/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Category/List.cshtml @@ -65,13 +65,18 @@ columns.Bound(x => x.FullName); columns.Bound(x => x.Alias); columns.Bound(x => x.Published) - .Width(100) + .Width(160) .Template(item => @Html.SymbolForBool(item.Published)) .ClientTemplate(@Html.SymbolForBool("Published")) .Centered(); columns.Bound(x => x.DisplayOrder) - .Width(100) + .Width(160) .Centered(); + columns.Bound(x => x.LimitedToStores) + .Width(160) + .Template(item => @Html.SymbolForBool(item.LimitedToStores)) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Centered(); }) .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Category")) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml index 2bca030bc9..9cd9abb59e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml @@ -100,81 +100,215 @@ } -@if (Model.EntityType == ExportEntityType.Product) -{ -
    +
    + @if (Model.EntityType == ExportEntityType.Product) + { @(Html.Telerik().Grid() - .Name("export-grid-product") - .DataKeys(keys => keys.Add(x => x.Id) - .RouteKey("Id")) - .Columns(columns => - { - columns.Bound(x => x.Id) - .ClientTemplate("") - .Title("") - .Width(50) - .HtmlAttributes(new { style = "text-align:center" }) - .HeaderHtmlAttributes(new { style = "text-align:center" }); - columns.Bound(x => x.Id) - .Width(80) - .Centered(); - columns.Bound(x => x.Name) - .ClientTemplate(@Html.LabeledProductName("Id", "Name")); - columns.Bound(x => x.Sku); - columns.Bound(x => x.Price) - .Format("{0:0.00}") - .RightAlign(); - columns.Bound(x => x.StockQuantity) - .Centered(); - columns.Bound(x => x.Published) - .Width(100) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered(); - columns.Bound(x => x.AdminComment); - }) - .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) - .ClientEvents(events => events.OnDataBound("OnDataBound")) - .EnableCustomBinding(true)) -
    -} -else if (Model.EntityType == ExportEntityType.Order) -{ -
    + .Name("export-grid-product") + .DataKeys(keys => keys.Add(x => x.Id) + .RouteKey("Id")) + .Columns(columns => + { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .Width(50) + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); + columns.Bound(x => x.Id) + .Width(80) + .Centered(); + columns.Bound(x => x.Name) + .ClientTemplate(@Html.LabeledProductName("Id", "Name")); + columns.Bound(x => x.Sku); + columns.Bound(x => x.Price) + .Format("{0:0.00}") + .RightAlign(); + columns.Bound(x => x.StockQuantity) + .Centered(); + columns.Bound(x => x.Published) + .Width(160) + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Bound(x => x.AdminComment); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) + .ClientEvents(events => events.OnDataBound("OnDataBound")) + .EnableCustomBinding(true)) + } + else if (Model.EntityType == ExportEntityType.Order) + { @(Html.Telerik().Grid() - .Name("export-grid-order") - .DataKeys(keys => keys.Add(x => x.Id) - .RouteKey("Id")) - .Columns(columns => - { - columns.Bound(x => x.Id) - .ClientTemplate("") - .Title("") - .Width(50) - .HtmlAttributes(new { style = "text-align:center" }) - .HeaderHtmlAttributes(new { style = "text-align:center" }); - columns.Bound(x => x.Id) - .Width(80) - .Centered(); - columns.Bound(x => x.OrderNumber) - .ClientTemplate(@Html.LabeledOrderNumber()); - columns.Bound(x => x.OrderStatus); - columns.Bound(x => x.PaymentStatus); - columns.Bound(x => x.ShippingStatus); - columns.Bound(x => x.CustomerEmail); - columns.Bound(x => x.StoreName); - columns.Bound(x => x.CreatedOn); - columns.Bound(x => x.OrderTotal) - .Format("{0:0.00}") - .RightAlign(); - }) - .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) - .ClientEvents(events => events.OnDataBound("OnDataBound")) - .EnableCustomBinding(true)) -
    -} -else -{ -
    @T("Admin.DataExchange.Export.NoPreview")
    -} \ No newline at end of file + .Name("export-grid-order") + .DataKeys(keys => keys.Add(x => x.Id) + .RouteKey("Id")) + .Columns(columns => + { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .Width(50) + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); + columns.Bound(x => x.Id) + .Width(80) + .Centered(); + columns.Bound(x => x.OrderNumber) + .ClientTemplate(@Html.LabeledOrderNumber()); + columns.Bound(x => x.OrderStatus); + columns.Bound(x => x.PaymentStatus); + columns.Bound(x => x.ShippingStatus); + columns.Bound(x => x.CustomerEmail); + columns.Bound(x => x.StoreName); + columns.Bound(x => x.CreatedOn); + columns.Bound(x => x.OrderTotal) + .Format("{0:0.00}") + .RightAlign(); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) + .ClientEvents(events => events.OnDataBound("OnDataBound")) + .EnableCustomBinding(true)) + } + else if (Model.EntityType == ExportEntityType.Category) + { + @(Html.Telerik().Grid() + .Name("export-grid-order") + .DataKeys(keys => keys.Add(x => x.Id) + .RouteKey("Id")) + .Columns(columns => + { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .Width(50) + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); + columns.Bound(x => x.Id) + .Width(80) + .Centered(); + columns.Bound(x => x.Breadcrumb); + columns.Bound(x => x.FullName); + columns.Bound(x => x.Alias); + columns.Bound(x => x.Published) + .Width(160) + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Width(160) + .Centered(); + columns.Bound(x => x.LimitedToStores) + .Width(160) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Centered(); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) + .ClientEvents(events => events.OnDataBound("OnDataBound")) + .EnableCustomBinding(true)) + } + else if (Model.EntityType == ExportEntityType.Manufacturer) + { + @(Html.Telerik().Grid() + .Name("export-grid-order") + .DataKeys(keys => keys.Add(x => x.Id) + .RouteKey("Id")) + .Columns(columns => + { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .Width(50) + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); + columns.Bound(x => x.Id) + .Width(80) + .Centered(); + columns.Bound(x => x.Name); + columns.Bound(x => x.Published) + .Width(160) + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Width(160) + .Centered(); + columns.Bound(x => x.LimitedToStores) + .Width(160) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Centered(); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) + .ClientEvents(events => events.OnDataBound("OnDataBound")) + .EnableCustomBinding(true)) + } + else if (Model.EntityType == ExportEntityType.Customer) + { + @(Html.Telerik().Grid() + .Name("export-grid-order") + .DataKeys(keys => keys.Add(x => x.Id) + .RouteKey("Id")) + .Columns(columns => + { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .Width(50) + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); + columns.Bound(x => x.Id) + .Width(80) + .Centered(); + columns.Bound(x => x.Email); + columns.Bound(x => x.Username) + .Visible(Model.UsernamesEnabled); + columns.Bound(x => x.FullName); + columns.Bound(x => x.CustomerRoleNames); + columns.Bound(x => x.Active) + .ClientTemplate(@Html.SymbolForBool("Active")) + .Width(160) + .Centered(); + columns.Bound(x => x.CreatedOn); + columns.Bound(x => x.LastActivityDate); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) + .ClientEvents(events => events.OnDataBound("OnDataBound")) + .EnableCustomBinding(true)) + } + else if (Model.EntityType == ExportEntityType.NewsLetterSubscription) + { + @(Html.Telerik().Grid() + .Name("export-grid-order") + .DataKeys(keys => keys.Add(x => x.Id) + .RouteKey("Id")) + .Columns(columns => + { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .Width(50) + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); + columns.Bound(x => x.Id) + .Width(80) + .Centered(); + columns.Bound(x => x.Email); + columns.Bound(x => x.Active) + .ClientTemplate(@Html.SymbolForBool("Active")) + .Centered() + .Width(160); + columns.Bound(x => x.StoreName); + columns.Bound(x => x.CreatedOn); + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("PreviewList", "Export", new { id = Model.Id, totalRecords = Model.TotalRecords })) + .ClientEvents(events => events.OnDataBound("OnDataBound")) + .EnableCustomBinding(true)) + } + else + { +
    @T("Admin.DataExchange.Export.NoPreview")
    + } +
    \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml index 87310a67ee..be3c64facb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml @@ -40,45 +40,43 @@
    +

    +
    @(Html.Telerik().Grid(Model.Manufacturers.Data) - .Name("manufacturers-grid") - .ClientEvents(events => events - .OnDataBinding("onDataBinding")) - .Columns(columns => - { - columns.Bound(x => x.Name).Width(300) - .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) - .ClientTemplate("\"><#= Name #>"); - - // .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) - //.ClientTemplate("'><#= Name #>"); - - columns.Bound(x => x.Published) - .Width(100) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .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); - }) - .Pageable(settings => settings.Total(Model.Manufacturers.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Manufacturer")) + .Name("manufacturers-grid") + .ClientEvents(events => events + .OnDataBinding("onDataBinding")) + .Columns(columns => + { + columns.Bound(x => x.Name) + .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) + .ClientTemplate("\"><#= Name #>"); + columns.Bound(x => x.Published) + .Width(160) + .Template(item => @Html.SymbolForBool(item.Published)) + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Width(160) + .Centered(); + columns.Bound(x => x.LimitedToStores) + .Width(160) + .Template(item => @Html.SymbolForBool(item.LimitedToStores)) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Centered(); + }) + .Pageable(settings => settings.Total(Model.Manufacturers.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Manufacturer")) .PreserveGridState() - .EnableCustomBinding(true)) + .EnableCustomBinding(true))
    + From afce7e6f3eac3cf6ae6b47a0cb774333be42f0be Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 17 Nov 2015 10:42:22 +0100 Subject: [PATCH 232/874] Minor changes --- .../Data/IDbContextExtensions.cs | 28 --------- .../SmartStore.Data/ObjectContextBase.cs | 60 +++++++++++-------- .../DataExchange/DataExporter.cs | 45 +++++++++----- .../Deployment/EmailFilePublisher.cs | 14 +++-- .../Deployment/FileSystemFilePublisher.cs | 9 --- .../Deployment/FtpFilePublisher.cs | 9 --- .../Deployment/HttpFilePublisher.cs | 8 --- .../DataExchange/Deployment/IFilePublisher.cs | 3 - .../DataExchange/DynamicEntityHelper.cs | 25 +++----- .../Internal/DataExporterContext.cs | 10 +--- 10 files changed, 79 insertions(+), 132 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs b/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs index 0bc3d4dc7f..0889cd6684 100644 --- a/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs +++ b/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs @@ -28,34 +28,6 @@ public static void DetachEntities(this IDbContext ctx, IEnumerable ctx.DetachEntity(x)); } - public static void Attach(this IDbContext ctx, TEntity entity) where TEntity : BaseEntity - { - var dbSet = ctx.Set(); - var alreadyAttached = dbSet.Local.FirstOrDefault(x => x.Id == entity.Id); - - if (alreadyAttached == null) - { - dbSet.Attach(entity); - } - } - - public static void Attach(this IDbContext ctx, IEnumerable entities) where TEntity : BaseEntity - { - Guard.ArgumentNotNull(() => ctx); - - var dbSet = ctx.Set(); - - entities.Each(entity => - { - var alreadyAttached = dbSet.Local.FirstOrDefault(x => x.Id == entity.Id); - - if (alreadyAttached == null) - { - dbSet.Attach(entity); - } - }); - } - /// /// Changes the object state to unchanged /// diff --git a/src/Libraries/SmartStore.Data/ObjectContextBase.cs b/src/Libraries/SmartStore.Data/ObjectContextBase.cs index 5f722e155f..278d88d984 100644 --- a/src/Libraries/SmartStore.Data/ObjectContextBase.cs +++ b/src/Libraries/SmartStore.Data/ObjectContextBase.cs @@ -2,27 +2,26 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; -using System.Data.SqlClient; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Entity.Validation; +using System.Data.SqlClient; using System.Linq; -using System.Linq.Expressions; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Microsoft.SqlServer.Management.Common; +using Microsoft.SqlServer.Management.Smo; using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Data.Hooks; -using Microsoft.SqlServer.Management.Common; -using Microsoft.SqlServer.Management.Smo; using SmartStore.Core.Events; namespace SmartStore.Data { - /// - /// Object context - /// + /// + /// Object context + /// [DbConfigurationType(typeof(SmartDbConfiguration))] public abstract class ObjectContextBase : DbContext, IDbContext { @@ -496,32 +495,41 @@ protected internal bool IsSqlServer2012OrHigher() return s_isSqlServer2012OrHigher.Value; } - /// - /// Attach an entity to the context or return an already attached entity (if it was already attached) - /// - /// TEntity - /// Entity - /// Attached entity - protected virtual TEntity AttachEntity(TEntity entity) where TEntity : BaseEntity, new() - { - // little hack here until Entity Framework really supports stored procedures - // otherwise, navigation properties of loaded entities are not loaded until an entity is attached to the context - var dbSet = Set(); - var alreadyAttached = dbSet.Local.Where(x => x.Id == entity.Id).FirstOrDefault(); + private TEntity Attach(DbSet dbSet, TEntity entity) where TEntity : BaseEntity + { + var alreadyAttached = dbSet.Local.FirstOrDefault(x => x.Id == entity.Id); + if (alreadyAttached == null) { - // attach new entity dbSet.Attach(entity); return entity; } - else - { - // entity is already loaded. - return alreadyAttached; - } + + return alreadyAttached; + } + + /// + /// Attach an entity to the context or return an already attached entity (if it was already attached) + /// + /// TEntity + /// Entity + /// Attached entity + public TEntity AttachEntity(TEntity entity) where TEntity : BaseEntity + { + // little hack here until Entity Framework really supports stored procedures + // otherwise, navigation properties of loaded entities are not loaded until an entity is attached to the context + + return Attach(Set(), entity); } - public bool IsAttached(TEntity entity) where TEntity : BaseEntity + public void AttachEntities(IEnumerable entities) where TEntity : BaseEntity + { + var dbSet = Set(); + + entities.Each(entity => Attach(dbSet, entity)); + } + + public bool IsAttached(TEntity entity) where TEntity : BaseEntity { if (entity != null) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs index 3ccd178409..1fb126c0de 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs @@ -19,6 +19,7 @@ using SmartStore.Core.Infrastructure; using SmartStore.Core.Localization; using SmartStore.Core.Logging; +using SmartStore.Data; using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; @@ -69,10 +70,10 @@ public partial class DataExporter : IDataExporter private readonly Lazy _shipmentService; private readonly Lazy _genericAttributeService; private readonly Lazy _emailAccountService; + private readonly Lazy _queuedEmailService; private readonly Lazy _emailSender; private readonly Lazy _deliveryTimeService; private readonly Lazy _quantityUnitService; - private readonly Lazy _typeFinder; private readonly Lazy>_customerRepository; private readonly Lazy> _subscriptionRepository; @@ -107,10 +108,10 @@ public DataExporter( Lazy shipmentService, Lazy genericAttributeService, Lazy emailAccountService, - Lazy emailSender, + Lazy queuedEmailService, + Lazy emailSender, Lazy deliveryTimeService, Lazy quantityUnitService, - Lazy typeFinder, Lazy> customerRepository, Lazy> subscriptionRepository, Lazy> orderRepository, @@ -142,10 +143,10 @@ public DataExporter( _shipmentService = shipmentService; _genericAttributeService = genericAttributeService; _emailAccountService = emailAccountService; + _queuedEmailService = queuedEmailService; _emailSender = emailSender; _deliveryTimeService = deliveryTimeService; _quantityUnitService = quantityUnitService; - _typeFinder = typeFinder; _customerRepository = customerRepository; _subscriptionRepository = subscriptionRepository; @@ -210,10 +211,11 @@ private void DetachAllEntitiesAndClear(DataExporterContext ctx) try { // now again attach what is globally required + var dataContextBase = _dbContext as ObjectContextBase; - _dbContext.Attach(ctx.Request.Profile); + dataContextBase.AttachEntity(ctx.Request.Profile); - _dbContext.Attach(ctx.Stores.Values); + dataContextBase.AttachEntities(ctx.Stores.Values); } catch (Exception exception) { @@ -421,10 +423,6 @@ private void Deploy(DataExporterContext ctx) var containerManager = EngineContext.Current.ContainerManager; var allFiles = System.IO.Directory.GetFiles(ctx.FolderContent, "*.*", SearchOption.AllDirectories); - var publishers = _typeFinder.Value.FindClassesOfType(ignoreInactivePlugins: true) - .Select(x => containerManager.ResolveUnregistered(x) as IFilePublisher) - .ToList(); - var context = new ExportDeploymentContext { Log = ctx.Log, @@ -439,17 +437,33 @@ private void Deploy(DataExporterContext ctx) else context.DeploymentFiles = allFiles; - foreach (var publisher in publishers.Where(x => x.DeploymentType == deployment.DeploymentType)) + try { - try + if (deployment.DeploymentType == ExportDeploymentType.Email) { + var publisher = new EmailFilePublisher(_emailAccountService.Value, _queuedEmailService.Value); publisher.Publish(context, deployment); } - catch (Exception exception) + else if (deployment.DeploymentType == ExportDeploymentType.FileSystem) { - ctx.Log.Error("Deployment \"{0}\" of type {1} failed: {2}".FormatInvariant( - deployment.Name, deployment.DeploymentType.ToString(), exception.Message), exception); + var publisher = new FileSystemFilePublisher(); + publisher.Publish(context, deployment); + } + else if (deployment.DeploymentType == ExportDeploymentType.Ftp) + { + var publisher = new FtpFilePublisher(); + publisher.Publish(context, deployment); } + else if (deployment.DeploymentType == ExportDeploymentType.Http) + { + var publisher = new HttpFilePublisher(); + publisher.Publish(context, deployment); + } + } + catch (Exception exception) + { + ctx.Log.Error("Deployment \"{0}\" of type {1} failed: {2}".FormatInvariant( + deployment.Name, deployment.DeploymentType.ToString(), exception.Message), exception); } } } @@ -1096,7 +1110,6 @@ private void ExportCoreOuter(DataExporterContext ctx) ctx.DeliveryTimes.Clear(); ctx.CategoryPathes.Clear(); ctx.Categories.Clear(); - ctx.PropertiesCache.Clear(); ctx.Request.CustomData.Clear(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs index bc9903e734..d6ee4b4f2c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs @@ -2,7 +2,6 @@ using System.IO; using System.Linq; using SmartStore.Core.Domain; -using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Messages; using SmartStore.Core.Email; using SmartStore.Core.Infrastructure; @@ -14,12 +13,15 @@ namespace SmartStore.Services.DataExchange.Deployment { public class EmailFilePublisher : IFilePublisher { - public ExportDeploymentType DeploymentType + private IEmailAccountService _emailAccountService; + private IQueuedEmailService _queuedEmailService; + + public EmailFilePublisher( + IEmailAccountService emailAccountService, + IQueuedEmailService queuedEmailService) { - get - { - return ExportDeploymentType.Email; - } + _emailAccountService = emailAccountService; + _queuedEmailService = queuedEmailService; } public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs index 392ab82680..2fba5924a0 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs @@ -1,7 +1,6 @@ using System.IO; using System.Web; using SmartStore.Core.Domain; -using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Logging; using SmartStore.Utilities; @@ -9,14 +8,6 @@ namespace SmartStore.Services.DataExchange.Deployment { public class FileSystemFilePublisher : IFilePublisher { - public ExportDeploymentType DeploymentType - { - get - { - return ExportDeploymentType.FileSystem; - } - } - public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) { string folderDestination = null; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FtpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/FtpFilePublisher.cs index 4fad3cc146..bba604f38a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FtpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/FtpFilePublisher.cs @@ -3,21 +3,12 @@ using System.Linq; using System.Net; using SmartStore.Core.Domain; -using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Logging; namespace SmartStore.Services.DataExchange.Deployment { public class FtpFilePublisher : IFilePublisher { - public ExportDeploymentType DeploymentType - { - get - { - return ExportDeploymentType.Ftp; - } - } - public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) { var bytesRead = 0; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/HttpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/HttpFilePublisher.cs index fadcf8a8d3..b2ab9001cb 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/HttpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/HttpFilePublisher.cs @@ -10,14 +10,6 @@ namespace SmartStore.Services.DataExchange.Deployment { public class HttpFilePublisher : IFilePublisher { - public ExportDeploymentType DeploymentType - { - get - { - return ExportDeploymentType.Http; - } - } - public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) { var succeeded = 0; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs index 9f13724efb..36b9d7eb6a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs @@ -1,13 +1,10 @@ using SmartStore.Core.Domain; -using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Logging; namespace SmartStore.Services.DataExchange.Deployment { public interface IFilePublisher { - ExportDeploymentType DeploymentType { get; } - void Publish(ExportDeploymentContext context, ExportDeployment deployment); } diff --git a/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs index a007c8763e..f88aa7cda1 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Linq.Expressions; using System.Reflection; @@ -26,6 +25,7 @@ using SmartStore.Services.DataExchange.Internal; using SmartStore.Services.Localization; using SmartStore.Services.Seo; +using SmartStore.Utilities.Reflection; namespace SmartStore.Services.DataExchange { @@ -953,28 +953,17 @@ private List Convert(DataExporterContext ctx, Product product) if (!ctx.IsPreview && ctx.Projection.AttributeCombinationAsProduct && combinations.Where(x => x.IsActive).Count() > 0) { var productType = typeof(Product); - IEnumerable properties = null; var productValues = new Dictionary(); - // fetch all cloneable product properties once per export - if (ctx.PropertiesCache.ContainsKey(productType)) - { - properties = ctx.PropertiesCache[productType]; - } - else - { - properties = productType - .GetProperties() - .Where(x => x.CanRead && x.CanWrite && 0 == x.GetCustomAttributes(typeof(NotMappedAttribute), true).Length); - - ctx.PropertiesCache.Add(productType, properties); - } + var properties = FastProperty.GetProperties(product.GetUnproxiedType()) + .Values + .Where(x => x.Property.CanRead && x.Property.CanWrite); // fetch all property values once per product foreach (var property in properties) { - productValues.Add(property.Name, property.GetValue(product, null)); - } + productValues.Add(property.Name, property.GetValue(product)); + } foreach (var combination in combinations.Where(x => x.IsActive)) { @@ -982,7 +971,7 @@ private List Convert(DataExporterContext ctx, Product product) foreach (var property in properties) { - property.SetValue(productClone, productValues[property.Name], null); + property.SetValue(productClone, productValues[property.Name]); } var dynObject = ToDynamic(ctx, productClone, combinations, combination); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs index 6dbaf059d4..0d44c9843e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; using System.Threading; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; @@ -41,7 +39,6 @@ public DataExporterContext( RecordsPerStore = new Dictionary(); EntityIdsLoaded = new List(); EntityIdsPerSegment = new List(); - PropertiesCache = new Dictionary>(); Result = new DataExportResult { @@ -69,11 +66,6 @@ public void SetLoadedEntityIds(IEnumerable ids) /// public List EntityIdsPerSegment { get; set; } - /// - /// For faster reflection - /// - public Dictionary> PropertiesCache { get; set; } - public int RecordCount { get; set; } public Dictionary RecordsPerStore { get; set; } public string ProgressInfo { get; set; } From 7883c7c7f607a37f164281ebeeeff2c258fa5384 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 17 Nov 2015 11:43:36 +0100 Subject: [PATCH 233/874] Removed necessity of handling with volatile export profiles --- .../DataExchange/ExportProfileService.cs | 38 +++++-------------- .../DataExchange/IExportProfileService.cs | 7 ---- .../Controllers/ExportController.cs | 2 +- 3 files changed, 11 insertions(+), 36 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs index faa5dcefb5..df8f6b6832 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs @@ -47,32 +47,6 @@ public ExportProfileService( #region Export profiles - public virtual ExportProfile CreateVolatileProfile(Provider provider) - { - var name = provider.GetName(_localizationService); - var seoName = SeoHelper.GetSeName(name, true, false).Replace("/", "").Replace("-", ""); - - var profile = new ExportProfile - { - Id = 0, - Name = name, - FolderName = seoName.ToValidPath().Truncate(_dataExchangeSettings.MaxFileNameLength), - FileNamePattern = _defaultFileNamePattern, - ProviderSystemName = provider.Metadata.SystemName, - Enabled = true, - SchedulingTaskId = 0, - PerStore = false, - CreateZipArchive = false, - Cleanup = false, - ScheduleTask = null, // volatile schedule task impossible cause of database accesses by core - Deployments = new List() - }; - - // profile.Projection and profile.Filtering should be null here - - return profile; - } - public virtual ExportProfile InsertExportProfile( string providerSystemName, string name, @@ -90,7 +64,6 @@ public virtual ExportProfile InsertExportProfile( ScheduleTask task = null; ExportProfile profile = null; - var seoName = SeoHelper.GetSeName(name, true, false).Replace("/", "").Replace("-", ""); if (cloneProfile == null) { @@ -155,10 +128,19 @@ public virtual ExportProfile InsertExportProfile( profile.IsSystemProfile = isSystemProfile; profile.Name = name; - profile.FolderName = seoName.ToValidPath().Truncate(_dataExchangeSettings.MaxFileNameLength); profile.ProviderSystemName = providerSystemName; profile.SchedulingTaskId = task.Id; + var folderName = providerSystemName + .Replace("Exports.", "") + .Replace("Feeds.", "") + .Replace("/", "") + .Replace("-", ""); + + profile.FolderName = SeoHelper.GetSeName(folderName, true, false) + .ToValidPath() + .Truncate(_dataExchangeSettings.MaxFileNameLength); + _exportProfileRepository.Insert(profile); task.Alias = profile.Id.ToString(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/IExportProfileService.cs index 4e583b38f2..27b353d532 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IExportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IExportProfileService.cs @@ -8,13 +8,6 @@ namespace SmartStore.Services.DataExchange { public interface IExportProfileService { - /// - /// Creates a volatile export profile - /// - /// Export provider - /// New export profile - ExportProfile CreateVolatileProfile(Provider provider); - /// /// Inserts an export profile /// diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index ce5494ab60..5fafb8d0df 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -460,7 +460,7 @@ public ActionResult List() if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) return AccessDeniedView(); - var providers = _exportService.LoadAllExportProviders().ToList(); + var providers = _exportService.LoadAllExportProviders(0, false).ToList(); var profiles = _exportService.GetExportProfiles().ToList(); var model = new List(); From a1428df101e727e4865e29680e1798c4aed5e682 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 17 Nov 2015 12:06:42 +0100 Subject: [PATCH 234/874] Added new permission record "ManageUrlRecords" --- .../Migrations/201511121139009_ExportFramework3.cs | 12 ++++++++++++ .../Security/StandardPermissionProvider.cs | 10 ++++++---- .../Controllers/UrlRecordController.cs | 13 ++++++------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs b/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs index b35af29179..093398f968 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs @@ -7,6 +7,8 @@ namespace SmartStore.Data.Migrations using Core.Domain; using System.Linq; using Utilities; + using Core.Domain.Security; + using Core.Domain.Customers; public partial class ExportFramework3 : DbMigration, ILocaleResourcesProvider, IDataSeeder { @@ -31,6 +33,16 @@ public void Seed(SmartObjectContext context) { context.MigrateLocaleResources(MigrateLocaleResources); + var permissionMigrator = new PermissionMigrator(context); + + permissionMigrator.AddPermission(new PermissionRecord + { + Name = "Admin area. Manage Url Records", + SystemName = "ManageUrlRecords", + Category = "Standard" + }, new string[] { SystemCustomerRoleNames.Administrators }); + + var systemProfilesInfos = new List { new SystemProfileInfo { SystemName = "SmartStoreCategoryXml", ProviderSystemName = "Exports.SmartStoreCategoryXml", Name = "Category XML Export" }, diff --git a/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs b/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs index 7018c51146..4f37e81bb0 100644 --- a/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs +++ b/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs @@ -50,10 +50,10 @@ public partial class StandardPermissionProvider : IPermissionProvider public static readonly PermissionRecord UploadPictures = new PermissionRecord { Name = "Admin area. Upload Pictures", SystemName = "UploadPictures", Category = "Configuration" }; public static readonly PermissionRecord ManageScheduleTasks = new PermissionRecord { Name = "Admin area. Manage Schedule Tasks", SystemName = "ManageScheduleTasks", Category = "Configuration" }; public static readonly PermissionRecord ManageExports = new PermissionRecord { Name = "Admin area. Manage Exports", SystemName = "ManageExports", Category = "Configuration" }; + public static readonly PermissionRecord ManageUrlRecords = new PermissionRecord { Name = "Admin area. Manage Url Records", SystemName = "ManageUrlRecords", Category = "Standard" }; - - //public store permissions - public static readonly PermissionRecord DisplayPrices = new PermissionRecord { Name = "Public store. Display Prices", SystemName = "DisplayPrices", Category = "PublicStore" }; + //public store permissions + public static readonly PermissionRecord DisplayPrices = new PermissionRecord { Name = "Public store. Display Prices", SystemName = "DisplayPrices", Category = "PublicStore" }; public static readonly PermissionRecord EnableShoppingCart = new PermissionRecord { Name = "Public store. Enable shopping cart", SystemName = "EnableShoppingCart", Category = "PublicStore" }; public static readonly PermissionRecord EnableWishlist = new PermissionRecord { Name = "Public store. Enable wishlist", SystemName = "EnableWishlist", Category = "PublicStore" }; public static readonly PermissionRecord PublicStoreAllowNavigation = new PermissionRecord { Name = "Public store. Allow navigation", SystemName = "PublicStoreAllowNavigation", Category = "PublicStore" }; @@ -105,7 +105,8 @@ public virtual IEnumerable GetPermissions() UploadPictures, ManageScheduleTasks, ManageExports, - DisplayPrices, + ManageUrlRecords, + DisplayPrices, EnableShoppingCart, EnableWishlist, PublicStoreAllowNavigation, @@ -163,6 +164,7 @@ public virtual IEnumerable GetDefaultPermissions() UploadPictures, ManageScheduleTasks, ManageExports, + ManageUrlRecords, DisplayPrices, EnableShoppingCart, EnableWishlist, diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs index 1eb07d10a5..358b174f27 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs @@ -14,7 +14,6 @@ namespace SmartStore.Admin.Controllers { - // TODO: add new permisssion record "ManageUrlRecords" [AdminAuthorize] public class UrlRecordController : AdminControllerBase { @@ -82,7 +81,7 @@ private void PrepareUrlRecordModel(UrlRecordModel model, UrlRecord urlRecord, bo public ActionResult List(string entityName, int? entityId) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageUrlRecords)) return AccessDeniedView(); var model = new UrlRecordListModel @@ -106,7 +105,7 @@ public ActionResult List(string entityName, int? entityId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command, UrlRecordListModel model) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageUrlRecords)) return AccessDeniedView(); var allLanguages = _languageService.GetAllLanguages(true); @@ -152,7 +151,7 @@ public ActionResult List(GridCommand command, UrlRecordListModel model) public ActionResult Edit(int id) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageUrlRecords)) return AccessDeniedView(); var urlRecord = _urlRecordService.GetUrlRecordById(id); @@ -168,7 +167,7 @@ public ActionResult Edit(int id) [HttpPost, ParameterBasedOnFormNameAttribute("save-continue", "continueEditing")] public ActionResult Edit(UrlRecordModel model, bool continueEditing) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageUrlRecords)) return AccessDeniedView(); var urlRecord = _urlRecordService.GetUrlRecordById(model.Id); @@ -206,7 +205,7 @@ public ActionResult Edit(UrlRecordModel model, bool continueEditing) [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageUrlRecords)) return AccessDeniedView(); var urlRecord = _urlRecordService.GetUrlRecordById(id); @@ -231,7 +230,7 @@ public ActionResult DeleteConfirmed(int id) [HttpPost] public ActionResult DeleteSelected(ICollection selectedIds) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageMaintenance)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageUrlRecords)) return AccessDeniedView(); if (selectedIds != null) From 2d81510e80dff032894a7eaefd4dd1e5ad94f38f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 17 Nov 2015 12:09:19 +0100 Subject: [PATCH 235/874] Minor change --- .../Migrations/201511121139009_ExportFramework3.cs | 2 +- .../SmartStore.Services/Security/StandardPermissionProvider.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs b/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs index 093398f968..b3b39a74de 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs @@ -39,7 +39,7 @@ public void Seed(SmartObjectContext context) { Name = "Admin area. Manage Url Records", SystemName = "ManageUrlRecords", - Category = "Standard" + Category = "Configuration" }, new string[] { SystemCustomerRoleNames.Administrators }); diff --git a/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs b/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs index 4f37e81bb0..886196b240 100644 --- a/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs +++ b/src/Libraries/SmartStore.Services/Security/StandardPermissionProvider.cs @@ -50,7 +50,7 @@ public partial class StandardPermissionProvider : IPermissionProvider public static readonly PermissionRecord UploadPictures = new PermissionRecord { Name = "Admin area. Upload Pictures", SystemName = "UploadPictures", Category = "Configuration" }; public static readonly PermissionRecord ManageScheduleTasks = new PermissionRecord { Name = "Admin area. Manage Schedule Tasks", SystemName = "ManageScheduleTasks", Category = "Configuration" }; public static readonly PermissionRecord ManageExports = new PermissionRecord { Name = "Admin area. Manage Exports", SystemName = "ManageExports", Category = "Configuration" }; - public static readonly PermissionRecord ManageUrlRecords = new PermissionRecord { Name = "Admin area. Manage Url Records", SystemName = "ManageUrlRecords", Category = "Standard" }; + public static readonly PermissionRecord ManageUrlRecords = new PermissionRecord { Name = "Admin area. Manage Url Records", SystemName = "ManageUrlRecords", Category = "Configuration" }; //public store permissions public static readonly PermissionRecord DisplayPrices = new PermissionRecord { Name = "Public store. Display Prices", SystemName = "DisplayPrices", Category = "PublicStore" }; From 97eecfbaf4d9b15f15ae217600da356168e20163 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 17 Nov 2015 14:21:28 +0100 Subject: [PATCH 236/874] Export framework should not create empty files --- src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs index 1fb126c0de..eb1a0d2306 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs @@ -383,7 +383,7 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho ctx.Request.Provider.Value.OnExecuted(ctx.ExecuteContext); } - if (ctx.IsFileBasedExport && path.HasValue()) + if (ctx.IsFileBasedExport && path.HasValue() && ctx.ExecuteContext.DataStream.Length > 0) { if (!ctx.ExecuteContext.DataStream.CanSeek) ctx.Log.Warning("Data stream seems to be closed!"); From 96156cf5001dc4cfb19df3fd7b1fc0fdb2a1d088 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 17 Nov 2015 15:31:50 +0100 Subject: [PATCH 237/874] Perf: Statically cache types of payment method filters and shipping method filters --- .../Payments/PaymentService.cs | 27 +++++++++++++++---- .../Shipping/ShippingService.cs | 25 ++++++++++++++--- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs index 6a5587bc72..9280f99bb0 100644 --- a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs +++ b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs @@ -25,10 +25,13 @@ public partial class PaymentService : IPaymentService #region Constants private const string PAYMENTMETHOD_ALL_KEY = "SmartStore.paymentmethod.all"; - + #endregion - #region Fields + #region Fields + + private readonly static object _lock = new object(); + private static IList _paymentMethodFilterTypes = null; private readonly IRepository _paymentMethodRepository; private readonly PaymentSettings _paymentSettings; @@ -722,11 +725,25 @@ public virtual string GetMaskedCreditCardNumber(string creditCardNumber) public virtual IList GetAllPaymentMethodFilters() { - return _typeFinder.FindClassesOfType(ignoreInactivePlugins: true) + if (_paymentMethodFilterTypes == null) + { + lock (_lock) + { + if (_paymentMethodFilterTypes == null) + { + _paymentMethodFilterTypes = _typeFinder.FindClassesOfType(ignoreInactivePlugins: true) + .ToList(); + } + } + } + + var paymentMethodFilters = _paymentMethodFilterTypes .Select(x => EngineContext.Current.ContainerManager.ResolveUnregistered(x) as IPaymentMethodFilter) .ToList(); + + return paymentMethodFilters; } - #endregion - } + #endregion + } } diff --git a/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs b/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs index 78d21c628b..6b35961b79 100644 --- a/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs +++ b/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs @@ -22,9 +22,12 @@ namespace SmartStore.Services.Shipping { public partial class ShippingService : IShippingService { - #region Fields + #region Fields - private readonly IRepository _shippingMethodRepository; + private readonly static object _lock = new object(); + private static IList _shippingMethodFilterTypes = null; + + private readonly IRepository _shippingMethodRepository; private readonly ICacheManager _cacheManager; private readonly ILogger _logger; private readonly IProductAttributeParser _productAttributeParser; @@ -461,10 +464,24 @@ public virtual GetShippingOptionResponse GetShippingOptions(IList GetAllShippingMethodFilters() { - return _typeFinder.FindClassesOfType(ignoreInactivePlugins: true) + if (_shippingMethodFilterTypes == null) + { + lock (_lock) + { + if (_shippingMethodFilterTypes == null) + { + _shippingMethodFilterTypes = _typeFinder.FindClassesOfType(ignoreInactivePlugins: true) + .ToList(); + } + } + } + + var shippingMethodFilters = _shippingMethodFilterTypes .Select(x => EngineContext.Current.ContainerManager.ResolveUnregistered(x) as IShippingMethodFilter) .ToList(); - } + + return shippingMethodFilters; + } #endregion From 96a50f743bea96947c3970300284e03fba81c862 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 17 Nov 2015 18:40:38 +0100 Subject: [PATCH 238/874] Export framework: minor refactoring --- .../SmartStore.Core/Data/IDbContext.cs | 20 ++++++--- .../Data/IDbContextExtensions.cs | 5 +++ .../SmartStore.Data/ObjectContextBase.cs | 41 +++++-------------- .../DataExchange/DataExporter.cs | 24 +++++------ .../DataExchange/DynamicEntityHelper.cs | 23 ++++++++--- 5 files changed, 59 insertions(+), 54 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Data/IDbContext.cs b/src/Libraries/SmartStore.Core/Data/IDbContext.cs index cec8a1a2ce..cb276a03ac 100644 --- a/src/Libraries/SmartStore.Core/Data/IDbContext.cs +++ b/src/Libraries/SmartStore.Core/Data/IDbContext.cs @@ -87,12 +87,20 @@ IList ExecuteStoredProcedureList(string commandText, params ob /// true when the entity is attched already, false otherwise bool IsAttached(TEntity entity) where TEntity : BaseEntity; - /// - /// Detaches an entity from the current object context if it's attached - /// - /// Type of entity - /// The entity instance to detach - void DetachEntity(TEntity entity) where TEntity : BaseEntity; + /// + /// Attaches an entity to the context or returns an already attached entity (if it was already attached) + /// + /// Type of entity + /// Entity + /// Attached entity + TEntity Attach(TEntity entity) where TEntity : BaseEntity; + + /// + /// Detaches an entity from the current object context if it's attached + /// + /// Type of entity + /// The entity instance to detach + void DetachEntity(TEntity entity) where TEntity : BaseEntity; /// /// Detaches all entities of type TEntity from the current object context diff --git a/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs b/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs index 0889cd6684..c4c1c82196 100644 --- a/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs +++ b/src/Libraries/SmartStore.Core/Data/IDbContextExtensions.cs @@ -167,5 +167,10 @@ public static void LoadReference( } } + public static void AttachRange(this IDbContext ctx, IEnumerable entities) where TEntity : BaseEntity + { + entities.Each(x => ctx.Attach(x)); + } + } } diff --git a/src/Libraries/SmartStore.Data/ObjectContextBase.cs b/src/Libraries/SmartStore.Data/ObjectContextBase.cs index 278d88d984..5db3d6b0ef 100644 --- a/src/Libraries/SmartStore.Data/ObjectContextBase.cs +++ b/src/Libraries/SmartStore.Data/ObjectContextBase.cs @@ -191,7 +191,7 @@ private IEnumerable ToParameters(params object[] parameters) { for (int i = 0; i < result.Count; i++) { - result[i] = AttachEntity(result[i]); + result[i] = Attach(result[i]); } } } @@ -224,7 +224,7 @@ private IEnumerable ToParameters(params object[] parameters) { for (int i = 0; i < result.Count; i++) { - result[i] = AttachEntity(result[i]); + result[i] = Attach(result[i]); } } // close up the reader, we're done saving results @@ -336,13 +336,14 @@ public IDictionary GetModifiedProperties(BaseEntity entity) // be aware of the entity state. you cannot get modified properties for detached entities. if (entry.State != System.Data.Entity.EntityState.Detached) { - var modifiedPropertyNames = from p in entry.CurrentValues.PropertyNames - where entry.Property(p).IsModified - select p; + var modifiedProperties = from p in entry.CurrentValues.PropertyNames + let prop = entry.Property(p) + where prop.IsModified + select prop; - foreach (var name in modifiedPropertyNames) + foreach (var prop in modifiedProperties) { - props.Add(name, entry.Property(name).OriginalValue); + props.Add(prop.Name, prop.OriginalValue); } } @@ -495,8 +496,9 @@ protected internal bool IsSqlServer2012OrHigher() return s_isSqlServer2012OrHigher.Value; } - private TEntity Attach(DbSet dbSet, TEntity entity) where TEntity : BaseEntity - { + public TEntity Attach(TEntity entity) where TEntity : BaseEntity + { + var dbSet = Set(); var alreadyAttached = dbSet.Local.FirstOrDefault(x => x.Id == entity.Id); if (alreadyAttached == null) @@ -508,27 +510,6 @@ private TEntity Attach(DbSet dbSet, TEntity entity) where TEnt return alreadyAttached; } - /// - /// Attach an entity to the context or return an already attached entity (if it was already attached) - /// - /// TEntity - /// Entity - /// Attached entity - public TEntity AttachEntity(TEntity entity) where TEntity : BaseEntity - { - // little hack here until Entity Framework really supports stored procedures - // otherwise, navigation properties of loaded entities are not loaded until an entity is attached to the context - - return Attach(Set(), entity); - } - - public void AttachEntities(IEnumerable entities) where TEntity : BaseEntity - { - var dbSet = Set(); - - entities.Each(entity => Attach(dbSet, entity)); - } - public bool IsAttached(TEntity entity) where TEntity : BaseEntity { if (entity != null) diff --git a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs index eb1a0d2306..5f7650cf10 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs @@ -211,11 +211,8 @@ private void DetachAllEntitiesAndClear(DataExporterContext ctx) try { // now again attach what is globally required - var dataContextBase = _dbContext as ObjectContextBase; - - dataContextBase.AttachEntity(ctx.Request.Profile); - - dataContextBase.AttachEntities(ctx.Stores.Values); + _dbContext.Attach(ctx.Request.Profile); + _dbContext.AttachRange(ctx.Stores.Values); } catch (Exception exception) { @@ -439,24 +436,27 @@ private void Deploy(DataExporterContext ctx) try { + IFilePublisher publisher = null; + if (deployment.DeploymentType == ExportDeploymentType.Email) { - var publisher = new EmailFilePublisher(_emailAccountService.Value, _queuedEmailService.Value); - publisher.Publish(context, deployment); + publisher = new EmailFilePublisher(_emailAccountService.Value, _queuedEmailService.Value); } else if (deployment.DeploymentType == ExportDeploymentType.FileSystem) { - var publisher = new FileSystemFilePublisher(); - publisher.Publish(context, deployment); + publisher = new FileSystemFilePublisher(); } else if (deployment.DeploymentType == ExportDeploymentType.Ftp) { - var publisher = new FtpFilePublisher(); - publisher.Publish(context, deployment); + publisher = new FtpFilePublisher(); } else if (deployment.DeploymentType == ExportDeploymentType.Http) { - var publisher = new HttpFilePublisher(); + publisher = new HttpFilePublisher(); + } + + if (publisher != null) + { publisher.Publish(context, deployment); } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs index f88aa7cda1..9e4940d4e7 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs @@ -540,7 +540,9 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product) return result; } - private dynamic ToDynamic(DataExporterContext ctx, Product product, + private dynamic ToDynamic( + DataExporterContext ctx, + Product product, ICollection combinations, ProductVariantAttributeCombination combination) { @@ -655,7 +657,7 @@ private dynamic ToDynamic(DataExporterContext ctx, Product product, .Select(x => ToDynamic(ctx, x)) .ToList(); - dynObject.ProductAttributeCombinations = combinations + dynObject.ProductAttributeCombinations = (combinations ?? Enumerable.Empty()) .Select(x => { dynamic dyn = ToDynamic(ctx, x); @@ -948,13 +950,20 @@ private List Convert(DataExporterContext ctx, Product product) { var result = new List(); - var combinations = ctx.ProductExportContext.AttributeCombinations.Load(product.Id); + var hasAttributeCombinations = false; - if (!ctx.IsPreview && ctx.Projection.AttributeCombinationAsProduct && combinations.Where(x => x.IsActive).Count() > 0) + if (!ctx.IsPreview && ctx.Projection.AttributeCombinationAsProduct) + { + hasAttributeCombinations = _dbContext.QueryForCollection(product, (Product p) => p.ProductVariantAttributeCombinations).Where(c => c.IsActive).Any(); + } + + if (hasAttributeCombinations) { var productType = typeof(Product); var productValues = new Dictionary(); + // TODO: work with Entry().CurrentValues instead of Reflection. + // First attach the object, determine all values and detach again. var properties = FastProperty.GetProperties(product.GetUnproxiedType()) .Values .Where(x => x.Property.CanRead && x.Property.CanWrite); @@ -965,7 +974,9 @@ private List Convert(DataExporterContext ctx, Product product) productValues.Add(property.Name, property.GetValue(product)); } - foreach (var combination in combinations.Where(x => x.IsActive)) + var combinations = ctx.ProductExportContext.AttributeCombinations.Load(product.Id); + + foreach (var combination in combinations) { var productClone = new Product(); @@ -980,7 +991,7 @@ private List Convert(DataExporterContext ctx, Product product) } else { - var dynObject = ToDynamic(ctx, product, combinations, null); + var dynObject = ToDynamic(ctx, product, null, null); result.Add(dynObject); } From fd08f0367ae431ed67e4776ec930a341450275a0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 18 Nov 2015 11:25:41 +0100 Subject: [PATCH 239/874] Changed folder for export profiles --- .gitignore | 1 + .../SmartStore.Services/DataExchange/ExportExtensions.cs | 8 ++++++-- .../DataExchange/Internal/DataExporterContext.cs | 3 +-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 3b997b1c64..460b0b95df 100644 --- a/.gitignore +++ b/.gitignore @@ -200,6 +200,7 @@ Kopie von* /build /src/Presentation/SmartStore.Web/Plugins /src/Presentation/SmartStore.Web/App_Data/_temp/ +/src/Presentation/SmartStore.Web/App_Data/ExportProfiles/ /src/Presentation/SmartStore.Web/App_Data/Settings.txt /src/Presentation/SmartStore.Web/App_Data/InstalledPlugins.txt /src/Presentation/SmartStore.Web/App_Data/Licenses.lic diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs index cf86f0d411..f46fb9094a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs @@ -42,9 +42,13 @@ public static string GetName(this Provider provider, ILocalizat /// /// Export profile /// Folder path - public static string GetExportFolder(this ExportProfile profile, bool content = false) + public static string GetExportFolder(this ExportProfile profile, bool content = false, bool create = false) { - var path = Path.Combine(FileSystemHelper.TempDir(), @"Profile\Export\{0}{1}".FormatInvariant(profile.FolderName, content ? @"\Content" : "")); + var path = CommonHelper.MapPath(string.Concat("~/App_Data/ExportProfiles/", profile.FolderName, content ? "/Content" : "")); + + if (create && !System.IO.Directory.Exists(path)) + System.IO.Directory.CreateDirectory(path); + return path; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs index 0d44c9843e..031406df3a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs @@ -10,7 +10,6 @@ using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Logging; -using SmartStore.Utilities; namespace SmartStore.Services.DataExchange.Internal { @@ -27,7 +26,7 @@ public DataExporterContext( Projection = XmlHelper.Deserialize(request.Profile.Projection); IsPreview = isPreview; - FolderContent = FileSystemHelper.TempDir(@"Profile\Export\{0}\Content".FormatInvariant(request.Profile.FolderName)); + FolderContent = request.Profile.GetExportFolder(true, true); FolderRoot = System.IO.Directory.GetParent(FolderContent).FullName; Categories = new Dictionary(); From 86b8d4485487debfd34c088fb581a6afbec9da3c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 18 Nov 2015 18:04:25 +0100 Subject: [PATCH 240/874] Added column with export file number in export profile list --- .../201511121139009_ExportFramework3.cs | 6 +- .../DataExchange/DataExporter.cs | 22 ++--- .../DataExchange/ExportExtensions.cs | 36 +++++++- .../Internal/DataExporterContext.cs | 15 ---- .../Controllers/ExportController.cs | 83 +++++++++++++++++-- .../Models/DataExchange/ExportProfileModel.cs | 20 +++++ .../Administration/SmartStore.Admin.csproj | 2 + .../Administration/Views/Export/Edit.cshtml | 2 +- .../Administration/Views/Export/List.cshtml | 53 ++++++++++-- .../Views/Export/ProfileFileCount.cshtml | 13 +++ .../Views/Export/ProfileFileDetails.cshtml | 23 +++++ .../Views/Export/_Tab.Deployment.cshtml | 1 - 12 files changed, 235 insertions(+), 41 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileCount.cshtml create mode 100644 src/Presentation/SmartStore.Web/Administration/Views/Export/ProfileFileDetails.cshtml diff --git a/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs b/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs index b3b39a74de..acf47b44eb 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201511121139009_ExportFramework3.cs @@ -197,7 +197,11 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.DataExchange.Export.MissingSystemProfile", "The system export profile {0} was not found.", "Das System-Exportprofil {0} wurde nicht gefunden."); - } + + builder.AddOrUpdate("Admin.DataExchange.Export.ExportFiles", + "Export files", + "Exportdateien"); + } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs index 5f7650cf10..25a0651cbd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs @@ -415,22 +415,21 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho return (ctx.ExecuteContext.Abort != ExportAbortion.Hard); } - private void Deploy(DataExporterContext ctx) + private void Deploy(DataExporterContext ctx, string zipPath) { - var containerManager = EngineContext.Current.ContainerManager; var allFiles = System.IO.Directory.GetFiles(ctx.FolderContent, "*.*", SearchOption.AllDirectories); var context = new ExportDeploymentContext { Log = ctx.Log, FolderContent = ctx.FolderContent, - ZipPath = ctx.ZipPath + ZipPath = zipPath }; foreach (var deployment in ctx.Request.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) { if (deployment.CreateZip) - context.DeploymentFiles = new string[] { ctx.ZipPath }; + context.DeploymentFiles = new string[] { zipPath }; else context.DeploymentFiles = allFiles; @@ -977,11 +976,14 @@ private void ExportCoreOuter(DataExporterContext ctx) if (ctx.Request.Profile == null || !ctx.Request.Profile.Enabled) return; - FileSystemHelper.Delete(ctx.LogPath); + var logPath = ctx.Request.Profile.GetExportLogPath(); + var zipPath = ctx.Request.Profile.GetExportZipPath(); + + FileSystemHelper.Delete(logPath); + FileSystemHelper.Delete(zipPath); FileSystemHelper.ClearDirectory(ctx.FolderContent, false); - FileSystemHelper.Delete(ctx.ZipPath); - using (var logger = new TraceLogger(ctx.LogPath)) + using (var logger = new TraceLogger(logPath)) { try { @@ -1053,14 +1055,14 @@ private void ExportCoreOuter(DataExporterContext ctx) { if (ctx.Request.Profile.CreateZipArchive || ctx.Request.Profile.Deployments.Any(x => x.Enabled && x.CreateZip)) { - ZipFile.CreateFromDirectory(ctx.FolderContent, ctx.ZipPath, CompressionLevel.Fastest, true); + ZipFile.CreateFromDirectory(ctx.FolderContent, zipPath, CompressionLevel.Fastest, true); } if (ctx.Request.Profile.Deployments.Any(x => x.Enabled)) { SetProgress(ctx, T("Common.Deployment")); - Deploy(ctx); + Deploy(ctx, zipPath); } } @@ -1127,7 +1129,7 @@ private void ExportCoreOuter(DataExporterContext ctx) // post process order entities if (ctx.EntityIdsLoaded.Count > 0 && ctx.Request.Provider.Value.EntityType == ExportEntityType.Order && ctx.Projection.OrderStatusChange != ExportOrderStatusChange.None) { - using (var logger = new TraceLogger(ctx.LogPath)) + using (var logger = new TraceLogger(logPath)) { try { diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs index f46fb9094a..4de3aadd7c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using System.Text; using SmartStore.Core.Domain; using SmartStore.Core.Domain.Stores; @@ -57,12 +59,40 @@ public static string GetExportFolder(this ExportProfile profile, bool content = /// /// Export profile /// Log file path - public static string GetExportLogFilePath(this ExportProfile profile) + public static string GetExportLogPath(this ExportProfile profile) { - var path = Path.Combine(profile.GetExportFolder(), "log.txt"); - return path; + return Path.Combine(profile.GetExportFolder(), "log.txt"); + } + + /// + /// Gets the ZIP path for a profile + /// + /// Export profile + /// ZIP file path + public static string GetExportZipPath(this ExportProfile profile) + { + return Path.Combine(profile.GetExportFolder(), profile.FolderName + ".zip"); } + /// + /// Gets existing export files for an export profile + /// + /// Export profile + /// List of file names + public static List GetExportFiles(this ExportProfile profile) + { + var exportFolder = profile.GetExportFolder(true); + + if (System.IO.Directory.Exists(exportFolder)) + { + return System.IO.Directory.EnumerateFiles(exportFolder, "*", SearchOption.TopDirectoryOnly) + .OrderBy(x => x) + .ToList(); + } + + return new List(); + } + /// /// Resolves the file name pattern for an export profile /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs index 031406df3a..0b09514a17 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading; using SmartStore.Core; @@ -27,7 +26,6 @@ public DataExporterContext( IsPreview = isPreview; FolderContent = request.Profile.GetExportFolder(true, true); - FolderRoot = System.IO.Directory.GetParent(FolderContent).FullName; Categories = new Dictionary(); CategoryPathes = new Dictionary(); @@ -87,20 +85,7 @@ public bool Supports(ExportFeatures feature) public TraceLogger Log { get; set; } public Store Store { get; set; } - public string FolderRoot { get; private set; } public string FolderContent { get; private set; } - public string ZipName - { - get { return Request.Profile.FolderName + ".zip"; } - } - public string ZipPath - { - get { return Path.Combine(FolderRoot, ZipName); } - } - public string LogPath - { - get { return Path.Combine(FolderRoot, "log.txt"); } - } public bool IsFileBasedExport { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 5fafb8d0df..aba647e6cf 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -16,12 +16,12 @@ using SmartStore.Core.Domain.Payments; using SmartStore.Core.Domain.Shipping; 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; -using SmartStore.Services.DataExchange.Internal; using SmartStore.Services.Directory; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; @@ -106,6 +106,20 @@ private string GetThumbnailUrl(Provider provider) return url; } + private ExportProfileDetailsModel PrepareProfileDetailsModel(ExportProfile profile) + { + var zipPath = profile.GetExportZipPath(); + + var model = new ExportProfileDetailsModel + { + Id = profile.Id, + ZipPath = (System.IO.File.Exists(zipPath) ? zipPath : null), + ExportFiles = profile.GetExportFiles() + }; + + return model; + } + private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile, Provider provider) { model.Id = profile.Id; @@ -120,13 +134,15 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile model.ScheduleTaskName = profile.ScheduleTask.Name.NaIfEmpty(); model.IsTaskRunning = profile.ScheduleTask.IsRunning; model.IsTaskEnabled = profile.ScheduleTask.Enabled; - model.LogFileExists = System.IO.File.Exists(profile.GetExportLogFilePath()); + model.LogFileExists = System.IO.File.Exists(profile.GetExportLogPath()); model.HasActiveProvider = (provider != null); model.FileNamePatternDescriptions = T("Admin.DataExchange.Export.FileNamePatternDescriptions").Text.SplitSafe(";"); model.Provider = new ExportProfileModel.ProviderModel(); model.Provider.ThumbnailUrl = GetThumbnailUrl(provider); + model.Details = PrepareProfileDetailsModel(profile); + if (provider != null) { var descriptor = provider.Metadata.PluginDescriptor; @@ -145,7 +161,7 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile model.Provider.EntityTypeName = provider.Value.EntityType.GetLocalizedEnum(_services.Localization, _services.WorkContext); model.Provider.FileExtension = provider.Value.FileExtension; } - } + } private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile profile, Provider provider) { @@ -478,6 +494,41 @@ public ActionResult List() return View(model); } + public ActionResult ProfileListDetails(int profileId) + { + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + { + var profile = _exportService.GetExportProfileById(profileId); + if (profile != null) + { + var model = PrepareProfileDetailsModel(profile); + + return Json(new + { + exportFileCount = this.RenderPartialViewToString("ProfileFileCount", model) + }, JsonRequestBehavior.AllowGet); + } + } + + return new EmptyResult(); + } + + public ActionResult ProfileFileDetails(int profileId) + { + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + { + var profile = _exportService.GetExportProfileById(profileId); + if (profile != null) + { + var model = PrepareProfileDetailsModel(profile); + + return PartialView(model); + } + } + + return new EmptyResult(); + } + public ActionResult Create() { if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) @@ -742,7 +793,7 @@ public ActionResult Preview(int id) GridPageSize = DataExporter.PageSize, EntityType = provider.Value.EntityType, TotalRecords = totalRecords, - LogFileExists = System.IO.File.Exists(profile.GetExportLogFilePath()), + LogFileExists = System.IO.File.Exists(profile.GetExportLogPath()), UsernamesEnabled = _customerSettings.Value.UsernamesEnabled }; @@ -979,7 +1030,7 @@ public ActionResult DownloadLogFile(int id) if (profile == null) return RedirectToAction("List"); - var path = profile.GetExportLogFilePath(); + var path = profile.GetExportLogPath(); var stream = new FileStream(path, FileMode.Open); var result = new FileStreamResult(stream, "text/plain; charset=utf-8"); @@ -988,6 +1039,28 @@ public ActionResult DownloadLogFile(int id) return result; } + public ActionResult DownloadExportFile(int id, string name) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + return AccessDeniedView(); + + var profile = _exportService.GetExportProfileById(id); + if (profile == null) + return RedirectToAction("List"); + + var path = Path.Combine(profile.GetExportFolder(true), name); + + if (!System.IO.File.Exists(path)) + path = Path.Combine(profile.GetExportFolder(false), name); + + var stream = new FileStream(path, FileMode.Open); + + var result = new FileStreamResult(stream, MimeTypes.MapNameToMimeType(path)); + result.FileDownloadName = Path.GetFileName(path); + + return result; + } + public ActionResult ResolveFileNamePatternExample(int id, string pattern) { var profile = _exportService.GetExportProfileById(id); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index 7ea59f0921..b41f8fc1f5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -95,6 +95,8 @@ public partial class ExportProfileModel : EntityModelBase public ScheduleTaskModel TaskModel { get; set; } + public ExportProfileDetailsModel Details { get; set; } + public class ProviderModel { public string ConfigPartialViewName { get; set; } @@ -152,4 +154,22 @@ public class ProviderSelectItem public string Description { get; set; } } } + + + public partial class ExportProfileDetailsModel : EntityModelBase + { + [SmartResourceDisplayName("Admin.DataExchange.Export.ExportFiles")] + public List ExportFiles { get; set; } + + public string ZipPath { get; set; } + + [SmartResourceDisplayName("Admin.DataExchange.Export.ExportFiles")] + public int ExportFileCount + { + get + { + return (ExportFiles.Count + (ZipPath.HasValue() ? 1 : 0)); + } + } + } } \ 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 5bc8e616ad..189b2f717d 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -535,6 +535,8 @@ + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index ecf22fd744..17a71d5ebc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -25,7 +25,7 @@ } @if (Model.LogFileExists) { - +  @T("Admin.Configuration.ActivityLog") } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 6448df05a3..c1e1a0beb3 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -27,11 +27,12 @@
    @T("Common.Image") @T("Admin.DataExchange.Export.Name")@T("Admin.DataExchange.Export.IsSystemProfile") @T("Common.Provider") @T("Common.Enabled") @T("Admin.DataExchange.Export.EntityType") @T("Admin.DataExchange.Export.FileExtension")@T("Admin.DataExchange.Export.IsSystemProfile")@T("Admin.DataExchange.Export.ExportFiles") @T("Admin.System.ScheduleTasks.LastStart") @T("Admin.System.ScheduleTasks.NextRun") @T("Admin.Common.Actions")
    @profile.Provider.FriendlyName + @Html.SymbolForBool(profile.IsSystemProfile) + @if (profile.Provider.ConfigurationUrl.HasValue()) { @@ -71,8 +75,8 @@ @Html.IconForFileExtension(profile.Provider.FileExtension, true) - @Html.SymbolForBool(profile.IsSystemProfile) + + @Html.Partial("ProfileFileCount", profile.Details)
    @@ -127,6 +131,17 @@ else
    + + -} - - - - +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml index 129406b9a5..9d9bacaa8a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml @@ -37,13 +37,13 @@ @@ -217,7 +217,7 @@
    \ No newline at end of file From 5a05d01dea5c2e8cc779617a573874168825b41d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 27 Nov 2015 14:17:19 +0100 Subject: [PATCH 269/874] Added manufacturer CSV export --- .../Controllers/ManufacturerController.cs | 19 +++--- .../Models/Catalog/ManufacturerListModel.cs | 5 +- .../Views/Manufacturer/List.cshtml | 61 +++++++++++++++++-- 3 files changed, 66 insertions(+), 19 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs index 6d40857243..1382fe86c0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs @@ -241,14 +241,12 @@ public ActionResult List() if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - var model = new ManufacturerListModel(); - var manufacturers = _manufacturerService.GetAllManufacturers(null, 0, _adminAreaSettings.GridPageSize, true); - model.Manufacturers = new GridModel - { - Data = manufacturers.Select(x => x.ToModel()), - Total = manufacturers.TotalCount - }; - return View(model); + var model = new ManufacturerListModel + { + GridPageSize = _adminAreaSettings.GridPageSize + }; + + return View(model); } [HttpPost, GridAction(EnableCustomBinding = true)] @@ -257,13 +255,14 @@ public ActionResult List(GridCommand command, ManufacturerListModel model) if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - var manufacturers = _manufacturerService.GetAllManufacturers(model.SearchManufacturerName, - command.Page - 1, command.PageSize, true); + var manufacturers = _manufacturerService.GetAllManufacturers(model.SearchManufacturerName, command.Page - 1, command.PageSize, true); + var gridModel = new GridModel { Data = manufacturers.Select(x => x.ToModel()), Total = manufacturers.TotalCount }; + return new JsonResult { Data = gridModel diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ManufacturerListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ManufacturerListModel.cs index 9fedd4f0bd..f683ac54c0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ManufacturerListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ManufacturerListModel.cs @@ -1,16 +1,15 @@ using System.Web.Mvc; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Mvc; -using Telerik.Web.Mvc; namespace SmartStore.Admin.Models.Catalog { - public class ManufacturerListModel : ModelBase + public class ManufacturerListModel : ModelBase { [SmartResourceDisplayName("Admin.Catalog.Manufacturers.List.SearchManufacturerName")] [AllowHtml] public string SearchManufacturerName { get; set; } - public GridModel Manufacturers { get; set; } + public int GridPageSize { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml index 4d867bfcad..c5f8fccc99 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml @@ -1,8 +1,6 @@ @model ManufacturerListModel @using Telerik.Web.Mvc.UI -@{ - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - +@{ ViewBag.Title = T("Admin.Catalog.Manufacturers").Text; }
    @@ -45,12 +43,19 @@
    - @(Html.Telerik().Grid(Model.Manufacturers.Data) + @(Html.Telerik().Grid() .Name("manufacturers-grid") .ClientEvents(events => events - .OnDataBinding("onDataBinding")) + .OnDataBinding("onDataBinding") + .OnDataBound("onDataBound")) .Columns(columns => { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .Width(50) + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); columns.Bound(x => x.Name) .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) .ClientTemplate("\"><#= Name #>"); @@ -68,7 +73,7 @@ .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) .Centered(); }) - .Pageable(settings => settings.Total(Model.Manufacturers.Total).PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Manufacturer")) .PreserveGridState() .EnableCustomBinding(true)) @@ -77,6 +82,9 @@
    \ No newline at end of file From 7233247becc5ecd01ea68fa7b64054832aa6af8d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 28 Nov 2015 11:12:22 +0100 Subject: [PATCH 270/874] Resolves #820 ConfigureTheme page contains German --- .../Migrations/201509150931528_ExportFramework2.cs | 4 ++++ .../SmartStore.Web/Views/Shared/ConfigureTheme.cshtml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs index c73e7769f1..d3fe755327 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509150931528_ExportFramework2.cs @@ -249,6 +249,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.System.SystemInfo.GarbageCollectSuccessful", "The memory has been successfully cleaned up.", "Der Arbeitsspeicher wurde erfolgreich aufgerumt."); + + builder.AddOrUpdate("Admin.Configuration.Themes.NoConfigurationRequired", + "Theme requires no configuration", + "Theme bentigt keine Konfiguration"); } } } diff --git a/src/Presentation/SmartStore.Web/Views/Shared/ConfigureTheme.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/ConfigureTheme.cshtml index ca0152ce09..a6e34081b9 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/ConfigureTheme.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/ConfigureTheme.cshtml @@ -2,4 +2,4 @@ Layout = null; } -
    Theme benötigt keine Konfiguration
    +
    @T("Admin.Configuration.Themes.NoConfigurationRequired")
    From fcc2608a7f946272da638d8c2331eb114d2f63e0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 30 Nov 2015 10:57:39 +0100 Subject: [PATCH 271/874] Further improvements in presenting download links of export files --- .../Logging/LoggingExtensions.cs | 12 ++- .../201511271019577_ExportFramework3.cs | 6 ++ .../DataExchange/DataExportResult.cs | 2 +- .../DataExchange/DataExporter.cs | 23 +++-- .../Controllers/ExportController.cs | 94 ++++++++++--------- .../DataExchange/ExportDeploymentModel.cs | 9 +- .../Models/DataExchange/ExportProfileModel.cs | 12 ++- .../Views/Export/InfoDeployment.cshtml | 59 +++++++----- .../Administration/Views/Export/List.cshtml | 2 +- .../Views/Export/ProfileFileCount.cshtml | 9 +- .../Views/Export/ProfileFileDetails.cshtml | 34 +++++-- .../Views/Export/_Tab.Deployment.cshtml | 7 ++ 12 files changed, 165 insertions(+), 104 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs b/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs index de1266efd4..f7c2633e40 100644 --- a/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs +++ b/src/Libraries/SmartStore.Core/Logging/LoggingExtensions.cs @@ -37,7 +37,17 @@ public static void Error(this ILogger logger, Exception exception, Customer cust FilteredLog(logger, LogLevel.Error, exception.ToAllMessages(), exception, customer); } - private static void FilteredLog(ILogger logger, LogLevel level, string message, Exception exception = null, Customer customer = null) + public static void ErrorsAll(this ILogger logger, Exception exception, Customer customer = null) + { + while (exception != null) + { + FilteredLog(logger, LogLevel.Error, exception.Message, exception, customer); + + exception = exception.InnerException; + } + } + + private static void FilteredLog(ILogger logger, LogLevel level, string message, Exception exception = null, Customer customer = null) { // don't log thread abort exception if ((exception != null) && (exception is System.Threading.ThreadAbortException)) diff --git a/src/Libraries/SmartStore.Data/Migrations/201511271019577_ExportFramework3.cs b/src/Libraries/SmartStore.Data/Migrations/201511271019577_ExportFramework3.cs index 42fe9dd04d..00ea19fba6 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201511271019577_ExportFramework3.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201511271019577_ExportFramework3.cs @@ -59,6 +59,12 @@ public void Seed(SmartObjectContext context) public void MigrateLocaleResources(LocaleResourcesBuilder builder) { builder.AddOrUpdate("Admin.Common.Export.PDF", "PDF Export", "PDF Export"); + builder.AddOrUpdate("Admin.Common.TemporaryFiles", "Temporary files", "Temporre Dateien"); + builder.AddOrUpdate("Admin.Common.PublicFiles", "Public files", "ffentliche Dateien"); + + builder.AddOrUpdate("Admin.Common.NoTempFilesFound", + "No temporary files were found.", + "Es wurden keine temporren Dateien gefunden."); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.DataExchange.ExportEntityType.NewsLetterSubscription", "Newsletter Subscribers", diff --git a/src/Libraries/SmartStore.Services/DataExchange/DataExportResult.cs b/src/Libraries/SmartStore.Services/DataExchange/DataExportResult.cs index cfbe0db7bf..d7e0d9fa6a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DataExportResult.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DataExportResult.cs @@ -29,7 +29,7 @@ public bool Succeeded /// /// Files created by last export /// - public IList Files { get; set; } + public List Files { get; set; } /// /// The path of the folder with the export files diff --git a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs index 528bc1f1b4..9193f38739 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs @@ -1135,7 +1135,7 @@ private void ExportCoreOuter(DataExporterContext ctx) } catch (Exception exception) { - logger.Error(exception); + logger.ErrorsAll(exception); ctx.Result.LastError = exception.ToString(); } finally @@ -1149,7 +1149,10 @@ private void ExportCoreOuter(DataExporterContext ctx) _exportProfileService.Value.UpdateExportProfile(ctx.Request.Profile); } } - catch { } + catch (Exception exception) + { + logger.ErrorsAll(exception); + } try { @@ -1158,7 +1161,10 @@ private void ExportCoreOuter(DataExporterContext ctx) FileSystemHelper.ClearDirectory(ctx.FolderContent, false); } } - catch { } + catch (Exception exception) + { + logger.ErrorsAll(exception); + } DetachAllEntitiesAndClear(ctx); @@ -1180,7 +1186,10 @@ private void ExportCoreOuter(DataExporterContext ctx) ctx.ExecuteContext.Log = null; ctx.Log = null; } - catch { } + catch (Exception exception) + { + logger.ErrorsAll(exception); + } } } @@ -1215,10 +1224,10 @@ private void ExportCoreOuter(DataExporterContext ctx) logger.Information("Updated order status for {0} order(s).".FormatInvariant(ctx.EntityIdsLoaded.Count())); } - catch (Exception exc) + catch (Exception exception) { - logger.Error(exc); - ctx.Result.LastError = exc.ToString(); + logger.ErrorsAll(exception); + ctx.Result.LastError = exception.ToString(); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 11d5681e86..b54951bcc0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -106,21 +106,56 @@ private string GetThumbnailUrl(Provider provider) return url; } - private ExportProfileDetailsModel PrepareProfileDetailsModel(ExportProfile profile) + private ExportProfileDetailsModel PrepareProfileDetailsModel(ExportProfile profile, bool forEdit) { - var zipPath = profile.GetExportZipPath(); - var model = new ExportProfileDetailsModel { Id = profile.Id, - ZipPath = (System.IO.File.Exists(zipPath) ? zipPath : null), - ExportFiles = profile.GetExportFiles() + PublicFiles = new List() }; + try + { + var zipPath = profile.GetExportZipPath(); + + model.ZipPath = (System.IO.File.Exists(zipPath) ? zipPath : null); + model.ExportFiles = profile.GetExportFiles(); + + if (forEdit && profile.Deployments.Any(x => x.IsPublic && x.DeploymentType == ExportDeploymentType.FileSystem)) + { + var allStores = _services.StoreService.GetAllStores(); + var publicFolder = Path.Combine(HttpRuntime.AppDomainAppPath, DataExporter.PublicFolder); + var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); + + if (resultInfo != null && resultInfo.Files != null) + { + foreach (var fileInfo 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(y => y.Id == fileInfo.StoreId) ?? _services.StoreContext.CurrentStore; + + model.PublicFiles.Add(new ExportProfileDetailsModel.PublicFile + { + StoreId = store.Id, + StoreName = store.Name, + FileName = fileInfo.FileName, + FileUrl = string.Concat(store.Url.EnsureEndsWith("/"), DataExporter.PublicFolder.EnsureEndsWith("/"), fileInfo.FileName) + }); + } + } + } + } + } + catch (Exception exception) + { + NotifyError(exception); + } + return model; } - private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile, Provider provider) + private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile, Provider provider, bool forEdit) { model.Id = profile.Id; model.Name = profile.Name; @@ -141,7 +176,7 @@ private void PrepareProfileModel(ExportProfileModel model, ExportProfile profile model.Provider = new ExportProfileModel.ProviderModel(); model.Provider.ThumbnailUrl = GetThumbnailUrl(provider); - model.Details = PrepareProfileDetailsModel(profile); + model.Details = PrepareProfileDetailsModel(profile, forEdit); if (provider != null) { @@ -266,37 +301,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile { var deploymentModel = PrepareDeploymentModel(x, null, false); - if (x.IsPublic) - { - try - { - var publicFolder = Path.Combine(HttpRuntime.AppDomainAppPath, DataExporter.PublicFolder); - var resultInfo = XmlHelper.Deserialize(profile.ResultInfo); - - if (resultInfo != null && resultInfo.Files != null) - { - foreach (var fileInfo in resultInfo.Files) - { - if (System.IO.File.Exists(Path.Combine(publicFolder, fileInfo.FileName)) && !deploymentModel.PublicFiles.Any(y => y.FileName == fileInfo.FileName)) - { - var store = allStores.FirstOrDefault(y => y.Id == fileInfo.StoreId) ?? _services.StoreContext.CurrentStore; - - deploymentModel.PublicFiles.Add(new ExportDeploymentModel.PublicFile - { - StoreId = store.Id, - StoreName = store.Name, - FileName = fileInfo.FileName, - FileUrl = string.Concat(store.Url.EnsureEndsWith("/"), DataExporter.PublicFolder.EnsureEndsWith("/"), fileInfo.FileName) - }); - } - } - } - } - catch (Exception exc) - { - exc.Dump(); - } - } + deploymentModel.ProfileDetails = PrepareProfileDetailsModel(profile, true); return deploymentModel; }) @@ -402,8 +407,7 @@ private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment EmailSubject = deployment.EmailSubject, EmailAccountId = deployment.EmailAccountId, PassiveMode = deployment.PassiveMode, - UseSsl = deployment.UseSsl, - PublicFiles = new List() + UseSsl = deployment.UseSsl }; if (forEdit) @@ -484,7 +488,7 @@ public ActionResult List() { var profileModel = new ExportProfileModel(); - PrepareProfileModel(profileModel, profile, providers.FirstOrDefault(x => x.Metadata.SystemName == profile.ProviderSystemName)); + PrepareProfileModel(profileModel, profile, providers.FirstOrDefault(x => x.Metadata.SystemName == profile.ProviderSystemName), false); profileModel.TaskModel = profile.ScheduleTask.ToScheduleTaskModel(_services.Localization, _dateTimeHelper, Url); @@ -501,7 +505,7 @@ public ActionResult ProfileListDetails(int profileId) var profile = _exportService.GetExportProfileById(profileId); if (profile != null) { - var model = PrepareProfileDetailsModel(profile); + var model = PrepareProfileDetailsModel(profile, false); return Json(new { @@ -520,7 +524,7 @@ public ActionResult ProfileFileDetails(int profileId) var profile = _exportService.GetExportProfileById(profileId); if (profile != null) { - var model = PrepareProfileDetailsModel(profile); + var model = PrepareProfileDetailsModel(profile, true); return PartialView(model); } @@ -610,7 +614,7 @@ public ActionResult Edit(int id) var model = new ExportProfileModel(); - PrepareProfileModel(model, profile, provider); + PrepareProfileModel(model, profile, provider, true); PrepareProfileModelForEdit(model, profile, provider); return View(model); @@ -631,7 +635,7 @@ public ActionResult Edit(ExportProfileModel model, bool continueEditing) if (!ModelState.IsValid) { - PrepareProfileModel(model, profile, provider); + PrepareProfileModel(model, profile, provider, true); PrepareProfileModelForEdit(model, profile, provider); 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 ffecae4f0a..6a0c317ab1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs @@ -55,7 +55,6 @@ public string DeploymentTypeIconClass [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.IsPublic")] public bool IsPublic { get; set; } - public List PublicFiles { get; set; } [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.Username")] public string Username { get; set; } @@ -91,12 +90,6 @@ public string DeploymentTypeIconClass [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.UseSsl")] public bool UseSsl { get; set; } - public class PublicFile - { - public int StoreId { get; set; } - public string StoreName { get; set; } - public string FileName { get; set; } - public string FileUrl { get; set; } - } + public ExportProfileDetailsModel ProfileDetails { get; set; } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs index b41f8fc1f5..a1a535a5f6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportProfileModel.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Admin.Models.Tasks; @@ -82,7 +81,6 @@ public partial class ExportProfileModel : EntityModelBase [SmartResourceDisplayName("Admin.DataExchange.Export.CloneProfile")] public int? CloneProfileId { get; set; } - //public List AvailableProfiles { get; set; } public List AvailableProfiles { get; set; } public ProviderModel Provider { get; set; } @@ -171,5 +169,15 @@ public int ExportFileCount return (ExportFiles.Count + (ZipPath.HasValue() ? 1 : 0)); } } + + public List PublicFiles { get; set; } + + public class PublicFile + { + public int StoreId { get; set; } + public string StoreName { get; set; } + public string FileName { get; set; } + public string FileUrl { get; set; } + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml index 67ce817d18..fce2ac18c7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml @@ -4,42 +4,51 @@ @{ Layout = null; } -

    +@helper FileDownloadLink(string path, string fileName) +{ +

    + @Html.IconForFileExtension(Path.GetExtension(fileName), false) + @fileName +
    +} +
     @Model.DeploymentTypeName @if (Model.DeploymentType == ExportDeploymentType.FileSystem && Model.IsPublic) { (@T("Common.Public")) } -

    -@if (Model.DeploymentType == ExportDeploymentType.FileSystem) -{ - if (Model.IsPublic) +
    +
    + @if (Model.DeploymentType == ExportDeploymentType.FileSystem) { - foreach (var grp in Model.PublicFiles.OrderBy(x => x.StoreId).GroupBy(x => x.StoreId)) + 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) + foreach (var file in grp) { - + } -

    + } } } - else + else if (Model.DeploymentType == ExportDeploymentType.Email) { -
    @Model.FileSystemPath
    +
    @Model.EmailAddresses
    } -} -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.Http) + { +
    @Model.Url
    + } + else if (Model.DeploymentType == ExportDeploymentType.Ftp) + { +
    @Model.Url
    + } +
    \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index c1e1a0beb3..9ddcf27347 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -131,7 +131,7 @@ else
    -
    @Html.EditorFor(x => x.CreateZipArchive) @Html.ValidationMessageFor(x => x.CreateZipArchive) + @if (Model.Details.ZipPath.HasValue()) + { +
    + @Html.IconForFileExtension(Path.GetExtension(Model.Details.ZipPath), false) + @Path.GetFileName(Model.Details.ZipPath) +
    + }
    + +
    @Html.SmartLabelFor(model => model.SearchStoreId) @@ -36,48 +36,45 @@
    -

    -

    + +

    +
    @(Html.Telerik().Grid() - .Name("topics-grid") - .Columns(columns => - { - columns.Bound(x => x.SystemName) - .Width(200) - .Template(x => Html.ActionLink(x.SystemName, "Edit", new { id = x.Id })) + .Name("topics-grid") + .Columns(columns => + { + columns.Bound(x => x.SystemName) + .Width(280) + .Template(x => Html.ActionLink(x.SystemName, "Edit", new { id = x.Id })) .ClientTemplate("<#= SystemName #>"); columns.Bound(x => x.Title); - columns.Bound(x => x.IsPasswordProtected) - .Width(100) - .Template(item => @Html.SymbolForBool(item.IsPasswordProtected)) - .ClientTemplate(@Html.SymbolForBool("IsPasswordProtected")) - .Centered(); - columns.Bound(x => x.IncludeInSitemap) - .Width(100) - .Template(item => @Html.SymbolForBool(item.IncludeInSitemap)) - .ClientTemplate(@Html.SymbolForBool("IncludeInSitemap")) - .Centered(); - columns.Bound(x => x.RenderAsWidget) - .Width(100) - .Template(item => @Html.SymbolForBool(item.RenderAsWidget)) - .ClientTemplate(@Html.SymbolForBool("RenderAsWidget")) - .Centered(); - columns.Bound(x => x.WidgetZone); - columns.Bound(x => x.Priority) + columns.Bound(x => x.IsPasswordProtected) + .Template(item => @Html.SymbolForBool(item.IsPasswordProtected)) + .ClientTemplate(@Html.SymbolForBool("IsPasswordProtected")) + .Centered(); + columns.Bound(x => x.IncludeInSitemap) + .Template(item => @Html.SymbolForBool(item.IncludeInSitemap)) + .ClientTemplate(@Html.SymbolForBool("IncludeInSitemap")) + .Centered(); + columns.Bound(x => x.RenderAsWidget) + .Template(item => @Html.SymbolForBool(item.RenderAsWidget)) + .ClientTemplate(@Html.SymbolForBool("RenderAsWidget")) + .Centered(); + columns.Bound(x => x.WidgetZone); + columns.Bound(x => x.LimitedToStores) + .Template(item => @Html.SymbolForBool(item.LimitedToStores)) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Hidden(Model.AvailableStores.Count <= 1) + .Centered(); + columns.Bound(x => x.Priority) .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); - }) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Topic")) + }) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "Topic")) .ClientEvents(events => events.OnDataBinding("onDataBinding")) - .EnableCustomBinding(true)) + .EnableCustomBinding(true))
    diff --git a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs index 839ca31595..600b06d52d 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs @@ -4,8 +4,8 @@ using System.Globalization; using System.Linq; using System.Text; -using System.Web; using System.Web.Mvc; +using SmartStore.Core.Domain; using SmartStore.Core.Domain.Blogs; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; @@ -41,15 +41,16 @@ using SmartStore.Web.Framework.UI; using SmartStore.Web.Infrastructure.Cache; using SmartStore.Web.Models.Common; -using SmartStore.Core.Domain; namespace SmartStore.Web.Controllers { - public partial class CommonController : PublicControllerBase + public partial class CommonController : PublicControllerBase { - #region Fields + private readonly static string[] s_hints = new string[] { "Shopsystem", "Onlineshop Software", "Shopsoftware", "E-Commerce Solution" }; - private readonly ITopicService _topicService; + #region Fields + + private readonly ITopicService _topicService; private readonly Lazy _languageService; private readonly Lazy _currencyService; private readonly IThemeContext _themeContext; @@ -59,8 +60,6 @@ public partial class CommonController : PublicControllerBase private readonly Lazy _mobileDeviceHelper; private readonly Lazy _compareProductsService; - private readonly static string[] s_hints = new string[] { "Shopsystem", "Onlineshop Software", "Shopsoftware", "E-Commerce Solution" }; - private readonly StoreInformationSettings _storeInfoSettings; private readonly CustomerSettings _customerSettings; private readonly TaxSettings _taxSettings; @@ -72,6 +71,7 @@ public partial class CommonController : PublicControllerBase private readonly ForumSettings _forumSettings; private readonly LocalizationSettings _localizationSettings; private readonly Lazy _securitySettings; + private readonly Lazy _socialSettings; private readonly IOrderTotalCalculationService _orderTotalCalculationService; private readonly IPriceFormatter _priceFormatter; @@ -104,7 +104,8 @@ public CommonController( ForumSettings forumSettings, LocalizationSettings localizationSettings, Lazy securitySettings, - IOrderTotalCalculationService orderTotalCalculationService, + Lazy socialSettings, + IOrderTotalCalculationService orderTotalCalculationService, IPriceFormatter priceFormatter, ThemeSettings themeSettings, IPageAssetsBuilder pageAssetsBuilder, @@ -131,6 +132,7 @@ public CommonController( this._forumSettings = forumSettings; this._localizationSettings = localizationSettings; this._securitySettings = securitySettings; + this._socialSettings = socialSettings; this._orderTotalCalculationService = orderTotalCalculationService; this._priceFormatter = priceFormatter; @@ -592,12 +594,11 @@ public ActionResult ShopBar() [ChildActionOnly] public ActionResult Footer() { - string taxInfo = (_services.WorkContext.GetTaxDisplayTypeFor(_services.WorkContext.CurrentCustomer, _services.StoreContext.CurrentStore.Id) == TaxDisplayType.IncludingTax) - ? T("Tax.InclVAT") - : T("Tax.ExclVAT"); - - string shippingInfoLink = Url.RouteUrl("Topic", new { SystemName = "shippinginfo" }); var store = _services.StoreContext.CurrentStore; + var allTopics = _topicService.GetAllTopics(store.Id); + var taxDisplayType = _services.WorkContext.GetTaxDisplayTypeFor(_services.WorkContext.CurrentCustomer, store.Id); + + var taxInfo = T(taxDisplayType == TaxDisplayType.IncludingTax ? "Tax.InclVAT" : "Tax.ExclVAT"); var availableStoreThemes = !_themeSettings.AllowCustomerToSelectTheme ? new List() : _themeRegistry.Value.GetThemeManifests() .Where(x => !x.MobileTheme) @@ -614,7 +615,6 @@ public ActionResult Footer() var model = new FooterModel { StoreName = store.Name, - LegalInfo = T("Tax.LegalInfoFooter", taxInfo, shippingInfoLink), ShowLegalInfo = _taxSettings.ShowLegalHintsInFooter, ShowThemeSelector = availableStoreThemes.Count > 1, BlogEnabled = _blogSettings.Enabled, @@ -622,6 +622,26 @@ public ActionResult Footer() HideNewsletterBlock = _customerSettings.HideNewsletterBlock, }; + model.TopicPageUrls = allTopics + .Where(x => !x.RenderAsWidget) + .GroupBy(x => x.SystemName) + .ToDictionary(x => x.Key.EmptyNull().ToLower(), x => + { + if (x.Key.IsCaseInsensitiveEqual("contactus")) + return Url.RouteUrl("ContactUs"); + + return Url.RouteUrl("Topic", new { SystemName = x.Key }); + }); + + if (model.TopicPageUrls.ContainsKey("shippinginfo")) + { + model.LegalInfo = T("Tax.LegalInfoFooter", taxInfo, model.TopicPageUrls["shippinginfo"]); + } + else + { + model.LegalInfo = T("Tax.LegalInfoFooter2", taxInfo); + } + var hint = _services.Settings.GetSettingByKey("Rnd_SmCopyrightHint", string.Empty, store.Id); if (hint.IsEmpty()) { @@ -629,30 +649,15 @@ public ActionResult Footer() _services.Settings.SetSetting("Rnd_SmCopyrightHint", hint, store.Id); } - var topics = new string[] { "paymentinfo", "imprint", "disclaimer" }; - foreach (var t in topics) - { - //load by store - var topic = _topicService.GetTopicBySystemName(t, store.Id); - if (topic == null) - //not found. let's find topic assigned to all stores - topic = _topicService.GetTopicBySystemName(t, 0); - - if (topic != null) - { - model.Topics.Add(t, topic.Title); - } - } + model.ShowSocialLinks = _socialSettings.Value.ShowSocialLinksInFooter; + model.FacebookLink = _socialSettings.Value.FacebookLink; + model.GooglePlusLink = _socialSettings.Value.GooglePlusLink; + model.TwitterLink = _socialSettings.Value.TwitterLink; + model.PinterestLink = _socialSettings.Value.PinterestLink; + model.YoutubeLink = _socialSettings.Value.YoutubeLink; - var socialSettings = EngineContext.Current.Resolve(); - - model.ShowSocialLinks = socialSettings.ShowSocialLinksInFooter; - model.FacebookLink = socialSettings.FacebookLink; - model.GooglePlusLink = socialSettings.GooglePlusLink; - model.TwitterLink = socialSettings.TwitterLink; - model.PinterestLink = socialSettings.PinterestLink; - model.YoutubeLink = socialSettings.YoutubeLink; - model.SmartStoreHint = "{0} by SmartStore AG © {1}".FormatCurrent(hint, DateTime.Now.Year); + model.SmartStoreHint = "{0} by SmartStore AG © {1}" + .FormatCurrent(hint, DateTime.Now.Year); return PartialView(model); } @@ -660,6 +665,7 @@ public ActionResult Footer() [ChildActionOnly] public ActionResult Menu() { + var store = _services.StoreContext.CurrentStore; var customer = _services.WorkContext.CurrentCustomer; var model = new MenuModel @@ -674,7 +680,8 @@ public ActionResult Menu() IsCustomerImpersonated = _services.WorkContext.OriginalCustomerIfImpersonated != null, IsAuthenticated = customer.IsRegistered(), DisplayAdminLink = _services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel), - }; + HasContactUsPage = (_topicService.GetTopicBySystemName("ContactUs", store.Id) != null) + }; return PartialView(model); } diff --git a/src/Presentation/SmartStore.Web/Models/Common/FooterModel.cs b/src/Presentation/SmartStore.Web/Models/Common/FooterModel.cs index 67aea71c20..7d9f2980a4 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/FooterModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/FooterModel.cs @@ -5,11 +5,6 @@ namespace SmartStore.Web.Models.Common { public partial class FooterModel : ModelBase { - public FooterModel() - { - Topics = new Dictionary(); - } - public string StoreName { get; set; } public string LegalInfo { get; set; } @@ -20,7 +15,6 @@ public FooterModel() public bool HideNewsletterBlock { get; set; } public bool BlogEnabled { get; set; } public bool ForumEnabled { get; set; } - public Dictionary Topics { get; set; } public bool ShowSocialLinks { get; set; } public string FacebookLink { get; set; } @@ -28,5 +22,7 @@ public FooterModel() public string TwitterLink { get; set; } public string PinterestLink { get; set; } public string YoutubeLink { get; set; } - } + + public Dictionary TopicPageUrls { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Models/Common/MenuModel.cs b/src/Presentation/SmartStore.Web/Models/Common/MenuModel.cs index d461318081..29bba0c11e 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/MenuModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/MenuModel.cs @@ -16,5 +16,7 @@ public partial class MenuModel : ModelBase public bool DisplayAdminLink { get; set; } public bool IsCustomerImpersonated { get; set; } public string CustomerEmailUsername { get; set; } - } + + public bool HasContactUsPage { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Views/Common/Footer.cshtml b/src/Presentation/SmartStore.Web/Views/Common/Footer.cshtml index 77b6c316da..cb9d4ab796 100644 --- a/src/Presentation/SmartStore.Web/Views/Common/Footer.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Common/Footer.cshtml @@ -52,28 +52,45 @@
    - @Html.LabelFor(model => model.ShippingOriginAddress, new { style="font-weight: bold" }) -
    + @Html.SmartLabelFor(model => model.ShippingOriginAddress) + @Html.SettingOverrideCheckbox(model => Model.ShippingOriginAddress, "#pnlShippingOriginAddress")
    @Html.EditorFor(model => model.ShippingOriginAddress, "Address") diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml index 2a4d51b0b4..34f62d4c96 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml @@ -1,6 +1,5 @@ @model ShoppingCartSettingsModel @{ - //page title ViewBag.Title = T("Admin.Configuration.Settings.ShoppingCart").Text; } @using (Html.BeginForm()) @@ -142,8 +141,8 @@
    +

    -
    -
    +
    +
    -
    -
    @Html.SmartLabelFor(model => model.ShowLegalHintsInProductList) @@ -182,8 +176,8 @@ @Html.ValidationMessageFor(model => model.ShowLegalHintsInFooter)
    +

    - @Html.LabelFor(model => model.DefaultTaxAddress, new { style="font-weight: bold" }) + @Html.SmartLabelFor(model => model.DefaultTaxAddress) @Html.SettingOverrideCheckbox(model => Model.DefaultTaxAddress, "#pnlDefaultTaxAddress")
    + @Html.EditorFor(x => x.DefaultTaxAddress, "Address")
    +

    +

    +

    } \ No newline at end of file From dd74f1328433631b91676934691606822164a58a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 4 Jan 2016 17:09:19 +0100 Subject: [PATCH 375/874] Permission list: Added label with permission category --- .../201506181858349_AclRecordCustomerRole.cs | 8 +-- .../201512151526290_ImportFramework.cs | 17 +++++ .../Security/PermissionService.cs | 16 +++-- .../Controllers/SecurityController.cs | 71 ++++++++++--------- .../Models/Security/PermissionRecordModel.cs | 22 +++++- .../Views/Security/Permissions.cshtml | 26 ++++--- 6 files changed, 105 insertions(+), 55 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201506181858349_AclRecordCustomerRole.cs b/src/Libraries/SmartStore.Data/Migrations/201506181858349_AclRecordCustomerRole.cs index 655737641f..d8c20d54d3 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201506181858349_AclRecordCustomerRole.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201506181858349_AclRecordCustomerRole.cs @@ -123,13 +123,13 @@ before you can assign them to all subcategories and products.
    builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowManufacturersOnHomepage", "Show manufacturers on homepage", "Zeige Hersteller auf der Homepage", - "Specifies whether manufacturers will be displayed on the homepage.", + "Specifies whether manufacturers are displayed on the homepage.", "Legt fest, ob Hersteller auf der Homepage angezeigt werden."); builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.ShowManufacturerPictures", "Show manufacturer pictures on homepage", "Zeige Herstellerbilder auf der Homepage", - "Specifies whether manufacturers will be displayed as images or textual links on the homepage.", + "Specifies whether manufacturers are displayed as images or textual links on the homepage.", "Legt fest, ob Hersteller auf der Homepage als Bilder oder textuelle Links angezeigt werden."); builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.CustomerNumberEnabled", @@ -145,8 +145,8 @@ before you can assign them to all subcategories and products.
    builder.AddOrUpdate("Admin.Configuration.Settings.Tax.VatRequired", "Customers must enter a VATIN", "Kunden mssen eine Steuernummer angeben", - "Specifies whether Customers must enter a VAT identification number.", - "Legt fest, ob Kunden bei der Registrierung eine Steuernummer angeben mssen."); + "Specifies whether customers must enter a VAT identification number.", + "Legt fest, ob Kunden bei der Registrierung eine Steuernummer angeben mssen."); builder.AddOrUpdate("Account.Fields.Vat.Required", "Please enter your VATIN", diff --git a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs index 2073f9aecc..1c6e87e649 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs @@ -278,6 +278,23 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Zeichenkette prfen", "Enter any string to check the SEO name creation. Changed settings must be saved before.", "Geben Sie eine beliebige Zeichenkette ein, um daraus den SEO Namen zu erstellen. Genderte Einstellungen mssen zuvor gespeichert werden."); + + + builder.AddOrUpdate("Admin.System.Warnings.NoPermissionsDefined", + "There are no permissions defined.", + "Es sind keine Zugriffsrechte festgelegt."); + + builder.AddOrUpdate("Admin.System.Warnings.NoCustomerRolesDefined", + "There are no customer roles defined.", + "Es sind keine Kundengruppen festgelegt."); + + builder.AddOrUpdate("Admin.System.Warnings.AccessDeniedToAnonymousRequest", + "Access denied to anonymous request on {0}.", + "Zugriffsverweigerung durch anonyme Anfrage bei {0}."); + + builder.AddOrUpdate("Admin.System.Warnings.AccessDeniedToUser", + "Access denied to user #{0} '{1}' on {2}.", + "Zugriffsverweigerung durch Kunde #{0} '{1}' bei {2}."); } } } diff --git a/src/Libraries/SmartStore.Services/Security/PermissionService.cs b/src/Libraries/SmartStore.Services/Security/PermissionService.cs index 3ea2815466..a3ada9721a 100644 --- a/src/Libraries/SmartStore.Services/Security/PermissionService.cs +++ b/src/Libraries/SmartStore.Services/Security/PermissionService.cs @@ -10,10 +10,10 @@ namespace SmartStore.Services.Security { - /// - /// Permission service - /// - public partial class PermissionService : IPermissionService + /// + /// Permission service + /// + public partial class PermissionService : IPermissionService { #region Constants /// @@ -141,9 +141,11 @@ orderby pr.Id /// Permissions public virtual IList GetAllPermissionRecords() { - var query = from pr in _permissionRecordRepository.Table - orderby pr.Name - select pr; + var query = + from pr in _permissionRecordRepository.Table + orderby pr.Category, pr.Name + select pr; + var permissions = query.ToList(); return permissions; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SecurityController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SecurityController.cs index 794c33e0f8..45b5aa901a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SecurityController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SecurityController.cs @@ -1,13 +1,11 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Customers; using SmartStore.Admin.Models.Security; using SmartStore.Core; -using SmartStore.Services.Customers; -using SmartStore.Services.Localization; using SmartStore.Core.Logging; +using SmartStore.Services.Customers; using SmartStore.Services.Security; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Security; @@ -22,7 +20,6 @@ public class SecurityController : AdminControllerBase private readonly IWorkContext _workContext; private readonly IPermissionService _permissionService; private readonly ICustomerService _customerService; - private readonly ILocalizationService _localizationService; #endregion @@ -30,12 +27,11 @@ public class SecurityController : AdminControllerBase public SecurityController(IWorkContext workContext, IPermissionService permissionService, - ICustomerService customerService, ILocalizationService localizationService) + ICustomerService customerService) { this._workContext = workContext; this._permissionService = permissionService; this._customerService = customerService; - this._localizationService = localizationService; } #endregion  @@ -45,14 +41,15 @@ public SecurityController(IWorkContext workContext, public ActionResult AccessDenied(string pageUrl) { var currentCustomer = _workContext.CurrentCustomer; + if (currentCustomer == null || currentCustomer.IsGuest()) { - Logger.Information(string.Format("Access denied to anonymous request on {0}", pageUrl)); + Logger.Information(T("Admin.System.Warnings.AccessDeniedToAnonymousRequest", pageUrl.NaIfEmpty())); return View(); } - Logger.Information(string.Format("Access denied to user #{0} '{1}' on {2}", currentCustomer.Email, currentCustomer.Email, pageUrl)); - + Logger.Information(T("Admin.System.Warnings.AccessDeniedToUser", + currentCustomer.Email.NaIfEmpty(), currentCustomer.Email.NaIfEmpty(), pageUrl.NaIfEmpty())); return View(); } @@ -66,30 +63,38 @@ public ActionResult Permissions() var permissionRecords = _permissionService.GetAllPermissionRecords(); var customerRoles = _customerService.GetAllCustomerRoles(true); + foreach (var pr in permissionRecords) { - model.AvailablePermissions.Add(new PermissionRecordModel() + model.AvailablePermissions.Add(new PermissionRecordModel { Name = pr.Name, - SystemName = pr.SystemName + SystemName = pr.SystemName, + Category = pr.Category }); } + foreach (var cr in customerRoles) { - model.AvailableCustomerRoles.Add(new CustomerRoleModel() + model.AvailableCustomerRoles.Add(new CustomerRoleModel { Id = cr.Id, Name = cr.Name }); } - foreach (var pr in permissionRecords) - foreach (var cr in customerRoles) - { - bool allowed = pr.CustomerRoles.Where(x => x.Id == cr.Id).ToList().Count() > 0; - if (!model.Allowed.ContainsKey(pr.SystemName)) - model.Allowed[pr.SystemName] = new Dictionary(); - model.Allowed[pr.SystemName][cr.Id] = allowed; - } + + foreach (var pr in permissionRecords) + { + foreach (var cr in customerRoles) + { + var allowed = pr.CustomerRoles.Any(x => x.Id == cr.Id); + + if (!model.Allowed.ContainsKey(pr.SystemName)) + model.Allowed[pr.SystemName] = new Dictionary(); + + model.Allowed[pr.SystemName][cr.Id] = allowed; + } + } return View(model); } @@ -103,38 +108,38 @@ public ActionResult PermissionsSave(FormCollection form) var permissionRecords = _permissionService.GetAllPermissionRecords(); var customerRoles = _customerService.GetAllCustomerRoles(true); - foreach (var cr in customerRoles) { - string formKey = "allow_" + cr.Id; - var permissionRecordSystemNamesToRestrict = form[formKey] != null ? form[formKey].Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList() : new List(); + var restrictedSystemNames = form["allow_" + cr.Id.ToString()].SplitSafe(",").ToList(); - foreach (var pr in permissionRecords) + foreach (var permission in permissionRecords) { + bool allow = restrictedSystemNames.Contains(permission.SystemName); - bool allow = permissionRecordSystemNamesToRestrict.Contains(pr.SystemName); if (allow) { - if (pr.CustomerRoles.Where(x => x.Id == cr.Id).FirstOrDefault() == null) + if (!permission.CustomerRoles.Any(x => x.Id == cr.Id)) { - pr.CustomerRoles.Add(cr); - _permissionService.UpdatePermissionRecord(pr); + permission.CustomerRoles.Add(cr); + _permissionService.UpdatePermissionRecord(permission); } } else { - if (pr.CustomerRoles.Where(x => x.Id == cr.Id).FirstOrDefault() != null) + if (permission.CustomerRoles.Any(x => x.Id == cr.Id)) { - pr.CustomerRoles.Remove(cr); - _permissionService.UpdatePermissionRecord(pr); + permission.CustomerRoles.Remove(cr); + _permissionService.UpdatePermissionRecord(permission); } } } } - NotifySuccess(_localizationService.GetResource("Admin.Configuration.ACL.Updated")); + NotifySuccess(T("Admin.Configuration.ACL.Updated")); + return RedirectToAction("Permissions"); } + #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Security/PermissionRecordModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Security/PermissionRecordModel.cs index 10840b978c..b444eaa04b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Security/PermissionRecordModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Security/PermissionRecordModel.cs @@ -6,5 +6,25 @@ public class PermissionRecordModel : ModelBase { public string Name { get; set; } public string SystemName { get; set; } - } + public string Category { get; set; } + + public string CategoryLabel + { + get + { + switch (Category) + { + case "PublicStore": + case "Standard": + return "label-success"; + + case "Plugin": + return ""; + + default: + return "label-info"; + } + } + } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Security/Permissions.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Security/Permissions.cshtml index 0d02bfd4b2..743c8f9d8f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Security/Permissions.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Security/Permissions.cshtml @@ -1,6 +1,6 @@ -@model PermissionMappingModel +@using SmartStore.Utilities; +@model PermissionMappingModel @{ - //page title ViewBag.Title = T("Admin.Configuration.ACL").Text; } @using (Html.BeginForm()) @@ -11,7 +11,9 @@ @T("Admin.Configuration.ACL")
    - +
    @@ -19,12 +21,16 @@
    @if (Model.AvailablePermissions.Count == 0) - { - No permissions defined - } - else if (Model.AvailableCustomerRoles.Count == 0) - { - No customer roles available + { +
    + @T("Admin.System.Warnings.NoPermissionsDefined") +
    + } + else if (Model.AvailableCustomerRoles.Count == 0) + { +
    + @T("Admin.System.Warnings.NoCustomerRolesDefined") +
    } else { @@ -48,6 +54,7 @@ {
    + @Inflector.Titleize(pr.Category).NaIfEmpty() @pr.Name
    - } From 8f8a8afb04b0a4d778f83bb8d6fb16871e4cdbca Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 4 Jan 2016 21:13:03 +0100 Subject: [PATCH 376/874] Resolves #26 Company or name in order list --- .../Controllers/OrderController.cs | 27 +- .../Models/Orders/OrderListModel.cs | 5 +- .../Models/Orders/OrderModel.cs | 3 + .../Administration/Views/Order/List.cshtml | 252 +++++++++--------- 4 files changed, 150 insertions(+), 137 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs index fd82849786..f7adf8147d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs @@ -89,8 +89,9 @@ public partial class OrderController : AdminControllerBase private readonly MeasureSettings _measureSettings; private readonly PdfSettings _pdfSettings; private readonly AddressSettings _addressSettings; + private readonly AdminAreaSettings _adminAreaSettings; - private readonly ICheckoutAttributeFormatter _checkoutAttributeFormatter; + private readonly ICheckoutAttributeFormatter _checkoutAttributeFormatter; private readonly IPdfConverter _pdfConverter; private readonly ICommonServices _services; private readonly Lazy _pictureService; @@ -124,7 +125,8 @@ public OrderController(IOrderService orderService, ICustomerActivityService customerActivityService, CatalogSettings catalogSettings, CurrencySettings currencySettings, TaxSettings taxSettings, MeasureSettings measureSettings, PdfSettings pdfSettings, AddressSettings addressSettings, - IPdfConverter pdfConverter, ICommonServices services, Lazy pictureService) + AdminAreaSettings adminAreaSettings, + IPdfConverter pdfConverter, ICommonServices services, Lazy pictureService) { this._orderService = orderService; this._orderReportService = orderReportService; @@ -167,6 +169,7 @@ public OrderController(IOrderService orderService, this._measureSettings = measureSettings; this._pdfSettings = pdfSettings; this._addressSettings = addressSettings; + this._adminAreaSettings = adminAreaSettings; this._checkoutAttributeFormatter = checkoutAttributeFormatter; _pdfConverter = pdfConverter; @@ -787,15 +790,20 @@ public ActionResult List(OrderListModel model) if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); - model.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); + var allStores = _storeService.GetAllStores(); + + model.AvailableOrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); model.AvailablePaymentStatuses = PaymentStatus.Pending.ToSelectList(false).ToList(); model.AvailableShippingStatuses = ShippingStatus.NotYetShipped.ToSelectList(false).ToList(); - //stores - model.AvailableStores.AddRange(_storeService.GetAllStores().ToList().ToSelectListItems()); - model.AvailableStores.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + model.AvailableStores = allStores + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); - return View(model); + model.GridPageSize = _adminAreaSettings.GridPageSize; + model.StoreCount = allStores.Count; + + return View(model); } [GridAction(EnableCustomBinding = true)] @@ -823,12 +831,13 @@ public ActionResult OrderList(GridCommand command, OrderListModel model) { Id = x.Id, OrderNumber = x.GetOrderNumber(), - StoreName = store != null ? store.Name : "Unknown", + StoreName = (store != null ? store.Name : "".NaIfEmpty()), OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), OrderStatus = x.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), PaymentStatus = x.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext), ShippingStatus = x.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext), - CustomerEmail = x.BillingAddress.Email, + CustomerName = x.BillingAddress.GetFullName(), + CustomerEmail = x.BillingAddress.Email, CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc), HasNewPaymentNotification = x.HasNewPaymentNotification }; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderListModel.cs index 2d64f5216c..8fc20246ad 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderListModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderListModel.cs @@ -54,9 +54,10 @@ public OrderListModel() [AllowHtml] public string GoDirectlyToNumber { get; set; } - + public int GridPageSize { get; set; } + public int StoreCount { get; set; } - public IList AvailableOrderStatuses { get; set; } + public IList AvailableOrderStatuses { get; set; } public IList AvailablePaymentStatuses { get; set; } public IList AvailableShippingStatuses { get; set; } public IList AvailableStores { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderModel.cs index 17c2b7be51..c622929029 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/OrderModel.cs @@ -37,10 +37,13 @@ public OrderModel() //customer info [SmartResourceDisplayName("Admin.Orders.Fields.Customer")] public int CustomerId { get; set; } + + [SmartResourceDisplayName("Admin.Orders.List.CustomerName")] public string CustomerName { get; set; } [SmartResourceDisplayName("Admin.Orders.Fields.CustomerEmail")] public string CustomerEmail { get; set; } + [SmartResourceDisplayName("Admin.Orders.Fields.CustomerIP")] public string CustomerIp { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml index 3e50f7232c..47e12fdb99 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml @@ -1,10 +1,10 @@ @model OrderListModel @using Telerik.Web.Mvc.UI @{ - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - var hideProfitReport = false; - - ViewBag.Title = T("Admin.Orders").Text; + var hideProfitReport = false; + var allString = T("Admin.Common.All").Text; + + ViewBag.Title = T("Admin.Orders").Text; } @using (Html.BeginForm()) @@ -39,39 +39,40 @@ @Html.Widget("admin_button_toolbar_after") - - - - - - - - - - - - - - - - - + +
    - @Html.SmartLabelFor(model => model.StartDate) - - @Html.EditorFor(model => model.StartDate) -
    - @Html.SmartLabelFor(model => model.EndDate) - - @Html.EditorFor(model => Model.EndDate) -
    - @Html.SmartLabelFor(model => model.CustomerName) - - @Html.EditorFor(model => Model.CustomerName) -
    - @Html.SmartLabelFor(model => model.CustomerEmail) - - @Html.EditorFor(model => Model.CustomerEmail) -
    + + + + + + + + + + + + + + + + - - - + + - - - - - - - - - + + - - - + + + + @if (Model.StoreCount > 1) + { + + + + + } - - - - - - + + + + + + + + + - - - - - -
    + @Html.SmartLabelFor(model => model.StartDate) + + @Html.EditorFor(model => model.StartDate) +
    + @Html.SmartLabelFor(model => model.EndDate) + + @Html.EditorFor(model => Model.EndDate) +
    + @Html.SmartLabelFor(model => model.CustomerName) + + @Html.EditorFor(model => Model.CustomerName) +
    + @Html.SmartLabelFor(model => model.CustomerEmail) + + @Html.EditorFor(model => Model.CustomerEmail) +
    @Html.SmartLabelFor(model => model.OrderStatusIds) @@ -80,69 +81,72 @@ @Html.DropDownList("OrderStatusIds", Model.AvailableOrderStatuses, null, new { multiple = "multiple" })
    - @Html.SmartLabelFor(model => model.PaymentStatusIds) - +
    + @Html.SmartLabelFor(model => model.PaymentStatusIds) + @Html.DropDownList("PaymentStatusIds", Model.AvailablePaymentStatuses, null, new { multiple = "multiple" }) -
    - @Html.SmartLabelFor(model => model.ShippingStatusIds) - - @Html.DropDownList("ShippingStatusIds", Model.AvailableShippingStatuses, null, new { multiple = "multiple" }) -
    - @Html.SmartLabelFor(model => model.StoreId) - - @Html.DropDownList("StoreId", Model.AvailableStores) -
    - @Html.SmartLabelFor(model => model.OrderGuid) - - @Html.EditorFor(model => Model.OrderGuid) -
    + @Html.SmartLabelFor(model => model.ShippingStatusIds) + + @Html.DropDownList("ShippingStatusIds", Model.AvailableShippingStatuses, null, new { multiple = "multiple" }) +
    + @Html.SmartLabelFor(model => model.StoreId) + + @Html.DropDownListFor(model => model.StoreId, Model.AvailableStores, allString) +
    - @Html.SmartLabelFor(model => model.OrderNumber) - - @Html.EditorFor(model => Model.OrderNumber) -
    - @Html.SmartLabelFor(model => model.GoDirectlyToNumber) - - @Html.EditorFor(model => Model.GoDirectlyToNumber) - + @Html.SmartLabelFor(model => model.OrderGuid) + + @Html.EditorFor(model => Model.OrderGuid) +
    + @Html.SmartLabelFor(model => model.OrderNumber) + + @Html.EditorFor(model => Model.OrderNumber) +
    + @Html.SmartLabelFor(model => model.GoDirectlyToNumber) + + @Html.EditorFor(model => Model.GoDirectlyToNumber) + -
    -   - - -
    + + + + +   + + + + + +

    @@ -150,29 +154,31 @@ @(Html.Telerik().Grid() - .Name("orders-grid") - .ClientEvents(events => events - .OnDataBinding("onDataBinding") - .OnDataBound("onDataBound") - .OnComplete("onComplete")) - .Columns(columns => - { - columns.Bound(x => x.Id) - .ClientTemplate("") - .Title("") - .Width(50) - .HtmlAttributes(new { style = "text-align:center" }) - .HeaderHtmlAttributes(new { style = "text-align:center" }); + .Name("orders-grid") + .ClientEvents(events => events + .OnDataBinding("onDataBinding") + .OnDataBound("onDataBound") + .OnComplete("onComplete")) + .Columns(columns => + { + columns.Bound(x => x.Id) + .ClientTemplate("") + .Title("") + .Width(50) + .HtmlAttributes(new { style = "text-align:center" }) + .HeaderHtmlAttributes(new { style = "text-align:center" }); - columns.Bound(x => x.OrderNumber) + columns.Bound(x => x.OrderNumber) .ClientTemplate(@Html.LabeledOrderNumber()); - columns.Bound(x => x.OrderStatus); - columns.Bound(x => x.PaymentStatus); - columns.Bound(x => x.ShippingStatus); - columns.Bound(x => x.CustomerEmail); - columns.Bound(x => x.StoreName); - columns.Bound(x => x.CreatedOn); - columns.Bound(x => x.OrderTotal) + columns.Bound(x => x.OrderStatus); + columns.Bound(x => x.PaymentStatus); + columns.Bound(x => x.ShippingStatus); + columns.Bound(x => x.CustomerName); + columns.Bound(x => x.CustomerEmail); + columns.Bound(x => x.StoreName) + .Hidden(Model.StoreCount <= 1); + columns.Bound(x => x.CreatedOn); + columns.Bound(x => x.OrderTotal) .Width(180) .RightAlign() .FooterTemplate( @@ -184,17 +190,11 @@ (hideProfitReport ? "" : @T("Admin.Orders.Report.Profit").Text), @T("Admin.Orders.Report.Tax").Text, @T("Admin.Orders.Report.Total").Text)); - columns.Bound(x => x.Id) - .Template(x => Html.ActionLink(T("Admin.Common.View").Text, "Edit", new { id = x.Id })) - .ClientTemplate("\">" + T("Admin.Common.View").Text + "") - .Width(50) - .Centered() - .Title(T("Admin.Common.View").Text); - }) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("OrderList", "Order")) + }) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) + .DataBinding(dataBinding => dataBinding.Ajax().Select("OrderList", "Order")) .PreserveGridState() - .EnableCustomBinding(true)) + .EnableCustomBinding(true)) From 203765ac463601a49c5d41624186d4cbd1d744d4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 4 Jan 2016 21:18:41 +0100 Subject: [PATCH 377/874] Updated changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index ac7dd3701f..2decbbfa2f 100644 --- a/changelog.md +++ b/changelog.md @@ -65,6 +65,7 @@ * Simplified handling of SEO names * URLs are not converted to lower case anymore * Product grid sortable by name, price and created on +* #26 Display company or name in order list ### Bugfixes * #523 Redirecting to payment provider performed by core instead of plugin From 2d07edba69861ffb3ddaa25b73181f686b10e457 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 5 Jan 2016 12:33:42 +0100 Subject: [PATCH 378/874] Added inline editing of country grid. Also resolves #96 --- changelog.md | 1 + .../201512151526290_ImportFramework.cs | 4 + .../Stores/StoreMappingService.cs | 2 +- .../Controllers/SmartController.cs | 17 ++ .../Controllers/CountryController.cs | 189 ++++++++++-------- .../Models/Directory/CountryListModel.cs | 9 + .../Administration/SmartStore.Admin.csproj | 1 + .../Administration/Views/Country/List.cshtml | 112 ++++++----- 8 files changed, 203 insertions(+), 132 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Administration/Models/Directory/CountryListModel.cs diff --git a/changelog.md b/changelog.md index 2decbbfa2f..72610122db 100644 --- a/changelog.md +++ b/changelog.md @@ -66,6 +66,7 @@ * URLs are not converted to lower case anymore * Product grid sortable by name, price and created on * #26 Display company or name in order list +* Added inline editing of country grid ### Bugfixes * #523 Redirecting to payment provider performed by core instead of plugin diff --git a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs index 1c6e87e649..24f368ef69 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs @@ -295,6 +295,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.System.Warnings.AccessDeniedToUser", "Access denied to user #{0} '{1}' on {2}.", "Zugriffsverweigerung durch Kunde #{0} '{1}' bei {2}."); + + builder.AddOrUpdate("Admin.Configuration.Countries.CannotDeleteDueToAssociatedAddresses", + "The country cannaot be deleted because it has associated addresses.", + "Das Land kann nicht gelscht werden, weil ihm Adressen zugeordnet sind."); } } } diff --git a/src/Libraries/SmartStore.Services/Stores/StoreMappingService.cs b/src/Libraries/SmartStore.Services/Stores/StoreMappingService.cs index da282801ef..04ff4206ff 100644 --- a/src/Libraries/SmartStore.Services/Stores/StoreMappingService.cs +++ b/src/Libraries/SmartStore.Services/Stores/StoreMappingService.cs @@ -219,7 +219,7 @@ public virtual void UpdateStoreMapping(StoreMapping storeMapping) public virtual int[] GetStoresIdsWithAccess(T entity) where T : BaseEntity, IStoreMappingSupported { if (entity == null) - throw new ArgumentNullException("entity"); + return new int[0]; int entityId = entity.Id; string entityName = typeof(T).Name; diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs index 3b6cf51813..a5169e45c4 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs @@ -95,6 +95,23 @@ protected virtual void NotifyError(Exception exception, bool durable = true, boo Services.Notifier.Error(exception.ToAllMessages(), durable); } + /// + /// Pushes an error message to the notification queue that the access to a resource has been denied + /// + /// A value indicating whether a message should be persisted for the next request + /// A value indicating whether the message should be logged + protected virtual void NotifyAccessDenied(bool durable = true, bool log = false) + { + var message = T("Admin.AccessDenied.Description"); + + if (log) + { + Logger.Error(message, null, Services.WorkContext.CurrentCustomer); + } + + Services.Notifier.Error(message, durable); + } + protected virtual ActionResult RedirectToReferrer() { if (Request.UrlReferrer != null && Request.UrlReferrer.ToString().HasValue()) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CountryController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CountryController.cs index 17300117c2..bf67df0dae 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CountryController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CountryController.cs @@ -3,8 +3,8 @@ using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Directory; -using SmartStore.Core; using SmartStore.Core.Domain.Directory; +using SmartStore.Services; using SmartStore.Services.Common; using SmartStore.Services.Directory; using SmartStore.Services.Localization; @@ -24,37 +24,31 @@ public class CountryController : AdminControllerBase private readonly ICountryService _countryService; private readonly IStateProvinceService _stateProvinceService; - private readonly ILocalizationService _localizationService; private readonly IAddressService _addressService; - private readonly IPermissionService _permissionService; private readonly ILocalizedEntityService _localizedEntityService; private readonly ILanguageService _languageService; - private readonly IStoreService _storeService; private readonly IStoreMappingService _storeMappingService; + private readonly ICommonServices _services; - #endregion + #endregion #region Constructors - public CountryController(ICountryService countryService, + public CountryController(ICountryService countryService, IStateProvinceService stateProvinceService, - ILocalizationService localizationService, IAddressService addressService, - IPermissionService permissionService, ILocalizedEntityService localizedEntityService, ILanguageService languageService, - IStoreService storeService, - IStoreMappingService storeMappingService) + IStoreMappingService storeMappingService, + ICommonServices services) { _countryService = countryService; _stateProvinceService = stateProvinceService; - _localizationService = localizationService; _addressService = addressService; - _permissionService = permissionService; _localizedEntityService = localizedEntityService; _languageService = languageService; - _storeService = storeService; _storeMappingService = storeMappingService; + _services = services; } #endregion  @@ -66,10 +60,7 @@ private void UpdateLocales(Country country, CountryModel model) { foreach (var localized in model.Locales) { - _localizedEntityService.SaveLocalizedValue(country, - x => x.Name, - localized.Name, - localized.LanguageId); + _localizedEntityService.SaveLocalizedValue(country, x => x.Name, localized.Name, localized.LanguageId); } } @@ -78,10 +69,7 @@ private void UpdateLocales(StateProvince stateProvince, StateProvinceModel model { foreach (var localized in model.Locales) { - _localizedEntityService.SaveLocalizedValue(stateProvince, - x => x.Name, - localized.Name, - localized.LanguageId); + _localizedEntityService.SaveLocalizedValue(stateProvince, x => x.Name, localized.Name, localized.LanguageId); } } @@ -91,19 +79,14 @@ private void PrepareCountryModel(CountryModel model, Country country, bool exclu if (model == null) throw new ArgumentNullException("model"); - var allStores = _storeService.GetAllStores(); + var allStores = _services.StoreService.GetAllStores(); model.AvailableStores = allStores.Select(s => s.ToModel()).ToList(); if (!excludeProperties) { - if (country != null) - model.SelectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(country); - else - model.SelectedStoreIds = new int[0]; + model.SelectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(country); } - - ViewBag.StoreCount = allStores.Count; } #endregion @@ -117,40 +100,85 @@ public ActionResult Index() public ActionResult List() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); - var countries = _countryService.GetAllCountries(true); - var model = new GridModel - { - Data = countries.Select(x => x.ToModel()), - Total = countries.Count - }; - return View(model); + var allStores = _services.StoreService.GetAllStores(); + + var model = new CountryListModel + { + StoreCount = allStores.Count + }; + + return View(model); } [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult CountryList(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) - return AccessDeniedView(); + var model = new GridModel(); - var countries = _countryService.GetAllCountries(true); - var model = new GridModel - { - Data = countries.Select(x => x.ToModel()), - Total = countries.Count - }; + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) + { + var countries = _countryService.GetAllCountries(true); + + model.Data = countries.Select(x => x.ToModel()); + model.Total = countries.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { Data = model }; } - - public ActionResult Create() + + [GridAction(EnableCustomBinding = true)] + public ActionResult CountryUpdate(CountryModel model, GridCommand command) + { + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } + + var country = _countryService.GetCountryById(model.Id); + + country = model.ToEntity(country); + _countryService.UpdateCountry(country); + } + + return CountryList(command); + } + + [GridAction(EnableCustomBinding = true)] + public ActionResult CountryDelete(int id, GridCommand command) + { + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) + { + if (_addressService.GetAddressTotalByCountryId(id) > 0) + { + return Content(T("Admin.Configuration.Countries.CannotDeleteDueToAssociatedAddresses")); + } + + var country = _countryService.GetCountryById(id); + + _countryService.DeleteCountry(country); + } + + return CountryList(command); + } + + public ActionResult Create() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); var model = new CountryModel(); @@ -169,7 +197,7 @@ public ActionResult Create() [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(CountryModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); if (ModelState.IsValid) @@ -181,7 +209,7 @@ public ActionResult Create(CountryModel model, bool continueEditing) _storeMappingService.SaveStoreMappings(country, model.SelectedStoreIds); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Countries.Added")); + NotifySuccess(T("Admin.Configuration.Countries.Added")); return continueEditing ? RedirectToAction("Edit", new { id = country.Id }) : RedirectToAction("List"); } @@ -193,7 +221,7 @@ public ActionResult Create(CountryModel model, bool continueEditing) public ActionResult Edit(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); var country = _countryService.GetCountryById(id); @@ -215,7 +243,7 @@ public ActionResult Edit(int id) [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(CountryModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); var country = _countryService.GetCountryById(model.Id); @@ -231,7 +259,7 @@ public ActionResult Edit(CountryModel model, bool continueEditing) _storeMappingService.SaveStoreMappings(country, model.SelectedStoreIds); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Countries.Updated")); + NotifySuccess(T("Admin.Configuration.Countries.Updated")); return continueEditing ? RedirectToAction("Edit", new { id = country.Id }) : RedirectToAction("List"); } @@ -244,7 +272,7 @@ public ActionResult Edit(CountryModel model, bool continueEditing) [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); var country = _countryService.GetCountryById(id); @@ -254,11 +282,12 @@ public ActionResult DeleteConfirmed(int id) try { if (_addressService.GetAddressTotalByCountryId(country.Id) > 0) - throw new SmartException("The country can't be deleted. It has associated addresses"); + throw new SmartException(T("Admin.Configuration.Countries.CannotDeleteDueToAssociatedAddresses")); _countryService.DeleteCountry(country); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Countries.Deleted")); + NotifySuccess(T("Admin.Configuration.Countries.Deleted")); + return RedirectToAction("List"); } catch (Exception exc) @@ -268,7 +297,6 @@ public ActionResult DeleteConfirmed(int id) } } - #endregion #region States / provinces @@ -276,7 +304,7 @@ public ActionResult DeleteConfirmed(int id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult States(int countryId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); var states = _stateProvinceService.GetStateProvincesByCountryId(countryId, true) @@ -287,30 +315,33 @@ public ActionResult States(int countryId, GridCommand command) Data = states, Total = states.Count() }; + return new JsonResult { Data = model }; } - //create public ActionResult StateCreatePopup(int countryId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); - var model = new StateProvinceModel(); - model.CountryId = countryId; - //locales + var model = new StateProvinceModel + { + CountryId = countryId + }; + AddLocales(_languageService, model.Locales); + return View(model); } [HttpPost] public ActionResult StateCreatePopup(string btnId, string formId, StateProvinceModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); var country = _countryService.GetCountryById(model.CountryId); @@ -327,6 +358,7 @@ public ActionResult StateCreatePopup(string btnId, string formId, StateProvinceM ViewBag.RefreshPage = true; ViewBag.btnId = btnId; ViewBag.formId = formId; + return View(model); } @@ -337,16 +369,15 @@ public ActionResult StateCreatePopup(string btnId, string formId, StateProvinceM //edit public ActionResult StateEditPopup(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); var sp = _stateProvinceService.GetStateProvinceById(id); if (sp == null) - //No state found with the specified id return RedirectToAction("List"); var model = sp.ToModel(); - //locales + AddLocales(_languageService, model.Locales, (locale, languageId) => { locale.Name = sp.GetLocalized(x => x.Name, languageId, false, false); @@ -358,12 +389,11 @@ public ActionResult StateEditPopup(int id) [HttpPost] public ActionResult StateEditPopup(string btnId, string formId, StateProvinceModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); var sp = _stateProvinceService.GetStateProvinceById(model.Id); if (sp == null) - //No state found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) @@ -376,6 +406,7 @@ public ActionResult StateEditPopup(string btnId, string formId, StateProvinceMod ViewBag.RefreshPage = true; ViewBag.btnId = btnId; ViewBag.formId = formId; + return View(model); } @@ -386,7 +417,7 @@ public ActionResult StateEditPopup(string btnId, string formId, StateProvinceMod [GridAction(EnableCustomBinding = true)] public ActionResult StateDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCountries)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) return AccessDeniedView(); var state = _stateProvinceService.GetStateProvinceById(id); @@ -394,35 +425,31 @@ public ActionResult StateDelete(int id, GridCommand command) throw new ArgumentException("No state found with the specified id"); if (_addressService.GetAddressTotalByStateProvinceId(state.Id) > 0) - return Content(_localizationService.GetResource("Admin.Configuration.Countries.States.CantDeleteWithAddresses")); + return Content(T("Admin.Configuration.Countries.States.CantDeleteWithAddresses")); int countryId = state.CountryId; _stateProvinceService.DeleteStateProvince(state); - return States(countryId, command); } [AcceptVerbs(HttpVerbs.Get)] public ActionResult GetStatesByCountryId(string countryId, bool? addEmptyStateIfRequired, bool? addAsterisk) { - //permission validation is not required here - + // permission validation is not required here // This action method gets called via an ajax request - int cid = 0; - int.TryParse(countryId, out cid); - /*if (String.IsNullOrEmpty(countryId)) - throw new ArgumentNullException("countryId");*/ + var country = _countryService.GetCountryById(countryId.ToInt()); - var country = _countryService.GetCountryById(cid /* Convert.ToInt32(countryId) */); var states = country != null ? _stateProvinceService.GetStateProvincesByCountryId(country.Id, true).ToList() : new List(); - var result = (from s in states - select new { id = s.Id, name = s.Name }).ToList(); + var result = (from s in states select new { id = s.Id, name = s.Name }).ToList(); + if (addEmptyStateIfRequired.HasValue && addEmptyStateIfRequired.Value && result.Count == 0) - result.Insert(0, new { id = 0, name = _localizationService.GetResource("Admin.Address.OtherNonUS") }); + result.Insert(0, new { id = 0, name = T("Admin.Address.OtherNonUS").Text }); + if (addAsterisk.HasValue && addAsterisk.Value) result.Insert(0, new { id = 0, name = "*" }); + return Json(result, JsonRequestBehavior.AllowGet); } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Directory/CountryListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Directory/CountryListModel.cs new file mode 100644 index 0000000000..ece27d8805 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/Directory/CountryListModel.cs @@ -0,0 +1,9 @@ +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.Directory +{ + public class CountryListModel : ModelBase + { + public int StoreCount { get; set; } + } +} \ 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 8aa8fd5f3f..c7ca89c0f8 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -258,6 +258,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Country/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Country/List.cshtml index 5285bcd4d5..833e3badee 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Country/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Country/List.cshtml @@ -1,4 +1,4 @@ -@model Telerik.Web.Mvc.GridModel +@model CountryListModel @using Telerik.Web.Mvc.UI @{ ViewBag.Title = T("Admin.Configuration.Countries").Text; @@ -12,62 +12,74 @@  @T("Admin.Common.AddNew") +
    @(Html.Telerik().Grid() - .Name("countries-grid") - .BindTo(Model.Data) - .Columns(columns => - { - columns.Bound(x => x.Name) - .Width(250) - .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) - .ClientTemplate("<#= Name #>"); - columns.Bound(x => x.AllowsBilling) - .Width(50) - .Template(item => @Html.SymbolForBool(item.AllowsBilling)) - .ClientTemplate(@Html.SymbolForBool("AllowsBilling")) - .Centered(); - columns.Bound(x => x.AllowsShipping) - .Width(50) - .Template(item => @Html.SymbolForBool(item.AllowsShipping)) - .ClientTemplate(@Html.SymbolForBool("AllowsShipping")) - .Centered(); - columns.Bound(x => x.TwoLetterIsoCode) - .Width(50) - .Centered(); - columns.Bound(x => x.ThreeLetterIsoCode) - .Width(50) - .Centered(); - columns.Bound(x => x.NumericIsoCode) - .Width(50) - .Centered(); - columns.Bound(x => x.SubjectToVat) - .Width(50) - .Template(item => @Html.SymbolForBool(item.SubjectToVat)) - .ClientTemplate(@Html.SymbolForBool("SubjectToVat")) - .Centered(); - columns.Bound(x => x.NumberOfStates) - .Width(50) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Width(50) - .Centered(); + .Name("countries-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("Id"); + }) + .Editable(x => + { + x.Mode(GridEditMode.InLine); + }) + .Columns(columns => + { + columns.Bound(x => x.Name) + .ClientTemplate("<#= Name #>"); + columns.Bound(x => x.AllowsBilling) + .Width(50) + .ClientTemplate(@Html.SymbolForBool("AllowsBilling")) + .Centered(); + columns.Bound(x => x.AllowsShipping) + .ClientTemplate(@Html.SymbolForBool("AllowsShipping")) + .Centered(); + columns.Bound(x => x.TwoLetterIsoCode) + .Centered(); + columns.Bound(x => x.ThreeLetterIsoCode) + .Centered(); + columns.Bound(x => x.NumericIsoCode) + .Centered(); + columns.Bound(x => x.SubjectToVat) + .ClientTemplate(@Html.SymbolForBool("SubjectToVat")) + .Centered(); + columns.Bound(x => x.NumberOfStates) + .ReadOnly() + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Centered(); columns.Bound(x => x.LimitedToStores) - .Template(item => @Html.SymbolForBool(item.LimitedToStores)) .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) .Centered() - .Width(100) - .Hidden(ViewBag.StoreCount <= 1); - columns.Bound(x => x.Published) - .Width(50) - .Template(item => @Html.SymbolForBool(item.Published)) - .ClientTemplate(@Html.SymbolForBool("Published")) - .Centered(); - }) - .DataBinding(dataBinding => dataBinding.Ajax().Select("CountryList", "Country")) - .EnableCustomBinding(true)) + .ReadOnly() + .Hidden(Model.StoreCount <= 1); + columns.Bound(x => x.Published) + .ClientTemplate(@Html.SymbolForBool("Published")) + .Centered(); + columns.Command(commands => + { + commands.Edit().Localize(T); + commands.Delete().Localize(T); + }); + }) + .DataBinding(dataBinding => dataBinding.Ajax() + .Select("CountryList", "Country") + .Update("CountryUpdate", "Country") + .Delete("CountryDelete", "Country") + ) + .ClientEvents(x => x.OnError("CountryGrid_onError")) + .PreserveGridState() + .EnableCustomBinding(true))
    + + From 718323ce9f06773c37a7bbd5e486c0da43fd805f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 5 Jan 2016 14:03:26 +0100 Subject: [PATCH 379/874] Product import: Store mappings were not applied when inserting new records --- changelog.md | 1 + .../Catalog/Importer/ProductImporter.cs | 38 ++++++++++++++++--- .../Controllers/SmartController.cs | 3 +- .../Controllers/ImportController.cs | 2 +- .../Views/Import/_ColumnMappings.cshtml | 2 +- 5 files changed, 37 insertions(+), 9 deletions(-) diff --git a/changelog.md b/changelog.md index 72610122db..0ca5d818fc 100644 --- a/changelog.md +++ b/changelog.md @@ -84,6 +84,7 @@ * #805 Product filter is reset if 'product sorting' or 'view mode' or 'amount of displayed products per page' is changed * Hide link to a topic page if it is limited to stores * #829 Activity log: Searching by customer email out of function +* Product import: Store mappings were not applied when inserting new records ## SmartStore.NET 2.2.2 diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index b115a270eb..c4321349f6 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -367,6 +367,21 @@ private int ProcessSlugs(IImportExecuteContext context, ImportRow[] bat return _urlRecordRepository.Context.SaveChanges(); } + private void ProcessStoreMappings(IImportExecuteContext context, ImportRow[] batch) + { + foreach (var row in batch) + { + var limitedToStore = row.GetDataValue("LimitedToStores"); + + if (limitedToStore) + { + var storeIds = row.GetDataValue>("StoreIds"); + + _storeMappingService.SaveStoreMappings(row.Entity, storeIds == null ? new int[0] : storeIds.ToArray()); + } + } + } + private int ProcessProducts(IImportExecuteContext context, ImportRow[] batch, DateTime utcNow) { _productRepository.AutoCommitEnabled = true; @@ -497,12 +512,6 @@ private int ProcessProducts(IImportExecuteContext context, ImportRow[] row.SetProperty(context.Result, product, (x) => x.AvailableEndDateTimeUtc); row.SetProperty(context.Result, product, (x) => x.LimitedToStores); - var storeIds = row.GetDataValue>("StoreIds"); - if (storeIds != null && storeIds.Any()) - { - _storeMappingService.SaveStoreMappings(product, storeIds.ToArray()); - } - row.SetProperty(context.Result, product, (x) => x.CreatedOnUtc, utcNow); product.UpdatedOnUtc = utcNow; @@ -590,6 +599,23 @@ public void Execute(IImportExecuteContext context) } } + if (segmenter.HasColumn("LimitedToStores") && segmenter.HasColumn("StoreIds")) + { + try + { + _productRepository.Context.AutoDetectChangesEnabled = true; + ProcessStoreMappings(context, batch); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessStoreMappings"); + } + finally + { + _productRepository.Context.AutoDetectChangesEnabled = false; + } + } + // =========================================================================== // 3.) Import Localizations // =========================================================================== diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs index a5169e45c4..2debc94ede 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs @@ -1,5 +1,6 @@ using System; using System.Text; +using System.Web; using System.Web.Mvc; using SmartStore.Core.Localization; using SmartStore.Core.Logging; @@ -92,7 +93,7 @@ protected virtual void NotifyError(Exception exception, bool durable = true, boo LogException(exception); } - Services.Notifier.Error(exception.ToAllMessages(), durable); + Services.Notifier.Error(HttpUtility.HtmlEncode(exception.ToAllMessages()), durable); } /// diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index 9c2d6b565f..c56638c2a1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -125,7 +125,7 @@ private void PrepareColumnMappingModels(ImportProfile profile, ImportProfileMode } catch (Exception exception) { - NotifyError(exception); + NotifyError(exception, true, false); } } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml index 4dc8045232..9e15de2aef 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml @@ -1,6 +1,6 @@ @using SmartStore.Admin.Models.DataExchange; @model ImportProfileModel -@if (Model.ColumnMappings.Any()) +@if (Model.ColumnMappings != null && Model.ColumnMappings.Any()) {

     

    From 9966961092d437f46883a308a2feea4f3dd75f21 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 5 Jan 2016 14:08:19 +0100 Subject: [PATCH 380/874] Dito new category import --- .../Catalog/Importer/CategoryImporter.cs | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index e0e5a2bdf2..08e43b0448 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -178,6 +178,21 @@ private int ProcessPictures(IImportExecuteContext context, return num; } + private void ProcessStoreMappings(IImportExecuteContext context, ImportRow[] batch) + { + foreach (var row in batch) + { + var limitedToStore = row.GetDataValue("LimitedToStores"); + + if (limitedToStore) + { + var storeIds = row.GetDataValue>("StoreIds"); + + _storeMappingService.SaveStoreMappings(row.Entity, storeIds == null ? new int[0] : storeIds.ToArray()); + } + } + } + private int ProcessCategories(IImportExecuteContext context, ImportRow[] batch, DateTime utcNow, @@ -249,12 +264,6 @@ private int ProcessCategories(IImportExecuteContext context, category.CategoryTemplateId = templateId; } - var storeIds = row.GetDataValue>("StoreIds"); - if (storeIds != null && storeIds.Any()) - { - _storeMappingService.SaveStoreMappings(category, storeIds.ToArray()); - } - row.SetProperty(context.Result, category, (x) => x.CreatedOnUtc, utcNow); category.UpdatedOnUtc = utcNow; @@ -355,6 +364,24 @@ public void Execute(IImportExecuteContext context) } } + // process store mappings + if (segmenter.HasColumn("LimitedToStores") && segmenter.HasColumn("StoreIds")) + { + try + { + _categoryRepository.Context.AutoDetectChangesEnabled = true; + ProcessStoreMappings(context, batch); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessStoreMappings"); + } + finally + { + _categoryRepository.Context.AutoDetectChangesEnabled = false; + } + } + // process pictures if (srcToDestId.Any() && segmenter.HasColumn("PictureThumbPath")) { From ef6da8abe065e2b65b1740525ec20310503b8eb5 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 5 Jan 2016 18:11:26 +0100 Subject: [PATCH 381/874] Faulty permission handling in ajax grid actions (no message, infinite loading icon)(part 1) --- changelog.md | 1 + .../201512151526290_ImportFramework.cs | 6 +- .../Controllers/SmartController.cs | 2 +- .../Controllers/AffiliateController.cs | 121 ++++++------ .../Controllers/BlogController.cs | 135 +++++++------ .../Controllers/CampaignController.cs | 35 ++-- .../Controllers/CategoryController.cs | 168 +++++++++-------- .../CheckoutAttributeController.cs | 90 ++++----- .../Controllers/CommonController.cs | 177 +++++++++--------- .../Controllers/CountryController.cs | 43 +++-- .../Controllers/CurrencyController.cs | 25 ++- .../Controllers/CustomerController.cs | 130 +++++++------ .../Controllers/CustomerRoleController.cs | 25 ++- .../Controllers/DiscountController.cs | 87 ++++----- .../Controllers/EmailAccountController.cs | 34 ++-- .../Controllers/ExportController.cs | 12 +- .../Controllers/GiftCardController.cs | 106 ++++++----- 17 files changed, 664 insertions(+), 533 deletions(-) diff --git a/changelog.md b/changelog.md index 0ca5d818fc..2308726b85 100644 --- a/changelog.md +++ b/changelog.md @@ -85,6 +85,7 @@ * Hide link to a topic page if it is limited to stores * #829 Activity log: Searching by customer email out of function * Product import: Store mappings were not applied when inserting new records +* Faulty permission handling in ajax grid actions (no message, infinite loading icon) ## SmartStore.NET 2.2.2 diff --git a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs index 24f368ef69..7de4cc953b 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs @@ -297,8 +297,12 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Zugriffsverweigerung durch Kunde #{0} '{1}' bei {2}."); builder.AddOrUpdate("Admin.Configuration.Countries.CannotDeleteDueToAssociatedAddresses", - "The country cannaot be deleted because it has associated addresses.", + "The country cannot be deleted because it has associated addresses.", "Das Land kann nicht gelscht werden, weil ihm Adressen zugeordnet sind."); + + builder.AddOrUpdate("Admin.Configuration.Countries.States.CantDeleteWithAddresses", + "The state\\province cannot be deleted because it has associated addresses.", + "Das Bundesland\\Region kann nicht gelscht werden, weil ihm Adressen zugeordnet sind."); } } } diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs index 2debc94ede..b5db948b54 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs @@ -101,7 +101,7 @@ protected virtual void NotifyError(Exception exception, bool durable = true, boo ///
    /// A value indicating whether a message should be persisted for the next request /// A value indicating whether the message should be logged - protected virtual void NotifyAccessDenied(bool durable = true, bool log = false) + protected virtual void NotifyAccessDenied(bool durable = true, bool log = true) { var message = T("Admin.AccessDenied.Description"); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/AffiliateController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/AffiliateController.cs index e98628b496..cf1e2b3c87 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/AffiliateController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/AffiliateController.cs @@ -24,7 +24,7 @@ namespace SmartStore.Admin.Controllers { - [AdminAuthorize] + [AdminAuthorize] public partial class AffiliateController : AdminControllerBase { #region Fields @@ -156,23 +156,31 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) - return AccessDeniedView(); + var model = new GridModel(); - var affiliates = _affiliateService.GetAllAffiliates(true); - var gridModel = new GridModel - { - Data = affiliates.PagedForCommand(command).Select(x => - { - var m = new AffiliateModel(); - PrepareAffiliateModel(m, x, false); - return m; - }), - Total = affiliates.Count, - }; - return new JsonResult + if (_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) + { + var affiliates = _affiliateService.GetAllAffiliates(true); + + model.Data = affiliates.PagedForCommand(command).Select(x => + { + var m = new AffiliateModel(); + PrepareAffiliateModel(m, x, false); + return m; + }); + + model.Total = affiliates.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + + return new JsonResult { - Data = gridModel + Data = model }; } @@ -287,31 +295,34 @@ public ActionResult Delete(int id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult AffiliatedOrderList(int affiliateId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) - return AccessDeniedView(); + var model = new GridModel(); - var affiliate = _affiliateService.GetAffiliateById(affiliateId); - if (affiliate == null) - throw new ArgumentException("No affiliate found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) + { + var affiliate = _affiliateService.GetAffiliateById(affiliateId); + var orders = _orderService.GetAllOrders(affiliate.Id, command.Page - 1, command.PageSize); + + model.Data = orders.Select(order => + { + var orderModel = new AffiliateModel.AffiliatedOrderModel(); + orderModel.Id = order.Id; + orderModel.OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext); + orderModel.PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext); + orderModel.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext); + orderModel.OrderTotal = _priceFormatter.FormatPrice(order.OrderTotal, true, false); + orderModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc); + return orderModel; + }); + model.Total = orders.TotalCount; + } + else + { + model.Data = Enumerable.Empty(); - var orders = _orderService.GetAllOrders(affiliate.Id, command.Page - 1, command.PageSize); - var model = new GridModel - { - Data = orders.Select(order => - { - var orderModel = new AffiliateModel.AffiliatedOrderModel(); - orderModel.Id = order.Id; - orderModel.OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext); - orderModel.PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext); - orderModel.ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext); - orderModel.OrderTotal = _priceFormatter.FormatPrice(order.OrderTotal, true, false); - orderModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc); - return orderModel; - }), - Total = orders.TotalCount - }; + NotifyAccessDenied(); + } - return new JsonResult + return new JsonResult { Data = model }; @@ -320,19 +331,16 @@ public ActionResult AffiliatedOrderList(int affiliateId, GridCommand command) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult AffiliatedCustomerList(int affiliateId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) - return AccessDeniedView(); + var model = new GridModel(); - var affiliate = _affiliateService.GetAffiliateById(affiliateId); - if (affiliate == null) - throw new ArgumentException("No affiliate found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageAffiliates)) + { + var affiliate = _affiliateService.GetAffiliateById(affiliateId); + var customers = _customerService.GetAllCustomers(affiliate.Id, command.Page - 1, command.PageSize); - var customers = _customerService.GetAllCustomers(affiliate.Id, command.Page - 1, command.PageSize); - var model = new GridModel - { - Data = customers.Select(customer => - { - var customerModel = new AffiliateModel.AffiliatedCustomerModel + model.Data = customers.Select(customer => + { + var customerModel = new AffiliateModel.AffiliatedCustomerModel { Id = customer.Id, Email = customer.Email, @@ -340,16 +348,23 @@ public ActionResult AffiliatedCustomerList(int affiliateId, GridCommand command) FullName = customer.GetFullName() }; - return customerModel; - }), - Total = customers.TotalCount - }; + return customerModel; + }); + model.Total = customers.TotalCount; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { Data = model }; } + #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/BlogController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/BlogController.cs index 98d4a868c4..f0883628b1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/BlogController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/BlogController.cs @@ -105,29 +105,37 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageBlog)) - return AccessDeniedView(); + var model = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageBlog)) + { + var blogPosts = _blogService.GetAllBlogPosts(0, 0, null, null, command.Page - 1, command.PageSize, true); + + model.Data = blogPosts.Select(x => + { + var m = x.ToModel(); + if (x.StartDateUtc.HasValue) + m.StartDate = _dateTimeHelper.ConvertToUserTime(x.StartDateUtc.Value, DateTimeKind.Utc); + if (x.EndDateUtc.HasValue) + m.EndDate = _dateTimeHelper.ConvertToUserTime(x.EndDateUtc.Value, DateTimeKind.Utc); + m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); + m.LanguageName = x.Language.Name; + m.Comments = x.ApprovedCommentCount + x.NotApprovedCommentCount; + return m; + }); + + model.Total = blogPosts.TotalCount; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var blogPosts = _blogService.GetAllBlogPosts(0, 0, null, null, command.Page - 1, command.PageSize, true); - var gridModel = new GridModel - { - Data = blogPosts.Select(x => - { - var m = x.ToModel(); - if (x.StartDateUtc.HasValue) - m.StartDate = _dateTimeHelper.ConvertToUserTime(x.StartDateUtc.Value, DateTimeKind.Utc); - if (x.EndDateUtc.HasValue) - m.EndDate = _dateTimeHelper.ConvertToUserTime(x.EndDateUtc.Value, DateTimeKind.Utc); - m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - m.LanguageName = x.Language.Name; - m.Comments = x.ApprovedCommentCount + x.NotApprovedCommentCount; - return m; - }), - Total = blogPosts.TotalCount - }; return new JsonResult { - Data = gridModel + Data = model }; } @@ -269,71 +277,76 @@ public ActionResult Comments(int? filterByBlogPostId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult Comments(int? filterByBlogPostId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageBlog)) - return AccessDeniedView(); + var model = new GridModel(); - IList comments; - if (filterByBlogPostId.HasValue) - { - //filter comments by blog - var blogPost = _blogService.GetBlogPostById(filterByBlogPostId.Value); - comments = blogPost.BlogComments.OrderBy(bc => bc.CreatedOnUtc).ToList(); - } - else - { - //load all blog comments - comments = _customerContentService.GetAllCustomerContent(0, null); - } + if (_permissionService.Authorize(StandardPermissionProvider.ManageBlog)) + { + IList comments; + if (filterByBlogPostId.HasValue) + { + //filter comments by blog + var blogPost = _blogService.GetBlogPostById(filterByBlogPostId.Value); + comments = blogPost.BlogComments.OrderBy(bc => bc.CreatedOnUtc).ToList(); + } + else + { + //load all blog comments + comments = _customerContentService.GetAllCustomerContent(0, null); + } - var gridModel = new GridModel - { - Data = comments.PagedForCommand(command).Select(blogComment => - { - var commentModel = new BlogCommentModel(); + model.Data = comments.PagedForCommand(command).Select(blogComment => + { + var commentModel = new BlogCommentModel(); var customer = _customerService.GetCustomerById(blogComment.CustomerId); - commentModel.Id = blogComment.Id; - commentModel.BlogPostId = blogComment.BlogPostId; - commentModel.BlogPostTitle = blogComment.BlogPost.Title; - commentModel.CustomerId = blogComment.CustomerId; - commentModel.IpAddress = blogComment.IpAddress; - commentModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(blogComment.CreatedOnUtc, DateTimeKind.Utc); - commentModel.Comment = Core.Html.HtmlUtils.FormatText(blogComment.CommentText, false, true, false, false, false, false); + commentModel.Id = blogComment.Id; + commentModel.BlogPostId = blogComment.BlogPostId; + commentModel.BlogPostTitle = blogComment.BlogPost.Title; + commentModel.CustomerId = blogComment.CustomerId; + commentModel.IpAddress = blogComment.IpAddress; + commentModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(blogComment.CreatedOnUtc, DateTimeKind.Utc); + commentModel.Comment = Core.Html.HtmlUtils.FormatText(blogComment.CommentText, false, true, false, false, false, false); if (customer == null) commentModel.CustomerName = "".NaIfEmpty(); else commentModel.CustomerName = customer.GetFullName(); - return commentModel; - }), - Total = comments.Count, - }; + return commentModel; + }); + + model.Total = comments.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { - Data = gridModel + Data = model }; } [GridAction(EnableCustomBinding = true)] public ActionResult CommentDelete(int? filterByBlogPostId, int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageBlog)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageBlog)) + { + var comment = _customerContentService.GetCustomerContentById(id) as BlogComment; - var comment = _customerContentService.GetCustomerContentById(id) as BlogComment; - if (comment == null) - throw new ArgumentException("No comment found with the specified id"); + var blogPost = comment.BlogPost; + _customerContentService.DeleteCustomerContent(comment); - var blogPost = comment.BlogPost; - _customerContentService.DeleteCustomerContent(comment); - //update totals - _blogService.UpdateCommentTotals(blogPost); + //update totals + _blogService.UpdateCommentTotals(blogPost); + } return Comments(filterByBlogPostId, command); } - #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CampaignController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CampaignController.cs index 7b3b290fd9..51f11cd40c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CampaignController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CampaignController.cs @@ -88,24 +88,31 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCampaigns)) - return AccessDeniedView(); + var model = new GridModel(); - var campaigns = _campaignService.GetAllCampaigns(); - var gridModel = new GridModel - { - Data = campaigns.Select(x => - { - var model = x.ToModel(); - model.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - return model; - }), - Total = campaigns.Count - }; + if (_permissionService.Authorize(StandardPermissionProvider.ManageCampaigns)) + { + var campaigns = _campaignService.GetAllCampaigns(); + + model.Data = campaigns.Select(x => + { + var m = x.ToModel(); + m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); + return m; + }); + + model.Total = campaigns.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { - Data = gridModel + Data = model }; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs index c8b714dfab..05822a9c17 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs @@ -285,24 +285,30 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command, CategoryListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var categories = _categoryService.GetAllCategories(model.SearchCategoryName, command.Page - 1, command.PageSize, true, model.SearchAlias, true, false, model.SearchStoreId); - var mappedCategories = categories.ToDictionary(x => x.Id); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var categories = _categoryService.GetAllCategories(model.SearchCategoryName, command.Page - 1, command.PageSize, true, model.SearchAlias, true, false, model.SearchStoreId); + var mappedCategories = categories.ToDictionary(x => x.Id); - var gridModel = new GridModel - { - Data = categories.Select(x => - { - var categoryModel = x.ToModel(); - categoryModel.Breadcrumb = x.GetCategoryBreadCrumb(_categoryService, mappedCategories); - return categoryModel; - }), - Total = categories.TotalCount - }; + gridModel.Data = categories.Select(x => + { + var categoryModel = x.ToModel(); + categoryModel.Breadcrumb = x.GetCategoryBreadCrumb(_categoryService, mappedCategories); + return categoryModel; + }); + + gridModel.Total = categories.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); - return new JsonResult + NotifyAccessDenied(); + } + + return new JsonResult { Data = gridModel }; @@ -758,17 +764,16 @@ public ActionResult Delete(int id, string deleteType) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductList(GridCommand command, int categoryId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var productCategories = _categoryService.GetProductCategoriesByCategoryId(categoryId, command.Page - 1, command.PageSize, true); + var model = new GridModel(); - var products = _productService.GetProductsByIds(productCategories.Select(x => x.ProductId).ToArray()); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productCategories = _categoryService.GetProductCategoriesByCategoryId(categoryId, command.Page - 1, command.PageSize, true); - var model = new GridModel - { - Data = productCategories.Select(x => - { + var products = _productService.GetProductsByIds(productCategories.Select(x => x.ProductId).ToArray()); + + model.Data = productCategories.Select(x => + { var productModel = new CategoryModel.CategoryProductModel { Id = x.Id, @@ -788,13 +793,20 @@ public ActionResult ProductList(GridCommand command, int categoryId) productModel.ProductTypeLabelHint = product.ProductTypeLabelHint; productModel.Published = product.Published; } - + return productModel; - }), - Total = productCategories.TotalCount - }; + }); + + model.Total = productCategories.TotalCount; + } + else + { + model.Data = Enumerable.Empty(); - return new JsonResult + NotifyAccessDenied(); + } + + return new JsonResult { Data = model }; @@ -803,34 +815,32 @@ public ActionResult ProductList(GridCommand command, int categoryId) [GridAction(EnableCustomBinding = true)] public ActionResult ProductUpdate(GridCommand command, CategoryModel.CategoryProductModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productCategory = _categoryService.GetProductCategoryById(model.Id); - var productCategory = _categoryService.GetProductCategoryById(model.Id); - if (productCategory == null) - throw new ArgumentException("No product category mapping found with the specified id"); + productCategory.IsFeaturedProduct = model.IsFeaturedProduct; + productCategory.DisplayOrder = model.DisplayOrder1; - productCategory.IsFeaturedProduct = model.IsFeaturedProduct; - productCategory.DisplayOrder = model.DisplayOrder1; - _categoryService.UpdateProductCategory(productCategory); + _categoryService.UpdateProductCategory(productCategory); + } - return ProductList(command, productCategory.CategoryId); + return ProductList(command, model.Id); } [GridAction(EnableCustomBinding = true)] public ActionResult ProductDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productCategory = _categoryService.GetProductCategoryById(id); - var productCategory = _categoryService.GetProductCategoryById(id); - if (productCategory == null) - throw new ArgumentException("No product category mapping found with the specified id"); + var categoryId = productCategory.CategoryId; - var categoryId = productCategory.CategoryId; - _categoryService.DeleteProductCategory(productCategory); + _categoryService.DeleteProductCategory(productCategory); + } - return ProductList(command, categoryId); + return ProductList(command, id); } public ActionResult ProductAddPopup(int categoryId) @@ -838,7 +848,6 @@ public ActionResult ProductAddPopup(int categoryId) if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - var ctx = new ProductSearchContext(); ctx.LanguageId = _workContext.WorkingLanguage.Id; ctx.OrderBy = ProductSortingEnum.Position; @@ -864,16 +873,18 @@ public ActionResult ProductAddPopup(int categoryId) var mappedCategories = allCategories.ToDictionary(x => x.Id); foreach (var c in allCategories) { - model.AvailableCategories.Add(new SelectListItem() { Text = c.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = c.Id.ToString() }); + model.AvailableCategories.Add(new SelectListItem { Text = c.GetCategoryNameWithPrefix(_categoryService, mappedCategories), Value = c.Id.ToString() }); } - //manufacturers - foreach (var m in _manufacturerService.GetAllManufacturers(true)) - model.AvailableManufacturers.Add(new SelectListItem() { Text = m.Name, Value = m.Id.ToString() }); + //manufacturers + foreach (var m in _manufacturerService.GetAllManufacturers(true)) + { + model.AvailableManufacturers.Add(new SelectListItem { Text = m.Name, Value = m.Id.ToString() }); + } //product types model.AvailableProductTypes = ProductType.SimpleProduct.ToSelectList(false).ToList(); - model.AvailableProductTypes.Insert(0, new SelectListItem() { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); + model.AvailableProductTypes.Insert(0, new SelectListItem { Text = _localizationService.GetResource("Admin.Common.All"), Value = "0" }); return View(model); } @@ -881,35 +892,44 @@ public ActionResult ProductAddPopup(int categoryId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductAddPopupList(GridCommand command, CategoryModel.AddCategoryProductModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var gridModel = new GridModel(); - var ctx = new ProductSearchContext(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var ctx = new ProductSearchContext(); - if (model.SearchCategoryId > 0) - ctx.CategoryIds.Add(model.SearchCategoryId); + if (model.SearchCategoryId > 0) + ctx.CategoryIds.Add(model.SearchCategoryId); - ctx.ManufacturerId = model.SearchManufacturerId; - ctx.Keywords = model.SearchProductName; - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageIndex = command.Page - 1; - ctx.PageSize = command.PageSize; - ctx.ShowHidden = true; - ctx.ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null; + ctx.ManufacturerId = model.SearchManufacturerId; + ctx.Keywords = model.SearchProductName; + ctx.LanguageId = _workContext.WorkingLanguage.Id; + ctx.OrderBy = ProductSortingEnum.Position; + ctx.PageIndex = command.Page - 1; + ctx.PageSize = command.PageSize; + ctx.ShowHidden = true; + ctx.ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null; - var products = _productService.SearchProducts(ctx); - gridModel.Data = products.Select(x => + var products = _productService.SearchProducts(ctx); + + gridModel.Data = products.Select(x => + { + var productModel = x.ToModel(); + productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); + + return productModel; + }); + + gridModel.Total = products.TotalCount; + } + else { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); + gridModel.Data = Enumerable.Empty(); - return productModel; - }); + NotifyAccessDenied(); + } - gridModel.Total = products.TotalCount; - return new JsonResult + return new JsonResult { Data = gridModel }; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CheckoutAttributeController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CheckoutAttributeController.cs index 78170d9674..4ac32d8493 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CheckoutAttributeController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CheckoutAttributeController.cs @@ -104,7 +104,6 @@ private void PrepareCheckoutAttributeModel(CheckoutAttributeModel model, Checkou #region Checkout attributes - //list public ActionResult Index() { return RedirectToAction("List"); @@ -121,23 +120,31 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(true); - var gridModel = new GridModel - { - Data = checkoutAttributes.Select(x => - { - var caModel = x.ToModel(); - caModel.AttributeControlTypeName = x.AttributeControlType.GetLocalizedEnum(_services.Localization, _services.WorkContext); - return caModel; - }), - Total = checkoutAttributes.Count() - }; - return new JsonResult + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(true); + + model.Data = checkoutAttributes.Select(x => + { + var caModel = x.ToModel(); + caModel.AttributeControlTypeName = x.AttributeControlType.GetLocalizedEnum(_services.Localization, _services.WorkContext); + return caModel; + }); + + model.Total = checkoutAttributes.Count(); + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + + return new JsonResult { - Data = gridModel + Data = model }; } @@ -254,31 +261,29 @@ public ActionResult DeleteConfirmed(int id) #region Checkout attribute values - //list [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ValueList(int checkoutAttributeId, GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var values = _checkoutAttributeService.GetCheckoutAttributeValues(checkoutAttributeId); - var gridModel = new GridModel - { - Data = values.Select(x => - { - var model = x.ToModel(); - //locales - //AddLocales(_languageService, model.Locales, (locale, languageId) => - //{ - // locale.Name = x.GetLocalized(y => y.Name, languageId, false, false); - //}); - return model; - }), - Total = values.Count() - }; - return new JsonResult + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var values = _checkoutAttributeService.GetCheckoutAttributeValues(checkoutAttributeId); + + model.Data = values.Select(x => x.ToModel()); + + model.Total = values.Count(); + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + + return new JsonResult { - Data = gridModel + Data = model }; } @@ -384,22 +389,19 @@ public ActionResult ValueEditPopup(string btnId, string formId, CheckoutAttribut return View(model); } - //delete [GridAction(EnableCustomBinding = true)] public ActionResult ValueDelete(int valueId, int checkoutAttributeId, GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var cav = _checkoutAttributeService.GetCheckoutAttributeValueById(valueId); - var cav = _checkoutAttributeService.GetCheckoutAttributeValueById(valueId); - if (cav == null) - throw new ArgumentException("No checkout attribute value found with the specified id"); - _checkoutAttributeService.DeleteCheckoutAttributeValue(cav); + _checkoutAttributeService.DeleteCheckoutAttributeValue(cav); + } return ValueList(checkoutAttributeId, command); } - #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs index ce08f8803f..5127d27256 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs @@ -1034,35 +1034,43 @@ public ActionResult GenericAttributes(string entityName, int entityId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult GenericAttributesSelect(string entityName, int entityId, GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel)) - return AccessDeniedView(); + var model = new GridModel(); var storeId = _services.StoreContext.CurrentStore.Id; - ViewBag.StoreId = storeId; + ViewBag.StoreId = storeId; - var model = new List(); - if (entityName.HasValue() && entityId > 0) - { - var attributes = _genericAttributeService.GetAttributesForEntity(entityId, entityName); - var query = from attr in attributes - where attr.StoreId == storeId || attr.StoreId == 0 - select new GenericAttributeModel - { - Id = attr.Id, - EntityId = attr.EntityId, - EntityName = attr.KeyGroup, - Key = attr.Key, - Value = attr.Value - }; - model.AddRange(query); - } + if (_services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel)) + { + if (entityName.HasValue() && entityId > 0) + { + var attributes = _genericAttributeService.GetAttributesForEntity(entityId, entityName); + model.Data = attributes + .Where(x => x.StoreId == storeId || x.StoreId == 0) + .Select(x => new GenericAttributeModel + { + Id = x.Id, + EntityId = x.EntityId, + EntityName = x.KeyGroup, + Key = x.Key, + Value = x.Value + }) + .ToList(); + + model.Total = model.Data.Count(); + } + else + { + model.Data = Enumerable.Empty(); + } + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var result = new GridModel - { - Data = model, - Total = model.Count - }; return new JsonResult { Data = model @@ -1072,38 +1080,36 @@ public ActionResult GenericAttributesSelect(string entityName, int entityId, Gri [GridAction(EnableCustomBinding = true)] public ActionResult GenericAttributeAdd(GenericAttributeModel model, GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel)) - return AccessDeniedView(); + if (_services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel)) + { + model.Key = model.Key.TrimSafe(); + model.Value = model.Value.TrimSafe(); - model.Key = model.Key.TrimSafe(); - model.Value = model.Value.TrimSafe(); + if (!ModelState.IsValid) + { + var modelStateErrorMessages = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrorMessages.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - // display the first model error - var modelStateErrorMessages = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrorMessages.FirstOrDefault()); - } + var storeId = _services.StoreContext.CurrentStore.Id; - var storeId = _services.StoreContext.CurrentStore.Id; - - var attr = _genericAttributeService.GetAttribute(model.EntityName, model.EntityId, model.Key, storeId); - if (attr == null) - { - var ga = new GenericAttribute - { - StoreId = storeId, - KeyGroup = model.EntityName, - EntityId = model.EntityId, - Key = model.Key, - Value = model.Value - }; - _genericAttributeService.InsertAttribute(ga); - } - else - { - return Content(string.Format(_localizationService.GetResource("Admin.Common.GenericAttributes.NameAlreadyExists"), model.Key)); - } + var attr = _genericAttributeService.GetAttribute(model.EntityName, model.EntityId, model.Key, storeId); + if (attr == null) + { + _genericAttributeService.InsertAttribute(new GenericAttribute + { + StoreId = storeId, + KeyGroup = model.EntityName, + EntityId = model.EntityId, + Key = model.Key, + Value = model.Value + }); + } + else + { + return Content(T("Admin.Common.GenericAttributes.NameAlreadyExists", model.Key)); + } + } return GenericAttributesSelect(model.EntityName, model.EntityId, command); } @@ -1111,38 +1117,38 @@ public ActionResult GenericAttributeAdd(GenericAttributeModel model, GridCommand [GridAction(EnableCustomBinding = true)] public ActionResult GenericAttributeUpdate(GenericAttributeModel model, GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel)) - return AccessDeniedView(); + if (_services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel)) + { + model.Key = model.Key.TrimSafe(); + model.Value = model.Value.TrimSafe(); - model.Key = model.Key.TrimSafe(); - model.Value = model.Value.TrimSafe(); + if (!ModelState.IsValid) + { + var modelStateErrorMessages = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrorMessages.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - // display the first model error - var modelStateErrorMessages = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrorMessages.FirstOrDefault()); - } + var storeId = _services.StoreContext.CurrentStore.Id; - var storeId = _services.StoreContext.CurrentStore.Id; + var attr = _genericAttributeService.GetAttributeById(model.Id); + // if the key changed, ensure it isn't being used by another attribute + if (!attr.Key.IsCaseInsensitiveEqual(model.Key)) + { + var attr2 = _genericAttributeService.GetAttributesForEntity(model.EntityId, model.EntityName) + .Where(x => x.StoreId == storeId && x.Key.Equals(model.Key, StringComparison.InvariantCultureIgnoreCase)) + .FirstOrDefault(); - var attr = _genericAttributeService.GetAttributeById(model.Id); - // if the key changed, ensure it isn't being used by another attribute - if (!attr.Key.IsCaseInsensitiveEqual(model.Key)) - { - var attr2 = _genericAttributeService.GetAttributesForEntity(model.EntityId, model.EntityName) - .Where(x => x.StoreId == storeId && x.Key.Equals(model.Key, StringComparison.InvariantCultureIgnoreCase)) - .FirstOrDefault(); - if (attr2 != null && attr2.Id != attr.Id) - { - return Content(string.Format(_localizationService.GetResource("Admin.Common.GenericAttributes.NameAlreadyExists"), model.Key)); - } - } + if (attr2 != null && attr2.Id != attr.Id) + { + return Content(T("Admin.Common.GenericAttributes.NameAlreadyExists", model.Key)); + } + } - attr.Key = model.Key; - attr.Value = model.Value; + attr.Key = model.Key; + attr.Value = model.Value; - _genericAttributeService.UpdateAttribute(attr); + _genericAttributeService.UpdateAttribute(attr); + } return GenericAttributesSelect(model.EntityName, model.EntityId, command); } @@ -1150,17 +1156,12 @@ public ActionResult GenericAttributeUpdate(GenericAttributeModel model, GridComm [GridAction(EnableCustomBinding = true)] public ActionResult GenericAttributeDelete(int id, GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel)) - return AccessDeniedView(); - var attr = _genericAttributeService.GetAttributeById(id); - if (attr == null) - { - throw new System.Web.HttpException(404, "No resource found with the specified id"); - } - - _genericAttributeService.DeleteAttribute(attr); + if (_services.Permissions.Authorize(StandardPermissionProvider.AccessAdminPanel)) + { + _genericAttributeService.DeleteAttribute(attr); + } return GenericAttributesSelect(attr.KeyGroup, attr.EntityId, command); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CountryController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CountryController.cs index bf67df0dae..3a4d573b43 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CountryController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CountryController.cs @@ -304,17 +304,22 @@ public ActionResult DeleteConfirmed(int id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult States(int countryId, GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) - return AccessDeniedView(); + var model = new GridModel(); - var states = _stateProvinceService.GetStateProvincesByCountryId(countryId, true) - .Select(x => x.ToModel()); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) + { + var states = _stateProvinceService.GetStateProvincesByCountryId(countryId, true) + .Select(x => x.ToModel()); - var model = new GridModel - { - Data = states, - Total = states.Count() - }; + model.Data = states; + model.Total = states.Count(); + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -417,18 +422,18 @@ public ActionResult StateEditPopup(string btnId, string formId, StateProvinceMod [GridAction(EnableCustomBinding = true)] public ActionResult StateDelete(int id, GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) - return AccessDeniedView(); - - var state = _stateProvinceService.GetStateProvinceById(id); - if (state == null) - throw new ArgumentException("No state found with the specified id"); + var state = _stateProvinceService.GetStateProvinceById(id); + var countryId = state.CountryId; - if (_addressService.GetAddressTotalByStateProvinceId(state.Id) > 0) - return Content(T("Admin.Configuration.Countries.States.CantDeleteWithAddresses")); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCountries)) + { + if (_addressService.GetAddressTotalByStateProvinceId(state.Id) > 0) + { + return Content(T("Admin.Configuration.Countries.States.CantDeleteWithAddresses")); + } - int countryId = state.CountryId; - _stateProvinceService.DeleteStateProvince(state); + _stateProvinceService.DeleteStateProvince(state); + } return States(countryId, command); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CurrencyController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CurrencyController.cs index 181071a2d2..e20b59013c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CurrencyController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CurrencyController.cs @@ -239,18 +239,25 @@ public ActionResult Save(FormCollection formValues) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCurrencies)) - return AccessDeniedView(); + var model = new GridModel(); + + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCurrencies)) + { + var currencies = _currencyService.GetAllCurrencies(true); + + model.Data = currencies.Select(x => x.ToModel()); + model.Total = currencies.Count(); + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var currencies = _currencyService.GetAllCurrencies(true); - var gridModel = new GridModel - { - Data = currencies.Select(x => x.ToModel()), - Total = currencies.Count() - }; return new JsonResult { - Data = gridModel + Data = model }; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs index 892bbaf5c6..0a91c9860f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs @@ -396,28 +396,37 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult CustomerList(GridCommand command, CustomerListModel model, [ModelBinderAttribute(typeof(CommaSeparatedModelBinder))] int[] searchCustomerRoleIds) { - //we use own own binder for searchCustomerRoleIds property - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); + // we use own own binder for searchCustomerRoleIds property + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + { + var searchDayOfBirth = 0; + var searchMonthOfBirth = 0; + + if (!String.IsNullOrWhiteSpace(model.SearchDayOfBirth)) + searchDayOfBirth = Convert.ToInt32(model.SearchDayOfBirth); + + if (!String.IsNullOrWhiteSpace(model.SearchMonthOfBirth)) + searchMonthOfBirth = Convert.ToInt32(model.SearchMonthOfBirth); + + var customers = _customerService.GetAllCustomers(null, null, + searchCustomerRoleIds, model.SearchEmail, model.SearchUsername, + model.SearchFirstName, model.SearchLastName, + searchDayOfBirth, searchMonthOfBirth, + model.SearchCompany, model.SearchPhone, model.SearchZipPostalCode, + false, null, command.Page - 1, command.PageSize); + + gridModel.Data = customers.Select(PrepareCustomerModelForList); + gridModel.Total = customers.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var searchDayOfBirth = 0; - int searchMonthOfBirth = 0; - if (!String.IsNullOrWhiteSpace(model.SearchDayOfBirth)) - searchDayOfBirth = Convert.ToInt32(model.SearchDayOfBirth); - if (!String.IsNullOrWhiteSpace(model.SearchMonthOfBirth)) - searchMonthOfBirth = Convert.ToInt32(model.SearchMonthOfBirth); - - var customers = _customerService.GetAllCustomers(null, null, - searchCustomerRoleIds, model.SearchEmail, model.SearchUsername, - model.SearchFirstName, model.SearchLastName, - searchDayOfBirth, searchMonthOfBirth, - model.SearchCompany, model.SearchPhone, model.SearchZipPostalCode, - false, null, command.Page - 1, command.PageSize); - var gridModel = new GridModel - { - Data = customers.Select(PrepareCustomerModelForList), - Total = customers.TotalCount - }; return new JsonResult { Data = gridModel @@ -1519,17 +1528,15 @@ public ActionResult AddressEdit(CustomerAddressModel model) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult OrderList(int customerId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); + var model = new GridModel(); - var orders = _orderService.SearchOrders(0, customerId, - null, null, null, null, null, null, null, null, 0, int.MaxValue); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + { + var orders = _orderService.SearchOrders(0, customerId, null, null, null, null, null, null, null, null, 0, int.MaxValue); - var model = new GridModel - { - Data = orders.PagedForCommand(command) - .Select(order => - { + model.Data = orders.PagedForCommand(command) + .Select(order => + { var store = _storeService.GetStoreById(order.StoreId); var orderModel = new CustomerModel.OrderModel() { @@ -1538,13 +1545,20 @@ public ActionResult OrderList(int customerId, GridCommand command) PaymentStatus = order.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext), ShippingStatus = order.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext), OrderTotal = _priceFormatter.FormatPrice(order.OrderTotal, true, false), - StoreName = store != null ? store.Name : "Unknown", + StoreName = store != null ? store.Name : "".NaIfEmpty(), CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc), }; - return orderModel; - }), - Total = orders.Count - }; + return orderModel; + }); + + model.Total = orders.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -1596,9 +1610,8 @@ public ActionResult ReportBestCustomersByOrderTotalList(GridCommand command, Bes PaymentStatus? paymentStatus = model.PaymentStatusId > 0 ? (PaymentStatus?)(model.PaymentStatusId) : null; ShippingStatus? shippingStatus = model.ShippingStatusId > 0 ? (ShippingStatus?)(model.ShippingStatusId) : null; + var items = _customerReportService.GetBestCustomersReport(startDateValue, endDateValue, orderStatus, paymentStatus, shippingStatus, 1); - var items = _customerReportService.GetBestCustomersReport(startDateValue, endDateValue, - orderStatus, paymentStatus, shippingStatus, 1); var gridModel = new GridModel { Data = items.Select(x => @@ -1609,23 +1622,25 @@ public ActionResult ReportBestCustomersByOrderTotalList(GridCommand command, Bes OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), OrderCount = x.OrderCount, }; + var customer = _customerService.GetCustomerById(x.CustomerId); if (customer != null) { - m.CustomerName = customer.IsGuest() - ? _localizationService.GetResource("Admin.Customers.Guest") - : customer.Email; + m.CustomerName = customer.IsGuest() ? T("Admin.Customers.Guest").Text : customer.Email; } + return m; }), Total = items.Count }; + return new JsonResult { Data = gridModel }; } - [GridAction(EnableCustomBinding = true)] + + [GridAction(EnableCustomBinding = true)] public ActionResult ReportBestCustomersByNumberOfOrdersList(GridCommand command, BestCustomersReportModel model) { DateTime? startDateValue = (model.StartDate == null) ? null @@ -1638,26 +1653,25 @@ public ActionResult ReportBestCustomersByNumberOfOrdersList(GridCommand command, PaymentStatus? paymentStatus = model.PaymentStatusId > 0 ? (PaymentStatus?)(model.PaymentStatusId) : null; ShippingStatus? shippingStatus = model.ShippingStatusId > 0 ? (ShippingStatus?)(model.ShippingStatusId) : null; + var items = _customerReportService.GetBestCustomersReport(startDateValue, endDateValue, orderStatus, paymentStatus, shippingStatus, 2); - var items = _customerReportService.GetBestCustomersReport(startDateValue, endDateValue, - orderStatus, paymentStatus, shippingStatus, 2); var gridModel = new GridModel { Data = items.Select(x => { - var m = new BestCustomerReportLineModel() + var m = new BestCustomerReportLineModel { CustomerId = x.CustomerId, OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), OrderCount = x.OrderCount, }; + var customer = _customerService.GetCustomerById(x.CustomerId); if (customer != null) { - m.CustomerName = customer.IsGuest() - ? _localizationService.GetResource("Admin.Customers.Guest") - : customer.Email; + m.CustomerName = customer.IsGuest() ? T("Admin.Customers.Guest").Text : customer.Email; } + return m; }), Total = items.Count @@ -1674,15 +1688,18 @@ public ActionResult ReportRegisteredCustomers() var model = GetReportRegisteredCustomersModel(); return PartialView(model); } - [GridAction(EnableCustomBinding = true)] + + [GridAction(EnableCustomBinding = true)] public ActionResult ReportRegisteredCustomersList(GridCommand command) { var model = GetReportRegisteredCustomersModel(); - var gridModel = new GridModel + + var gridModel = new GridModel { Data = model, Total = model.Count }; + return new JsonResult { Data = gridModel @@ -1699,16 +1716,17 @@ public ActionResult GetCartList(int customerId, int cartTypeId) var customer = _customerService.GetCustomerById(customerId); var cart = customer.GetCartItems((ShoppingCartType)cartTypeId); - var gridModel = new GridModel() + var gridModel = new GridModel { Data = cart.Select(sci => { decimal taxRate; var store = _storeService.GetStoreById(sci.Item.StoreId); - var sciModel = new ShoppingCartItemModel() + + var sciModel = new ShoppingCartItemModel { Id = sci.Item.Id, - Store = store != null ? store.Name : "Unknown", + Store = store != null ? store.Name : "".NaIfEmpty(), ProductId = sci.Item.ProductId, Quantity = sci.Item.Quantity, ProductName = sci.Item.Product.Name, @@ -1722,6 +1740,7 @@ public ActionResult GetCartList(int customerId, int cartTypeId) }), Total = cart.Count }; + return new JsonResult { Data = gridModel @@ -1736,11 +1755,12 @@ public ActionResult GetCartList(int customerId, int cartTypeId) public JsonResult ListActivityLog(GridCommand command, int customerId) { var activityLog = _customerActivityService.GetAllActivities(null, null, customerId, 0, command.Page - 1, command.PageSize); + var gridModel = new GridModel { Data = activityLog.Select(x => { - var m = new CustomerModel.ActivityLogModel() + var m = new CustomerModel.ActivityLogModel { Id = x.Id, ActivityLogTypeName = x.ActivityLogType.Name, @@ -1748,11 +1768,11 @@ public JsonResult ListActivityLog(GridCommand command, int customerId) CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) }; return m; - }), Total = activityLog.TotalCount }; - return new JsonResult { Data = gridModel }; ; + + return new JsonResult { Data = gridModel }; } #endregion diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerRoleController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerRoleController.cs index 7cc7fa5575..6bb2d10b47 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerRoleController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerRoleController.cs @@ -102,18 +102,25 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomerRoles)) - return AccessDeniedView(); - - var customerRoles = _customerService.GetAllCustomerRoles(true); - var gridModel = new GridModel + var model = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCustomerRoles)) { - Data = customerRoles.Select(x => x.ToModel()), - Total = customerRoles.Count() - }; + var customerRoles = _customerService.GetAllCustomerRoles(true); + + model.Data = customerRoles.Select(x => x.ToModel()); + model.Total = customerRoles.Count(); + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { - Data = gridModel + Data = model }; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/DiscountController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/DiscountController.cs index 6e18b8ec06..c9ab6e4496 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/DiscountController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/DiscountController.cs @@ -138,7 +138,6 @@ private void PrepareDiscountModel(DiscountModel model, Discount discount) #region Discounts - //list public ActionResult Index() { return RedirectToAction("List"); @@ -161,22 +160,28 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) - return AccessDeniedView(); + var model = new GridModel(); + + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) + { + var discounts = _discountService.GetAllDiscounts(null, null, true); + + model.Data = discounts.Select(x => x.ToModel()); + model.Total = discounts.Count(); + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var discounts = _discountService.GetAllDiscounts(null, null, true); - var gridModel = new GridModel - { - Data = discounts.Select(x => x.ToModel()), - Total = discounts.Count() - }; return new JsonResult { - Data = gridModel + Data = model }; } - //create public ActionResult Create() { if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) @@ -212,7 +217,6 @@ public ActionResult Create(DiscountModel model, bool continueEditing) return View(model); } - //edit public ActionResult Edit(int id) { if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) @@ -281,7 +285,6 @@ public ActionResult Edit(DiscountModel model, bool continueEditing) return View(model); } - //delete [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { @@ -386,28 +389,31 @@ public ActionResult DeleteDiscountRequirement(int discountRequirementId, int dis [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult UsageHistoryList(int discountId, GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) - return AccessDeniedView(); + var model = new GridModel(); - var discount = _discountService.GetDiscountById(discountId); - if (discount == null) - throw new ArgumentException("No discount found with the specified id"); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) + { + var discount = _discountService.GetDiscountById(discountId); + + var discountHistories = _discountService.GetAllDiscountUsageHistory(discount.Id, null, command.Page - 1, command.PageSize); + + model.Data = discountHistories.Select(x => new DiscountModel.DiscountUsageHistoryModel + { + Id = x.Id, + DiscountId = x.DiscountId, + OrderId = x.OrderId, + CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) + }); + + model.Total = discountHistories.TotalCount; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var duh = _discountService.GetAllDiscountUsageHistory(discount.Id, null, command.Page - 1, command.PageSize); - var model = new GridModel - { - Data = duh.Select(x => - { - return new DiscountModel.DiscountUsageHistoryModel - { - Id = x.Id, - DiscountId = x.DiscountId, - OrderId = x.OrderId, - CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) - }; - }), - Total = duh.TotalCount - }; return new JsonResult { Data = model @@ -417,16 +423,13 @@ public ActionResult UsageHistoryList(int discountId, GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult UsageHistoryDelete(int discountId, int id, GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) - return AccessDeniedView(); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageDiscounts)) + { + var discountHistory = _discountService.GetDiscountUsageHistoryById(id); + + _discountService.DeleteDiscountUsageHistory(discountHistory); + } - var discount = _discountService.GetDiscountById(discountId); - if (discount == null) - throw new ArgumentException("No discount found with the specified id"); - - var duh = _discountService.GetDiscountUsageHistoryById(id); - if (duh != null) - _discountService.DeleteDiscountUsageHistory(duh); return UsageHistoryList(discountId, command); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/EmailAccountController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/EmailAccountController.cs index c883d5f266..2586a826cb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/EmailAccountController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/EmailAccountController.cs @@ -78,24 +78,32 @@ public ActionResult List(string id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageEmailAccounts)) - return AccessDeniedView(); + var model = new GridModel(); - var emailAccountModels = _emailAccountService.GetAllEmailAccounts() - .Select(x => x.ToModel()) - .ToList(); - foreach (var eam in emailAccountModels) - eam.IsDefaultEmailAccount = eam.Id == _emailAccountSettings.DefaultEmailAccountId; + if (_permissionService.Authorize(StandardPermissionProvider.ManageEmailAccounts)) + { + var emailAccountModels = _emailAccountService.GetAllEmailAccounts() + .Select(x => x.ToModel()) + .ToList(); - var gridModel = new GridModel - { - Data = emailAccountModels, - Total = emailAccountModels.Count() - }; + foreach (var eam in emailAccountModels) + { + eam.IsDefaultEmailAccount = eam.Id == _emailAccountSettings.DefaultEmailAccountId; + } + + model.Data = emailAccountModels; + model.Total = emailAccountModels.Count(); + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { - Data = gridModel + Data = model }; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 2e719959ec..39579caa57 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -823,11 +823,17 @@ public ActionResult Preview(int id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult PreviewList(GridCommand command, int id, int totalRecords) { + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + { + NotifyAccessDenied(); + + return new JsonResult { Data = Enumerable.Empty() }; + } + ExportProfile profile = null; Provider provider = null; - if (_services.Permissions.Authorize(StandardPermissionProvider.ManageExports) && - (profile = _exportService.GetExportProfileById(id)) != null && + if ((profile = _exportService.GetExportProfileById(id)) != null && (provider = _exportService.LoadProvider(profile.ProviderSystemName)) != null && !provider.Metadata.IsHidden) { @@ -984,7 +990,7 @@ public ActionResult PreviewList(GridCommand command, int id, int totalRecords) return new JsonResult { Data = gridData }; } - return new EmptyResult(); + return new JsonResult { Data = Enumerable.Empty() }; } [HttpPost] diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/GiftCardController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/GiftCardController.cs index ea3eff5cd1..f52756dd84 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/GiftCardController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/GiftCardController.cs @@ -94,27 +94,37 @@ public ActionResult List() [GridAction(EnableCustomBinding = true)] public ActionResult GiftCardList(GridCommand command, GiftCardListModel model) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageGiftCards)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageGiftCards)) + { + bool? isGiftCardActivated = null; + + if (model.ActivatedId == 1) + isGiftCardActivated = true; + else if (model.ActivatedId == 2) + isGiftCardActivated = false; + + var giftCards = _giftCardService.GetAllGiftCards(null, null, null, isGiftCardActivated, model.CouponCode); + + gridModel.Data = giftCards.PagedForCommand(command).Select(x => + { + var m = x.ToModel(); + m.RemainingAmountStr = _priceFormatter.FormatPrice(x.GetGiftCardRemainingAmount(), true, false); + m.AmountStr = _priceFormatter.FormatPrice(x.Amount, true, false); + m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); + return m; + }); + + gridModel.Total = giftCards.Count(); + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - bool? isGiftCardActivated = null; - if (model.ActivatedId == 1) - isGiftCardActivated = true; - else if (model.ActivatedId == 2) - isGiftCardActivated = false; - var giftCards = _giftCardService.GetAllGiftCards(null, null, null, isGiftCardActivated, model.CouponCode); - var gridModel = new GridModel - { - Data = giftCards.PagedForCommand(command).Select(x => - { - var m = x.ToModel(); - m.RemainingAmountStr = _priceFormatter.FormatPrice(x.GetGiftCardRemainingAmount(), true, false); - m.AmountStr = _priceFormatter.FormatPrice(x.Amount, true, false); - m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - return m; - }), - Total = giftCards.Count() - }; return new JsonResult { Data = gridModel @@ -274,46 +284,48 @@ public ActionResult DeleteConfirmed(int id) var giftCard = _giftCardService.GetGiftCardById(id); if (giftCard == null) - //No gift card found with the specified id return RedirectToAction("List"); _giftCardService.DeleteGiftCard(giftCard); - //activity log _customerActivityService.InsertActivity("DeleteGiftCard", _services.Localization.GetResource("ActivityLog.DeleteGiftCard"), giftCard.GiftCardCouponCode); NotifySuccess(_services.Localization.GetResource("Admin.GiftCards.Deleted")); return RedirectToAction("List"); } - //Gif card usage history [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult UsageHistoryList(int giftCardId, GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageGiftCards)) - return AccessDeniedView(); - - var giftCard = _giftCardService.GetGiftCardById(giftCardId); - if (giftCard == null) - throw new ArgumentException("No gift card found with the specified id"); - - var usageHistoryModel = giftCard.GiftCardUsageHistory.OrderByDescending(gcuh => gcuh.CreatedOnUtc) - .Select(x => - { - return new GiftCardModel.GiftCardUsageHistoryModel() - { - Id = x.Id, - OrderId = x.UsedWithOrderId, - UsedValue = _priceFormatter.FormatPrice(x.UsedValue, true, false), - CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) - }; - }) - .ToList(); - var model = new GridModel - { - Data = usageHistoryModel.PagedForCommand(command), - Total = usageHistoryModel.Count - }; + var model = new GridModel(); + + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageGiftCards)) + { + var giftCard = _giftCardService.GetGiftCardById(giftCardId); + + var usageHistoryModel = giftCard.GiftCardUsageHistory + .OrderByDescending(gcuh => gcuh.CreatedOnUtc) + .Select(x => + { + return new GiftCardModel.GiftCardUsageHistoryModel() + { + Id = x.Id, + OrderId = x.UsedWithOrderId, + UsedValue = _priceFormatter.FormatPrice(x.UsedValue, true, false), + CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc) + }; + }) + .ToList(); + + model.Data = usageHistoryModel.PagedForCommand(command); + model.Total = usageHistoryModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { From 619e8dcb4426c7503ce455b9d6be10b0e7bf08b8 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 6 Jan 2016 12:19:09 +0100 Subject: [PATCH 382/874] Faulty permission handling in ajax grid actions (no message, infinite loading icon)(part 2) --- .../Controllers/WebApiController.cs | 2 +- .../Controllers/LanguageController.cs | 192 +- .../Controllers/LogController.cs | 78 +- .../Controllers/ManufacturerController.cs | 160 +- .../Controllers/MeasureController.cs | 198 +- .../Controllers/MessageTemplateController.cs | 20 +- .../Controllers/NewsController.cs | 138 +- .../NewsLetterSubscriptionController.cs | 70 +- .../Controllers/OnlineCustomerController.cs | 48 +- .../Controllers/OrderController.cs | 331 +-- .../Controllers/PollController.cs | 172 +- .../Controllers/ProductAttributeController.cs | 26 +- .../Controllers/ProductController.cs | 1766 +++++++++-------- .../Controllers/ProductReviewController.cs | 45 +- .../Controllers/QueuedEmailController.cs | 64 +- .../Controllers/RecurringPaymentController.cs | 81 +- .../Controllers/ReturnRequestController.cs | 38 +- .../Controllers/SettingController.cs | 173 +- .../Controllers/ShippingController.cs | 27 +- .../Controllers/ShoppingCartController.cs | 200 +- .../SpecificationAttributeController.cs | 96 +- .../Controllers/StoreController.cs | 35 +- .../Controllers/TaxController.cs | 83 +- .../Controllers/TopicController.cs | 27 +- .../Controllers/UrlRecordController.cs | 32 +- 25 files changed, 2200 insertions(+), 1902 deletions(-) diff --git a/src/Plugins/SmartStore.WebApi/Controllers/WebApiController.cs b/src/Plugins/SmartStore.WebApi/Controllers/WebApiController.cs index 808387b270..4fb254bbff 100644 --- a/src/Plugins/SmartStore.WebApi/Controllers/WebApiController.cs +++ b/src/Plugins/SmartStore.WebApi/Controllers/WebApiController.cs @@ -97,7 +97,7 @@ public ActionResult SaveGeneralSettings(WebApiConfigModel model) public ActionResult GridUserData(GridCommand command) { if (!HasPermission()) - return new JsonResult { Data = new GridModel { Data = new List() }}; + return new JsonResult { Data = new GridModel { Data = new List() } }; var model = _webApiPluginService.GetGridModel(command.Page - 1, command.PageSize); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs index e546998d03..02587df34f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs @@ -60,7 +60,8 @@ public LanguageController(ILanguageService languageService, } #endregion - #region Utilities + + #region Utilities [NonAction] private void PrepareFlagsModel(LanguageModel model) @@ -123,18 +124,25 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) - return AccessDeniedView(); + var model = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + { + var languages = _languageService.GetAllLanguages(true); + + model.Data = languages.Select(x => x.ToModel()); + model.Total = languages.Count(); + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var languages = _languageService.GetAllLanguages(true); - var gridModel = new GridModel - { - Data = languages.Select(x => x.ToModel()), - Total = languages.Count() - }; return new JsonResult { - Data = gridModel + Data = model }; } @@ -329,31 +337,37 @@ public ActionResult Resources(int languageId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult Resources(int languageId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) - return AccessDeniedView(); + var model = new GridModel(); - var language = _languageService.GetLanguageById(languageId); + if (_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + { + var language = _languageService.GetLanguageById(languageId); + + var resources = _localizationService + .GetResourceValues(languageId, true) + .OrderBy(x => x.Key) + .Where(x => x.Key != "!!___EOF___!!" && x.Value != null) + .Select(x => new LanguageResourceModel + { + LanguageId = languageId, + LanguageName = language.Name, + Id = x.Value.Item1, + Name = x.Key, + Value = x.Value.Item2.EmptyNull(), + }) + .ForCommand(command) + .ToList(); + + model.Data = resources.PagedForCommand(command); + model.Total = resources.Count; + } + else + { + model.Data = Enumerable.Empty(); - var resources = _localizationService - .GetResourceValues(languageId, true) - .OrderBy(x => x.Key) - .Where(x => x.Key != "!!___EOF___!!" && x.Value != null) - .Select(x => new LanguageResourceModel - { - LanguageId = languageId, - LanguageName = language.Name, - Id = x.Value.Item1, - Name = x.Key, - Value = x.Value.Item2.EmptyNull(), - }) - .ForCommand(command) - .ToList(); + NotifyAccessDenied(); + } - var model = new GridModel - { - Data = resources.PagedForCommand(command), - Total = resources.Count - }; return new JsonResult { Data = model @@ -363,36 +377,36 @@ public ActionResult Resources(int languageId, GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult ResourceUpdate(LanguageResourceModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + { + if (model.Name != null) + model.Name = model.Name.Trim(); + if (model.Value != null) + model.Value = model.Value.Trim(); - if (model.Name != null) - model.Name = model.Name.Trim(); - if (model.Value != null) - model.Value = model.Value.Trim(); + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + var resource = _localizationService.GetLocaleStringResourceById(model.Id); + // if the resourceName changed, ensure it isn't being used by another resource + if (!resource.ResourceName.Equals(model.Name, StringComparison.InvariantCultureIgnoreCase)) + { + var res = _localizationService.GetLocaleStringResourceByName(model.Name, model.LanguageId, false); + if (res != null && res.Id != resource.Id) + { + return Content(T("Admin.Configuration.Languages.Resources.NameAlreadyExists", res.ResourceName)); + } + } - var resource = _localizationService.GetLocaleStringResourceById(model.Id); - // if the resourceName changed, ensure it isn't being used by another resource - if (!resource.ResourceName.Equals(model.Name, StringComparison.InvariantCultureIgnoreCase)) - { - var res = _localizationService.GetLocaleStringResourceByName(model.Name, model.LanguageId, false); - if (res != null && res.Id != resource.Id) - { - return Content(string.Format(_localizationService.GetResource("Admin.Configuration.Languages.Resources.NameAlreadyExists"), res.ResourceName)); - } - } + resource.ResourceName = model.Name; + resource.ResourceValue = model.Value; + resource.IsTouched = true; - resource.ResourceName = model.Name; - resource.ResourceValue = model.Value; - resource.IsTouched = true; - _localizationService.UpdateLocaleStringResource(resource); + _localizationService.UpdateLocaleStringResource(resource); + } return Resources(model.LanguageId, command); } @@ -400,47 +414,47 @@ public ActionResult ResourceUpdate(LanguageResourceModel model, GridCommand comm [GridAction(EnableCustomBinding = true)] public ActionResult ResourceAdd(int id, LanguageResourceModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + { + if (model.Name != null) + model.Name = model.Name.Trim(); + if (model.Value != null) + model.Value = model.Value.Trim(); - if (model.Name != null) - model.Name = model.Name.Trim(); - if (model.Value != null) - model.Value = model.Value.Trim(); + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + var res = _localizationService.GetLocaleStringResourceByName(model.Name, model.LanguageId, false); + if (res == null) + { + var resource = new LocaleStringResource { LanguageId = id }; + resource.ResourceName = model.Name; + resource.ResourceValue = model.Value; + resource.IsTouched = true; + + _localizationService.InsertLocaleStringResource(resource); + } + else + { + return Content(T("Admin.Configuration.Languages.Resources.NameAlreadyExists", model.Name)); + } + } - var res = _localizationService.GetLocaleStringResourceByName(model.Name, model.LanguageId, false); - if (res == null) - { - var resource = new LocaleStringResource { LanguageId = id }; - resource.ResourceName = model.Name; - resource.ResourceValue = model.Value; - resource.IsTouched = true; - _localizationService.InsertLocaleStringResource(resource); - } - else - { - return Content(string.Format(_localizationService.GetResource("Admin.Configuration.Languages.Resources.NameAlreadyExists"), model.Name)); - } return Resources(id, command); } [GridAction(EnableCustomBinding = true)] public ActionResult ResourceDelete(int id, int languageId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + { + var resource = _localizationService.GetLocaleStringResourceById(id); - var resource = _localizationService.GetLocaleStringResourceById(id); - if (resource == null) - throw new ArgumentException("No resource found with the specified id"); - _localizationService.DeleteLocaleStringResource(resource); + _localizationService.DeleteLocaleStringResource(resource); + } return Resources(languageId, command); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/LogController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/LogController.cs index 8b82d5d894..9fb8db0105 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/LogController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/LogController.cs @@ -64,49 +64,55 @@ public ActionResult List() [GridAction(EnableCustomBinding = true)] public ActionResult LogList(GridCommand command, LogListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageSystemLog)) - return AccessDeniedView(); - - DateTime? createdOnFromValue = (model.CreatedOnFrom == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnFrom.Value, _dateTimeHelper.CurrentTimeZone); - - DateTime? createdToFromValue = (model.CreatedOnTo == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnTo.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - - LogLevel? logLevel = model.LogLevelId > 0 ? (LogLevel?)(model.LogLevelId) : null; - - - var logItems = Logger.GetAllLogs(createdOnFromValue, createdToFromValue, model.Message, - logLevel, command.Page - 1, command.PageSize, model.MinFrequency); - - var gridModel = new GridModel - { - Data = logItems.Select(x => - { - var logModel = new LogModel() - { - Id = x.Id, - LogLevelHint = s_logLevelHintMap[x.LogLevel], - LogLevel = x.LogLevel.GetLocalizedEnum(_localizationService, _workContext), - ShortMessage = x.ShortMessage, - FullMessage = x.FullMessage, - IpAddress = x.IpAddress, - CustomerId = x.CustomerId, - CustomerEmail = x.Customer != null ? x.Customer.Email : null, - PageUrl = x.PageUrl, - ReferrerUrl = x.ReferrerUrl, - CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc), + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageSystemLog)) + { + DateTime? createdOnFromValue = (model.CreatedOnFrom == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnFrom.Value, _dateTimeHelper.CurrentTimeZone); + + DateTime? createdToFromValue = (model.CreatedOnTo == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnTo.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + LogLevel? logLevel = model.LogLevelId > 0 ? (LogLevel?)(model.LogLevelId) : null; + + var logItems = Logger.GetAllLogs(createdOnFromValue, createdToFromValue, model.Message, + logLevel, command.Page - 1, command.PageSize, model.MinFrequency); + + gridModel.Data = logItems.Select(x => + { + var logModel = new LogModel + { + Id = x.Id, + LogLevelHint = s_logLevelHintMap[x.LogLevel], + LogLevel = x.LogLevel.GetLocalizedEnum(_localizationService, _workContext), + ShortMessage = x.ShortMessage, + FullMessage = x.FullMessage, + IpAddress = x.IpAddress, + CustomerId = x.CustomerId, + CustomerEmail = x.Customer != null ? x.Customer.Email : null, + PageUrl = x.PageUrl, + ReferrerUrl = x.ReferrerUrl, + CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc), Frequency = x.Frequency, ContentHash = x.ContentHash - }; + }; if (x.UpdatedOnUtc.HasValue) logModel.UpdatedOn = _dateTimeHelper.ConvertToUserTime(x.UpdatedOnUtc.Value, DateTimeKind.Utc); return logModel; - }), - Total = logItems.TotalCount - }; + }); + + gridModel.Total = logItems.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { Data = gridModel diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs index e107a81fac..5f345fcb42 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs @@ -254,16 +254,21 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command, ManufacturerListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var manufacturers = _manufacturerService.GetAllManufacturers(model.SearchManufacturerName, command.Page - 1, command.PageSize, true); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var manufacturers = _manufacturerService.GetAllManufacturers(model.SearchManufacturerName, command.Page - 1, command.PageSize, true); - var gridModel = new GridModel - { - Data = manufacturers.Select(x => x.ToModel()), - Total = manufacturers.TotalCount - }; + gridModel.Data = manufacturers.Select(x => x.ToModel()); + gridModel.Total = manufacturers.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -441,35 +446,43 @@ public ActionResult Delete(int id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductList(GridCommand command, int manufacturerId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productManufacturers = _manufacturerService.GetProductManufacturersByManufacturerId(manufacturerId, command.Page - 1, command.PageSize, true); - var productManufacturers = _manufacturerService.GetProductManufacturersByManufacturerId(manufacturerId, - command.Page - 1, command.PageSize, true); + var productIds = productManufacturers.Select(x => x.ProductId).ToArray(); + var products = _productService.GetProductsByIds(productIds); - var model = new GridModel - { - Data = productManufacturers - .Select(x => - { - var product = _productService.GetProductById(x.ProductId); + model.Data = productManufacturers + .Select(x => + { + var product = products.FirstOrDefault(y => y.Id == x.ProductId); + + return new ManufacturerModel.ManufacturerProductModel + { + Id = x.Id, + ManufacturerId = x.ManufacturerId, + ProductId = x.ProductId, + ProductName = product.Name, + Sku = product.Sku, + ProductTypeName = product.GetProductTypeLabel(_localizationService), + ProductTypeLabelHint = product.ProductTypeLabelHint, + Published = product.Published, + IsFeaturedProduct = x.IsFeaturedProduct, + DisplayOrder1 = x.DisplayOrder + }; + }); - return new ManufacturerModel.ManufacturerProductModel() - { - Id = x.Id, - ManufacturerId = x.ManufacturerId, - ProductId = x.ProductId, - ProductName = product.Name, - Sku = product.Sku, - ProductTypeName = product.GetProductTypeLabel(_localizationService), - ProductTypeLabelHint = product.ProductTypeLabelHint, - Published = product.Published, - IsFeaturedProduct = x.IsFeaturedProduct, - DisplayOrder1 = x.DisplayOrder - }; - }), - Total = productManufacturers.TotalCount - }; + model.Total = productManufacturers.TotalCount; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -480,16 +493,15 @@ public ActionResult ProductList(GridCommand command, int manufacturerId) [GridAction(EnableCustomBinding = true)] public ActionResult ProductUpdate(GridCommand command, ManufacturerModel.ManufacturerProductModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var productManufacturer = _manufacturerService.GetProductManufacturerById(model.Id); - var productManufacturer = _manufacturerService.GetProductManufacturerById(model.Id); - if (productManufacturer == null) - throw new ArgumentException("No product manufacturer mapping found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + productManufacturer.IsFeaturedProduct = model.IsFeaturedProduct; + productManufacturer.DisplayOrder = model.DisplayOrder1; - productManufacturer.IsFeaturedProduct = model.IsFeaturedProduct; - productManufacturer.DisplayOrder = model.DisplayOrder1; - _manufacturerService.UpdateProductManufacturer(productManufacturer); + _manufacturerService.UpdateProductManufacturer(productManufacturer); + } return ProductList(command, productManufacturer.ManufacturerId); } @@ -497,15 +509,13 @@ public ActionResult ProductUpdate(GridCommand command, ManufacturerModel.Manufac [GridAction(EnableCustomBinding = true)] public ActionResult ProductDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var productManufacturer = _manufacturerService.GetProductManufacturerById(id); - if (productManufacturer == null) - throw new ArgumentException("No product manufacturer mapping found with the specified id"); + var productManufacturer = _manufacturerService.GetProductManufacturerById(id); + var manufacturerId = productManufacturer.ManufacturerId; - var manufacturerId = productManufacturer.ManufacturerId; - _manufacturerService.DeleteProductManufacturer(productManufacturer); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _manufacturerService.DeleteProductManufacturer(productManufacturer); + } return ProductList(command, manufacturerId); } @@ -560,35 +570,43 @@ public ActionResult ProductAddPopup(int manufacturerId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductAddPopupList(GridCommand command, ManufacturerModel.AddManufacturerProductModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var gridModel = new GridModel(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var ctx = new ProductSearchContext(); - var ctx = new ProductSearchContext(); + if (model.SearchCategoryId > 0) + ctx.CategoryIds.Add(model.SearchCategoryId); - if (model.SearchCategoryId > 0) - ctx.CategoryIds.Add(model.SearchCategoryId); + ctx.ManufacturerId = model.SearchManufacturerId; + ctx.Keywords = model.SearchProductName; + ctx.LanguageId = _workContext.WorkingLanguage.Id; + ctx.OrderBy = ProductSortingEnum.Position; + ctx.PageIndex = command.Page - 1; + ctx.PageSize = command.PageSize; + ctx.ShowHidden = true; + ctx.ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null; - ctx.ManufacturerId = model.SearchManufacturerId; - ctx.Keywords = model.SearchProductName; - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageIndex = command.Page - 1; - ctx.PageSize = command.PageSize; - ctx.ShowHidden = true; - ctx.ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null; + var products = _productService.SearchProducts(ctx); - var products = _productService.SearchProducts(ctx); + gridModel.Data = products.Select(x => + { + var productModel = x.ToModel(); + productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); + + return productModel; + }); - gridModel.Data = products.Select(x => + gridModel.Total = products.TotalCount; + } + else { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - return productModel; - }); - gridModel.Total = products.TotalCount; return new JsonResult { Data = gridModel diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/MeasureController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/MeasureController.cs index ec1265db77..f8b33e6102 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/MeasureController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/MeasureController.cs @@ -79,20 +79,29 @@ public ActionResult Weights(string id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult Weights(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) - return AccessDeniedView(); + var model = new GridModel(); - var weightsModel = _measureService.GetAllMeasureWeights() - .Select(x => x.ToModel()) - .ForCommand(command) - .ToList(); - foreach (var wm in weightsModel) - wm.IsPrimaryWeight = wm.Id == _measureSettings.BaseWeightId; - var model = new GridModel - { - Data = weightsModel, - Total = weightsModel.Count - }; + if (_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + { + var weightsModel = _measureService.GetAllMeasureWeights() + .Select(x => x.ToModel()) + .ForCommand(command) + .ToList(); + + foreach (var wm in weightsModel) + { + wm.IsPrimaryWeight = wm.Id == _measureSettings.BaseWeightId; + } + + model.Data = weightsModel; + model.Total = weightsModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -100,22 +109,22 @@ public ActionResult Weights(GridCommand command) }; } - [GridAction(EnableCustomBinding=true)] + [GridAction(EnableCustomBinding = true)] public ActionResult WeightUpdate(MeasureWeightModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) - return AccessDeniedView(); - - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + if (_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - var weight = _measureService.GetMeasureWeightById(model.Id); - weight = model.ToEntity(weight); - _measureService.UpdateMeasureWeight(weight); + var weight = _measureService.GetMeasureWeightById(model.Id); + weight = model.ToEntity(weight); + + _measureService.UpdateMeasureWeight(weight); + } return Weights(command); } @@ -123,19 +132,19 @@ public ActionResult WeightUpdate(MeasureWeightModel model, GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult WeightAdd([Bind(Exclude="Id")] MeasureWeightModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + var weight = new MeasureWeight(); + weight = model.ToEntity(weight); - var weight = new MeasureWeight(); - weight = model.ToEntity(weight); - _measureService.InsertMeasureWeight(weight); + _measureService.InsertMeasureWeight(weight); + } return Weights(command); } @@ -143,17 +152,17 @@ public ActionResult WeightAdd([Bind(Exclude="Id")] MeasureWeightModel model, Gri [GridAction(EnableCustomBinding = true)] public ActionResult WeightDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) - return AccessDeniedView(); - - var weight = _measureService.GetMeasureWeightById(id); - if (weight == null) - throw new ArgumentException("No weight found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + { + var weight = _measureService.GetMeasureWeightById(id); - if (weight.Id == _measureSettings.BaseWeightId) - return Content(_localizationService.GetResource("Admin.Configuration.Measures.Weights.CantDeletePrimary")); + if (weight.Id == _measureSettings.BaseWeightId) + { + return Content(T("Admin.Configuration.Measures.Weights.CantDeletePrimary")); + } - _measureService.DeleteMeasureWeight(weight); + _measureService.DeleteMeasureWeight(weight); + } return Weights(command); } @@ -195,20 +204,29 @@ public ActionResult Dimensions(string id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult Dimensions(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) - return AccessDeniedView(); + var model = new GridModel(); - var dimensionsModel = _measureService.GetAllMeasureDimensions() - .Select(x => x.ToModel()) - .ForCommand(command) - .ToList(); - foreach (var wm in dimensionsModel) - wm.IsPrimaryDimension = wm.Id == _measureSettings.BaseDimensionId; - var model = new GridModel - { - Data = dimensionsModel, - Total = dimensionsModel.Count - }; + if (_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + { + var dimensionsModel = _measureService.GetAllMeasureDimensions() + .Select(x => x.ToModel()) + .ForCommand(command) + .ToList(); + + foreach (var wm in dimensionsModel) + { + wm.IsPrimaryDimension = wm.Id == _measureSettings.BaseDimensionId; + } + + model.Data = dimensionsModel; + model.Total = dimensionsModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -219,19 +237,19 @@ public ActionResult Dimensions(GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult DimensionUpdate(MeasureDimensionModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + var dimension = _measureService.GetMeasureDimensionById(model.Id); + dimension = model.ToEntity(dimension); - var dimension = _measureService.GetMeasureDimensionById(model.Id); - dimension = model.ToEntity(dimension); - _measureService.UpdateMeasureDimension(dimension); + _measureService.UpdateMeasureDimension(dimension); + } return Dimensions(command); } @@ -239,19 +257,19 @@ public ActionResult DimensionUpdate(MeasureDimensionModel model, GridCommand com [GridAction(EnableCustomBinding = true)] public ActionResult DimensionAdd([Bind(Exclude="Id")] MeasureDimensionModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + var dimension = new MeasureDimension(); + dimension = model.ToEntity(dimension); - var dimension = new MeasureDimension(); - dimension = model.ToEntity(dimension); - _measureService.InsertMeasureDimension(dimension); + _measureService.InsertMeasureDimension(dimension); + } return Dimensions(command); } @@ -259,17 +277,17 @@ public ActionResult DimensionAdd([Bind(Exclude="Id")] MeasureDimensionModel mode [GridAction(EnableCustomBinding = true)] public ActionResult DimensionDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) - return AccessDeniedView(); - - var dimension = _measureService.GetMeasureDimensionById(id); - if (dimension == null) - throw new ArgumentException("No dimension found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageMeasures)) + { + var dimension = _measureService.GetMeasureDimensionById(id); - if (dimension.Id == _measureSettings.BaseDimensionId) - return Content(_localizationService.GetResource("Admin.Configuration.Measures.Dimensions.CantDeletePrimary")); + if (dimension.Id == _measureSettings.BaseDimensionId) + { + return Content(T("Admin.Configuration.Measures.Dimensions.CantDeletePrimary")); + } - _measureService.DeleteMeasureDimension(dimension); + _measureService.DeleteMeasureDimension(dimension); + } return Dimensions(command); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/MessageTemplateController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/MessageTemplateController.cs index be6c73a92b..eb0b111b06 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/MessageTemplateController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/MessageTemplateController.cs @@ -162,15 +162,21 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command, MessageTemplateListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMessageTemplates)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var messageTemplates = _messageTemplateService.GetAllMessageTemplates(model.SearchStoreId); - var gridModel = new GridModel + if (_permissionService.Authorize(StandardPermissionProvider.ManageMessageTemplates)) { - Data = messageTemplates.Select(x => x.ToModel()), - Total = messageTemplates.Count - }; + var messageTemplates = _messageTemplateService.GetAllMessageTemplates(model.SearchStoreId); + + gridModel.Data = messageTemplates.Select(x => x.ToModel()); + gridModel.Total = messageTemplates.Count; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/NewsController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/NewsController.cs index 2ac51b9184..2d12ecc9d6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/NewsController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/NewsController.cs @@ -115,26 +115,38 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command, NewsItemListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageNews)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageNews)) + { + var news = _newsService.GetAllNews(0, model.SearchStoreId, command.Page - 1, command.PageSize, true); + + gridModel.Data = news.Select(x => + { + var m = x.ToModel(); + + if (x.StartDateUtc.HasValue) + m.StartDate = _dateTimeHelper.ConvertToUserTime(x.StartDateUtc.Value, DateTimeKind.Utc); + + if (x.EndDateUtc.HasValue) + m.EndDate = _dateTimeHelper.ConvertToUserTime(x.EndDateUtc.Value, DateTimeKind.Utc); + + m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); + m.LanguageName = x.Language.Name; + m.Comments = x.ApprovedCommentCount + x.NotApprovedCommentCount; + + return m; + }); + + gridModel.Total = news.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var news = _newsService.GetAllNews(0, model.SearchStoreId, command.Page - 1, command.PageSize, true); - var gridModel = new GridModel - { - Data = news.Select(x => - { - var m = x.ToModel(); - if (x.StartDateUtc.HasValue) - m.StartDate = _dateTimeHelper.ConvertToUserTime(x.StartDateUtc.Value, DateTimeKind.Utc); - if (x.EndDateUtc.HasValue) - m.EndDate = _dateTimeHelper.ConvertToUserTime(x.EndDateUtc.Value, DateTimeKind.Utc); - m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - m.LanguageName = x.Language.Name; - m.Comments = x.ApprovedCommentCount + x.NotApprovedCommentCount; - return m; - }), - Total = news.TotalCount - }; return new JsonResult { Data = gridModel @@ -297,47 +309,54 @@ public ActionResult Comments(int? filterByNewsItemId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult Comments(int? filterByNewsItemId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageNews)) - return AccessDeniedView(); + var gridModel = new GridModel(); - IList comments; - if (filterByNewsItemId.HasValue) - { - //filter comments by news item - var newsItem = _newsService.GetNewsById(filterByNewsItemId.Value); - comments = newsItem.NewsComments.OrderBy(bc => bc.CreatedOnUtc).ToList(); - } - else - { - //load all news comments - comments = _customerContentService.GetAllCustomerContent(0, null); - } + if (_permissionService.Authorize(StandardPermissionProvider.ManageNews)) + { + IList comments; + if (filterByNewsItemId.HasValue) + { + //filter comments by news item + var newsItem = _newsService.GetNewsById(filterByNewsItemId.Value); + comments = newsItem.NewsComments.OrderBy(bc => bc.CreatedOnUtc).ToList(); + } + else + { + //load all news comments + comments = _customerContentService.GetAllCustomerContent(0, null); + } - var gridModel = new GridModel - { - Data = comments.PagedForCommand(command).Select(newsComment => - { - var commentModel = new NewsCommentModel(); + gridModel.Data = comments.PagedForCommand(command).Select(newsComment => + { + var commentModel = new NewsCommentModel(); var customer = _customerService.GetCustomerById(newsComment.CustomerId); - commentModel.Id = newsComment.Id; - commentModel.NewsItemId = newsComment.NewsItemId; - commentModel.NewsItemTitle = newsComment.NewsItem.Title; - commentModel.CustomerId = newsComment.CustomerId; - commentModel.IpAddress = newsComment.IpAddress; - commentModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(newsComment.CreatedOnUtc, DateTimeKind.Utc); - commentModel.CommentTitle = newsComment.CommentTitle; - commentModel.CommentText = Core.Html.HtmlUtils.FormatText(newsComment.CommentText, false, true, false, false, false, false); + commentModel.Id = newsComment.Id; + commentModel.NewsItemId = newsComment.NewsItemId; + commentModel.NewsItemTitle = newsComment.NewsItem.Title; + commentModel.CustomerId = newsComment.CustomerId; + commentModel.IpAddress = newsComment.IpAddress; + commentModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(newsComment.CreatedOnUtc, DateTimeKind.Utc); + commentModel.CommentTitle = newsComment.CommentTitle; + commentModel.CommentText = Core.Html.HtmlUtils.FormatText(newsComment.CommentText, false, true, false, false, false, false); if (customer == null) commentModel.CustomerName = "".NaIfEmpty(); else commentModel.CustomerName = customer.GetFullName(); - return commentModel; - }), - Total = comments.Count, - }; + return commentModel; + }); + + gridModel.Total = comments.Count; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { Data = gridModel @@ -347,17 +366,16 @@ public ActionResult Comments(int? filterByNewsItemId, GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult CommentDelete(int? filterByNewsItemId, int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageNews)) - return AccessDeniedView(); - - var comment = _customerContentService.GetCustomerContentById(id) as NewsComment; - if (comment == null) - throw new ArgumentException("No comment found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageNews)) + { + var comment = _customerContentService.GetCustomerContentById(id) as NewsComment; - var newsItem = comment.NewsItem; - _customerContentService.DeleteCustomerContent(comment); - //update totals - _newsService.UpdateCommentTotals(newsItem); + var newsItem = comment.NewsItem; + _customerContentService.DeleteCustomerContent(comment); + + //update totals + _newsService.UpdateCommentTotals(newsItem); + } return Comments(filterByNewsItemId, command); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/NewsLetterSubscriptionController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/NewsLetterSubscriptionController.cs index e8e92184a2..9c688979ea 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/NewsLetterSubscriptionController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/NewsLetterSubscriptionController.cs @@ -68,7 +68,7 @@ public ActionResult List() var store = _storeService.GetStoreById(x.StoreId); m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - m.StoreName = store != null ? store.Name : "Unknown"; + m.StoreName = store != null ? store.Name : "".NaIfEmpty(); return m; }), @@ -80,26 +80,33 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult SubscriptionList(GridCommand command, NewsLetterSubscriptionListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var newsletterSubscriptions = _newsLetterSubscriptionService.GetAllNewsLetterSubscriptions( - model.SearchEmail, command.Page - 1, command.PageSize, true, model.StoreId); + if (_permissionService.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers)) + { + var newsletterSubscriptions = _newsLetterSubscriptionService.GetAllNewsLetterSubscriptions( + model.SearchEmail, command.Page - 1, command.PageSize, true, model.StoreId); - var gridModel = new GridModel - { - Data = newsletterSubscriptions.Select(x => + gridModel.Data = newsletterSubscriptions.Select(x => { var m = x.ToModel(); var store = _storeService.GetStoreById(x.StoreId); m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - m.StoreName = store != null ? store.Name : "Unknown"; + m.StoreName = store != null ? store.Name : "".NaIfEmpty(); return m; - }), - Total = newsletterSubscriptions.TotalCount - }; + }); + + gridModel.Total = newsletterSubscriptions.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { Data = gridModel @@ -109,39 +116,36 @@ public ActionResult SubscriptionList(GridCommand command, NewsLetterSubscription [GridAction(EnableCustomBinding = true)] public ActionResult SubscriptionUpdate(NewsLetterSubscriptionModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers)) - return AccessDeniedView(); - - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + if (_permissionService.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionById(model.Id); - subscription.Email = model.Email; - subscription.Active = model.Active; + var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionById(model.Id); + subscription.Email = model.Email; + subscription.Active = model.Active; - _newsLetterSubscriptionService.UpdateNewsLetterSubscription(subscription); + _newsLetterSubscriptionService.UpdateNewsLetterSubscription(subscription); + } var listModel = new NewsLetterSubscriptionListModel(); PrepareNewsLetterSubscriptionListModel(listModel); - return SubscriptionList(command, listModel); + return SubscriptionList(command, listModel); } [GridAction(EnableCustomBinding = true)] public ActionResult SubscriptionDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers)) - return AccessDeniedView(); - - var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionById(id); - if (subscription == null) - throw new ArgumentException("No subscription found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageNewsletterSubscribers)) + { + var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionById(id); - _newsLetterSubscriptionService.DeleteNewsLetterSubscription(subscription); + _newsLetterSubscriptionService.DeleteNewsLetterSubscription(subscription); + } var listModel = new NewsLetterSubscriptionListModel(); PrepareNewsLetterSubscriptionListModel(listModel); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/OnlineCustomerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/OnlineCustomerController.cs index 222bb0863e..3aca0ddd54 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/OnlineCustomerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/OnlineCustomerController.cs @@ -81,27 +81,35 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) - return AccessDeniedView(); + var model = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCustomers)) + { + var lastActivityFrom = DateTime.UtcNow.AddMinutes(-_customerSettings.OnlineCustomerMinutes); + var customers = _customerService.GetOnlineCustomers(lastActivityFrom, null, command.Page - 1, command.PageSize); + + model.Data = customers.Select(x => + { + return new OnlineCustomerModel + { + Id = x.Id, + CustomerInfo = x.IsRegistered() ? x.Email : T("Admin.Customers.Guest").Text, + LastIpAddress = x.LastIpAddress, + Location = _geoCountryLookup.LookupCountryName(x.LastIpAddress), + LastActivityDate = _dateTimeHelper.ConvertToUserTime(x.LastActivityDateUtc, DateTimeKind.Utc), + LastVisitedPage = x.GetAttribute(SystemCustomerAttributeNames.LastVisitedPage) + }; + }); + + model.Total = customers.TotalCount; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var customers = _customerService.GetOnlineCustomers(DateTime.UtcNow.AddMinutes(-_customerSettings.OnlineCustomerMinutes), - null, command.Page - 1, command.PageSize); - var model = new GridModel - { - Data = customers.Select(x => - { - return new OnlineCustomerModel() - { - Id = x.Id, - CustomerInfo = x.IsRegistered() ? x.Email : _localizationService.GetResource("Admin.Customers.Guest"), - LastIpAddress = x.LastIpAddress, - Location = _geoCountryLookup.LookupCountryName(x.LastIpAddress), - LastActivityDate = _dateTimeHelper.ConvertToUserTime(x.LastActivityDateUtc, DateTimeKind.Utc), - LastVisitedPage = x.GetAttribute(SystemCustomerAttributeNames.LastVisitedPage) - }; - }), - Total = customers.TotalCount - }; return new JsonResult { Data = model diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs index f7adf8147d..911d14b44b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs @@ -809,57 +809,65 @@ public ActionResult List(OrderListModel model) [GridAction(EnableCustomBinding = true)] public ActionResult OrderList(GridCommand command, OrderListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); + var gridModel = new GridModel(); - DateTime? startDateValue = (model.StartDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - DateTime? endDateValue = (model.EndDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + DateTime? startDateValue = (model.StartDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + DateTime? endDateValue = (model.EndDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - var orderStatusIds = model.OrderStatusIds.ToIntArray(); - var paymentStatusIds = model.PaymentStatusIds.ToIntArray(); - var shippingStatusIds = model.ShippingStatusIds.ToIntArray(); + var orderStatusIds = model.OrderStatusIds.ToIntArray(); + var paymentStatusIds = model.PaymentStatusIds.ToIntArray(); + var shippingStatusIds = model.ShippingStatusIds.ToIntArray(); - var orders = _orderService.SearchOrders(model.StoreId, 0, startDateValue, endDateValue, orderStatusIds, paymentStatusIds, shippingStatusIds, - model.CustomerEmail, model.OrderGuid, model.OrderNumber, command.Page - 1, command.PageSize, model.CustomerName); + var orders = _orderService.SearchOrders(model.StoreId, 0, startDateValue, endDateValue, orderStatusIds, paymentStatusIds, shippingStatusIds, + model.CustomerEmail, model.OrderGuid, model.OrderNumber, command.Page - 1, command.PageSize, model.CustomerName); - var gridModel = new GridModel - { - Data = orders.Select(x => - { + gridModel.Data = orders.Select(x => + { var store = _storeService.GetStoreById(x.StoreId); - return new OrderModel - { - Id = x.Id, - OrderNumber = x.GetOrderNumber(), + return new OrderModel + { + Id = x.Id, + OrderNumber = x.GetOrderNumber(), StoreName = (store != null ? store.Name : "".NaIfEmpty()), - OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), - OrderStatus = x.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), - PaymentStatus = x.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext), - ShippingStatus = x.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext), + OrderTotal = _priceFormatter.FormatPrice(x.OrderTotal, true, false), + OrderStatus = x.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), + PaymentStatus = x.PaymentStatus.GetLocalizedEnum(_localizationService, _workContext), + ShippingStatus = x.ShippingStatus.GetLocalizedEnum(_localizationService, _workContext), CustomerName = x.BillingAddress.GetFullName(), CustomerEmail = x.BillingAddress.Email, - CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc), + CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc), HasNewPaymentNotification = x.HasNewPaymentNotification - }; - }), - Total = orders.TotalCount - }; + }; + }); + + gridModel.Total = orders.TotalCount; + + //summary report + //implemented as a workaround described here: http://www.telerik.com/community/forums/aspnet-mvc/grid/gridmodel-aggregates-how-to-use.aspx + var reportSummary = _orderReportService.GetOrderAverageReportLine(model.StoreId, orderStatusIds, + paymentStatusIds, shippingStatusIds, startDateValue, endDateValue, model.CustomerEmail); + + var profit = _orderReportService.ProfitReport(model.StoreId, orderStatusIds, + paymentStatusIds, shippingStatusIds, startDateValue, endDateValue, model.CustomerEmail); + + var aggregator = new OrderModel + { + aggregatorprofit = _priceFormatter.FormatPrice(profit, true, false), + aggregatortax = _priceFormatter.FormatPrice(reportSummary.SumTax, true, false), + aggregatortotal = _priceFormatter.FormatPrice(reportSummary.SumOrders, true, false) + }; + + gridModel.Aggregates = aggregator; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - //summary report - //implemented as a workaround described here: http://www.telerik.com/community/forums/aspnet-mvc/grid/gridmodel-aggregates-how-to-use.aspx - var reportSummary = _orderReportService.GetOrderAverageReportLine(model.StoreId, orderStatusIds, - paymentStatusIds, shippingStatusIds, startDateValue, endDateValue, model.CustomerEmail); - - var profit = _orderReportService.ProfitReport(model.StoreId, orderStatusIds, - paymentStatusIds, shippingStatusIds, startDateValue, endDateValue, model.CustomerEmail); - - var aggregator = new OrderModel() - { - aggregatorprofit = _priceFormatter.FormatPrice(profit, true, false), - aggregatortax = _priceFormatter.FormatPrice(reportSummary.SumTax, true, false), - aggregatortotal = _priceFormatter.FormatPrice(reportSummary.SumOrders, true, false) - }; - gridModel.Aggregates = aggregator; return new JsonResult { Data = gridModel @@ -1676,36 +1684,46 @@ public ActionResult AddProductToOrder(int orderId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult AddProductToOrder(GridCommand command, OrderModel.AddOrderProductModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var gridModel = new GridModel(); - var searchContext = new ProductSearchContext() + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) { - CategoryIds = new List() { model.SearchCategoryId }, - ManufacturerId = model.SearchManufacturerId, - Keywords = model.SearchProductName, - PageIndex = command.Page - 1, - PageSize = command.PageSize, - ShowHidden = true, - ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null - }; + var searchContext = new ProductSearchContext + { + CategoryIds = new List { model.SearchCategoryId }, + ManufacturerId = model.SearchManufacturerId, + Keywords = model.SearchProductName, + PageIndex = command.Page - 1, + PageSize = command.PageSize, + ShowHidden = true, + ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null + }; - var products = _productService.SearchProducts(searchContext); - gridModel.Data = products.Select(x => - { - var productModel = new OrderModel.AddOrderProductModel.ProductModel - { - Id = x.Id, - Name = x.Name, - Sku = x.Sku, - ProductTypeName = x.GetProductTypeLabel(_localizationService), - ProductTypeLabelHint = x.ProductTypeLabelHint - }; + var products = _productService.SearchProducts(searchContext); + + gridModel.Data = products.Select(x => + { + var productModel = new OrderModel.AddOrderProductModel.ProductModel + { + Id = x.Id, + Name = x.Name, + Sku = x.Sku, + ProductTypeName = x.GetProductTypeLabel(_localizationService), + ProductTypeLabelHint = x.ProductTypeLabelHint + }; + + return productModel; + }); + + gridModel.Total = products.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - return productModel; - }); - gridModel.Total = products.TotalCount; return new JsonResult { Data = gridModel @@ -1983,23 +2001,29 @@ public ActionResult ShipmentList() [GridAction(EnableCustomBinding = true)] public ActionResult ShipmentListSelect(GridCommand command, ShipmentListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); + var gridModel = new GridModel(); - DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + DateTime? startDateValue = (model.StartDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); - DateTime? endDateValue = (model.EndDate == null) ? null - :(DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + DateTime? endDateValue = (model.EndDate == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + //load shipments + var shipments = _shipmentService.GetAllShipments(model.TrackingNumber, startDateValue, endDateValue, command.Page - 1, command.PageSize); + + gridModel.Data = shipments.Select(shipment => PrepareShipmentModel(shipment, false, false)); + gridModel.Total = shipments.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - //load shipments - var shipments = _shipmentService.GetAllShipments(model.TrackingNumber, startDateValue, endDateValue, - command.Page - 1, command.PageSize); - var gridModel = new GridModel - { - Data = shipments.Select(shipment => PrepareShipmentModel(shipment, false, false)), - Total = shipments.TotalCount - }; return new JsonResult { Data = gridModel @@ -2009,24 +2033,29 @@ public ActionResult ShipmentListSelect(GridCommand command, ShipmentListModel mo [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ShipmentsSelect(int orderId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); + var model = new GridModel(); - var order = _orderService.GetOrderById(orderId); - if (order == null) - throw new ArgumentException("No order found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var order = _orderService.GetOrderById(orderId); - //shipments - var shipmentModels = new List(); - var shipments = order.Shipments.OrderBy(s => s.CreatedOnUtc).ToList(); - foreach (var shipment in shipments) - shipmentModels.Add(PrepareShipmentModel(shipment, false, false)); + var shipmentModels = new List(); + var shipments = order.Shipments.OrderBy(s => s.CreatedOnUtc).ToList(); - var model = new GridModel - { - Data = shipmentModels, - Total = shipmentModels.Count - }; + foreach (var shipment in shipments) + { + shipmentModels.Add(PrepareShipmentModel(shipment, false, false)); + } + + model.Data = shipmentModels; + model.Total = shipmentModels.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -2311,37 +2340,40 @@ public ActionResult PdfPackagingSlips(bool all, string selectedIds = null) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult OrderNotesSelect(int orderId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); + var model = new GridModel(); - var order = _orderService.GetOrderById(orderId); - if (order == null) - throw new ArgumentException("No order found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var order = _orderService.GetOrderById(orderId); - var orderNoteModels = new List(); + var orderNoteModels = new List(); - foreach (var orderNote in order.OrderNotes.OrderByDescending(on => on.CreatedOnUtc)) - { - orderNoteModels.Add(new OrderModel.OrderNote - { - Id = orderNote.Id, - OrderId = orderNote.OrderId, - DisplayToCustomer = orderNote.DisplayToCustomer, - Note = orderNote.FormatOrderNoteText(), - CreatedOn = _dateTimeHelper.ConvertToUserTime(orderNote.CreatedOnUtc, DateTimeKind.Utc) - }); - } + foreach (var orderNote in order.OrderNotes.OrderByDescending(on => on.CreatedOnUtc)) + { + orderNoteModels.Add(new OrderModel.OrderNote + { + Id = orderNote.Id, + OrderId = orderNote.OrderId, + DisplayToCustomer = orderNote.DisplayToCustomer, + Note = orderNote.FormatOrderNoteText(), + CreatedOn = _dateTimeHelper.ConvertToUserTime(orderNote.CreatedOnUtc, DateTimeKind.Utc) + }); + } - var model = new GridModel - { - Data = orderNoteModels, - Total = orderNoteModels.Count - }; + model.Data = orderNoteModels; + model.Total = orderNoteModels.Count; - if (order.HasNewPaymentNotification) + if (order.HasNewPaymentNotification) + { + order.HasNewPaymentNotification = false; + _orderService.UpdateOrder(order); + } + } + else { - order.HasNewPaymentNotification = false; - _orderService.UpdateOrder(order); + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); } return new JsonResult @@ -2385,17 +2417,13 @@ public ActionResult OrderNoteAdd(int orderId, bool displayToCustomer, string mes [GridAction(EnableCustomBinding = true)] public ActionResult OrderNoteDelete(int orderId, int orderNoteId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var order = _orderService.GetOrderById(orderId); - if (order == null) - throw new ArgumentException("No order found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var order = _orderService.GetOrderById(orderId); + var orderNote = order.OrderNotes.Where(on => on.Id == orderNoteId).FirstOrDefault(); - var orderNote = order.OrderNotes.Where(on => on.Id == orderNoteId).FirstOrDefault(); - if (orderNote == null) - throw new ArgumentException("No order note found with the specified id"); - _orderService.DeleteOrderNote(orderNote); + _orderService.DeleteOrderNote(orderNote); + } return OrderNotesSelect(orderId, command); } @@ -2433,12 +2461,14 @@ protected IList GetBestsellersBriefReportModel(int r return model; } - public ActionResult BestsellersBriefReportByQuantity() + + public ActionResult BestsellersBriefReportByQuantity() { var model = GetBestsellersBriefReportModel(5, 1); return PartialView(model); } - [GridAction(EnableCustomBinding = true)] + + [GridAction(EnableCustomBinding = true)] public ActionResult BestsellersBriefReportByQuantityList(GridCommand command) { var model = GetBestsellersBriefReportModel(5, 1); @@ -2452,12 +2482,14 @@ public ActionResult BestsellersBriefReportByQuantityList(GridCommand command) Data = gridModel }; } - public ActionResult BestsellersBriefReportByAmount() + + public ActionResult BestsellersBriefReportByAmount() { var model = GetBestsellersBriefReportModel(5, 2); return PartialView(model); } - [GridAction(EnableCustomBinding = true)] + + [GridAction(EnableCustomBinding = true)] public ActionResult BestsellersBriefReportByAmountList(GridCommand command) { var model = GetBestsellersBriefReportModel(5, 2); @@ -2496,27 +2528,29 @@ public ActionResult BestsellersReport() return View(model); } - [GridAction(EnableCustomBinding = true)] + + [GridAction(EnableCustomBinding = true)] public ActionResult BestsellersReportList(GridCommand command, BestsellersReportModel model) { DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); DateTime? endDateValue = (model.EndDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); OrderStatus? orderStatus = model.OrderStatusId > 0 ? (OrderStatus?)(model.OrderStatusId) : null; PaymentStatus? paymentStatus = model.PaymentStatusId > 0 ? (PaymentStatus?)(model.PaymentStatusId) : null; var items = _orderReportService.BestSellersReport(0, startDateValue, endDateValue, orderStatus, paymentStatus, null, model.BillingCountryId, 100, 2, true); + var gridModel = new GridModel { Data = items.Select(x => { var product = _productService.GetProductById(x.ProductId); - var m = new BestsellersReportLineModel() + var m = new BestsellersReportLineModel { ProductId = x.ProductId, TotalAmount = _priceFormatter.FormatPrice(x.TotalAmount, true, false), @@ -2533,6 +2567,7 @@ public ActionResult BestsellersReportList(GridCommand command, BestsellersReport }), Total = items.Count }; + return new JsonResult { Data = gridModel @@ -2546,17 +2581,18 @@ public ActionResult NeverSoldReport() var model = new NeverSoldReportModel(); return View(model); } - [GridAction(EnableCustomBinding = true)] + + [GridAction(EnableCustomBinding = true)] public ActionResult NeverSoldReportList(GridCommand command, NeverSoldReportModel model) { DateTime? startDateValue = (model.StartDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.StartDate.Value, _dateTimeHelper.CurrentTimeZone); DateTime? endDateValue = (model.EndDate == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.EndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - var items = _orderReportService.ProductsNeverSold(startDateValue, endDateValue, - command.Page - 1, command.PageSize, true); + var items = _orderReportService.ProductsNeverSold(startDateValue, endDateValue, command.Page - 1, command.PageSize, true); + var gridModel = new GridModel { Data = items.Select(x => @@ -2572,6 +2608,7 @@ public ActionResult NeverSoldReportList(GridCommand command, NeverSoldReportMode }), Total = items.TotalCount }; + return new JsonResult { Data = gridModel @@ -2680,13 +2717,15 @@ protected virtual IList GetOrderIncompleteReport return model; } - [ChildActionOnly] + + [ChildActionOnly] public ActionResult OrderIncompleteReport() { var model = GetOrderIncompleteReportModel(); return PartialView(model); } - [GridAction(EnableCustomBinding = true)] + + [GridAction(EnableCustomBinding = true)] public ActionResult OrderIncompleteReportList(GridCommand command) { var model = GetOrderIncompleteReportModel(); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PollController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PollController.cs index c6c6abac89..efbf8ad4ad 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PollController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PollController.cs @@ -103,24 +103,36 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) + { + var polls = _pollService.GetPolls(0, false, command.Page - 1, command.PageSize, true); + + gridModel.Data = polls.Select(x => + { + var m = x.ToModel(); + + if (x.StartDateUtc.HasValue) + m.StartDate = _dateTimeHelper.ConvertToUserTime(x.StartDateUtc.Value, DateTimeKind.Utc); + + if (x.EndDateUtc.HasValue) + m.EndDate = _dateTimeHelper.ConvertToUserTime(x.EndDateUtc.Value, DateTimeKind.Utc); + + m.LanguageName = x.Language.Name; + + return m; + }); + + gridModel.Total = polls.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var polls = _pollService.GetPolls(0, false, command.Page - 1, command.PageSize, true); - var gridModel = new GridModel - { - Data = polls.Select(x => - { - var m = x.ToModel(); - if (x.StartDateUtc.HasValue) - m.StartDate = _dateTimeHelper.ConvertToUserTime(x.StartDateUtc.Value, DateTimeKind.Utc); - if (x.EndDateUtc.HasValue) - m.EndDate = _dateTimeHelper.ConvertToUserTime(x.EndDateUtc.Value, DateTimeKind.Utc); - m.LanguageName = x.Language.Name; - return m; - }), - Total = polls.TotalCount - }; return new JsonResult { Data = gridModel @@ -245,30 +257,35 @@ public ActionResult DeleteConfirmed(int id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult PollAnswers(int pollId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) - return AccessDeniedView(); + var model = new GridModel(); - var poll = _pollService.GetPollById(pollId); - if (poll == null) - throw new ArgumentException("No poll found with the specified id", "pollId"); + if (_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) + { + var poll = _pollService.GetPollById(pollId); + + var answers = poll.PollAnswers.OrderBy(x => x.DisplayOrder).ToList(); + + model.Data = answers.Select(x => + { + return new PollAnswerModel + { + Id = x.Id, + PollId = x.PollId, + Name = x.Name, + NumberOfVotes = x.NumberOfVotes, + DisplayOrder1 = x.DisplayOrder + }; + }); + + model.Total = answers.Count; + } + else + { + model.Data = Enumerable.Empty(); - var answers = poll.PollAnswers.OrderBy(x=>x.DisplayOrder).ToList(); + NotifyAccessDenied(); + } - var model = new GridModel - { - Data = answers.Select(x => - { - return new PollAnswerModel() - { - Id = x.Id, - PollId = x.PollId, - Name = x.Name, - NumberOfVotes = x.NumberOfVotes, - DisplayOrder1 = x.DisplayOrder - }; - }), - Total = answers.Count - }; return new JsonResult { Data = model @@ -279,23 +296,21 @@ public ActionResult PollAnswers(int pollId, GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult PollAnswerUpdate(PollAnswerModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) - return AccessDeniedView(); - - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + var pollAnswer = _pollService.GetPollAnswerById(model.Id); - var pollAnswer = _pollService.GetPollAnswerById(model.Id); - if (pollAnswer == null) - throw new ArgumentException("No poll answer found with the specified id", "id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } + + pollAnswer.Name = model.Name; + pollAnswer.DisplayOrder = model.DisplayOrder1; - pollAnswer.Name = model.Name; - pollAnswer.DisplayOrder = model.DisplayOrder1; - _pollService.UpdatePoll(pollAnswer.Poll); + _pollService.UpdatePoll(pollAnswer.Poll); + } return PollAnswers(pollAnswer.PollId, command); } @@ -303,44 +318,39 @@ public ActionResult PollAnswerUpdate(PollAnswerModel model, GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult PollAnswerAdd(int pollId, PollAnswerModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) - return AccessDeniedView(); - - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + if (_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - var poll = _pollService.GetPollById(pollId); - if (poll == null) - throw new ArgumentException("No poll found with the specified id", "pollId"); + var poll = _pollService.GetPollById(pollId); - poll.PollAnswers.Add(new PollAnswer - { - Name = model.Name, - DisplayOrder = model.DisplayOrder1 - }); - _pollService.UpdatePoll(poll); + poll.PollAnswers.Add(new PollAnswer + { + Name = model.Name, + DisplayOrder = model.DisplayOrder1 + }); + + _pollService.UpdatePoll(poll); + } - return PollAnswers(poll.Id, command); + return PollAnswers(pollId, command); } [GridAction(EnableCustomBinding = true)] public ActionResult PollAnswerDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) - return AccessDeniedView(); - - var pollAnswer = _pollService.GetPollAnswerById(id); - if (pollAnswer == null) - throw new ArgumentException("No poll answer found with the specified id", "id"); - - int pollId = pollAnswer.PollId; - _pollService.DeletePollAnswer(pollAnswer); + var pollAnswer = _pollService.GetPollAnswerById(id); + var pollId = pollAnswer.PollId; + if (_permissionService.Authorize(StandardPermissionProvider.ManagePolls)) + { + _pollService.DeletePollAnswer(pollAnswer); + } return PollAnswers(pollId, command); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductAttributeController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductAttributeController.cs index 9381bd50ee..ea2a6ae606 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductAttributeController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductAttributeController.cs @@ -91,29 +91,35 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productAttributes = _productAttributeService.GetAllProductAttributes(); + + gridModel.Data = productAttributes.Select(x => x.ToModel()); + gridModel.Total = productAttributes.Count(); + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var productAttributes = _productAttributeService.GetAllProductAttributes(); - var gridModel = new GridModel - { - Data = productAttributes.Select(x => x.ToModel()), - Total = productAttributes.Count() - }; return new JsonResult { Data = gridModel }; } - //create public ActionResult Create() { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); var model = new ProductAttributeModel(); - //locales + AddLocales(_languageService, model.Locales); return View(model); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index e1f0748bfc..1371b0d5c8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -895,67 +895,74 @@ public ActionResult List(ProductListModel model) return View(model); } - [HttpPost, GridAction(EnableCustomBinding = true)] - public ActionResult ProductList(GridCommand command, ProductListModel model) - { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + [HttpPost, GridAction(EnableCustomBinding = true)] + public ActionResult ProductList(GridCommand command, ProductListModel model) + { + var gridModel = new GridModel(); - var gridModel = new GridModel(); - var searchContext = new ProductSearchContext - { - ManufacturerId = model.SearchManufacturerId, - StoreId = model.SearchStoreId, - Keywords = model.SearchProductName, - SearchSku = !_catalogSettings.SuppressSkuSearch, - LanguageId = _workContext.WorkingLanguage.Id, - OrderBy = ProductSortingEnum.Position, - PageIndex = command.Page - 1, - PageSize = command.PageSize, - ShowHidden = true, - ProductType = (model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null), - WithoutCategories = model.SearchWithoutCategories, - WithoutManufacturers = model.SearchWithoutManufacturers, - IsPublished = model.SearchIsPublished, - HomePageProducts = model.SearchHomePageProducts - }; + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var searchContext = new ProductSearchContext + { + ManufacturerId = model.SearchManufacturerId, + StoreId = model.SearchStoreId, + Keywords = model.SearchProductName, + SearchSku = !_catalogSettings.SuppressSkuSearch, + LanguageId = _workContext.WorkingLanguage.Id, + OrderBy = ProductSortingEnum.Position, + PageIndex = command.Page - 1, + PageSize = command.PageSize, + ShowHidden = true, + ProductType = (model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null), + WithoutCategories = model.SearchWithoutCategories, + WithoutManufacturers = model.SearchWithoutManufacturers, + IsPublished = model.SearchIsPublished, + HomePageProducts = model.SearchHomePageProducts + }; - if (model.SearchCategoryId > 0) - searchContext.CategoryIds.Add(model.SearchCategoryId); + if (model.SearchCategoryId > 0) + searchContext.CategoryIds.Add(model.SearchCategoryId); - if (command.SortDescriptors != null && command.SortDescriptors.Count > 0) - { - var sort = command.SortDescriptors.First(); - if (sort.Member == "Name") - { - searchContext.OrderBy = (sort.SortDirection == ListSortDirection.Ascending ? ProductSortingEnum.NameAsc : ProductSortingEnum.NameDesc); - } - else if (sort.Member == "Price") - { - searchContext.OrderBy = (sort.SortDirection == ListSortDirection.Ascending ? ProductSortingEnum.PriceAsc : ProductSortingEnum.PriceDesc); - } - else if (sort.Member == "CreatedOn") + if (command.SortDescriptors != null && command.SortDescriptors.Count > 0) { - searchContext.OrderBy = (sort.SortDirection == ListSortDirection.Ascending ? ProductSortingEnum.CreatedOnAsc : ProductSortingEnum.CreatedOn); + var sort = command.SortDescriptors.First(); + if (sort.Member == "Name") + { + searchContext.OrderBy = (sort.SortDirection == ListSortDirection.Ascending ? ProductSortingEnum.NameAsc : ProductSortingEnum.NameDesc); + } + else if (sort.Member == "Price") + { + searchContext.OrderBy = (sort.SortDirection == ListSortDirection.Ascending ? ProductSortingEnum.PriceAsc : ProductSortingEnum.PriceDesc); + } + else if (sort.Member == "CreatedOn") + { + searchContext.OrderBy = (sort.SortDirection == ListSortDirection.Ascending ? ProductSortingEnum.CreatedOnAsc : ProductSortingEnum.CreatedOn); + } } - } - var products = _productService.SearchProducts(searchContext); + var products = _productService.SearchProducts(searchContext); - gridModel.Data = products.Select(x => - { - var productModel = x.ToModel(); - productModel.FullDescription = ""; // Perf - PrepareProductPictureThumbnailModel(productModel, x); + gridModel.Data = products.Select(x => + { + var productModel = x.ToModel(); + productModel.FullDescription = ""; // Perf + PrepareProductPictureThumbnailModel(productModel, x); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); - productModel.UpdatedOn = _dateTimeHelper.ConvertToUserTime(x.UpdatedOnUtc, DateTimeKind.Utc); - productModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); + productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); + productModel.UpdatedOn = _dateTimeHelper.ConvertToUserTime(x.UpdatedOnUtc, DateTimeKind.Utc); + productModel.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - return productModel; - }); + return productModel; + }); + + gridModel.Total = products.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); - gridModel.Total = products.TotalCount; + NotifyAccessDenied(); + } return new JsonResult { @@ -1288,30 +1295,35 @@ public ActionResult CopyProduct(ProductModel model) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductCategoryList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var productCategories = _categoryService.GetProductCategoriesByProductId(productId, true); - var productCategoriesModel = productCategories - .Select(x => - { - return new ProductModel.ProductCategoryModel() - { - Id = x.Id, - Category = _categoryService.GetCategoryById(x.CategoryId).GetCategoryBreadCrumb(_categoryService), - ProductId = x.ProductId, - CategoryId = x.CategoryId, - IsFeaturedProduct = x.IsFeaturedProduct, - DisplayOrder = x.DisplayOrder - }; - }) - .ToList(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productCategories = _categoryService.GetProductCategoriesByProductId(productId, true); + var productCategoriesModel = productCategories + .Select(x => + { + return new ProductModel.ProductCategoryModel() + { + Id = x.Id, + Category = _categoryService.GetCategoryById(x.CategoryId).GetCategoryBreadCrumb(_categoryService), + ProductId = x.ProductId, + CategoryId = x.CategoryId, + IsFeaturedProduct = x.IsFeaturedProduct, + DisplayOrder = x.DisplayOrder + }; + }) + .ToList(); - var model = new GridModel - { - Data = productCategoriesModel, - Total = productCategoriesModel.Count - }; + model.Data = productCategoriesModel; + model.Total = productCategoriesModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -1322,23 +1334,23 @@ public ActionResult ProductCategoryList(GridCommand command, int productId) [GridAction(EnableCustomBinding = true)] public ActionResult ProductCategoryInsert(GridCommand command, ProductModel.ProductCategoryModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productCategory = new ProductCategory + { + ProductId = model.ProductId, + CategoryId = Int32.Parse(model.Category), //use Category property (not CategoryId) because appropriate property is stored in it + IsFeaturedProduct = model.IsFeaturedProduct, + DisplayOrder = model.DisplayOrder + }; - var productCategory = new ProductCategory() - { - ProductId = model.ProductId, - CategoryId = Int32.Parse(model.Category), //use Category property (not CategoryId) because appropriate property is stored in it - IsFeaturedProduct = model.IsFeaturedProduct, - DisplayOrder = model.DisplayOrder - }; - _categoryService.InsertProductCategory(productCategory); + _categoryService.InsertProductCategory(productCategory); - - var mru = new MostRecentlyUsedList(_workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.MostRecentlyUsedCategories), - model.Category, _catalogSettings.MostRecentlyUsedCategoriesMaxSize); + var mru = new MostRecentlyUsedList(_workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.MostRecentlyUsedCategories), + model.Category, _catalogSettings.MostRecentlyUsedCategoriesMaxSize); - _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.MostRecentlyUsedCategories, mru.ToString()); + _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.MostRecentlyUsedCategories, mru.ToString()); + } return ProductCategoryList(command, model.ProductId); } @@ -1346,27 +1358,25 @@ public ActionResult ProductCategoryInsert(GridCommand command, ProductModel.Prod [GridAction(EnableCustomBinding = true)] public ActionResult ProductCategoryUpdate(GridCommand command, ProductModel.ProductCategoryModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var productCategory = _categoryService.GetProductCategoryById(model.Id); - var productCategory = _categoryService.GetProductCategoryById(model.Id); - if (productCategory == null) - throw new ArgumentException("No product category mapping found with the specified id"); - - bool categoryChanged = (Int32.Parse(model.Category) != productCategory.CategoryId); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var categoryChanged = (Int32.Parse(model.Category) != productCategory.CategoryId); - //use Category property (not CategoryId) because appropriate property is stored in it - productCategory.CategoryId = Int32.Parse(model.Category); - productCategory.IsFeaturedProduct = model.IsFeaturedProduct; - productCategory.DisplayOrder = model.DisplayOrder; - _categoryService.UpdateProductCategory(productCategory); + //use Category property (not CategoryId) because appropriate property is stored in it + productCategory.CategoryId = Int32.Parse(model.Category); + productCategory.IsFeaturedProduct = model.IsFeaturedProduct; + productCategory.DisplayOrder = model.DisplayOrder; + _categoryService.UpdateProductCategory(productCategory); - if (categoryChanged) - { - var mru = new MostRecentlyUsedList(_workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.MostRecentlyUsedCategories), - model.Category, _catalogSettings.MostRecentlyUsedCategoriesMaxSize); + if (categoryChanged) + { + var mru = new MostRecentlyUsedList(_workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.MostRecentlyUsedCategories), + model.Category, _catalogSettings.MostRecentlyUsedCategoriesMaxSize); - _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.MostRecentlyUsedCategories, mru.ToString()); + _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.MostRecentlyUsedCategories, mru.ToString()); + } } return ProductCategoryList(command, productCategory.ProductId); @@ -1375,15 +1385,13 @@ public ActionResult ProductCategoryUpdate(GridCommand command, ProductModel.Prod [GridAction(EnableCustomBinding = true)] public ActionResult ProductCategoryDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var productCategory = _categoryService.GetProductCategoryById(id); - if (productCategory == null) - throw new ArgumentException("No product category mapping found with the specified id"); + var productCategory = _categoryService.GetProductCategoryById(id); + var productId = productCategory.ProductId; - var productId = productCategory.ProductId; - _categoryService.DeleteProductCategory(productCategory); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _categoryService.DeleteProductCategory(productCategory); + } return ProductCategoryList(command, productId); } @@ -1395,30 +1403,35 @@ public ActionResult ProductCategoryDelete(int id, GridCommand command) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductManufacturerList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var productManufacturers = _manufacturerService.GetProductManufacturersByProductId(productId, true); - var productManufacturersModel = productManufacturers - .Select(x => - { - return new ProductModel.ProductManufacturerModel() - { - Id = x.Id, - Manufacturer = _manufacturerService.GetManufacturerById(x.ManufacturerId).Name, - ProductId = x.ProductId, - ManufacturerId = x.ManufacturerId, - IsFeaturedProduct = x.IsFeaturedProduct, - DisplayOrder = x.DisplayOrder - }; - }) - .ToList(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productManufacturers = _manufacturerService.GetProductManufacturersByProductId(productId, true); + var productManufacturersModel = productManufacturers + .Select(x => + { + return new ProductModel.ProductManufacturerModel + { + Id = x.Id, + Manufacturer = _manufacturerService.GetManufacturerById(x.ManufacturerId).Name, + ProductId = x.ProductId, + ManufacturerId = x.ManufacturerId, + IsFeaturedProduct = x.IsFeaturedProduct, + DisplayOrder = x.DisplayOrder + }; + }) + .ToList(); - var model = new GridModel - { - Data = productManufacturersModel, - Total = productManufacturersModel.Count - }; + model.Data = productManufacturersModel; + model.Total = productManufacturersModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -1429,22 +1442,23 @@ public ActionResult ProductManufacturerList(GridCommand command, int productId) [GridAction(EnableCustomBinding = true)] public ActionResult ProductManufacturerInsert(GridCommand command, ProductModel.ProductManufacturerModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productManufacturer = new ProductManufacturer + { + ProductId = model.ProductId, + ManufacturerId = Int32.Parse(model.Manufacturer), //use Manufacturer property (not ManufacturerId) because appropriate property is stored in it + IsFeaturedProduct = model.IsFeaturedProduct, + DisplayOrder = model.DisplayOrder + }; - var productManufacturer = new ProductManufacturer() - { - ProductId = model.ProductId, - ManufacturerId = Int32.Parse(model.Manufacturer), //use Manufacturer property (not ManufacturerId) because appropriate property is stored in it - IsFeaturedProduct = model.IsFeaturedProduct, - DisplayOrder = model.DisplayOrder - }; - _manufacturerService.InsertProductManufacturer(productManufacturer); + _manufacturerService.InsertProductManufacturer(productManufacturer); - var mru = new MostRecentlyUsedList(_workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.MostRecentlyUsedManufacturers), - model.Manufacturer, _catalogSettings.MostRecentlyUsedManufacturersMaxSize); + var mru = new MostRecentlyUsedList(_workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.MostRecentlyUsedManufacturers), + model.Manufacturer, _catalogSettings.MostRecentlyUsedManufacturersMaxSize); - _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.MostRecentlyUsedManufacturers, mru.ToString()); + _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.MostRecentlyUsedManufacturers, mru.ToString()); + } return ProductManufacturerList(command, model.ProductId); } @@ -1452,27 +1466,26 @@ public ActionResult ProductManufacturerInsert(GridCommand command, ProductModel. [GridAction(EnableCustomBinding = true)] public ActionResult ProductManufacturerUpdate(GridCommand command, ProductModel.ProductManufacturerModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var productManufacturer = _manufacturerService.GetProductManufacturerById(model.Id); - var productManufacturer = _manufacturerService.GetProductManufacturerById(model.Id); - if (productManufacturer == null) - throw new ArgumentException("No product manufacturer mapping found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var manufacturerChanged = (Int32.Parse(model.Manufacturer) != productManufacturer.ManufacturerId); - bool manufacturerChanged = (Int32.Parse(model.Manufacturer) != productManufacturer.ManufacturerId); + //use Manufacturer property (not ManufacturerId) because appropriate property is stored in it + productManufacturer.ManufacturerId = Int32.Parse(model.Manufacturer); + productManufacturer.IsFeaturedProduct = model.IsFeaturedProduct; + productManufacturer.DisplayOrder = model.DisplayOrder; - //use Manufacturer property (not ManufacturerId) because appropriate property is stored in it - productManufacturer.ManufacturerId = Int32.Parse(model.Manufacturer); - productManufacturer.IsFeaturedProduct = model.IsFeaturedProduct; - productManufacturer.DisplayOrder = model.DisplayOrder; - _manufacturerService.UpdateProductManufacturer(productManufacturer); + _manufacturerService.UpdateProductManufacturer(productManufacturer); - if (manufacturerChanged) - { - var mru = new MostRecentlyUsedList(_workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.MostRecentlyUsedManufacturers), - model.Manufacturer, _catalogSettings.MostRecentlyUsedManufacturersMaxSize); + if (manufacturerChanged) + { + var mru = new MostRecentlyUsedList(_workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.MostRecentlyUsedManufacturers), + model.Manufacturer, _catalogSettings.MostRecentlyUsedManufacturersMaxSize); - _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.MostRecentlyUsedManufacturers, mru.ToString()); + _genericAttributeService.SaveAttribute(_workContext.CurrentCustomer, SystemCustomerAttributeNames.MostRecentlyUsedManufacturers, mru.ToString()); + } } return ProductManufacturerList(command, productManufacturer.ProductId); @@ -1481,15 +1494,13 @@ public ActionResult ProductManufacturerUpdate(GridCommand command, ProductModel. [GridAction(EnableCustomBinding = true)] public ActionResult ProductManufacturerDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var productManufacturer = _manufacturerService.GetProductManufacturerById(id); + var productId = productManufacturer.ProductId; - var productManufacturer = _manufacturerService.GetProductManufacturerById(id); - if (productManufacturer == null) - throw new ArgumentException("No product manufacturer mapping found with the specified id"); - - var productId = productManufacturer.ProductId; - _manufacturerService.DeleteProductManufacturer(productManufacturer); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _manufacturerService.DeleteProductManufacturer(productManufacturer); + } return ProductManufacturerList(command, productId); } @@ -1501,34 +1512,39 @@ public ActionResult ProductManufacturerDelete(int id, GridCommand command) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult RelatedProductList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var relatedProducts = _productService.GetRelatedProductsByProductId1(productId, true); - var relatedProductsModel = relatedProducts - .Select(x => - { - var product2 = _productService.GetProductById(x.ProductId2); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var relatedProducts = _productService.GetRelatedProductsByProductId1(productId, true); + var relatedProductsModel = relatedProducts + .Select(x => + { + var product2 = _productService.GetProductById(x.ProductId2); - return new ProductModel.RelatedProductModel() - { - Id = x.Id, - ProductId2 = x.ProductId2, - Product2Name = product2.Name, - ProductTypeName = product2.GetProductTypeLabel(_localizationService), - ProductTypeLabelHint = product2.ProductTypeLabelHint, - DisplayOrder = x.DisplayOrder, - Product2Sku = product2.Sku, - Product2Published = product2.Published - }; - }) - .ToList(); + return new ProductModel.RelatedProductModel() + { + Id = x.Id, + ProductId2 = x.ProductId2, + Product2Name = product2.Name, + ProductTypeName = product2.GetProductTypeLabel(_localizationService), + ProductTypeLabelHint = product2.ProductTypeLabelHint, + DisplayOrder = x.DisplayOrder, + Product2Sku = product2.Sku, + Product2Published = product2.Published + }; + }) + .ToList(); - var model = new GridModel - { - Data = relatedProductsModel, - Total = relatedProductsModel.Count - }; + model.Data = relatedProductsModel; + model.Total = relatedProductsModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -1539,15 +1555,13 @@ public ActionResult RelatedProductList(GridCommand command, int productId) [GridAction(EnableCustomBinding = true)] public ActionResult RelatedProductUpdate(GridCommand command, ProductModel.RelatedProductModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var relatedProduct = _productService.GetRelatedProductById(model.Id); - if (relatedProduct == null) - throw new ArgumentException("No related product found with the specified id"); + var relatedProduct = _productService.GetRelatedProductById(model.Id); - relatedProduct.DisplayOrder = model.DisplayOrder; - _productService.UpdateRelatedProduct(relatedProduct); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + relatedProduct.DisplayOrder = model.DisplayOrder; + _productService.UpdateRelatedProduct(relatedProduct); + } return RelatedProductList(command, relatedProduct.ProductId1); } @@ -1555,15 +1569,13 @@ public ActionResult RelatedProductUpdate(GridCommand command, ProductModel.Relat [GridAction(EnableCustomBinding = true)] public ActionResult RelatedProductDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var relatedProduct = _productService.GetRelatedProductById(id); - if (relatedProduct == null) - throw new ArgumentException("No related product found with the specified id"); + var relatedProduct = _productService.GetRelatedProductById(id); + var productId = relatedProduct.ProductId1; - var productId = relatedProduct.ProductId1; - _productService.DeleteRelatedProduct(relatedProduct); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _productService.DeleteRelatedProduct(relatedProduct); + } return RelatedProductList(command, productId); } @@ -1623,36 +1635,45 @@ public ActionResult RelatedProductAddPopup(int productId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult RelatedProductAddPopupList(GridCommand command, ProductModel.AddRelatedProductModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var gridModel = new GridModel(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var ctx = new ProductSearchContext(); - var ctx = new ProductSearchContext(); + if (model.SearchCategoryId > 0) + ctx.CategoryIds.Add(model.SearchCategoryId); - if (model.SearchCategoryId > 0) - ctx.CategoryIds.Add(model.SearchCategoryId); + ctx.ManufacturerId = model.SearchManufacturerId; + ctx.StoreId = model.SearchStoreId; + ctx.Keywords = model.SearchProductName; + ctx.SearchSku = !_catalogSettings.SuppressSkuSearch; + ctx.LanguageId = _workContext.WorkingLanguage.Id; + ctx.OrderBy = ProductSortingEnum.Position; + ctx.PageIndex = command.Page - 1; + ctx.PageSize = command.PageSize; + ctx.ShowHidden = true; + ctx.ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null; - ctx.ManufacturerId = model.SearchManufacturerId; - ctx.StoreId = model.SearchStoreId; - ctx.Keywords = model.SearchProductName; - ctx.SearchSku = !_catalogSettings.SuppressSkuSearch; - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageIndex = command.Page - 1; - ctx.PageSize = command.PageSize; - ctx.ShowHidden = true; - ctx.ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null; + var products = _productService.SearchProducts(ctx); - var products = _productService.SearchProducts(ctx); - gridModel.Data = products.Select(x => + gridModel.Data = products.Select(x => + { + var productModel = x.ToModel(); + productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); + + return productModel; + }); + + gridModel.Total = products.TotalCount; + } + else { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - return productModel; - }); - gridModel.Total = products.TotalCount; return new JsonResult { Data = gridModel @@ -1732,33 +1753,38 @@ public ActionResult CreateAllMutuallyRelatedProducts(int productId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult CrossSellProductList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var crossSellProducts = _productService.GetCrossSellProductsByProductId1(productId, true); - var crossSellProductsModel = crossSellProducts - .Select(x => - { - var product2 = _productService.GetProductById(x.ProductId2); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var crossSellProducts = _productService.GetCrossSellProductsByProductId1(productId, true); + var crossSellProductsModel = crossSellProducts + .Select(x => + { + var product2 = _productService.GetProductById(x.ProductId2); - return new ProductModel.CrossSellProductModel() - { - Id = x.Id, - ProductId2 = x.ProductId2, - Product2Name = product2.Name, - ProductTypeName = product2.GetProductTypeLabel(_localizationService), - ProductTypeLabelHint = product2.ProductTypeLabelHint, - Product2Sku = product2.Sku, - Product2Published = product2.Published - }; - }) - .ToList(); + return new ProductModel.CrossSellProductModel + { + Id = x.Id, + ProductId2 = x.ProductId2, + Product2Name = product2.Name, + ProductTypeName = product2.GetProductTypeLabel(_localizationService), + ProductTypeLabelHint = product2.ProductTypeLabelHint, + Product2Sku = product2.Sku, + Product2Published = product2.Published + }; + }) + .ToList(); - var model = new GridModel - { - Data = crossSellProductsModel, - Total = crossSellProductsModel.Count - }; + model.Data = crossSellProductsModel; + model.Total = crossSellProductsModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -1769,15 +1795,13 @@ public ActionResult CrossSellProductList(GridCommand command, int productId) [GridAction(EnableCustomBinding = true)] public ActionResult CrossSellProductDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var crossSellProduct = _productService.GetCrossSellProductById(id); - if (crossSellProduct == null) - throw new ArgumentException("No cross-sell product found with the specified id"); + var crossSellProduct = _productService.GetCrossSellProductById(id); + var productId = crossSellProduct.ProductId1; - var productId = crossSellProduct.ProductId1; - _productService.DeleteCrossSellProduct(crossSellProduct); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _productService.DeleteCrossSellProduct(crossSellProduct); + } return CrossSellProductList(command, productId); } @@ -1837,37 +1861,45 @@ public ActionResult CrossSellProductAddPopup(int productId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult CrossSellProductAddPopupList(GridCommand command, ProductModel.AddCrossSellProductModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var gridModel = new GridModel(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var ctx = new ProductSearchContext(); - var ctx = new ProductSearchContext(); + if (model.SearchCategoryId > 0) + ctx.CategoryIds.Add(model.SearchCategoryId); - if (model.SearchCategoryId > 0) - ctx.CategoryIds.Add(model.SearchCategoryId); + ctx.ManufacturerId = model.SearchManufacturerId; + ctx.StoreId = model.SearchStoreId; + ctx.Keywords = model.SearchProductName; + ctx.SearchSku = !_catalogSettings.SuppressSkuSearch; + ctx.LanguageId = _workContext.WorkingLanguage.Id; + ctx.OrderBy = ProductSortingEnum.Position; + ctx.PageIndex = command.Page - 1; + ctx.PageSize = command.PageSize; + ctx.ShowHidden = true; + ctx.ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null; - ctx.ManufacturerId = model.SearchManufacturerId; - ctx.StoreId = model.SearchStoreId; - ctx.Keywords = model.SearchProductName; - ctx.SearchSku = !_catalogSettings.SuppressSkuSearch; - ctx.LanguageId = _workContext.WorkingLanguage.Id; - ctx.OrderBy = ProductSortingEnum.Position; - ctx.PageIndex = command.Page - 1; - ctx.PageSize = command.PageSize; - ctx.ShowHidden = true; - ctx.ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null; + var products = _productService.SearchProducts(ctx); - var products = _productService.SearchProducts(ctx); + gridModel.Data = products.Select(x => + { + var productModel = x.ToModel(); + productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); + + return productModel; + }); - gridModel.Data = products.Select(x => + gridModel.Total = products.TotalCount; + } + else { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - return productModel; - }); - gridModel.Total = products.TotalCount; return new JsonResult { Data = gridModel @@ -1946,38 +1978,43 @@ public ActionResult CreateAllMutuallyCrossSellProducts(int productId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult AssociatedProductList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var searchContext = new ProductSearchContext() + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) { - ParentGroupedProductId = productId, - PageSize = int.MaxValue, - ShowHidden = true - }; - - var associatedProducts = _productService.SearchProducts(searchContext); - var associatedProductsModel = associatedProducts - .Select(x => + var searchContext = new ProductSearchContext { - return new ProductModel.AssociatedProductModel() + ParentGroupedProductId = productId, + PageSize = int.MaxValue, + ShowHidden = true + }; + + var associatedProducts = _productService.SearchProducts(searchContext); + var associatedProductsModel = associatedProducts + .Select(x => { - Id = x.Id, - ProductName = x.Name, - ProductTypeName = x.GetProductTypeLabel(_localizationService), - ProductTypeLabelHint = x.ProductTypeLabelHint, - DisplayOrder = x.DisplayOrder, - Sku = x.Sku, - Published = x.Published - }; - }) - .ToList(); + return new ProductModel.AssociatedProductModel + { + Id = x.Id, + ProductName = x.Name, + ProductTypeName = x.GetProductTypeLabel(_localizationService), + ProductTypeLabelHint = x.ProductTypeLabelHint, + DisplayOrder = x.DisplayOrder, + Sku = x.Sku, + Published = x.Published + }; + }) + .ToList(); - var model = new GridModel + model.Data = associatedProductsModel; + model.Total = associatedProductsModel.Count; + } + else { - Data = associatedProductsModel, - Total = associatedProductsModel.Count - }; + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -1988,15 +2025,13 @@ public ActionResult AssociatedProductList(GridCommand command, int productId) [GridAction(EnableCustomBinding = true)] public ActionResult AssociatedProductUpdate(GridCommand command, ProductModel.AssociatedProductModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - var associatedProduct = _productService.GetProductById(model.Id); - if (associatedProduct == null) - throw new ArgumentException("No associated product found with the specified id"); - associatedProduct.DisplayOrder = model.DisplayOrder; - _productService.UpdateProduct(associatedProduct); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + associatedProduct.DisplayOrder = model.DisplayOrder; + _productService.UpdateProduct(associatedProduct); + } return AssociatedProductList(command, associatedProduct.ParentGroupedProductId); } @@ -2004,19 +2039,16 @@ public ActionResult AssociatedProductUpdate(GridCommand command, ProductModel.As [GridAction(EnableCustomBinding = true)] public ActionResult AssociatedProductDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - var product = _productService.GetProductById(id); - if (product == null) - throw new ArgumentException("No associated product found with the specified id"); - var originalParentGroupedProductId = product.ParentGroupedProductId; - product.ParentGroupedProductId = 0; - _productService.UpdateProduct(product); - - return AssociatedProductList(command, originalParentGroupedProductId); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + product.ParentGroupedProductId = 0; + _productService.UpdateProduct(product); + } + + return AssociatedProductList(command, originalParentGroupedProductId); } public ActionResult AssociatedProductAddPopup(int productId) @@ -2057,33 +2089,41 @@ public ActionResult AssociatedProductAddPopup(int productId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult AssociatedProductAddPopupList(GridCommand command, ProductModel.AddAssociatedProductModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var searchContext = new ProductSearchContext() - { - CategoryIds = new List() { model.SearchCategoryId }, - ManufacturerId = model.SearchManufacturerId, - StoreId = model.SearchStoreId, - Keywords = model.SearchProductName, - SearchSku = !_catalogSettings.SuppressSkuSearch, - PageIndex = command.Page - 1, - PageSize = command.PageSize, - ShowHidden = true, - ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null - }; + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var searchContext = new ProductSearchContext + { + CategoryIds = new List() { model.SearchCategoryId }, + ManufacturerId = model.SearchManufacturerId, + StoreId = model.SearchStoreId, + Keywords = model.SearchProductName, + SearchSku = !_catalogSettings.SuppressSkuSearch, + PageIndex = command.Page - 1, + PageSize = command.PageSize, + ShowHidden = true, + ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null + }; + + var products = _productService.SearchProducts(searchContext); - var products = _productService.SearchProducts(searchContext); + gridModel.Data = products.Select(x => + { + var productModel = x.ToModel(); + productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); - var gridModel = new GridModel(); - gridModel.Data = products.Select(x => + return productModel; + }); + + gridModel.Total = products.TotalCount; + } + else { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); + gridModel.Data = Enumerable.Empty(); - return productModel; - }); - gridModel.Total = products.TotalCount; + NotifyAccessDenied(); + } return new JsonResult { @@ -2222,34 +2262,39 @@ private void SaveFilteredAttributes(ProductBundleItem bundleItem, FormCollection [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult BundleItemList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var bundleItems = _productService.GetBundleItems(productId, true).Select(x => x.Item); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var bundleItems = _productService.GetBundleItems(productId, true).Select(x => x.Item); - var bundleItemsModel = bundleItems.Select(x => - { - return new ProductModel.BundleItemModel() + var bundleItemsModel = bundleItems.Select(x => { - Id = x.Id, - ProductId = x.Product.Id, - ProductName = x.Product.Name, - ProductTypeName = x.Product.GetProductTypeLabel(_localizationService), - ProductTypeLabelHint = x.Product.ProductTypeLabelHint, - Sku = x.Product.Sku, - Quantity = x.Quantity, - Discount = x.Discount, - DisplayOrder = x.DisplayOrder, - Visible = x.Visible, - Published = x.Published - }; - }).ToList(); + return new ProductModel.BundleItemModel + { + Id = x.Id, + ProductId = x.Product.Id, + ProductName = x.Product.Name, + ProductTypeName = x.Product.GetProductTypeLabel(_localizationService), + ProductTypeLabelHint = x.Product.ProductTypeLabelHint, + Sku = x.Product.Sku, + Quantity = x.Quantity, + Discount = x.Discount, + DisplayOrder = x.DisplayOrder, + Visible = x.Visible, + Published = x.Published + }; + }).ToList(); - var model = new GridModel() + model.Data = bundleItemsModel; + model.Total = bundleItemsModel.Count; + } + else { - Data = bundleItemsModel, - Total = bundleItemsModel.Count - }; + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { Data = model }; } @@ -2257,15 +2302,12 @@ public ActionResult BundleItemList(GridCommand command, int productId) [GridAction(EnableCustomBinding = true)] public ActionResult BundleItemDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - var bundleItem = _productService.GetBundleItemById(id); - if (bundleItem == null) - throw new ArgumentException("No product bundle item found with the specified id"); - - _productService.DeleteBundleItem(bundleItem); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _productService.DeleteBundleItem(bundleItem); + } return BundleItemList(command, bundleItem.BundleProductId); } @@ -2369,34 +2411,42 @@ public ActionResult BundleItemAddPopup(string btnId, string formId, ProductModel [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult BundleItemAddPopupList(GridCommand command, ProductModel.AddBundleItemModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var searchContext = new ProductSearchContext() - { - CategoryIds = new List() { model.SearchCategoryId }, - ManufacturerId = model.SearchManufacturerId, - StoreId = model.SearchStoreId, - Keywords = model.SearchProductName, - SearchSku = !_catalogSettings.SuppressSkuSearch, - PageIndex = command.Page - 1, - PageSize = command.PageSize, - ShowHidden = true, - ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null - }; + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var searchContext = new ProductSearchContext + { + CategoryIds = new List { model.SearchCategoryId }, + ManufacturerId = model.SearchManufacturerId, + StoreId = model.SearchStoreId, + Keywords = model.SearchProductName, + SearchSku = !_catalogSettings.SuppressSkuSearch, + PageIndex = command.Page - 1, + PageSize = command.PageSize, + ShowHidden = true, + ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null + }; - var products = _productService.SearchProducts(searchContext); + var products = _productService.SearchProducts(searchContext); - var gridModel = new GridModel(); - gridModel.Data = products.Select(x => + gridModel.Data = products.Select(x => + { + var productModel = x.ToModel(); + productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); + productModel.ProductSelectCheckboxClass = (!x.CanBeBundleItem() ? " hide" : ""); + + return productModel; + }); + + gridModel.Total = products.TotalCount; + } + else { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); - productModel.ProductSelectCheckboxClass = (!x.CanBeBundleItem() ? " hide" : ""); + gridModel.Data = Enumerable.Empty(); - return productModel; - }); - gridModel.Total = products.TotalCount; + NotifyAccessDenied(); + } return new JsonResult { @@ -2498,29 +2548,34 @@ public ActionResult ProductPictureAdd(int pictureId, int displayOrder, int produ [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductPictureList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var productPictures = _productService.GetProductPicturesByProductId(productId); - var productPicturesModel = productPictures - .Select(x => - { - return new ProductModel.ProductPictureModel() - { - Id = x.Id, - ProductId = x.ProductId, - PictureId = x.PictureId, - PictureUrl = _pictureService.GetPictureUrl(x.PictureId), - DisplayOrder = x.DisplayOrder - }; - }) - .ToList(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productPictures = _productService.GetProductPicturesByProductId(productId); + var productPicturesModel = productPictures + .Select(x => + { + return new ProductModel.ProductPictureModel + { + Id = x.Id, + ProductId = x.ProductId, + PictureId = x.PictureId, + PictureUrl = _pictureService.GetPictureUrl(x.PictureId), + DisplayOrder = x.DisplayOrder + }; + }) + .ToList(); - var model = new GridModel - { - Data = productPicturesModel, - Total = productPicturesModel.Count - }; + model.Data = productPicturesModel; + model.Total = productPicturesModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -2531,15 +2586,13 @@ public ActionResult ProductPictureList(GridCommand command, int productId) [GridAction(EnableCustomBinding = true)] public ActionResult ProductPictureUpdate(ProductModel.ProductPictureModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var productPicture = _productService.GetProductPictureById(model.Id); - var productPicture = _productService.GetProductPictureById(model.Id); - if (productPicture == null) - throw new ArgumentException("No product picture found with the specified id"); - - productPicture.DisplayOrder = model.DisplayOrder; - _productService.UpdateProductPicture(productPicture); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + productPicture.DisplayOrder = model.DisplayOrder; + _productService.UpdateProductPicture(productPicture); + } return ProductPictureList(command, productPicture.ProductId); } @@ -2547,18 +2600,16 @@ public ActionResult ProductPictureUpdate(ProductModel.ProductPictureModel model, [GridAction(EnableCustomBinding = true)] public ActionResult ProductPictureDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var productPicture = _productService.GetProductPictureById(id); + var productId = productPicture.ProductId; - var productPicture = _productService.GetProductPictureById(id); - if (productPicture == null) - throw new ArgumentException("No product picture found with the specified id"); - - var productId = productPicture.ProductId; - _productService.DeleteProductPicture(productPicture); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _productService.DeleteProductPicture(productPicture); - var picture = _pictureService.GetPictureById(productPicture.PictureId); - _pictureService.DeletePicture(picture); + var picture = _pictureService.GetPictureById(productPicture.PictureId); + _pictureService.DeletePicture(picture); + } return ProductPictureList(command, productId); } @@ -2589,51 +2640,56 @@ public ActionResult ProductSpecificationAttributeAdd(int specificationAttributeO [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductSpecAttrList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var productrSpecs = _specificationAttributeService.GetProductSpecificationAttributesByProductId(productId); + var model = new GridModel(); - var productrSpecsModel = productrSpecs - .Select(x => - { - var psaModel = new ProductSpecificationAttributeModel() - { - Id = x.Id, - SpecificationAttributeName = x.SpecificationAttributeOption.SpecificationAttribute.Name, - SpecificationAttributeOptionName = x.SpecificationAttributeOption.Name, - SpecificationAttributeOptionAttributeId = x.SpecificationAttributeOption.SpecificationAttributeId, - SpecificationAttributeOptionId = x.SpecificationAttributeOptionId, - AllowFiltering = x.AllowFiltering, - ShowOnProductPage = x.ShowOnProductPage, - DisplayOrder = x.DisplayOrder - }; - return psaModel; - }) - .ToList(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productrSpecs = _specificationAttributeService.GetProductSpecificationAttributesByProductId(productId); - foreach(var attr in productrSpecsModel) { + var productrSpecsModel = productrSpecs + .Select(x => + { + var psaModel = new ProductSpecificationAttributeModel + { + Id = x.Id, + SpecificationAttributeName = x.SpecificationAttributeOption.SpecificationAttribute.Name, + SpecificationAttributeOptionName = x.SpecificationAttributeOption.Name, + SpecificationAttributeOptionAttributeId = x.SpecificationAttributeOption.SpecificationAttributeId, + SpecificationAttributeOptionId = x.SpecificationAttributeOptionId, + AllowFiltering = x.AllowFiltering, + ShowOnProductPage = x.ShowOnProductPage, + DisplayOrder = x.DisplayOrder + }; + return psaModel; + }) + .ToList(); - var options = _specificationAttributeService.GetSpecificationAttributeOptionsBySpecificationAttribute(attr.SpecificationAttributeOptionAttributeId); + foreach (var attr in productrSpecsModel) + { + var options = _specificationAttributeService.GetSpecificationAttributeOptionsBySpecificationAttribute(attr.SpecificationAttributeOptionAttributeId); - foreach(var option in options) { + foreach (var option in options) + { + attr.SpecificationAttributeOptions.Add(new ProductSpecificationAttributeModel.SpecificationAttributeOption + { + id = option.Id, + name = option.Name, + text = option.Name + }); + } - attr.SpecificationAttributeOptions.Add(new ProductSpecificationAttributeModel.SpecificationAttributeOption() - { - id = option.Id, - name = option.Name, - text = option.Name - }); - } + attr.SpecificationAttributeOptionsJsonString = JsonConvert.SerializeObject(attr.SpecificationAttributeOptions); + } - attr.SpecificationAttributeOptionsJsonString = JsonConvert.SerializeObject(attr.SpecificationAttributeOptions); - } + model.Data = productrSpecsModel; + model.Total = productrSpecsModel.Count; + } + else + { + model.Data = Enumerable.Empty(); - var model = new GridModel - { - Data = productrSpecsModel, - Total = productrSpecsModel.Count - }; + NotifyAccessDenied(); + } return new JsonResult { @@ -2642,17 +2698,17 @@ public ActionResult ProductSpecAttrList(GridCommand command, int productId) } [GridAction(EnableCustomBinding = true)] - public ActionResult ProductSpecAttrUpdate(int psaId, ProductSpecificationAttributeModel model, - GridCommand command) + public ActionResult ProductSpecAttrUpdate(int psaId, ProductSpecificationAttributeModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var psa = _specificationAttributeService.GetProductSpecificationAttributeById(psaId); - var psa = _specificationAttributeService.GetProductSpecificationAttributeById(psaId); - psa.AllowFiltering = model.AllowFiltering; - psa.ShowOnProductPage = model.ShowOnProductPage; - psa.DisplayOrder = model.DisplayOrder; - _specificationAttributeService.UpdateProductSpecificationAttribute(psa); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + psa.AllowFiltering = model.AllowFiltering; + psa.ShowOnProductPage = model.ShowOnProductPage; + psa.DisplayOrder = model.DisplayOrder; + _specificationAttributeService.UpdateProductSpecificationAttribute(psa); + } return ProductSpecAttrList(command, psa.ProductId); } @@ -2660,15 +2716,13 @@ public ActionResult ProductSpecAttrUpdate(int psaId, ProductSpecificationAttribu [GridAction(EnableCustomBinding = true)] public ActionResult ProductSpecAttrDelete(int psaId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var psa = _specificationAttributeService.GetProductSpecificationAttributeById(psaId); - if (psa == null) - throw new ArgumentException("No specification attribute found with the specified id"); + var psa = _specificationAttributeService.GetProductSpecificationAttributeById(psaId); + var productId = psa.ProductId; - var productId = psa.ProductId; - _specificationAttributeService.DeleteProductSpecificationAttribute(psa); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _specificationAttributeService.DeleteProductSpecificationAttribute(psa); + } return ProductSpecAttrList(command, productId); } @@ -2688,28 +2742,33 @@ public ActionResult ProductTags() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductTags(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var tags = _productTagService.GetAllProductTags() + .OrderByDescending(x => _productTagService.GetProductCount(x.Id, 0)) + .Select(x => + { + return new ProductTagModel + { + Id = x.Id, + Name = x.Name, + ProductCount = _productTagService.GetProductCount(x.Id, 0) + }; + }) + .ForCommand(command); + + model.Data = tags.PagedForCommand(command); + model.Total = tags.Count(); + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var tags = _productTagService.GetAllProductTags() - //order by product count - .OrderByDescending(x => _productTagService.GetProductCount(x.Id, 0)) - .Select(x => - { - return new ProductTagModel() - { - Id = x.Id, - Name = x.Name, - ProductCount = _productTagService.GetProductCount(x.Id, 0) - }; - }) - .ForCommand(command); - - var model = new GridModel - { - Data = tags.PagedForCommand(command), - Total = tags.Count() - }; return new JsonResult { Data = model @@ -2719,13 +2778,12 @@ public ActionResult ProductTags(GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult ProductTagDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var tag = _productTagService.GetProductTagById(id); - var tag = _productTagService.GetProductTagById(id); - if (tag == null) - throw new ArgumentException("No product tag found with the specified id"); - _productTagService.DeleteProductTag(tag); + _productTagService.DeleteProductTag(tag); + } return ProductTags(command); } @@ -2799,21 +2857,29 @@ public ActionResult LowStockReport() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult LowStockReportList(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var allProducts = _productService.GetLowStockProducts(); - var model = new GridModel() + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) { - Data = allProducts.PagedForCommand(command).Select(x => + var allProducts = _productService.GetLowStockProducts(); + + model.Data = allProducts.PagedForCommand(command).Select(x => { var productModel = x.ToModel(); productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); return productModel; - }), - Total = allProducts.Count - }; + }); + + model.Total = allProducts.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { Data = model @@ -2860,47 +2926,54 @@ public ActionResult BulkEdit() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult BulkEditSelect(GridCommand command, BulkEditListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var searchContext = new ProductSearchContext + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) { - StoreId = model.SearchStoreId, - ManufacturerId = model.SearchManufacturerId, - Keywords = model.SearchProductName, - SearchSku = !_catalogSettings.SuppressSkuSearch, - PageIndex = command.Page - 1, - PageSize = command.PageSize, - ShowHidden = true, - ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null - }; + var searchContext = new ProductSearchContext + { + StoreId = model.SearchStoreId, + ManufacturerId = model.SearchManufacturerId, + Keywords = model.SearchProductName, + SearchSku = !_catalogSettings.SuppressSkuSearch, + PageIndex = command.Page - 1, + PageSize = command.PageSize, + ShowHidden = true, + ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null + }; - if (model.SearchCategoryId > 0) - searchContext.CategoryIds.Add(model.SearchCategoryId); + if (model.SearchCategoryId > 0) + searchContext.CategoryIds.Add(model.SearchCategoryId); - var products = _productService.SearchProducts(searchContext); - var gridModel = new GridModel(); + var products = _productService.SearchProducts(searchContext); - gridModel.Data = products.Select(x => - { - var productModel = new BulkEditProductModel() + gridModel.Data = products.Select(x => { - Id = x.Id, - Name = x.Name, - ProductTypeName = x.GetProductTypeLabel(_localizationService), - ProductTypeLabelHint = x.ProductTypeLabelHint, - Sku = x.Sku, - OldPrice = x.OldPrice, - Price = x.Price, - ManageInventoryMethod = x.ManageInventoryMethod.GetLocalizedEnum(_localizationService, _workContext.WorkingLanguage.Id), - StockQuantity = x.StockQuantity, - Published = x.Published - }; + var productModel = new BulkEditProductModel + { + Id = x.Id, + Name = x.Name, + ProductTypeName = x.GetProductTypeLabel(_localizationService), + ProductTypeLabelHint = x.ProductTypeLabelHint, + Sku = x.Sku, + OldPrice = x.OldPrice, + Price = x.Price, + ManageInventoryMethod = x.ManageInventoryMethod.GetLocalizedEnum(_localizationService, _workContext.WorkingLanguage.Id), + StockQuantity = x.StockQuantity, + Published = x.Published + }; - return productModel; - }); + return productModel; + }); + + gridModel.Total = products.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); - gridModel.Total = products.TotalCount; + NotifyAccessDenied(); + } return new JsonResult { @@ -2915,39 +2988,39 @@ public ActionResult BulkEditSave(GridCommand command, [Bind(Prefix = "deleted")]IEnumerable deletedProducts, BulkEditListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - if (updatedProducts != null) + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) { - foreach (var pModel in updatedProducts) + if (updatedProducts != null) { - //update - var product = _productService.GetProductById(pModel.Id); - if (product != null) + foreach (var pModel in updatedProducts) { - product.Sku = pModel.Sku; - product.Price = pModel.Price; - product.OldPrice = pModel.OldPrice; - product.StockQuantity = pModel.StockQuantity; - product.Published = pModel.Published; + var product = _productService.GetProductById(pModel.Id); + if (product != null) + { + product.Sku = pModel.Sku; + product.Price = pModel.Price; + product.OldPrice = pModel.OldPrice; + product.StockQuantity = pModel.StockQuantity; + product.Published = pModel.Published; - _productService.UpdateProduct(product); + _productService.UpdateProduct(product); + } } } - } - if (deletedProducts != null) - { - foreach (var pModel in deletedProducts) + if (deletedProducts != null) { - var product = _productService.GetProductById(pModel.Id); - if (product != null) + foreach (var pModel in deletedProducts) { - _productService.DeleteProduct(product); + var product = _productService.GetProductById(pModel.Id); + if (product != null) + { + _productService.DeleteProduct(product); + } } } } + return BulkEditSelect(command, model); } @@ -2958,64 +3031,67 @@ public ActionResult BulkEditSave(GridCommand command, [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult TierPriceList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var product = _productService.GetProductById(productId); - if (product == null) - throw new ArgumentException("No product variant found with the specified id"); + var model = new GridModel(); - var allStores = _services.StoreService.GetAllStores(); - var allCustomerRoles = _customerService.GetAllCustomerRoles(true); - string allRolesString = T("Admin.Catalog.Products.TierPrices.Fields.CustomerRole.AllRoles"); - string allStoresString = T("Admin.Common.StoresAll"); - string deletedString = "[{0}]".FormatInvariant(T("Admin.Common.Deleted")); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var product = _productService.GetProductById(productId); - var tierPricesModel = product.TierPrices - .OrderBy(x => x.StoreId) - .ThenBy(x => x.Quantity) - .ThenBy(x => x.CustomerRoleId) - .Select(x => - { - var tierPriceModel = new ProductModel.TierPriceModel + var allStores = _services.StoreService.GetAllStores(); + var allCustomerRoles = _customerService.GetAllCustomerRoles(true); + string allRolesString = T("Admin.Catalog.Products.TierPrices.Fields.CustomerRole.AllRoles"); + string allStoresString = T("Admin.Common.StoresAll"); + string deletedString = "[{0}]".FormatInvariant(T("Admin.Common.Deleted")); + + var tierPricesModel = product.TierPrices + .OrderBy(x => x.StoreId) + .ThenBy(x => x.Quantity) + .ThenBy(x => x.CustomerRoleId) + .Select(x => { - Id = x.Id, - StoreId = x.StoreId, - ProductId = x.ProductId, - CustomerRoleId = x.CustomerRoleId ?? 0, - Quantity = x.Quantity, - Price1 = x.Price - }; + var tierPriceModel = new ProductModel.TierPriceModel + { + Id = x.Id, + StoreId = x.StoreId, + ProductId = x.ProductId, + CustomerRoleId = x.CustomerRoleId ?? 0, + Quantity = x.Quantity, + Price1 = x.Price + }; - if (x.CustomerRoleId.HasValue) - { - var role = allCustomerRoles.FirstOrDefault(r => r.Id == x.CustomerRoleId.Value); - tierPriceModel.CustomerRole = (role == null ? allRolesString : role.Name); - } - else - { - tierPriceModel.CustomerRole = allRolesString; - } + if (x.CustomerRoleId.HasValue) + { + var role = allCustomerRoles.FirstOrDefault(r => r.Id == x.CustomerRoleId.Value); + tierPriceModel.CustomerRole = (role == null ? allRolesString : role.Name); + } + else + { + tierPriceModel.CustomerRole = allRolesString; + } - if (x.StoreId > 0) - { - var store = allStores.FirstOrDefault(s => s.Id == x.StoreId); - tierPriceModel.Store = (store == null ? deletedString : store.Name); - } - else - { - tierPriceModel.Store = allStoresString; - } + if (x.StoreId > 0) + { + var store = allStores.FirstOrDefault(s => s.Id == x.StoreId); + tierPriceModel.Store = (store == null ? deletedString : store.Name); + } + else + { + tierPriceModel.Store = allStoresString; + } - return tierPriceModel; - }) - .ToList(); + return tierPriceModel; + }) + .ToList(); - var model = new GridModel + model.Data = tierPricesModel; + model.Total = tierPricesModel.Count; + } + else { - Data = tierPricesModel, - Total = tierPricesModel.Count - }; + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -3026,27 +3102,26 @@ public ActionResult TierPriceList(GridCommand command, int productId) [GridAction(EnableCustomBinding = true)] public ActionResult TierPriceInsert(GridCommand command, ProductModel.TierPriceModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var product = _productService.GetProductById(model.ProductId); - var product = _productService.GetProductById(model.ProductId); - if (product == null) - throw new ArgumentException("No product found with the specified id"); + var tierPrice = new TierPrice + { + ProductId = model.ProductId, + // use Store property (not Store propertyId) because appropriate property is stored in it + StoreId = model.Store.ToInt(), + // use CustomerRole property (not CustomerRoleId) because appropriate property is stored in it + CustomerRoleId = model.CustomerRole.IsNumeric() && Int32.Parse(model.CustomerRole) != 0 ? Int32.Parse(model.CustomerRole) : (int?)null, + Quantity = model.Quantity, + Price = model.Price1 + }; - var tierPrice = new TierPrice() - { - ProductId = model.ProductId, - // use Store property (not Store propertyId) because appropriate property is stored in it - StoreId = model.Store.ToInt(), - // use CustomerRole property (not CustomerRoleId) because appropriate property is stored in it - CustomerRoleId = model.CustomerRole.IsNumeric() && Int32.Parse(model.CustomerRole) != 0 ? Int32.Parse(model.CustomerRole) : (int?)null, - Quantity = model.Quantity, - Price = model.Price1 - }; - _productService.InsertTierPrice(tierPrice); + _productService.InsertTierPrice(tierPrice); - //update "HasTierPrices" property - _productService.UpdateHasTierPricesProperty(product); + //update "HasTierPrices" property + _productService.UpdateHasTierPricesProperty(product); + } return TierPriceList(command, model.ProductId); } @@ -3054,20 +3129,19 @@ public ActionResult TierPriceInsert(GridCommand command, ProductModel.TierPriceM [GridAction(EnableCustomBinding = true)] public ActionResult TierPriceUpdate(GridCommand command, ProductModel.TierPriceModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - var tierPrice = _productService.GetTierPriceById(model.Id); - if (tierPrice == null) - throw new ArgumentException("No tier price found with the specified id"); - //use Store property (not Store propertyId) because appropriate property is stored in it - tierPrice.StoreId = model.Store.ToInt(); - //use CustomerRole property (not CustomerRoleId) because appropriate property is stored in it - tierPrice.CustomerRoleId = model.CustomerRole.IsNumeric() && Int32.Parse(model.CustomerRole) != 0 ? Int32.Parse(model.CustomerRole) : (int?)null; - tierPrice.Quantity = model.Quantity; - tierPrice.Price = model.Price1; - _productService.UpdateTierPrice(tierPrice); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + //use Store property (not Store propertyId) because appropriate property is stored in it + tierPrice.StoreId = model.Store.ToInt(); + //use CustomerRole property (not CustomerRoleId) because appropriate property is stored in it + tierPrice.CustomerRoleId = model.CustomerRole.IsNumeric() && Int32.Parse(model.CustomerRole) != 0 ? Int32.Parse(model.CustomerRole) : (int?)null; + tierPrice.Quantity = model.Quantity; + tierPrice.Price = model.Price1; + + _productService.UpdateTierPrice(tierPrice); + } return TierPriceList(command, tierPrice.ProductId); } @@ -3075,22 +3149,18 @@ public ActionResult TierPriceUpdate(GridCommand command, ProductModel.TierPriceM [GridAction(EnableCustomBinding = true)] public ActionResult TierPriceDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - var tierPrice = _productService.GetTierPriceById(id); - if (tierPrice == null) - throw new ArgumentException("No tier price found with the specified id"); - var productId = tierPrice.ProductId; - var product = _productService.GetProductById(productId); - if (product == null) - throw new ArgumentException("No product found with the specified id"); - _productService.DeleteTierPrice(tierPrice); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var product = _productService.GetProductById(productId); + + _productService.DeleteTierPrice(tierPrice); - //update "HasTierPrices" property - _productService.UpdateHasTierPricesProperty(product); + //update "HasTierPrices" property + _productService.UpdateHasTierPricesProperty(product); + } return TierPriceList(command, productId); } @@ -3102,40 +3172,45 @@ public ActionResult TierPriceDelete(int id, GridCommand command) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductVariantAttributeList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - var productVariantAttributes = _productAttributeService.GetProductVariantAttributesByProductId(productId); - var productVariantAttributesModel = productVariantAttributes - .Select(x => - { - var pvaModel = new ProductModel.ProductVariantAttributeModel() + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var productVariantAttributes = _productAttributeService.GetProductVariantAttributesByProductId(productId); + var productVariantAttributesModel = productVariantAttributes + .Select(x => { - Id = x.Id, - ProductId = x.ProductId, - ProductAttribute = _productAttributeService.GetProductAttributeById(x.ProductAttributeId).Name, - ProductAttributeId = x.ProductAttributeId, - TextPrompt = x.TextPrompt, - IsRequired = x.IsRequired, - AttributeControlType = x.AttributeControlType.GetLocalizedEnum(_localizationService, _workContext), - AttributeControlTypeId = x.AttributeControlTypeId, - DisplayOrder1 = x.DisplayOrder - }; + var pvaModel = new ProductModel.ProductVariantAttributeModel + { + Id = x.Id, + ProductId = x.ProductId, + ProductAttribute = _productAttributeService.GetProductAttributeById(x.ProductAttributeId).Name, + ProductAttributeId = x.ProductAttributeId, + TextPrompt = x.TextPrompt, + IsRequired = x.IsRequired, + AttributeControlType = x.AttributeControlType.GetLocalizedEnum(_localizationService, _workContext), + AttributeControlTypeId = x.AttributeControlTypeId, + DisplayOrder1 = x.DisplayOrder + }; - if (x.ShouldHaveValues()) - { - pvaModel.ViewEditUrl = Url.Action("EditAttributeValues", "Product", new { productVariantAttributeId = x.Id }); - pvaModel.ViewEditText = string.Format(_localizationService.GetResource("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.ViewLink"), x.ProductVariantAttributeValues != null ? x.ProductVariantAttributeValues.Count : 0); - } - return pvaModel; - }) - .ToList(); + if (x.ShouldHaveValues()) + { + pvaModel.ViewEditUrl = Url.Action("EditAttributeValues", "Product", new { productVariantAttributeId = x.Id }); + pvaModel.ViewEditText = string.Format(_localizationService.GetResource("Admin.Catalog.Products.ProductVariantAttributes.Attributes.Values.ViewLink"), x.ProductVariantAttributeValues != null ? x.ProductVariantAttributeValues.Count : 0); + } + return pvaModel; + }) + .ToList(); - var model = new GridModel + model.Data = productVariantAttributesModel; + model.Total = productVariantAttributesModel.Count; + } + else { - Data = productVariantAttributesModel, - Total = productVariantAttributesModel.Count - }; + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -3146,19 +3221,20 @@ public ActionResult ProductVariantAttributeList(GridCommand command, int product [GridAction(EnableCustomBinding = true)] public ActionResult ProductVariantAttributeInsert(GridCommand command, ProductModel.ProductVariantAttributeModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var pva = new ProductVariantAttribute() + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) { - ProductId = model.ProductId, - ProductAttributeId = Int32.Parse(model.ProductAttribute), //use ProductAttribute property (not ProductAttributeId) because appropriate property is stored in it - TextPrompt = model.TextPrompt, - IsRequired = model.IsRequired, - AttributeControlTypeId = Int32.Parse(model.AttributeControlType), //use AttributeControlType property (not AttributeControlTypeId) because appropriate property is stored in it - DisplayOrder = model.DisplayOrder1 - }; - _productAttributeService.InsertProductVariantAttribute(pva); + var pva = new ProductVariantAttribute + { + ProductId = model.ProductId, + ProductAttributeId = Int32.Parse(model.ProductAttribute), //use ProductAttribute property (not ProductAttributeId) because appropriate property is stored in it + TextPrompt = model.TextPrompt, + IsRequired = model.IsRequired, + AttributeControlTypeId = Int32.Parse(model.AttributeControlType), //use AttributeControlType property (not AttributeControlTypeId) because appropriate property is stored in it + DisplayOrder = model.DisplayOrder1 + }; + + _productAttributeService.InsertProductVariantAttribute(pva); + } return ProductVariantAttributeList(command, model.ProductId); } @@ -3166,21 +3242,20 @@ public ActionResult ProductVariantAttributeInsert(GridCommand command, ProductMo [GridAction(EnableCustomBinding = true)] public ActionResult ProductVariantAttributeUpdate(GridCommand command, ProductModel.ProductVariantAttributeModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - var pva = _productAttributeService.GetProductVariantAttributeById(model.Id); - if (pva == null) - throw new ArgumentException("No product variant attribute found with the specified id"); - //use ProductAttribute property (not ProductAttributeId) because appropriate property is stored in it - pva.ProductAttributeId = Int32.Parse(model.ProductAttribute); - pva.TextPrompt = model.TextPrompt; - pva.IsRequired = model.IsRequired; - //use AttributeControlType property (not AttributeControlTypeId) because appropriate property is stored in it - pva.AttributeControlTypeId = Int32.Parse(model.AttributeControlType); - pva.DisplayOrder = model.DisplayOrder1; - _productAttributeService.UpdateProductVariantAttribute(pva); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + //use ProductAttribute property (not ProductAttributeId) because appropriate property is stored in it + pva.ProductAttributeId = Int32.Parse(model.ProductAttribute); + pva.TextPrompt = model.TextPrompt; + pva.IsRequired = model.IsRequired; + //use AttributeControlType property (not AttributeControlTypeId) because appropriate property is stored in it + pva.AttributeControlTypeId = Int32.Parse(model.AttributeControlType); + pva.DisplayOrder = model.DisplayOrder1; + + _productAttributeService.UpdateProductVariantAttribute(pva); + } return ProductVariantAttributeList(command, pva.ProductId); } @@ -3188,16 +3263,13 @@ public ActionResult ProductVariantAttributeUpdate(GridCommand command, ProductMo [GridAction(EnableCustomBinding = true)] public ActionResult ProductVariantAttributeDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - var pva = _productAttributeService.GetProductVariantAttributeById(id); - if (pva == null) - throw new ArgumentException("No product variant attribute found with the specified id"); - var productId = pva.ProductId; - _productAttributeService.DeleteProductVariantAttribute(pva); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _productAttributeService.DeleteProductVariantAttribute(pva); + } return ProductVariantAttributeList(command, productId); } @@ -3254,22 +3326,19 @@ public ActionResult EditAttributeValues(int productVariantAttributeId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductAttributeValueList(int productVariantAttributeId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var pva = _productAttributeService.GetProductVariantAttributeById(productVariantAttributeId); - if (pva == null) - throw new ArgumentException("No product variant attribute found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var pva = _productAttributeService.GetProductVariantAttributeById(productVariantAttributeId); - var values = _productAttributeService.GetProductVariantAttributeValues(productVariantAttributeId); + var values = _productAttributeService.GetProductVariantAttributeValues(productVariantAttributeId); - var gridModel = new GridModel - { - Data = values.Select(x => + gridModel.Data = values.Select(x => { var linkedProduct = _productService.GetProductById(x.LinkedProductId); - var model = new ProductModel.ProductVariantAttributeValueModel() + var model = new ProductModel.ProductVariantAttributeValueModel { Id = x.Id, ProductVariantAttributeId = x.ProductVariantAttributeId, @@ -3300,9 +3369,17 @@ public ActionResult ProductAttributeValueList(int productVariantAttributeId, Gri } return model; - }), - Total = values.Count() - }; + }); + + gridModel.Total = values.Count(); + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { Data = gridModel @@ -3493,14 +3570,12 @@ public ActionResult ProductAttributeValueEditPopup(string btnId, string formId, [GridAction(EnableCustomBinding = true)] public ActionResult ProductAttributeValueDelete(int pvavId, int productVariantAttributeId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var pvav = _productAttributeService.GetProductVariantAttributeValueById(pvavId); - if (pvav == null) - throw new ArgumentException("No product variant attribute value found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var pvav = _productAttributeService.GetProductVariantAttributeValueById(pvavId); - _productAttributeService.DeleteProductVariantAttributeValue(pvav); + _productAttributeService.DeleteProductVariantAttributeValue(pvav); + } return ProductAttributeValueList(productVariantAttributeId, command); } @@ -3543,33 +3618,41 @@ public ActionResult ProductAttributeValueLinkagePopup() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductAttributeValueLinkagePopupList(GridCommand command, ProductModel.ProductVariantAttributeValueModel.AddProductLinkageModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var searchContext = new ProductSearchContext() - { - CategoryIds = new List() { model.SearchCategoryId }, - ManufacturerId = model.SearchManufacturerId, - StoreId = model.SearchStoreId, - Keywords = model.SearchProductName, - SearchSku = !_catalogSettings.SuppressSkuSearch, - PageIndex = command.Page - 1, - PageSize = command.PageSize, - ShowHidden = true, - ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null - }; + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var searchContext = new ProductSearchContext + { + CategoryIds = new List { model.SearchCategoryId }, + ManufacturerId = model.SearchManufacturerId, + StoreId = model.SearchStoreId, + Keywords = model.SearchProductName, + SearchSku = !_catalogSettings.SuppressSkuSearch, + PageIndex = command.Page - 1, + PageSize = command.PageSize, + ShowHidden = true, + ProductType = model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null + }; + + var products = _productService.SearchProducts(searchContext); + + gridModel.Data = products.Select(x => + { + var productModel = x.ToModel(); + productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); - var products = _productService.SearchProducts(searchContext); + return productModel; + }); - var gridModel = new GridModel(); - gridModel.Data = products.Select(x => + gridModel.Total = products.TotalCount; + } + else { - var productModel = x.ToModel(); - productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); + gridModel.Data = Enumerable.Empty(); - return productModel; - }); - gridModel.Total = products.TotalCount; + NotifyAccessDenied(); + } return new JsonResult { @@ -3704,65 +3787,69 @@ private void PrepareDeliveryTimes(ProductVariantAttributeCombinationModel model, [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult ProductVariantAttributeCombinationList(GridCommand command, int productId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var model = new GridModel(); - // TODO: Replace ProductModel.ProductVariantAttributeCombinationModel by AddProductVariantAttributeCombinationModel - // when there's no grid-inline-editing anymore. + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + // TODO: Replace ProductModel.ProductVariantAttributeCombinationModel by AddProductVariantAttributeCombinationModel + // when there's no grid-inline-editing anymore. - var product = _productService.GetProductById(productId); - if (product == null) - throw new ArgumentException("No product found with the specified id"); + var product = _productService.GetProductById(productId); - var allCombinations = _productAttributeService.GetAllProductVariantAttributeCombinations(product.Id, command.Page - 1, command.PageSize); + var allCombinations = _productAttributeService.GetAllProductVariantAttributeCombinations(product.Id, command.Page - 1, command.PageSize); - var productUrlTitle = _localizationService.GetResource("Common.OpenInShop"); - var productSeName = product.GetSeName(); + var productUrlTitle = _localizationService.GetResource("Common.OpenInShop"); + var productSeName = product.GetSeName(); - var productVariantAttributesModel = allCombinations.Select(x => - { - var pvacModel = x.ToModel(); - PrepareProductAttributeCombinationModel(pvacModel, x, product, true); + var productVariantAttributesModel = allCombinations.Select(x => + { + var pvacModel = x.ToModel(); + PrepareProductAttributeCombinationModel(pvacModel, x, product, true); - pvacModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(x.AttributesXml, product.Id, productSeName); - pvacModel.ProductUrlTitle = productUrlTitle; + pvacModel.ProductUrl = _productAttributeParser.GetProductUrlWithAttributes(x.AttributesXml, product.Id, productSeName); + pvacModel.ProductUrlTitle = productUrlTitle; - try - { - var firstAttribute = _productAttributeParser.DeserializeProductVariantAttributes(x.AttributesXml).FirstOrDefault(); - - var attribute = x.Product.ProductVariantAttributes.FirstOrDefault(y => y.Id == firstAttribute.Key); - var attributeValue = attribute.ProductVariantAttributeValues.FirstOrDefault(y => y.Id == int.Parse(firstAttribute.Value.First())); + try + { + var firstAttribute = _productAttributeParser.DeserializeProductVariantAttributes(x.AttributesXml).FirstOrDefault(); - pvacModel.DisplayOrder = attributeValue.DisplayOrder; - } - catch (Exception exc) - { - exc.Dump(); - } + var attribute = x.Product.ProductVariantAttributes.FirstOrDefault(y => y.Id == firstAttribute.Key); + var attributeValue = attribute.ProductVariantAttributeValues.FirstOrDefault(y => y.Id == int.Parse(firstAttribute.Value.First())); - //if (x.IsDefaultCombination) - // pvacModel.AttributesXml = "{0}".FormatWith(pvacModel.AttributesXml); + pvacModel.DisplayOrder = attributeValue.DisplayOrder; + } + catch (Exception exc) + { + exc.Dump(); + } - //warnings - var warnings = _shoppingCartService.GetShoppingCartItemAttributeWarnings( - _workContext.CurrentCustomer, - ShoppingCartType.ShoppingCart, - x.Product, - x.AttributesXml, - combination: x); - - pvacModel.Warnings.AddRange(warnings); + //if (x.IsDefaultCombination) + // pvacModel.AttributesXml = "{0}".FormatWith(pvacModel.AttributesXml); + + //warnings + var warnings = _shoppingCartService.GetShoppingCartItemAttributeWarnings( + _workContext.CurrentCustomer, + ShoppingCartType.ShoppingCart, + x.Product, + x.AttributesXml, + combination: x); - return pvacModel; - }) - .OrderBy(x => x.DisplayOrder).ToList(); + pvacModel.Warnings.AddRange(warnings); - var model = new GridModel + return pvacModel; + }) + .OrderBy(x => x.DisplayOrder) + .ToList(); + + model.Data = productVariantAttributesModel; + model.Total = allCombinations.TotalCount; + } + else { - Data = productVariantAttributesModel, - Total = allCombinations.TotalCount, - }; + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -3773,19 +3860,16 @@ public ActionResult ProductVariantAttributeCombinationList(GridCommand command, [GridAction(EnableCustomBinding = true)] public ActionResult ProductVariantAttributeCombinationDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - var pvac = _productAttributeService.GetProductVariantAttributeCombinationById(id); - if (pvac == null) - throw new ArgumentException("No product variant attribute combination found with the specified id"); - var productId = pvac.ProductId; - _productAttributeService.DeleteProductVariantAttributeCombination(pvac); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + _productAttributeService.DeleteProductVariantAttributeCombination(pvac); - var product = _productService.GetProductById(productId); - _productService.UpdateLowestAttributeCombinationPriceProperty(product); + var product = _productService.GetProductById(productId); + _productService.UpdateLowestAttributeCombinationPriceProperty(product); + } return ProductVariantAttributeCombinationList(command, productId); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductReviewController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductReviewController.cs index 9858dc1745..dff5a6de80 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductReviewController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductReviewController.cs @@ -109,34 +109,40 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command, ProductReviewListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); - DateTime? createdOnFromValue = (model.CreatedOnFrom == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnFrom.Value, _dateTimeHelper.CurrentTimeZone); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + DateTime? createdOnFromValue = (model.CreatedOnFrom == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnFrom.Value, _dateTimeHelper.CurrentTimeZone); - DateTime? createdToFromValue = (model.CreatedOnTo == null) ? null - : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnTo.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + DateTime? createdToFromValue = (model.CreatedOnTo == null) ? null + : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.CreatedOnTo.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); + + var productReviews = _customerContentService.GetAllCustomerContent(0, null, createdOnFromValue, createdToFromValue); + + gridModel.Data = productReviews.PagedForCommand(command).Select(x => + { + var m = new ProductReviewModel(); + PrepareProductReviewModel(m, x, false, true); + return m; + }); + + gridModel.Total = productReviews.Count; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var productReviews = _customerContentService.GetAllCustomerContent(0, null, - createdOnFromValue, createdToFromValue); - var gridModel = new GridModel - { - Data = productReviews.PagedForCommand(command).Select(x => - { - var m = new ProductReviewModel(); - PrepareProductReviewModel(m, x, false, true); - return m; - }), - Total = productReviews.Count, - }; return new JsonResult { Data = gridModel }; } - //edit public ActionResult Edit(int id) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -186,7 +192,6 @@ public ActionResult Edit(ProductReviewModel model, bool continueEditing) return View(model); } - //delete [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/QueuedEmailController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/QueuedEmailController.cs index 58057c2d80..6bc007e811 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/QueuedEmailController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/QueuedEmailController.cs @@ -53,41 +53,47 @@ public ActionResult List() [GridAction(EnableCustomBinding = true)] public ActionResult QueuedEmailList(GridCommand command, QueuedEmailListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageMessageQueue)) - return AccessDeniedView(); + var gridModel = new GridModel(); - DateTime? startDateValue = (model.SearchStartDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.SearchStartDate.Value, _dateTimeHelper.CurrentTimeZone); - DateTime? endDateValue = (model.SearchEndDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.SearchEndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - - var q = new SearchEmailsQuery + if (_permissionService.Authorize(StandardPermissionProvider.ManageMessageQueue)) { - EndTime = endDateValue, - From = model.SearchFromEmail, - MaxSendTries = model.SearchMaxSentTries, - OrderByLatest = true, - PageIndex = command.Page - 1, - PageSize = command.PageSize, - SendManually = model.SearchSendManually, - StartTime = startDateValue, - To = model.SearchToEmail, - UnsentOnly = model.SearchLoadNotSent - }; - var queuedEmails = _queuedEmailService.SearchEmails(q); + DateTime? startDateValue = (model.SearchStartDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.SearchStartDate.Value, _dateTimeHelper.CurrentTimeZone); + DateTime? endDateValue = (model.SearchEndDate == null) ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(model.SearchEndDate.Value, _dateTimeHelper.CurrentTimeZone).AddDays(1); - var gridModel = new GridModel - { - Data = queuedEmails.Select(x => + var q = new SearchEmailsQuery + { + EndTime = endDateValue, + From = model.SearchFromEmail, + MaxSendTries = model.SearchMaxSentTries, + OrderByLatest = true, + PageIndex = command.Page - 1, + PageSize = command.PageSize, + SendManually = model.SearchSendManually, + StartTime = startDateValue, + To = model.SearchToEmail, + UnsentOnly = model.SearchLoadNotSent + }; + var queuedEmails = _queuedEmailService.SearchEmails(q); + + gridModel.Data = queuedEmails.Select(x => { - var m = x.ToModel(); - m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); + var m = x.ToModel(); + m.CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc); - if (x.SentOnUtc.HasValue) - m.SentOn = _dateTimeHelper.ConvertToUserTime(x.SentOnUtc.Value, DateTimeKind.Utc); + if (x.SentOnUtc.HasValue) + m.SentOn = _dateTimeHelper.ConvertToUserTime(x.SentOnUtc.Value, DateTimeKind.Utc); - return m; - }), - Total = queuedEmails.TotalCount - }; + return m; + }); + + gridModel.Total = queuedEmails.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/RecurringPaymentController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/RecurringPaymentController.cs index 39d549fc08..0f4b15968e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/RecurringPaymentController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/RecurringPaymentController.cs @@ -113,7 +113,6 @@ private void PrepareRecurringPaymentHistoryModel(RecurringPaymentModel.Recurring #region Recurring payment - //list public ActionResult Index() { return RedirectToAction("List"); @@ -131,27 +130,34 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var payments = _orderService.SearchRecurringPayments(0, 0, 0, null, true); + + gridModel.Data = payments.PagedForCommand(command).Select(x => + { + var m = new RecurringPaymentModel(); + PrepareRecurringPaymentModel(m, x, false); + return m; + }); + + gridModel.Total = payments.Count; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var payments = _orderService.SearchRecurringPayments(0, 0, 0, null, true); - var gridModel = new GridModel - { - Data = payments.PagedForCommand(command).Select(x => - { - var m = new RecurringPaymentModel(); - PrepareRecurringPaymentModel(m, x, false); - return m; - }), - Total = payments.Count, - }; return new JsonResult { Data = gridModel }; } - //edit public ActionResult Edit(int id) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) @@ -189,7 +195,6 @@ public ActionResult Edit(RecurringPaymentModel model, bool continueEditing) return continueEditing ? RedirectToAction("Edit", payment.Id) : RedirectToAction("List"); } - //delete [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { @@ -214,26 +219,30 @@ public ActionResult DeleteConfirmed(int id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult HistoryList(int recurringPaymentId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var payment = _orderService.GetRecurringPaymentById(recurringPaymentId); - if (payment == null) - throw new ArgumentException("No recurring payment found with the specified id"); - - var historyModel = payment.RecurringPaymentHistory.OrderBy(x => x.CreatedOnUtc) - .Select(x => - { - var m = new RecurringPaymentModel.RecurringPaymentHistoryModel(); - PrepareRecurringPaymentHistoryModel(m, x); - return m; - }) - .ToList(); - var model = new GridModel - { - Data = historyModel, - Total = historyModel.Count - }; + var model = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var payment = _orderService.GetRecurringPaymentById(recurringPaymentId); + + var historyModel = payment.RecurringPaymentHistory.OrderBy(x => x.CreatedOnUtc) + .Select(x => + { + var m = new RecurringPaymentModel.RecurringPaymentHistoryModel(); + PrepareRecurringPaymentHistoryModel(m, x); + return m; + }) + .ToList(); + + model.Data = historyModel; + model.Total = historyModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ReturnRequestController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ReturnRequestController.cs index 1180d71fc7..f210fef4fd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ReturnRequestController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ReturnRequestController.cs @@ -171,7 +171,6 @@ private bool PrepareReturnRequestModel(ReturnRequestModel model, ReturnRequest r #region Methods - //list public ActionResult Index() { return RedirectToAction("List"); @@ -191,26 +190,31 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command, ReturnRequestListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageReturnRequests)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var data = new List(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageReturnRequests)) + { + var data = new List(); - var returnRequests = _orderService.SearchReturnRequests(model.SearchStoreId, 0, 0, model.SearchReturnRequestStatus, - command.Page - 1, command.PageSize, model.SearchId ?? 0); + var returnRequests = _orderService.SearchReturnRequests(model.SearchStoreId, 0, 0, model.SearchReturnRequestStatus, + command.Page - 1, command.PageSize, model.SearchId ?? 0); - foreach (var rr in returnRequests) - { - var m = new ReturnRequestModel(); - if (PrepareReturnRequestModel(m, rr, false)) - data.Add(m); - } + foreach (var rr in returnRequests) + { + var m = new ReturnRequestModel(); + if (PrepareReturnRequestModel(m, rr, false)) + data.Add(m); + } - var gridModel = new GridModel + gridModel.Data = data; + gridModel.Total = returnRequests.TotalCount; + } + else { - Data = data, - Total = returnRequests.TotalCount, - }; + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -218,7 +222,6 @@ public ActionResult List(GridCommand command, ReturnRequestListModel model) }; } - //edit public ActionResult Edit(int id) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageReturnRequests)) @@ -300,7 +303,6 @@ public ActionResult NotifyCustomer(ReturnRequestModel model) return RedirectToAction("Edit", returnRequest.Id); } - //delete [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs index 086be63a26..85ea07067b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs @@ -1532,44 +1532,49 @@ public ActionResult AllSettings() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult AllSettings(GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) - return AccessDeniedView(); + var model = new GridModel(); - var stores = _services.StoreService.GetAllStores(); - string allStoresString = _services.Localization.GetResource("Admin.Common.StoresAll"); - - var settings = _services.Settings - .GetAllSettings() - .Select(x => - { - var settingModel = new SettingModel() - { - Id = x.Id, - Name = x.Name, - Value = x.Value, - StoreId = x.StoreId - }; + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) + { + var stores = _services.StoreService.GetAllStores(); + string allStoresString = _services.Localization.GetResource("Admin.Common.StoresAll"); - if (x.StoreId == 0) - { - settingModel.Store = allStoresString; - } - else + var settings = _services.Settings + .GetAllSettings() + .Select(x => { - var store = stores.FirstOrDefault(s => s.Id == x.StoreId); - settingModel.Store = store != null ? store.Name : "Unknown"; - } - - return settingModel; - }) - .ForCommand(command) - .ToList(); - - var model = new GridModel - { - Data = settings.PagedForCommand(command), - Total = settings.Count - }; + var settingModel = new SettingModel() + { + Id = x.Id, + Name = x.Name, + Value = x.Value, + StoreId = x.StoreId + }; + + if (x.StoreId == 0) + { + settingModel.Store = allStoresString; + } + else + { + var store = stores.FirstOrDefault(s => s.Id == x.StoreId); + settingModel.Store = store != null ? store.Name : "".NaIfEmpty(); + } + + return settingModel; + }) + .ForCommand(command) + .ToList(); + + model.Data = settings.PagedForCommand(command); + model.Total = settings.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -1580,80 +1585,76 @@ public ActionResult AllSettings(GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult SettingUpdate(SettingModel model, GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) - return AccessDeniedView(); - - if (model.Name != null) - model.Name = model.Name.Trim(); - if (model.Value != null) - model.Value = model.Value.Trim(); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) + { + if (model.Name != null) + model.Name = model.Name.Trim(); + if (model.Value != null) + model.Value = model.Value.Trim(); - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - var setting = _services.Settings.GetSettingById(model.Id); - if (setting == null) - return Content(_services.Localization.GetResource("Admin.Configuration.Settings.NoneWithThatId")); + var setting = _services.Settings.GetSettingById(model.Id); + if (setting == null) + return Content(T("Admin.Configuration.Settings.NoneWithThatId")); - var storeId = model.Store.ToInt(); //use Store property (not StoreId) because appropriate property is stored in it + var storeId = model.Store.ToInt(); //use Store property (not StoreId) because appropriate property is stored in it - if (!setting.Name.Equals(model.Name, StringComparison.InvariantCultureIgnoreCase) || - setting.StoreId != storeId) - { - //setting name or store has been changed - _services.Settings.DeleteSetting(setting); - } + if (!setting.Name.Equals(model.Name, StringComparison.InvariantCultureIgnoreCase) || setting.StoreId != storeId) + { + //setting name or store has been changed + _services.Settings.DeleteSetting(setting); + } - _services.Settings.SetSetting(model.Name, model.Value, storeId); + _services.Settings.SetSetting(model.Name, model.Value, storeId); - //activity log - _customerActivityService.InsertActivity("EditSettings", _services.Localization.GetResource("ActivityLog.EditSettings")); + //activity log + _customerActivityService.InsertActivity("EditSettings", T("ActivityLog.EditSettings")); + } return AllSettings(command); } [GridAction(EnableCustomBinding = true)] public ActionResult SettingAdd([Bind(Exclude = "Id")] SettingModel model, GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) - return AccessDeniedView(); - - if (model.Name != null) - model.Name = model.Name.Trim(); - if (model.Value != null) - model.Value = model.Value.Trim(); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) + { + if (model.Name != null) + model.Name = model.Name.Trim(); + if (model.Value != null) + model.Value = model.Value.Trim(); - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - var storeId = model.Store.ToInt(); //use Store property (not StoreId) because appropriate property is stored in it - _services.Settings.SetSetting(model.Name, model.Value, storeId); + var storeId = model.Store.ToInt(); //use Store property (not StoreId) because appropriate property is stored in it + _services.Settings.SetSetting(model.Name, model.Value, storeId); - //activity log - _customerActivityService.InsertActivity("AddNewSetting", _services.Localization.GetResource("ActivityLog.AddNewSetting"), model.Name); + //activity log + _customerActivityService.InsertActivity("AddNewSetting", T("ActivityLog.AddNewSetting", model.Name)); + } return AllSettings(command); } [GridAction(EnableCustomBinding = true)] public ActionResult SettingDelete(int id, GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) - return AccessDeniedView(); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) + { + var setting = _services.Settings.GetSettingById(id); - var setting = _services.Settings.GetSettingById(id); - if (setting == null) - throw new ArgumentException("No setting found with the specified id"); - _services.Settings.DeleteSetting(setting); + _services.Settings.DeleteSetting(setting); - //activity log - _customerActivityService.InsertActivity("DeleteSetting", _services.Localization.GetResource("ActivityLog.DeleteSetting"), setting.Name); + //activity log + _customerActivityService.InsertActivity("DeleteSetting", T("ActivityLog.DeleteSetting", setting.Name)); + } return AllSettings(command); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ShippingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ShippingController.cs index a63f02506f..887b5dabbc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ShippingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ShippingController.cs @@ -149,19 +149,24 @@ public ActionResult Methods() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult Methods(GridCommand command) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageShippingSettings)) - return AccessDeniedView(); + var model = new GridModel(); - var shippingMethodsModel = _shippingService.GetAllShippingMethods() - .Select(x => x.ToModel()) - .ForCommand(command) - .ToList(); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageShippingSettings)) + { + var shippingMethodsModel = _shippingService.GetAllShippingMethods() + .Select(x => x.ToModel()) + .ForCommand(command) + .ToList(); - var model = new GridModel - { - Data = shippingMethodsModel, - Total = shippingMethodsModel.Count - }; + model.Data = shippingMethodsModel; + model.Total = shippingMethodsModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ShoppingCartController.cs index b6868c18ce..fccebd040d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ShoppingCartController.cs @@ -65,28 +65,32 @@ public ActionResult CurrentCarts() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult CurrentCarts(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var customers = _customerService.GetAllCustomers(null, null, null, null, null, - null, null, 0, 0, null, null, null, true, ShoppingCartType.ShoppingCart, - command.Page - 1, command.PageSize); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var customers = _customerService.GetAllCustomers(null, null, null, null, null, null, null, + 0, 0, null, null, null, true, ShoppingCartType.ShoppingCart, command.Page - 1, command.PageSize); + + gridModel.Data = customers.Select(x => + { + return new ShoppingCartModel + { + CustomerId = x.Id, + CustomerEmail = x.IsGuest() ? T("Admin.Customers.Guest").Text : x.Email, + TotalItems = x.CountProductsInCart(ShoppingCartType.ShoppingCart) + }; + }); + + gridModel.Total = customers.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var gridModel = new GridModel - { - Data = customers.Select(x => - { - return new ShoppingCartModel() - { - CustomerId = x.Id, - CustomerEmail = x.IsGuest() ? - _localizationService.GetResource("Admin.Customers.Guest") : - x.Email, - TotalItems = x.CountProductsInCart(ShoppingCartType.ShoppingCart) - }; - }), - Total = customers.TotalCount - }; return new JsonResult { Data = gridModel @@ -96,35 +100,43 @@ public ActionResult CurrentCarts(GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult GetCartDetails(int customerId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var customer = _customerService.GetCustomerById(customerId); - var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart); + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var customer = _customerService.GetCustomerById(customerId); + var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart); - var gridModel = new GridModel() - { - Data = cart.Select(sci => - { - decimal taxRate; + gridModel.Data = cart.Select(sci => + { + decimal taxRate; var store = _storeService.GetStoreById(sci.Item.StoreId); - var sciModel = new ShoppingCartItemModel() - { - Id = sci.Item.Id, - Store = store != null ? store.Name : "Unknown", + + var sciModel = new ShoppingCartItemModel + { + Id = sci.Item.Id, + Store = store != null ? store.Name : "".NaIfEmpty(), ProductId = sci.Item.ProductId, - Quantity = sci.Item.Quantity, + Quantity = sci.Item.Quantity, ProductName = sci.Item.Product.Name, ProductTypeName = sci.Item.Product.GetProductTypeLabel(_localizationService), ProductTypeLabelHint = sci.Item.Product.ProductTypeLabelHint, - UnitPrice = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetUnitPrice(sci, true), out taxRate)), - Total = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetSubTotal(sci, true), out taxRate)), - UpdatedOn = _dateTimeHelper.ConvertToUserTime(sci.Item.UpdatedOnUtc, DateTimeKind.Utc) - }; - return sciModel; - }), - Total = cart.Count - }; + UnitPrice = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetUnitPrice(sci, true), out taxRate)), + Total = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetSubTotal(sci, true), out taxRate)), + UpdatedOn = _dateTimeHelper.ConvertToUserTime(sci.Item.UpdatedOnUtc, DateTimeKind.Utc) + }; + return sciModel; + }); + + gridModel.Total = cart.Count; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { Data = gridModel @@ -144,28 +156,32 @@ public ActionResult CurrentWishlists() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult CurrentWishlists(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); - - var customers = _customerService.GetAllCustomers(null, null, null, null, null, - null, null, 0, 0, null, null, null, - true, ShoppingCartType.Wishlist, command.Page - 1, command.PageSize); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var customers = _customerService.GetAllCustomers(null, null, null, null, null, null, null, + 0, 0, null, null, null, true, ShoppingCartType.Wishlist, command.Page - 1, command.PageSize); + + gridModel.Data = customers.Select(x => + { + return new ShoppingCartModel + { + CustomerId = x.Id, + CustomerEmail = x.IsGuest() ? T("Admin.Customers.Guest").Text : x.Email, + TotalItems = x.CountProductsInCart(ShoppingCartType.Wishlist) + }; + }); + + gridModel.Total = customers.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var gridModel = new GridModel - { - Data = customers.Select(x => - { - return new ShoppingCartModel() - { - CustomerId = x.Id, - CustomerEmail = x.IsGuest() ? - _localizationService.GetResource("Admin.Customers.Guest") : - x.Email, - TotalItems = x.CountProductsInCart(ShoppingCartType.Wishlist) - }; - }), - Total = customers.TotalCount - }; return new JsonResult { Data = gridModel @@ -175,35 +191,43 @@ public ActionResult CurrentWishlists(GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult GetWishlistDetails(int customerId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var customer = _customerService.GetCustomerById(customerId); - var cart = customer.GetCartItems(ShoppingCartType.Wishlist); + if (_permissionService.Authorize(StandardPermissionProvider.ManageOrders)) + { + var customer = _customerService.GetCustomerById(customerId); + var cart = customer.GetCartItems(ShoppingCartType.Wishlist); - var gridModel = new GridModel() - { - Data = cart.Select(sci => - { - decimal taxRate; - var store = _storeService.GetStoreById(sci.Item.StoreId); - var sciModel = new ShoppingCartItemModel() - { - Id = sci.Item.Id, - Store = store != null ? store.Name : "Unknown", - ProductId = sci.Item.ProductId, - Quantity = sci.Item.Quantity, + gridModel.Data = cart.Select(sci => + { + decimal taxRate; + var store = _storeService.GetStoreById(sci.Item.StoreId); + + var sciModel = new ShoppingCartItemModel + { + Id = sci.Item.Id, + Store = store != null ? store.Name : "".NaIfEmpty(), + ProductId = sci.Item.ProductId, + Quantity = sci.Item.Quantity, ProductName = sci.Item.Product.Name, ProductTypeName = sci.Item.Product.GetProductTypeLabel(_localizationService), ProductTypeLabelHint = sci.Item.Product.ProductTypeLabelHint, - UnitPrice = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetUnitPrice(sci, true), out taxRate)), - Total = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetSubTotal(sci, true), out taxRate)), - UpdatedOn = _dateTimeHelper.ConvertToUserTime(sci.Item.UpdatedOnUtc, DateTimeKind.Utc) - }; - return sciModel; - }), - Total = cart.Count - }; + UnitPrice = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetUnitPrice(sci, true), out taxRate)), + Total = _priceFormatter.FormatPrice(_taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetSubTotal(sci, true), out taxRate)), + UpdatedOn = _dateTimeHelper.ConvertToUserTime(sci.Item.UpdatedOnUtc, DateTimeKind.Utc) + }; + return sciModel; + }); + + gridModel.Total = cart.Count; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { Data = gridModel diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SpecificationAttributeController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SpecificationAttributeController.cs index 92877bbb9b..a15ca73afc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SpecificationAttributeController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SpecificationAttributeController.cs @@ -121,28 +121,31 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var optionsText = _localizationService.GetResource("Admin.Catalog.Attributes.SpecificationAttributes.Options"); - - var data = _specificationAttributeService.GetSpecificationAttributes() - .Expand(x => x.SpecificationAttributeOptions) - .ForCommand(command) - .Select(x => - { - var model = x.ToModel(); - model.OptionCount = x.SpecificationAttributeOptions.Count; - - return model; - }) - .ToList(); + var gridModel = new GridModel(); - var gridModel = new GridModel + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var data = _specificationAttributeService.GetSpecificationAttributes() + .Expand(x => x.SpecificationAttributeOptions) + .ForCommand(command) + .Select(x => + { + var model = x.ToModel(); + model.OptionCount = x.SpecificationAttributeOptions.Count; + + return model; + }) + .ToList(); + + gridModel.Data = data.PagedForCommand(command); + gridModel.Total = data.Count; + } + else { - Data = data.PagedForCommand(command), - Total = data.Count - }; + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -150,7 +153,6 @@ public ActionResult List(GridCommand command) }; } - //create public ActionResult Create() { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -185,7 +187,6 @@ public ActionResult Create(SpecificationAttributeModel model, bool continueEditi return View(model); } - //edit public ActionResult Edit(int id) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -233,7 +234,6 @@ public ActionResult Edit(SpecificationAttributeModel model, bool continueEditing return View(model); } - //delete [HttpPost, ActionName("Delete")] public ActionResult DeleteConfirmed(int id) { @@ -289,35 +289,31 @@ public ActionResult ProductMappingEdit(int specificationAttributeId, string fiel #region Specification attribute options - //list [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult OptionList(int specificationAttributeId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); + var gridModel = new GridModel(); + + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var options = _specificationAttributeService.GetSpecificationAttributeOptionsBySpecificationAttribute(specificationAttributeId); + + gridModel.Data = options.Select(x => x.ToModel()); + gridModel.Total = options.Count(); + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } - var options = _specificationAttributeService.GetSpecificationAttributeOptionsBySpecificationAttribute(specificationAttributeId); - var gridModel = new GridModel - { - Data = options.Select(x => - { - var model = x.ToModel(); - //locales - //AddLocales(_languageService, model.Locales, (locale, languageId) => - //{ - // locale.Name = x.GetLocalized(y => y.Name, languageId, false, false); - //}); - return model; - }), - Total = options.Count() - }; return new JsonResult { Data = gridModel }; } - //create public ActionResult OptionCreatePopup(int specificationAttributeId) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -367,7 +363,6 @@ public ActionResult OptionCreatePopup(string btnId, string formId, Specification return View(model); } - //edit public ActionResult OptionEditPopup(int id) { if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) @@ -414,23 +409,19 @@ public ActionResult OptionEditPopup(string btnId, string formId, SpecificationAt return View(model); } - //delete [GridAction(EnableCustomBinding = true)] public ActionResult OptionDelete(int optionId, int specificationAttributeId, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) - return AccessDeniedView(); - - var sao = _specificationAttributeService.GetSpecificationAttributeOptionById(optionId); - if (sao == null) - throw new ArgumentException("No specification attribute option found with the specified id"); + if (_permissionService.Authorize(StandardPermissionProvider.ManageCatalog)) + { + var sao = _specificationAttributeService.GetSpecificationAttributeOptionById(optionId); - _specificationAttributeService.DeleteSpecificationAttributeOption(sao); + _specificationAttributeService.DeleteSpecificationAttributeOption(sao); + } return OptionList(specificationAttributeId, command); } - //ajax [AcceptVerbs(HttpVerbs.Get)] public ActionResult GetOptionsByAttributeId(string attributeId) @@ -468,7 +459,6 @@ public ActionResult SetAttributeValue(string pk, string value, string name, Form } } - #endregion } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/StoreController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/StoreController.cs index 78a441460e..38df324a4c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/StoreController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/StoreController.cs @@ -81,27 +81,32 @@ from m in stores [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageStores)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var storeModels = _storeService.GetAllStores() - .Select(x => - { - var model = x.ToModel(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageStores)) + { + var storeModels = _storeService.GetAllStores() + .Select(x => + { + var model = x.ToModel(); - PrepareStoreModel(model, x); + PrepareStoreModel(model, x); - model.Hosts = model.Hosts.EmptyNull().Replace(",", "
    "); + model.Hosts = model.Hosts.EmptyNull().Replace(",", "
    "); - return model; - }) - .ToList(); + return model; + }) + .ToList(); - var gridModel = new GridModel + gridModel.Data = storeModels; + gridModel.Total = storeModels.Count(); + } + else { - Data = storeModels, - Total = storeModels.Count() - }; + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/TaxController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/TaxController.cs index b78404407d..a694f7df48 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/TaxController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/TaxController.cs @@ -116,18 +116,24 @@ public ActionResult Categories() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult Categories(GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageTaxSettings)) - return AccessDeniedView(); + var model = new GridModel(); - var categoriesModel = _taxCategoryService.GetAllTaxCategories() - .Select(x => x.ToModel()) - .ForCommand(command) - .ToList(); - var model = new GridModel - { - Data = categoriesModel, - Total = categoriesModel.Count - }; + if (_permissionService.Authorize(StandardPermissionProvider.ManageTaxSettings)) + { + var categoriesModel = _taxCategoryService.GetAllTaxCategories() + .Select(x => x.ToModel()) + .ForCommand(command) + .ToList(); + + model.Data = categoriesModel; + model.Total = categoriesModel.Count; + } + else + { + model.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { @@ -138,19 +144,19 @@ public ActionResult Categories(GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult CategoryUpdate(TaxCategoryModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageTaxSettings)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageTaxSettings)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + var taxCategory = _taxCategoryService.GetTaxCategoryById(model.Id); + taxCategory = model.ToEntity(taxCategory); - var taxCategory = _taxCategoryService.GetTaxCategoryById(model.Id); - taxCategory = model.ToEntity(taxCategory); - _taxCategoryService.UpdateTaxCategory(taxCategory); + _taxCategoryService.UpdateTaxCategory(taxCategory); + } return Categories(command); } @@ -158,19 +164,19 @@ public ActionResult CategoryUpdate(TaxCategoryModel model, GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult CategoryAdd([Bind(Exclude = "Id")] TaxCategoryModel model, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageTaxSettings)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageTaxSettings)) + { + if (!ModelState.IsValid) + { + var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); + return Content(modelStateErrors.FirstOrDefault()); + } - if (!ModelState.IsValid) - { - //display the first model error - var modelStateErrors = this.ModelState.Values.SelectMany(x => x.Errors).Select(x => x.ErrorMessage); - return Content(modelStateErrors.FirstOrDefault()); - } + var taxCategory = new TaxCategory(); + taxCategory = model.ToEntity(taxCategory); - var taxCategory = new TaxCategory(); - taxCategory = model.ToEntity(taxCategory); - _taxCategoryService.InsertTaxCategory(taxCategory); + _taxCategoryService.InsertTaxCategory(taxCategory); + } return Categories(command); } @@ -178,13 +184,12 @@ public ActionResult CategoryAdd([Bind(Exclude = "Id")] TaxCategoryModel model, G [GridAction(EnableCustomBinding = true)] public ActionResult CategoryDelete(int id, GridCommand command) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageTaxSettings)) - return AccessDeniedView(); + if (_permissionService.Authorize(StandardPermissionProvider.ManageTaxSettings)) + { + var taxCategory = _taxCategoryService.GetTaxCategoryById(id); - var taxCategory = _taxCategoryService.GetTaxCategoryById(id); - if (taxCategory == null) - throw new ArgumentException("No tax category found with the specified id"); - _taxCategoryService.DeleteTaxCategory(taxCategory); + _taxCategoryService.DeleteTaxCategory(taxCategory); + } return Categories(command); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/TopicController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/TopicController.cs index 29f29762c6..03b4a60c62 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/TopicController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/TopicController.cs @@ -137,20 +137,29 @@ public ActionResult List() [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command, TopicListModel model) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageTopics)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var topics = _topicService.GetAllTopics(model.SearchStoreId); - var gridModel = new GridModel - { - Data = topics.Select(x => { + if (_permissionService.Authorize(StandardPermissionProvider.ManageTopics)) + { + var topics = _topicService.GetAllTopics(model.SearchStoreId); + + gridModel.Data = topics.Select(x => + { var item = x.ToModel(); // otherwise maxJsonLength could be exceeded item.Body = ""; return item; - }), - Total = topics.Count - }; + }); + + gridModel.Total = topics.Count; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } + return new JsonResult { MaxJsonLength = int.MaxValue, diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs index c088c0805d..d51b32bb19 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/UrlRecordController.cs @@ -107,20 +107,19 @@ public ActionResult List(string entityName, int? entityId) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult List(GridCommand command, UrlRecordListModel model) { - if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageUrlRecords)) - return AccessDeniedView(); + var gridModel = new GridModel(); - var allLanguages = _languageService.GetAllLanguages(true); - var defaultLanguageName = T("Admin.System.SeNames.Language.Standard"); + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageUrlRecords)) + { + var allLanguages = _languageService.GetAllLanguages(true); + var defaultLanguageName = T("Admin.System.SeNames.Language.Standard"); - var urlRecords = _urlRecordService.GetAllUrlRecords(command.Page - 1, command.PageSize, - model.SeName, model.EntityName, model.EntityId, model.LanguageId, model.IsActive); + var urlRecords = _urlRecordService.GetAllUrlRecords(command.Page - 1, command.PageSize, + model.SeName, model.EntityName, model.EntityId, model.LanguageId, model.IsActive); - var slugsPerEntity = _urlRecordService.CountSlugsPerEntity(urlRecords.Select(x => x.Id).Distinct().ToArray()); + var slugsPerEntity = _urlRecordService.CountSlugsPerEntity(urlRecords.Select(x => x.Id).Distinct().ToArray()); - var gridModel = new GridModel - { - Data = urlRecords.Select(x => + gridModel.Data = urlRecords.Select(x => { string languageName; @@ -141,9 +140,16 @@ public ActionResult List(GridCommand command, UrlRecordListModel model) urlRecordModel.SlugsPerEntity = (slugsPerEntity.ContainsKey(x.Id) ? slugsPerEntity[x.Id] : 0); return urlRecordModel; - }), - Total = urlRecords.TotalCount - }; + }); + + gridModel.Total = urlRecords.TotalCount; + } + else + { + gridModel.Data = Enumerable.Empty(); + + NotifyAccessDenied(); + } return new JsonResult { From 1fa83bdc2017f11dd20ee50efd901e4336aceabe Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 6 Jan 2016 12:57:00 +0100 Subject: [PATCH 383/874] Localized some hard-coded strings --- .../201512151526290_ImportFramework.cs | 9 +++++++ .../Controllers/ByTotalController.cs | 6 +++-- .../Controllers/FixedRateController.cs | 2 +- .../Providers/ByTotalProvider.cs | 27 ++++++++++--------- .../Providers/FixedRateProvider.cs | 25 +++++++++-------- .../ByWeightShippingComputationMethod.cs | 23 ++++++++++------ .../Controllers/ShippingByWeightController.cs | 6 +++-- .../Controllers/TaxByRegionController.cs | 4 +-- 8 files changed, 64 insertions(+), 38 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs index 7de4cc953b..35b6497ae1 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs @@ -76,6 +76,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Legt die Anzahl der zu berspringenden Datenstze fest."); builder.AddOrUpdate("Common.Unknown", "Unknown", "Unbekannt"); + builder.AddOrUpdate("Common.Unavailable", "Unavailable", "Nicht verfgbar"); builder.AddOrUpdate("Common.Language", "Language", "Sprache"); builder.AddOrUpdate("Admin.Common.ImportFile", "Import file", "Importdatei"); builder.AddOrUpdate("Admin.Common.ImportFiles", "Import files", "Importdateien"); @@ -303,6 +304,14 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Countries.States.CantDeleteWithAddresses", "The state\\province cannot be deleted because it has associated addresses.", "Das Bundesland\\Region kann nicht gelscht werden, weil ihm Adressen zugeordnet sind."); + + builder.AddOrUpdate("Admin.Configuration.Shipping.Methods.NoMethodsLoaded", + "No shipping methods could be loaded.", + "Es konnten keine Versandarten geladen werden."); + + builder.AddOrUpdate("Admin.System.Warnings.NoShipmentItems", + "No shipment items", + "Keine Versand-Artikel"); } } } diff --git a/src/Plugins/SmartStore.Shipping/Controllers/ByTotalController.cs b/src/Plugins/SmartStore.Shipping/Controllers/ByTotalController.cs index 1bf1f643b1..5c5e03f8ab 100644 --- a/src/Plugins/SmartStore.Shipping/Controllers/ByTotalController.cs +++ b/src/Plugins/SmartStore.Shipping/Controllers/ByTotalController.cs @@ -43,7 +43,7 @@ public ActionResult Configure() var shippingMethods = _shippingService.GetAllShippingMethods(); if (shippingMethods.Count == 0) { - return Content("No shipping methods can be loaded"); + return Content(T("Admin.Configuration.Shipping.Methods.NoMethodsLoaded")); } var model = new ByTotalListModel(); @@ -100,10 +100,11 @@ public ActionResult RateUpdate(ByTotalModel model, GridCommand command) { if (!ModelState.IsValid) { - return new JsonResult { Data = "error" }; + return new JsonResult { Data = T("Admin.Common.UnknownError").Text }; } var shippingByTotalRecord = _shippingByTotalService.GetShippingByTotalRecordById(model.Id); + shippingByTotalRecord.Zip = model.Zip == "*" ? null : model.Zip; shippingByTotalRecord.From = model.From; shippingByTotalRecord.To = model.To; @@ -112,6 +113,7 @@ public ActionResult RateUpdate(ByTotalModel model, GridCommand command) shippingByTotalRecord.ShippingChargePercentage = model.ShippingChargePercentage; shippingByTotalRecord.BaseCharge = model.BaseCharge; shippingByTotalRecord.MaxCharge = model.MaxCharge; + _shippingByTotalService.UpdateShippingByTotalRecord(shippingByTotalRecord); return RatesList(command); diff --git a/src/Plugins/SmartStore.Shipping/Controllers/FixedRateController.cs b/src/Plugins/SmartStore.Shipping/Controllers/FixedRateController.cs index f0e510f3b7..353bafe59d 100644 --- a/src/Plugins/SmartStore.Shipping/Controllers/FixedRateController.cs +++ b/src/Plugins/SmartStore.Shipping/Controllers/FixedRateController.cs @@ -29,7 +29,7 @@ public ActionResult Configure() { var shippingMethods = _shippingService.GetAllShippingMethods(); if (shippingMethods.Count == 0) - return Content("No shipping methods can be loaded"); + return Content(T("Admin.Configuration.Shipping.Methods.NoMethodsLoaded")); var tmp = new List(); foreach (var shippingMethod in shippingMethods) diff --git a/src/Plugins/SmartStore.Shipping/Providers/ByTotalProvider.cs b/src/Plugins/SmartStore.Shipping/Providers/ByTotalProvider.cs index 882a576f15..82ec989cf4 100644 --- a/src/Plugins/SmartStore.Shipping/Providers/ByTotalProvider.cs +++ b/src/Plugins/SmartStore.Shipping/Providers/ByTotalProvider.cs @@ -1,20 +1,20 @@ using System; -using System.Data.Entity.Migrations; using System.Web.Routing; using SmartStore.Core; using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -using SmartStore.Shipping.Services; using SmartStore.Services.Catalog; using SmartStore.Services.Configuration; using SmartStore.Services.Localization; -using SmartStore.Core.Logging; using SmartStore.Services.Shipping; using SmartStore.Services.Shipping.Tracking; +using SmartStore.Shipping.Services; namespace SmartStore.Shipping { - [SystemName("Shipping.ByTotal")] + [SystemName("Shipping.ByTotal")] [FriendlyName("Shipping by total")] [DisplayOrder(1)] public class ByTotalProvider : IShippingRateComputationMethod, IConfigurable @@ -28,7 +28,6 @@ public class ByTotalProvider : IShippingRateComputationMethod, IConfigurable private readonly ISettingService _settingService; private readonly ILocalizationService _localizationService; - /// /// Ctor /// @@ -56,14 +55,18 @@ public ByTotalProvider(IShippingService shippingService, this._logger = logger; this._settingService = settingService; this._localizationService = localizationService; - } - #region Properties + T = NullLocalizer.Instance; + } - /// - /// Gets a shipping rate computation method type - /// - public ShippingRateComputationMethodType ShippingRateComputationMethodType + #region Properties + + public Localizer T { get; set; } + + /// + /// Gets a shipping rate computation method type + /// + public ShippingRateComputationMethodType ShippingRateComputationMethodType { get { @@ -158,7 +161,7 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest get if (getShippingOptionRequest.Items == null || getShippingOptionRequest.Items.Count == 0) { - response.AddError("No shipment items"); + response.AddError(T("Admin.System.Warnings.NoShipmentItems")); return response; } diff --git a/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs b/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs index a4ae31c94e..cc303cee6e 100644 --- a/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs +++ b/src/Plugins/SmartStore.Shipping/Providers/FixedRateProvider.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Web.Routing; using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Localization; using SmartStore.Core.Plugins; using SmartStore.Services.Configuration; using SmartStore.Services.Localization; @@ -10,27 +11,29 @@ namespace SmartStore.Shipping { - /// - /// Fixed rate shipping computation provider - /// - [SystemName("Shipping.FixedRate")] + /// + /// Fixed rate shipping computation provider + /// + [SystemName("Shipping.FixedRate")] [FriendlyName("Fixed Rate Shipping")] [DisplayOrder(0)] public class FixedRateProvider : IShippingRateComputationMethod, IConfigurable { private readonly ISettingService _settingService; private readonly IShippingService _shippingService; - private readonly ILocalizationService _localizationService; public FixedRateProvider(ISettingService settingService, - IShippingService shippingService, ILocalizationService localizationService) + IShippingService shippingService) { this._settingService = settingService; this._shippingService = shippingService; - _localizationService = localizationService; - } - - private decimal GetRate(int shippingMethodId) + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + private decimal GetRate(int shippingMethodId) { string key = string.Format("ShippingRateComputationMethod.FixedRate.Rate.ShippingMethodId{0}", shippingMethodId); decimal rate = this._settingService.GetSettingByKey(key); @@ -51,7 +54,7 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest get if (getShippingOptionRequest.Items == null || getShippingOptionRequest.Items.Count == 0) { - response.AddError("No shipment items"); + response.AddError(T("Admin.System.Warnings.NoShipmentItems")); return response; } diff --git a/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs b/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs index 0c59828d2a..40334476a9 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/ByWeightShippingComputationMethod.cs @@ -3,15 +3,16 @@ using System.Web.Routing; using SmartStore.Core; using SmartStore.Core.Domain.Shipping; +using SmartStore.Core.Localization; using SmartStore.Core.Plugins; -using SmartStore.ShippingByWeight.Data; -using SmartStore.ShippingByWeight.Data.Migrations; -using SmartStore.ShippingByWeight.Services; using SmartStore.Services; using SmartStore.Services.Catalog; using SmartStore.Services.Localization; using SmartStore.Services.Shipping; using SmartStore.Services.Shipping.Tracking; +using SmartStore.ShippingByWeight.Data; +using SmartStore.ShippingByWeight.Data.Migrations; +using SmartStore.ShippingByWeight.Services; namespace SmartStore.ShippingByWeight { @@ -32,6 +33,7 @@ public class ByWeightShippingComputationMethod : BasePlugin, IShippingRateComput #endregion #region Ctor + public ByWeightShippingComputationMethod(IShippingService shippingService, IStoreContext storeContext, IShippingByWeightService shippingByWeightService, @@ -51,12 +53,17 @@ public ByWeightShippingComputationMethod(IShippingService shippingService, this._localizationService = localizationService; this._priceFormatter = priceFormatter; this._services = services; - } - #endregion - #region Utilities + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + #endregion + + #region Utilities - private decimal? GetRate(decimal subTotal, decimal weight, int shippingMethodId, int storeId, int countryId, string zip) + private decimal? GetRate(decimal subTotal, decimal weight, int shippingMethodId, int storeId, int countryId, string zip) { decimal? shippingTotal = null; @@ -120,7 +127,7 @@ public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest get if (getShippingOptionRequest.Items == null || getShippingOptionRequest.Items.Count == 0) { - response.AddError("No shipment items"); + response.AddError(T("Admin.System.Warnings.NoShipmentItems")); return response; } diff --git a/src/Plugins/SmartStore.ShippingByWeight/Controllers/ShippingByWeightController.cs b/src/Plugins/SmartStore.ShippingByWeight/Controllers/ShippingByWeightController.cs index aaa92a955f..383d77dfa2 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Controllers/ShippingByWeightController.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/Controllers/ShippingByWeightController.cs @@ -50,8 +50,10 @@ public ShippingByWeightController( public ActionResult Configure() { var shippingMethods = _shippingService.GetAllShippingMethods(); - if (shippingMethods.Count == 0) - return Content("No shipping methods can be loaded"); + if (shippingMethods.Count == 0) + { + return Content(T("Admin.Configuration.Shipping.Methods.NoMethodsLoaded")); + } var model = new ShippingByWeightListModel(); var countries = _countryService.GetAllCountries(true); diff --git a/src/Plugins/SmartStore.Tax/Controllers/TaxByRegionController.cs b/src/Plugins/SmartStore.Tax/Controllers/TaxByRegionController.cs index 53bc5628e7..ca43d31f14 100644 --- a/src/Plugins/SmartStore.Tax/Controllers/TaxByRegionController.cs +++ b/src/Plugins/SmartStore.Tax/Controllers/TaxByRegionController.cs @@ -70,7 +70,7 @@ public ActionResult Configure() var tc = _taxCategoryService.GetTaxCategoryById(x.TaxCategoryId); m.TaxCategoryName = (tc != null) ? tc.Name : ""; var c = _countryService.GetCountryById(x.CountryId); - m.CountryName = (c != null) ? c.Name : "Unavailable"; + m.CountryName = (c != null) ? c.Name : T("Common.Unavailable").Text; var s = _stateProvinceService.GetStateProvinceById(x.StateProvinceId); m.StateProvinceName = (s != null) ? s.Name : "*"; m.Zip = (!String.IsNullOrEmpty(x.Zip)) ? x.Zip : "*"; @@ -99,7 +99,7 @@ public ActionResult RatesList(GridCommand command) var tc = _taxCategoryService.GetTaxCategoryById(x.TaxCategoryId); m.TaxCategoryName = (tc != null) ? tc.Name : ""; var c = _countryService.GetCountryById(x.CountryId); - m.CountryName = (c != null) ? c.Name : "Unavailable"; + m.CountryName = (c != null) ? c.Name : T("Common.Unavailable").Text; var s = _stateProvinceService.GetStateProvinceById(x.StateProvinceId); m.StateProvinceName = (s != null) ? s.Name : "*"; m.Zip = (!String.IsNullOrEmpty(x.Zip)) ? x.Zip : "*"; From 07432d249b21eb3b5f8e089bb20ef63d322ea786 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 7 Jan 2016 11:52:49 +0100 Subject: [PATCH 384/874] GMC feed: Supporting fields multipack, bundle, adult, energy efficiency class and custom label (0 to 4) --- changelog.md | 1 + .../201512151526290_ImportFramework.cs | 4 + .../FeedGoogleMerchantCenterController.cs | 31 +++- .../Data/GoogleProductRecordMap.cs | 10 +- .../201601061649324_IsBundle.Designer.cs | 29 ++++ .../Migrations/201601061649324_IsBundle.cs | 34 ++++ .../Migrations/201601061649324_IsBundle.resx | 126 +++++++++++++++ .../Description.txt | 2 +- .../Domain/GoogleProductRecord.cs | 13 +- .../SmartStore.GoogleMerchantCenter/Events.cs | 39 ++--- .../Extensions/MiscExtensions.cs | 13 +- .../Localization/resources.de-de.xml | 84 +++++++--- .../Localization/resources.en-us.xml | 84 +++++++--- .../Models/FeedGoogleMerchantCenterModel.cs | 53 +++++-- .../Models/ProfileConfigurationModel.cs | 4 - .../Providers/GmcXmlExportProvider.cs | 59 ++++++- .../Services/GoogleFeedService.cs | 54 ++++++- .../SmartStore.GoogleMerchantCenter.csproj | 7 + .../FeedGoogleMerchantCenter/Configure.cshtml | 146 ++++++++++++++---- .../ProductEditTab.cshtml | 97 +++++++++++- .../ProfileConfiguration.cshtml | 15 +- .../changelog.md | 7 + 22 files changed, 748 insertions(+), 164 deletions(-) create mode 100644 src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.Designer.cs create mode 100644 src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.cs create mode 100644 src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.resx diff --git a/changelog.md b/changelog.md index 2308726b85..f6fa16e556 100644 --- a/changelog.md +++ b/changelog.md @@ -32,6 +32,7 @@ * #840 Activity log: Have option to exclude search engine activity * #841 Activity log for deleting an order * More settings to control creation of SEO names +* GMC feed: Supporting fields multipack, bundle, adult, energy efficiency class and custom label (0 to 4) ### Improvements * (Perf) Implemented static caches for URL aliases and localized properties. Increases app startup and request speed by up to 30%. diff --git a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs index 35b6497ae1..7f2dec3086 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs @@ -312,6 +312,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.System.Warnings.NoShipmentItems", "No shipment items", "Keine Versand-Artikel"); + + builder.AddOrUpdate("Admin.System.Warnings.DigitsOnly", + "Please enter digits only.", + "Bitte nur Ziffern eingeben."); } } } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs index 74b368fe98..6c37461c87 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Controllers/FeedGoogleMerchantCenterController.cs @@ -36,21 +36,31 @@ public ActionResult ProductEditTab(int productId) var culture = CultureInfo.InvariantCulture; var model = new GoogleProductModel { ProductId = productId }; var entity = _googleFeedService.GetGoogleProductRecord(productId); + string notSpecified = T("Common.Unspecified"); if (entity != null) { model.Taxonomy = entity.Taxonomy; model.Gender = entity.Gender; model.AgeGroup = entity.AgeGroup; + model.IsAdult = entity.IsAdult; model.Color = entity.Color; model.Size = entity.Size; model.Material = entity.Material; model.Pattern = entity.Pattern; - model.Exporting = entity.Export; + model.Export2 = entity.Export; + model.Multipack2 = entity.Multipack; + model.IsBundle = entity.IsBundle; + model.EnergyEfficiencyClass = entity.EnergyEfficiencyClass; + model.CustomLabel0 = entity.CustomLabel0; + model.CustomLabel1 = entity.CustomLabel1; + model.CustomLabel2 = entity.CustomLabel2; + model.CustomLabel3 = entity.CustomLabel3; + model.CustomLabel4 = entity.CustomLabel4; } else { - model.Exporting = true; + model.Export2 = true; } ViewBag.DefaultCategory = ""; @@ -58,8 +68,13 @@ public ActionResult ProductEditTab(int productId) ViewBag.DefaultSize = ""; ViewBag.DefaultMaterial = ""; ViewBag.DefaultPattern = ""; - ViewBag.DefaultGender = T("Common.Auto"); - ViewBag.DefaultAgeGroup = T("Common.Auto"); + ViewBag.DefaultGender = notSpecified; + ViewBag.DefaultAgeGroup = notSpecified; + ViewBag.DefaultIsAdult = ""; + ViewBag.DefaultMultipack2 = ""; + ViewBag.DefaultIsBundle = ""; + ViewBag.DefaultEnergyEfficiencyClass = notSpecified; + ViewBag.DefaultCustomLabel = ""; // we do not have export profile context here, so we simply use the first profile var profile = _exportService.GetExportProfilesBySystemName(GmcXmlExportProvider.SystemName).FirstOrDefault(); @@ -99,6 +114,11 @@ public ActionResult ProductEditTab(int productId) new SelectListItem { Value = "kids", Text = T("Plugins.Feed.Froogle.AgeGroupKids") }, }; + ViewBag.AvailableEnergyEfficiencyClasses = T("Plugins.Feed.Froogle.EnergyEfficiencyClasses").Text + .SplitSafe(",") + .Select(x => new SelectListItem { Value = x, Text = x }) + .ToList(); + var result = PartialView(model); result.ViewData.TemplateInfo = new TemplateInfo { HtmlFieldPrefix = "CustomProperties[GMC]" }; return result; @@ -114,8 +134,9 @@ public ActionResult Configure() { var model = new FeedGoogleMerchantCenterModel(); - model.AvailableGoogleCategories = _googleFeedService.GetTaxonomyList(); model.GridPageSize = _adminAreaSettings.GridPageSize; + model.AvailableGoogleCategories = _googleFeedService.GetTaxonomyList(); + model.EnergyEfficiencyClasses = T("Plugins.Feed.Froogle.EnergyEfficiencyClasses").Text.SplitSafe(","); return View(model); } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/GoogleProductRecordMap.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/GoogleProductRecordMap.cs index 5b4e0817ab..98aaec1aa4 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/GoogleProductRecordMap.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/GoogleProductRecordMap.cs @@ -9,6 +9,14 @@ public GoogleProductRecordMap() { this.ToTable("GoogleProduct"); this.HasKey(x => x.Id); - } + + this.Property(x => x.EnergyEfficiencyClass).HasMaxLength(50); + + this.Property(x => x.CustomLabel0).HasMaxLength(100); + this.Property(x => x.CustomLabel1).HasMaxLength(100); + this.Property(x => x.CustomLabel2).HasMaxLength(100); + this.Property(x => x.CustomLabel3).HasMaxLength(100); + this.Property(x => x.CustomLabel4).HasMaxLength(100); + } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.Designer.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.Designer.cs new file mode 100644 index 0000000000..652bba012c --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.GoogleMerchantCenter.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 IsBundle : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(IsBundle)); + + string IMigrationMetadata.Id + { + get { return "201601061649324_IsBundle"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.cs new file mode 100644 index 0000000000..bc8af7c35e --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.cs @@ -0,0 +1,34 @@ +namespace SmartStore.GoogleMerchantCenter.Data.Migrations +{ + using System; + using System.Data.Entity.Migrations; + + public partial class IsBundle : DbMigration + { + public override void Up() + { + AddColumn("dbo.GoogleProduct", "Multipack", c => c.Int(nullable: false)); + AddColumn("dbo.GoogleProduct", "IsBundle", c => c.Boolean()); + AddColumn("dbo.GoogleProduct", "IsAdult", c => c.Boolean()); + AddColumn("dbo.GoogleProduct", "EnergyEfficiencyClass", c => c.String(maxLength: 50)); + AddColumn("dbo.GoogleProduct", "CustomLabel0", c => c.String(maxLength: 100)); + AddColumn("dbo.GoogleProduct", "CustomLabel1", c => c.String(maxLength: 100)); + AddColumn("dbo.GoogleProduct", "CustomLabel2", c => c.String(maxLength: 100)); + AddColumn("dbo.GoogleProduct", "CustomLabel3", c => c.String(maxLength: 100)); + AddColumn("dbo.GoogleProduct", "CustomLabel4", c => c.String(maxLength: 100)); + } + + public override void Down() + { + DropColumn("dbo.GoogleProduct", "CustomLabel4"); + DropColumn("dbo.GoogleProduct", "CustomLabel3"); + DropColumn("dbo.GoogleProduct", "CustomLabel2"); + DropColumn("dbo.GoogleProduct", "CustomLabel1"); + DropColumn("dbo.GoogleProduct", "CustomLabel0"); + DropColumn("dbo.GoogleProduct", "EnergyEfficiencyClass"); + DropColumn("dbo.GoogleProduct", "IsAdult"); + DropColumn("dbo.GoogleProduct", "IsBundle"); + DropColumn("dbo.GoogleProduct", "Multipack"); + } + } +} diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.resx b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.resx new file mode 100644 index 0000000000..ebf95a7c54 --- /dev/null +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Data/Migrations/201601061649324_IsBundle.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 + + + H4sIAAAAAAAEAM2a227jNhCG7wv0HQRdtUDW8iEF2sDeReIki6BxHETJ3tPS2FGXIlWSCuy+Wi/6SH2Fjs6iJNuSHQe9s0fkNwdS1K+x//37n/GXtU+NNxDS42xiDnp90wDmcNdjq4kZquWnX80vn3/8YXzj+mvjWzZuFI3DmUxOzFelggvLks4r+ET2fM8RXPKl6jnct4jLrWG//5s1GFiACBNZhjF+CpnyfIi/4NcpZw4EKiR0xl2gMrXjFTumGg/EBxkQByam7ROhbMUF9L5yvqIwA+G8EqamwBSI3jVRxDQuqUcwNhvo0jQIY1wRhZFfvEiwleBsZQdoIPR5EwCOWxIqIc3oohjeNrn+MErOKiZmKCeUivsdgYNRWi2rOv2gmpt5NbGeN1h3tYmyjms6MZMaPgruho56AocL1zSqfi+mVERzWlQfp3ms10A9M/bMPcu317A37PV7/TNjGlIVCpgwCJUg9Mx4DBfUc36HzTP/DmzCQkrL6WGCeE0zoAnDCECozRMs06TvMEdLn2dVJ+bTSnOSGtwxNRqaxgM6JwsK+e4p1SvO8iswEESB+0gU5sciBsT1r3mv+EoLt9/lbswzWXPG/U1GwZ2Pt7VpzMj6HthKvU5M/Ggat94a3MySkl+Yh6cATlIi3OsIM3VBnNzN5Qq+Ch4GJ3c05ZSfPh3b+wtO7mSGG1DgQXdyR+kmP7mfOwV+vAuKu+N0vuQzD/GMzT1dcU6BsM534lRAdBDM2YtyMhY+qOAZH4KdYS+B+36wm3XAhTo2vxke1B4+n78fd2DdyauQuRRq4eybduliAB1n3eDpvNrcLJee46Hk2UwpkXLHlvql/x4nS/xkvScLoP0dvgb9d3Y2+Ehnw490NvpIZ+fv7GxsFYKsLtNQFitUUyCatNp88Qc4KhoCa9Ug2VDppqpNpp71JBMXNqjtQhDvhiK8REw3KbvmlPLgCzFvJWo+U/3WFtk/npEgwOqWXgNSi2En7wDTT3Z3KewnDMuRDYo4jzb3hAqOrKByNZIGLtx6QqroPWNBovWdun5t2I6l2rIMmeMdq1EVtsXiZJOjz6m4aPGa1LiYFSdF8W+xHj5OjksDedzNrxA1SvwuRygRDbIa5Vbos23SfNfsklAuQ0rm9qxCLZdRhbU9KZPDZU5ma08p1G6ZU1jbk1I5W8akpvaMRKyWEYmlPaFQomVKYe2w6pnU1NY8M7bnaFJS24TlCx14hVzUaIW5w5ppilFbOu1Ke6IuG8tE/Up7YqYdy6zM1mFfFNpR2xiFucsKZAJSX4DM2oWUakodlBo71KhZZWolax7SYa9oolLbK9qVg4iDrcTBgcThVuLwQOJoK3F0IPF8K/G8iTi2Kg/J6qPaqj2rK+2nqg7YpaiqQ3LvubKqKKhxqmb2d1dr8iYZYhpYqjfPjaSNvZF4PCYCwv6TTiluWlUMmBHmLUGqpENnDvuDYaUd+/9pjVpSurR7f/TDO45eVOC9PcWOL9u1JmPs5cgWI3sjkcwUP/lk/XOZ1r2NeBSq2io8Cqa1A48ilVt+R4Gqbb2jYJXW3VGshvbccbxqC27hdd+lTe23SPKo92q/HQzT22+H5FZrvR1yG1cbb5VA2i2U1oQ7gLCzIZdtolpLruOt3NB/a0TH3ZyD2YMTsocnZI9OyD4/jt2tVVZv03TufrVtfiUKCc+ABccMk/C1gQf3x+qqbWyVfzkfX4P0VgUi+h2dgRPJoQKajbljS54tDuZdjigbUj1ZQBE82MilUN6SOAovOyBl3Pf8RmgY3bL+Atw7Ng9VEKpLKcFfUK1VMrZ2+4+bgHrM43kQfZPvkQKG6UVn85xdhR5187hv64fiNkS0k1LVhVHZKlJfq01OeuCsJSgt3zUEkcxh6hn8gCJMzplN3uCQ2F4k3MOKOJtMfG+H7F8Ivezja4+sBPFlyijmR/8GsaK/g3z+D2Zr4jJAIgAA + + + dbo + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt index 043c9f3476..55dfd4845a 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.2.0.4 +Version: 2.2.0.5 MinAppVersion: 2.2.0 Author: SmartStore AG DisplayOrder: 1 diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Domain/GoogleProductRecord.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Domain/GoogleProductRecord.cs index cca0f73827..7064e84880 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Domain/GoogleProductRecord.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Domain/GoogleProductRecord.cs @@ -29,5 +29,16 @@ public GoogleProductRecord() public DateTime UpdatedOnUtc { get; set; } public bool Export { get; set; } - } + + public int Multipack { get; set; } + public bool? IsBundle { get; set; } + public bool? IsAdult { get; set; } + public string EnergyEfficiencyClass { get; set; } + + public string CustomLabel0 { get; set; } + public string CustomLabel1 { get; set; } + public string CustomLabel2 { get; set; } + public string CustomLabel3 { get; set; } + public string CustomLabel4 { get; set; } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs index 3673c52c20..ea0bd90c3a 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Events.cs @@ -10,8 +10,7 @@ namespace SmartStore.GoogleMerchantCenter { public class Events : IConsumer, - IConsumer/*, - IConsumer*/ + IConsumer { private readonly IGoogleFeedService _googleService; @@ -25,6 +24,7 @@ public void HandleEvent(TabStripCreated eventMessage) if (eventMessage.TabStripName == "product-edit") { var productId = ((TabbableModel)eventMessage.Model).Id; + eventMessage.ItemFactory.Add().Text("GMC") .Name("tab-gmc") .Icon("fa fa-google fa-lg fa-fw") @@ -34,30 +34,6 @@ public void HandleEvent(TabStripCreated eventMessage) } } - //public void HandleEvent(RowExportingEvent eventMessage) - //{ - // if (eventMessage.EntityType != ExportEntityType.Product) - // return; - - // var row = eventMessage.Row; - // var product = eventMessage.Row.Entity as Product; - - // if (product == null) - // return; - - // var gmc = _googleService.GetGoogleProductRecord(product.Id); - // if (gmc == null) - // return; - - // row["_GMC_AgeGroup"] = gmc.AgeGroup; - // row["_GMC_Color"] = gmc.Color; - // row["_GMC_Gender"] = gmc.Gender; - // row["_GMC_Size"] = gmc.Size; - // row["_GMC_Taxonomy"] = gmc.Taxonomy; - // row["_GMC_Material"] = gmc.Material; - // row["_GMC_Pattern"] = gmc.Pattern; - //} - public void HandleEvent(ModelBoundEvent eventMessage) { if (!eventMessage.BoundModel.CustomProperties.ContainsKey("GMC")) @@ -88,8 +64,17 @@ public void HandleEvent(ModelBoundEvent eventMessage) entity.Taxonomy = model.Taxonomy; entity.Material = model.Material; entity.Pattern = model.Pattern; - entity.Export = model.Exporting; + entity.Export = model.Export2; entity.UpdatedOnUtc = utcNow; + entity.Multipack = model.Multipack2 ?? 0; + entity.IsBundle = model.IsBundle; + entity.IsAdult = model.IsAdult; + entity.EnergyEfficiencyClass = model.EnergyEfficiencyClass; + entity.CustomLabel0 = model.CustomLabel0; + entity.CustomLabel1 = model.CustomLabel1; + entity.CustomLabel2 = model.CustomLabel2; + entity.CustomLabel3 = model.CustomLabel3; + entity.CustomLabel4 = model.CustomLabel4; entity.IsTouched = entity.IsTouched(); diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs index c140111c1e..5a8dd0168d 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Extensions/MiscExtensions.cs @@ -9,7 +9,7 @@ public static string XEditableLink(this HtmlHelper hlp, string fieldName, string { string displayText = null; - if (fieldName == "Gender" || fieldName == "AgeGroup" || fieldName == "Exporting") + if (fieldName == "Gender" || fieldName == "AgeGroup" || fieldName == "Export2" || fieldName == "IsBundle" || fieldName == "IsAdult") displayText = "<#= {0}Localize #>".FormatInvariant(fieldName); else displayText = "<#= {0} #>".FormatInvariant(fieldName); @@ -22,12 +22,15 @@ public static string XEditableLink(this HtmlHelper hlp, string fieldName, string return skeleton.FormatInvariant(fieldName, fieldName.ToLower(), type, displayText); } - public static bool IsTouched(this GoogleProductRecord product) + public static bool IsTouched(this GoogleProductRecord p) { - if (product != null) + if (p != null) { - return product.Taxonomy.HasValue() || product.Gender.HasValue() || product.AgeGroup.HasValue() || product.Color.HasValue() || - product.Size.HasValue() || product.Material.HasValue() || product.Pattern.HasValue() || product.ItemGroupId.HasValue() || !product.Export; + return + p.Taxonomy.HasValue() || p.Gender.HasValue() || p.AgeGroup.HasValue() || p.Color.HasValue() || + p.Size.HasValue() || p.Material.HasValue() || p.Pattern.HasValue() || p.ItemGroupId.HasValue() || + !p.Export || p.Multipack != 0 || p.IsBundle.HasValue || p.IsAdult.HasValue || p.EnergyEfficiencyClass.HasValue() || + p.CustomLabel0.HasValue() || p.CustomLabel1.HasValue() || p.CustomLabel2.HasValue() || p.CustomLabel3.HasValue() || p.CustomLabel4.HasValue(); } return false; } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml index e4aa14c576..17b90e856c 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.de-de.xml @@ -37,24 +37,9 @@ Google-Kategorie - - Geschlecht - - - Altersgruppe - - - Farbe - - - Größe - - - Material - - - Muster - + + Legt die dem Artikel entsprechende Google-Kategorie fest. Zwingend erforderlich. + Zusätzliche Bilder @@ -148,12 +133,6 @@ Muster oder grafisches Druckdesign eines Produktes. Angabe ist z.B. bei Bekleidung sinnvoll. - - Nur online zu kaufen - - - Legt fest, ob Produkte nur online erworben werden können. Aktivieren Sie diese Option nicht, falls Produkte auch in Ihrem Ladengeschäft erhältlich sind. - Leer @@ -196,4 +175,61 @@ Legt fest, ob der Grundpreis eines Produktes exportiert werden soll. + + Multipack + + + Anzahl identischer Produkte in einem händlerdefinierten Multipack. Muss größer 1 sein. + + + Bundle + + + Händlerdefiniertes Produktpaket bestehend aus einem Haupt- und mehreren Zubehörartikel bzw. Add-ons. + + + Nicht jugendfrei + + + Produkt, welches nur für erwachsene Nutzer bestimmt ist. + + + Energieeffizienz + + + Die Energieeffizienzklasse des Produktes gemäß EU-Richtlinie 2010/30/EU. Mögliche Werte sind G, F, E, D, C, B, A, A+, A++, A+++. + + + A+++,A++,A+,A,B,C,D,E,F,G + + + Label 0 + + + Benutzerdefiniertes Label 0. Dient der individuellen Gruppierung von Produkten. + + + Label 1 + + + Benutzerdefiniertes Label 1. Dient der individuellen Gruppierung von Produkten. + + + Label 2 + + + Benutzerdefiniertes Label 2. Dient der individuellen Gruppierung von Produkten. + + + Label 3 + + + Benutzerdefiniertes Label 3. Dient der individuellen Gruppierung von Produkten. + + + Label 4 + + + Benutzerdefiniertes Label 4. Dient der individuellen Gruppierung von Produkten. + \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml index a80721be22..3b75a8c306 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Localization/resources.en-us.xml @@ -37,24 +37,9 @@ Google Category - - Gender - - - Age group - - - Color - - - Size - - - Material - - - Pattern - + + Specifies the Google category corresponding to the product. Mandatory. + Additional images @@ -148,12 +133,6 @@ The pattern or graphic print featured on a product. Usefull for clothes for instance. - - Only online available - - - Check the box if products can only be purchased online. - Empty @@ -196,4 +175,61 @@ Activate this option if you want to export the base price of the product. + + Multipack + + + Number of identical products in a merchant defined multipack. Must be greater than 1. + + + Bundle + + + Merchant defined product package consisting of one main and several accessories or add-ons. + + + Adult + + + Product, which is intended only for adult users. + + + Energy efficiency + + + The energy efficiency class of the product in accordance with EU directive 2010/30/EU. Possible values are G, F, E, D, C, B, A, A +, A++, A+++. + + + A+++,A++,A+,A,B,C,D,E,F,G + + + Label 0 + + + Custom label 0 serves the individual grouping of products. + + + Label 1 + + + Custom label 1 serves the individual grouping of products. + + + Label 2 + + + Custom label 2 serves the individual grouping of products. + + + Label 3 + + + Custom label 3 serves the individual grouping of products. + + + Label 4 + + + Custom label 4 serves the individual grouping of products. + \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedGoogleMerchantCenterModel.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedGoogleMerchantCenterModel.cs index 0ec41770fe..36c5f02799 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedGoogleMerchantCenterModel.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/FeedGoogleMerchantCenterModel.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using System.ComponentModel.DataAnnotations; +using System.Text; using Newtonsoft.Json; using SmartStore.Core.Domain.Catalog; using SmartStore.Web.Framework; @@ -18,10 +19,12 @@ public string AvailableGoogleCategoriesAsJson { if (AvailableGoogleCategories != null && AvailableGoogleCategories.Length > 0) return JsonConvert.SerializeObject(AvailableGoogleCategories); - return ""; + return "[ ]"; } } + public string[] EnergyEfficiencyClasses { get; set; } + [SmartResourceDisplayName("Plugins.Feed.Froogle.SearchProductName")] public string SearchProductName { get; set; } @@ -29,7 +32,6 @@ public string AvailableGoogleCategoriesAsJson public string SearchIsTouched { get; set; } } - public class GoogleProductModel : ModelBase { public int TotalCount { get; set; } @@ -74,35 +76,66 @@ public string ProductTypeLabelHint [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.GoogleCategory")] public string Taxonomy { get; set; } - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Gender")] + [SmartResourceDisplayName("Plugins.Feed.Froogle.Gender")] public string Gender { get; set; } - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.AgeGroup")] + [SmartResourceDisplayName("Plugins.Feed.Froogle.AgeGroup")] public string AgeGroup { get; set; } - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Color")] + [SmartResourceDisplayName("Plugins.Feed.Froogle.Color")] public string Color { get; set; } - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Size")] + [SmartResourceDisplayName("Plugins.Feed.Froogle.Size")] public string Size { get; set; } - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Material")] + [SmartResourceDisplayName("Plugins.Feed.Froogle.Material")] public string Material { get; set; } - [SmartResourceDisplayName("Plugins.Feed.Froogle.Products.Pattern")] + [SmartResourceDisplayName("Plugins.Feed.Froogle.Pattern")] public string Pattern { get; set; } [SmartResourceDisplayName("Common.Export")] public int Export { get; set; } [SmartResourceDisplayName("Common.Export")] - public bool Exporting + public bool Export2 { get { return Export != 0; } set { Export = (value ? 1 : 0); } } + [SmartResourceDisplayName("Plugins.Feed.Froogle.Multipack")] + public int Multipack { get; set; } + [SmartResourceDisplayName("Plugins.Feed.Froogle.Multipack")] + public int? Multipack2 + { + get { return Multipack > 0 ? Multipack : (int?)null; } + set { Multipack = (value ?? 0); } + } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.IsBundle")] + public bool? IsBundle { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.IsAdult")] + public bool? IsAdult { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.EnergyEfficiencyClass")] + public string EnergyEfficiencyClass { get; set; } + + [SmartResourceDisplayName("Plugins.Feed.Froogle.CustomLabel0")] + public string CustomLabel0 { get; set; } + [SmartResourceDisplayName("Plugins.Feed.Froogle.CustomLabel1")] + public string CustomLabel1 { get; set; } + [SmartResourceDisplayName("Plugins.Feed.Froogle.CustomLabel2")] + public string CustomLabel2 { get; set; } + [SmartResourceDisplayName("Plugins.Feed.Froogle.CustomLabel3")] + public string CustomLabel3 { get; set; } + [SmartResourceDisplayName("Plugins.Feed.Froogle.CustomLabel4")] + public string CustomLabel4 { get; set; } + public string GenderLocalize { get; set; } public string AgeGroupLocalize { get; set; } - public string ExportingLocalize { get; set; } + public string Export2Localize { get; set; } + public string IsBundleLocalize { get; set; } + public string IsAdultLocalize { get; set; } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs index 6b96944375..69d81d50dd 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Models/ProfileConfigurationModel.cs @@ -14,7 +14,6 @@ public class ProfileConfigurationModel public ProfileConfigurationModel() { Condition = "new"; - OnlineOnly = true; AdditionalImages = true; SpecialPrice = true; } @@ -66,9 +65,6 @@ public string AvailableGoogleCategoriesAsJson [SmartResourceDisplayName("Plugins.Feed.Froogle.Pattern")] public string Pattern { get; set; } - [SmartResourceDisplayName("Plugins.Feed.Froogle.OnlineOnly")] - public bool OnlineOnly { get; set; } - [SmartResourceDisplayName("Plugins.Feed.Froogle.ExpirationDays")] public int ExpirationDays { get; set; } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index b43d2abe70..3a5cc536f6 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -157,6 +157,7 @@ protected override void Export(IExportExecuteContext context) { dynamic currency = context.Currency; string measureWeightSystemKey = ""; + var dateFormat = "yyyy-MM-ddTHH:mmZ"; var measureWeight = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId); @@ -287,9 +288,15 @@ protected override void Export(IExportExecuteContext context) writer.WriteElementString("g", "condition", _googleNamespace, condition); writer.WriteElementString("g", "availability", _googleNamespace, availability); + if (availability == "preorder" && entity.AvailableStartDateTimeUtc.HasValue && entity.AvailableStartDateTimeUtc.Value > DateTime.UtcNow) + { + var availabilityDate = entity.AvailableStartDateTimeUtc.Value.ToString(dateFormat); + + writer.WriteElementString("g", "availability_date", _googleNamespace, availabilityDate); + } + if (config.SpecialPrice && specialPrice.HasValue && entity.SpecialPriceStartDateTimeUtc.HasValue && entity.SpecialPriceEndDateTimeUtc.HasValue) { - var dateFormat = "yyyy-MM-ddTHH:mmZ"; var specialPriceDate = "{0}/{1}".FormatInvariant( entity.SpecialPriceStartDateTimeUtc.Value.ToString(dateFormat), entity.SpecialPriceEndDateTimeUtc.Value.ToString(dateFormat)); @@ -321,7 +328,6 @@ protected override void Export(IExportExecuteContext context) writer.WriteCData("pattern", gmc != null && gmc.Pattern.HasValue() ? gmc.Pattern : config.Pattern, "g", _googleNamespace); writer.WriteCData("item_group_id", gmc != null && gmc.ItemGroupId.HasValue() ? gmc.ItemGroupId : "", "g", _googleNamespace); - writer.WriteElementString("g", "online_only", _googleNamespace, config.OnlineOnly ? "y" : "n"); writer.WriteElementString("g", "identifier_exists", _googleNamespace, gtin.HasValue() || brand.HasValue() || mpn.HasValue() ? "TRUE" : "FALSE"); if (config.ExpirationDays > 0) @@ -360,11 +366,56 @@ protected override void Export(IExportExecuteContext context) } } + if (gmc != null && gmc.Multipack > 1) + { + writer.WriteElementString("g", "multipack", _googleNamespace, gmc.Multipack.ToString()); + } + + if (gmc != null && gmc.IsBundle.HasValue) + { + writer.WriteElementString("g", "is_bundle", _googleNamespace, gmc.IsBundle.Value ? "TRUE" : "FALSE"); + } + + if (gmc != null && gmc.IsAdult.HasValue) + { + writer.WriteElementString("g", "adult", _googleNamespace, gmc.IsAdult.Value ? "TRUE" : "FALSE"); + } + + if (gmc != null && gmc.EnergyEfficiencyClass.HasValue()) + { + writer.WriteElementString("g", "energy_efficiency_class", _googleNamespace, gmc.EnergyEfficiencyClass); + } + + if (gmc != null && gmc.CustomLabel0.HasValue()) + { + writer.WriteElementString("g", "custom_label_0", _googleNamespace, gmc.CustomLabel0); + } + + if (gmc != null && gmc.CustomLabel1.HasValue()) + { + writer.WriteElementString("g", "custom_label_1", _googleNamespace, gmc.CustomLabel1); + } + + if (gmc != null && gmc.CustomLabel2.HasValue()) + { + writer.WriteElementString("g", "custom_label_2", _googleNamespace, gmc.CustomLabel2); + } + + if (gmc != null && gmc.CustomLabel3.HasValue()) + { + writer.WriteElementString("g", "custom_label_3", _googleNamespace, gmc.CustomLabel3); + } + + if (gmc != null && gmc.CustomLabel4.HasValue()) + { + writer.WriteElementString("g", "custom_label_4", _googleNamespace, gmc.CustomLabel4); + } + ++context.RecordsSucceeded; } - catch (Exception exc) + catch (Exception exception) { - context.RecordException(exc, entity.Id); + context.RecordException(exception, entity.Id); } writer.WriteEndElement(); // item diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs index 48d574215e..b5fab3af04 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Services/GoogleFeedService.cs @@ -125,9 +125,36 @@ public void Upsert(int pk, string name, string value) case "Pattern": product.Pattern = value; break; - case "Exporting": + case "Export2": product.Export = value.ToBool(true); break; + case "Multipack2": + product.Multipack = value.ToInt(); + break; + case "IsBundle": + product.IsBundle = (value.IsEmpty() ? (bool?)null : value.ToBool()); + break; + case "IsAdult": + product.IsAdult = (value.IsEmpty() ? (bool?)null : value.ToBool()); + break; + case "EnergyEfficiencyClass": + product.EnergyEfficiencyClass = value; + break; + case "CustomLabel0": + product.CustomLabel0 = value; + break; + case "CustomLabel1": + product.CustomLabel1 = value; + break; + case "CustomLabel2": + product.CustomLabel2 = value; + break; + case "CustomLabel3": + product.CustomLabel3 = value; + break; + case "CustomLabel4": + product.CustomLabel4 = value; + break; } product.UpdatedOnUtc = utcNow; @@ -156,7 +183,8 @@ public GridModel GetGridModel(GridCommand command, string se string yes = T("Admin.Common.Yes"); string no = T("Admin.Common.No"); - // there's no way to share a context instance across repositories which makes GoogleProductObjectContext pretty useless here. + // there's no way to share a context instance across repositories in EF. + // so we have to fallback to pure SQL here to get the data paged and filtered. var whereClause = new StringBuilder("(NOT ([t2].[Deleted] = 1)) AND ([t2].[VisibleIndividually] = 1)"); @@ -181,11 +209,11 @@ public GridModel GetGridModel(GridCommand command, string se { // fastest possible paged data query sql = - "SELECT [TotalCount], [t3].[Id], [t3].[Name], [t3].[SKU], [t3].[ProductTypeId], [t3].[value] AS [Taxonomy], [t3].[value2] AS [Gender], [t3].[value3] AS [AgeGroup], [t3].[value4] AS [Color], [t3].[value5] AS [Size], [t3].[value6] AS [Material], [t3].[value7] AS [Pattern], [t3].[value8] AS [Export]" + + "SELECT [TotalCount], [t3].[Id], [t3].[Name], [t3].[SKU], [t3].[ProductTypeId], [t3].[value] AS [Taxonomy], [t3].[value2] AS [Gender], [t3].[value3] AS [AgeGroup], [t3].[value4] AS [Color], [t3].[value5] AS [Size], [t3].[value6] AS [Material], [t3].[value7] AS [Pattern], [t3].[value8] AS [Export], [t3].[value9] AS [Multipack], [t3].[value10] AS [IsBundle], [t3].[value11] AS [IsAdult], [t3].[value12] AS [EnergyEfficiencyClass], [t3].[value13] AS [CustomLabel0], [t3].[value14] AS [CustomLabel1], [t3].[value15] AS [CustomLabel2], [t3].[value16] AS [CustomLabel3], [t3].[value17] AS [CustomLabel4]" + " FROM (" + - " SELECT COUNT(id) OVER() [TotalCount], ROW_NUMBER() OVER (ORDER BY [t2].[Name]) AS [ROW_NUMBER], [t2].[Id], [t2].[Name], [t2].[SKU], [t2].[ProductTypeId], [t2].[value], [t2].[value2], [t2].[value3], [t2].[value4], [t2].[value5], [t2].[value6], [t2].[value7], [t2].[value8]" + + " SELECT COUNT(id) OVER() [TotalCount], ROW_NUMBER() OVER (ORDER BY [t2].[Name]) AS [ROW_NUMBER], [t2].[Id], [t2].[Name], [t2].[SKU], [t2].[ProductTypeId], [t2].[value], [t2].[value2], [t2].[value3], [t2].[value4], [t2].[value5], [t2].[value6], [t2].[value7], [t2].[value8], [t2].[value9], [t2].[value10], [t2].[value11], [t2].[value12], [t2].[value13], [t2].[value14], [t2].[value15], [t2].[value16], [t2].[value17]" + " FROM (" + - " SELECT [t0].[Id], [t0].[Name], [t0].[SKU], [t0].[ProductTypeId], [t1].[Taxonomy] AS [value], [t1].[Gender] AS [value2], [t1].[AgeGroup] AS [value3], [t1].[Color] AS [value4], [t1].[Size] AS [value5], [t1].[Material] AS [value6], [t1].[Pattern] AS [value7], COALESCE([t1].[Export],1) AS [value8], [t0].[Deleted], [t0].[VisibleIndividually], [t1].[IsTouched]" + + " SELECT [t0].[Id], [t0].[Name], [t0].[SKU], [t0].[ProductTypeId], [t1].[Taxonomy] AS [value], [t1].[Gender] AS [value2], [t1].[AgeGroup] AS [value3], [t1].[Color] AS [value4], [t1].[Size] AS [value5], [t1].[Material] AS [value6], [t1].[Pattern] AS [value7], COALESCE([t1].[Export],1) AS [value8], COALESCE([t1].[Multipack],0) AS [value9], [t1].[IsBundle] AS [value10], [t1].[IsAdult] AS [value11], [t1].[EnergyEfficiencyClass] AS [value12], [t1].[CustomLabel0] AS [value13], [t1].[CustomLabel1] AS [value14], [t1].[CustomLabel2] AS [value15], [t1].[CustomLabel3] AS [value16], [t1].[CustomLabel4] AS [value17], [t0].[Deleted], [t0].[VisibleIndividually], [t1].[IsTouched]" + " FROM [Product] AS [t0]" + " LEFT OUTER JOIN [GoogleProduct] AS [t1] ON [t0].[Id] = [t1].[ProductId]" + " ) AS [t2]" + @@ -198,9 +226,9 @@ public GridModel GetGridModel(GridCommand command, string se { // OFFSET... FETCH NEXT requires SQL Server 2012 or SQL CE 4 sql = - "SELECT [t2].[Id], [t2].[Name], [t2].[SKU], [t2].[ProductTypeId], [t2].[value] AS [Taxonomy], [t2].[value2] AS [Gender], [t2].[value3] AS [AgeGroup], [t2].[value4] AS [Color], [t2].[value5] AS [Size], [t2].[value6] AS [Material], [t2].[value7] AS [Pattern], [t2].[value8] AS [Export]" + + "SELECT [t2].[Id], [t2].[Name], [t2].[SKU], [t2].[ProductTypeId], [t2].[value] AS [Taxonomy], [t2].[value2] AS [Gender], [t2].[value3] AS [AgeGroup], [t2].[value4] AS [Color], [t2].[value5] AS [Size], [t2].[value6] AS [Material], [t2].[value7] AS [Pattern], [t2].[value8] AS [Export], [t2].[value9] AS [Multipack], [t2].[value10] AS [IsBundle], [t2].[value11] AS [IsAdult], [t2].[value12] AS [EnergyEfficiencyClass], [t2].[value13] AS [CustomLabel0], [t2].[value14] AS [CustomLabel1], [t2].[value15] AS [CustomLabel2], [t2].[value16] AS [CustomLabel3], [t2].[value17] AS [CustomLabel4]" + " FROM (" + - " SELECT [t0].[Id], [t0].[Name], [t0].[SKU], [t0].[ProductTypeId], [t1].[Taxonomy] AS [value], [t1].[Gender] AS [value2], [t1].[AgeGroup] AS [value3], [t1].[Color] AS [value4], [t1].[Size] AS [value5], [t1].[Material] AS [value6], [t1].[Pattern] AS [value7], COALESCE([t1].[Export],1) AS [value8], [t0].[Deleted], [t0].[VisibleIndividually], [t1].[IsTouched] AS [IsTouched]" + + " SELECT [t0].[Id], [t0].[Name], [t0].[SKU], [t0].[ProductTypeId], [t1].[Taxonomy] AS [value], [t1].[Gender] AS [value2], [t1].[AgeGroup] AS [value3], [t1].[Color] AS [value4], [t1].[Size] AS [value5], [t1].[Material] AS [value6], [t1].[Pattern] AS [value7], COALESCE([t1].[Export],1) AS [value8], COALESCE([t1].[Multipack],0) AS [value9], [t1].[IsBundle] AS [value10], [t1].[IsAdult] AS [value11], [t1].[EnergyEfficiencyClass] AS [value12], [t1].[CustomLabel0] AS [value13], [t1].[CustomLabel1] AS [value14], [t1].[CustomLabel2] AS [value15], [t1].[CustomLabel3] AS [value16], [t1].[CustomLabel4] AS [value17], [t0].[Deleted], [t0].[VisibleIndividually], [t1].[IsTouched] AS [IsTouched]" + " FROM [Product] AS [t0]" + " LEFT OUTER JOIN [GoogleProduct] AS [t1] ON [t0].[Id] = [t1].[ProductId]" + " ) AS [t2]" + @@ -232,7 +260,17 @@ public GridModel GetGridModel(GridCommand command, string se if (x.AgeGroup.HasValue()) x.AgeGroupLocalize = T("Plugins.Feed.Froogle.AgeGroup" + textInfo.ToTitleCase(x.AgeGroup)); - x.ExportingLocalize = (x.Export == 0 ? no : yes); + x.Export2Localize = (x.Export == 0 ? no : yes); + + if (x.IsBundle.HasValue) + x.IsBundleLocalize = (x.IsBundle.Value ? yes : no); + else + x.IsBundleLocalize = null; + + if (x.IsAdult.HasValue) + x.IsAdultLocalize = (x.IsAdult.Value ? yes : no); + else + x.IsAdultLocalize = null; }); model.Data = data; diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj index aa2b7dca53..f027f7dd39 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj @@ -169,6 +169,10 @@ 201504211854125_IsActive.cs + + + 201601061649324_IsBundle.cs + @@ -218,6 +222,9 @@ 201504211854125_IsActive.cs + + 201601061649324_IsBundle.cs + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml index 6312648112..d91ec16571 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/Configure.cshtml @@ -3,7 +3,6 @@ @using SmartStore.GoogleMerchantCenter.Models; @using SmartStore.GoogleMerchantCenter.Providers; @using SmartStore.Web.Framework; -@using SmartStore.Web.Framework.Plugins; @using Telerik.Web.Mvc.UI; @using SmartStore.Web.Framework.UI; @@ -53,7 +52,8 @@ @Html.SmartLabelFor(m => m.SearchIsTouched) - @Html.DropDownList("SearchIsTouched", new List { + @Html.DropDownList("SearchIsTouched", new List + { new SelectListItem { Text = T("Common.Unspecified"), Value = "" }, new SelectListItem { Text = T("Plugins.Feed.Froogle.SearchIsTouched.Touched"), Value = "touched" }, new SelectListItem { Text = T("Plugins.Feed.Froogle.SearchIsTouched.Untouched"), Value = "untouched" } @@ -82,25 +82,56 @@ }) .Columns(c => { - c.Bound(x => x.ProductId).ReadOnly().Visible(false); + c.Bound(x => x.ProductId) + .ReadOnly() + .Visible(false); c.Bound(x => x.Name) - .ReadOnly().Visible(true).Width(420) + .ReadOnly().Visible(true) .Template(x => @Html.LabeledProductName(x.ProductId, x.Name, x.ProductTypeName, x.ProductTypeLabelHint)) .ClientTemplate(@Html.LabeledProductName("ProductId", "Name")); - c.Bound(x => x.SKU).ReadOnly().Visible(true); - c.Bound(x => x.Exporting).ClientTemplate(Html.XEditableLink("Exporting", "select2")); - c.Bound(x => x.Taxonomy).ClientTemplate(Html.XEditableLink("Taxonomy", "typeahead")); - c.Bound(x => x.Gender).ClientTemplate(Html.XEditableLink("Gender", "select2")).Width(100); - c.Bound(x => x.AgeGroup).ClientTemplate(Html.XEditableLink("AgeGroup", "select2")).Width(100); - c.Bound(x => x.Color).ClientTemplate(Html.XEditableLink("Color", "text")); - c.Bound(x => x.Size).ClientTemplate(Html.XEditableLink("Size", "text")); - c.Bound(x => x.Material).ClientTemplate(Html.XEditableLink("Material", "text")); - c.Bound(x => x.Pattern).ClientTemplate(Html.XEditableLink("Pattern", "text")); + c.Bound(x => x.SKU) + .ReadOnly() + .Visible(true); + c.Bound(x => x.Export2) + .ClientTemplate(Html.XEditableLink("Export2", "select2")); + c.Bound(x => x.Taxonomy) + .ClientTemplate(Html.XEditableLink("Taxonomy", "typeahead")); + c.Bound(x => x.Gender) + .ClientTemplate(Html.XEditableLink("Gender", "select2")); + c.Bound(x => x.AgeGroup) + .ClientTemplate(Html.XEditableLink("AgeGroup", "select2")); + c.Bound(x => x.IsAdult) + .ClientTemplate(Html.XEditableLink("IsAdult", "select2")); + c.Bound(x => x.Color) + .ClientTemplate(Html.XEditableLink("Color", "text")); + c.Bound(x => x.Size) + .ClientTemplate(Html.XEditableLink("Size", "text")); + c.Bound(x => x.Material) + .ClientTemplate(Html.XEditableLink("Material", "text")); + c.Bound(x => x.Pattern) + .ClientTemplate(Html.XEditableLink("Pattern", "text")); + c.Bound(x => x.Multipack2) + .ClientTemplate(Html.XEditableLink("Multipack2", "text")); + c.Bound(x => x.IsBundle) + .ClientTemplate(Html.XEditableLink("IsBundle", "select2")); + c.Bound(x => x.EnergyEfficiencyClass) + .ClientTemplate(Html.XEditableLink("EnergyEfficiencyClass", "select2")); + c.Bound(x => x.CustomLabel0) + .ClientTemplate(Html.XEditableLink("CustomLabel0", "text")); + c.Bound(x => x.CustomLabel1) + .ClientTemplate(Html.XEditableLink("CustomLabel1", "text")); + c.Bound(x => x.CustomLabel2) + .ClientTemplate(Html.XEditableLink("CustomLabel2", "text")); + c.Bound(x => x.CustomLabel3) + .ClientTemplate(Html.XEditableLink("CustomLabel3", "text")); + c.Bound(x => x.CustomLabel4) + .ClientTemplate(Html.XEditableLink("CustomLabel4", "text")); }) .ClientEvents(e => { e.OnDataBound("OnGridDataBound"); e.OnDataBinding("OnGridDataBinding"); + e.OnError("OnGridError"); }) .DataBinding(dataBinding => { @@ -127,6 +158,11 @@ return false; }); + function OnGridError(e) { + e.preventDefault(); + alert(e.XMLHttpRequest.responseText); + } + function OnGridDataBinding(e) { e.data = { searchProductName: $('#SearchProductName').val(), @@ -135,28 +171,32 @@ } function OnGridDataBound(e) { - var grid = $('#gmc-products-grid'), - options = { - emptytext: '@T("Plugins.Feed.Froogle.EmptyGridCell")', - url: '@Url.Action("GoogleProductEdit", "FeedGoogleMerchantCenter", new { Namespaces = "SmartStore.GoogleMerchantCenter.Controllers", area = GoogleMerchantCenterFeedPlugin.SystemName })' - }, - select2Options = { - placeholder: '@T("Plugins.Feed.Froogle.EmptyGridCell")', - minimumResultsForSearch: 20, - allowClear: true - }; + var grid = $('#gmc-products-grid'); + + var emptyCell = '@T("Plugins.Feed.Froogle.EmptyGridCell")'; + + var options = { + emptytext: emptyCell, + url: '@Url.Action("GoogleProductEdit", "FeedGoogleMerchantCenter", new { Namespaces = "SmartStore.GoogleMerchantCenter.Controllers", area = GoogleMerchantCenterFeedPlugin.SystemName })' + }; + var select2Options = { + placeholder: emptyCell, + minimumResultsForSearch: 20, + allowClear: true + }; + var boolSource = [ + { id: 'true', text: '@T("Admin.Common.Yes")' }, + { id: 'false', text: '@T("Admin.Common.No")' } + ]; - grid.find('a.edit-link-exporting').editable($.extend(options, { + grid.find('a.edit-link-export2').editable($.extend({}, options, { select2: { minimumResultsForSearch: 20 }, - source: [ - { id: 'true', text: '@T("Admin.Common.Yes")' }, - { id: 'false', text: '@T("Admin.Common.No")' } - ] + source: boolSource })); - grid.find('a.edit-link-gender').editable($.extend(options, { + grid.find('a.edit-link-gender').editable($.extend({}, options, { select2: select2Options, source: [ { id: 'male', text: '@T("Plugins.Feed.Froogle.GenderMale")' }, @@ -165,7 +205,7 @@ ] })); - grid.find('a.edit-link-agegroup').editable($.extend(options, { + grid.find('a.edit-link-agegroup').editable($.extend({}, options, { select2: select2Options, source: [ { id: 'adult', text: '@T("Plugins.Feed.Froogle.AgeGroupAdult").Text' }, @@ -173,6 +213,11 @@ ] })); + grid.find('a.edit-link-isadult').editable($.extend({}, options, { + select2: select2Options, + source: boolSource + })); + grid.find('a.edit-link-color').editable(options); grid.find('a.edit-link-size').editable(options); @@ -181,7 +226,46 @@ grid.find('a.edit-link-pattern').editable(options); - grid.find('a.edit-link-taxonomy').editable($.extend(options, { + grid.find('a.edit-link-multipack2').editable($.extend({}, options, { + validate: function(value) { + if (value.length !== 0) { + var regex = /^[0-9]+$/; + if (!regex.test(value)) { + return '@T("Admin.System.Warnings.DigitsOnly")'; + } + } + return null; + } + })); + + grid.find('a.edit-link-isbundle').editable($.extend({}, options, { + select2: select2Options, + source: boolSource + })); + + grid.find('a.edit-link-energyefficiencyclass').editable($.extend({}, options, { + select2: select2Options, + source: [ + @if (Model.EnergyEfficiencyClasses != null && Model.EnergyEfficiencyClasses.Length > 0) + { + foreach (var str in Model.EnergyEfficiencyClasses.Select(x => x.Replace("'", "\""))) + { + { id: '@str', text: '@str' }, + } + } + ] + })); + + grid.find('a.edit-link-customlabel0').editable(options); + grid.find('a.edit-link-customlabel1').editable(options); + grid.find('a.edit-link-customlabel2').editable(options); + grid.find('a.edit-link-customlabel3').editable(options); + grid.find('a.edit-link-customlabel4').editable(options); + + grid.find('a.edit-link-taxonomy').editable($.extend({}, options, { + select2: { + minimumResultsForSearch: 20 + }, source: @Html.Raw(Model.AvailableGoogleCategoriesAsJson) })); } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProductEditTab.cshtml b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProductEditTab.cshtml index fa3273da09..c20ef9acd7 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProductEditTab.cshtml +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Views/FeedGoogleMerchantCenter/ProductEditTab.cshtml @@ -6,24 +6,24 @@ - } - + if (ViewBag.RefreshPage == true) + { + + } - +
    @@ -51,7 +49,7 @@ @Html.SmartLabelFor(model => model.SearchManufacturerId) @@ -59,7 +57,7 @@ @Html.SmartLabelFor(model => model.SearchProductTypeId) @@ -71,8 +69,9 @@
    @Html.SmartLabelFor(model => model.SearchProductName) @@ -43,7 +41,7 @@ @Html.SmartLabelFor(model => model.SearchCategoryId) - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All").Text) + @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All"))
    - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All").Text) + @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All"))
    - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes) + @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes, T("Admin.Common.All"))
    -

    -

    + +

    +
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Category/Tree.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Category/Tree.cshtml index 68162d1e32..1f3d747077 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Category/Tree.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Category/Tree.cshtml @@ -15,26 +15,29 @@ -@using (Html.BeginForm()) +@if (Model.AvailableStores.Count > 1) { - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) -
    -   - - -
    + using (Html.BeginForm()) + { + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
    +   + + +
    + } }

    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ContentSlider/Index.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ContentSlider/Index.cshtml index 51251e2491..e847170de8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ContentSlider/Index.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ContentSlider/Index.cshtml @@ -120,24 +120,27 @@ - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) -
    -   - - -
    + if (Model.AvailableStores.Count > 1) + { + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
    +   + + +
    + }
    @@ -164,7 +167,7 @@ .Width(100) .Template(x => @Html.SymbolForBool(x.LimitedToStores)) .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) - .Hidden(Model.StoreCount <= 1) + .Hidden(Model.AvailableStores.Count <= 1) .Centered(); columns.Bound(x => x.Published) .Width(100) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByNumberOfOrders.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByNumberOfOrders.cshtml index 98165844ae..919a1af379 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByNumberOfOrders.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByNumberOfOrders.cshtml @@ -1,59 +1,59 @@ @model BestCustomersReportModel - @using Telerik.Web.Mvc.UI - - - - - - - - - - - - - - - - - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.StartDate) - - @Html.EditorFor(model => model.StartDate) -
    - @Html.SmartLabelFor(model => model.EndDate) - - @Html.EditorFor(model => Model.EndDate) -
    - @Html.SmartLabelFor(model => model.OrderStatusId) - - @Html.DropDownList("OrderStatusId", Model.AvailableOrderStatuses) -
    - @Html.SmartLabelFor(model => model.PaymentStatusId) - - @Html.DropDownList("PaymentStatusId", Model.AvailablePaymentStatuses) -
    - @Html.SmartLabelFor(model => model.ShippingStatusId) - - @Html.DropDownList("ShippingStatusId", Model.AvailableShippingStatuses) -
    -   - - -
    -

    -

    + + + + + + + + + + + + + + + + + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.StartDate) + + @Html.EditorFor(model => model.StartDate) +
    + @Html.SmartLabelFor(model => model.EndDate) + + @Html.EditorFor(model => Model.EndDate) +
    + @Html.SmartLabelFor(model => model.OrderStatusId) + + @Html.DropDownList("OrderStatusId", Model.AvailableOrderStatuses, T("Admin.Common.All")) +
    + @Html.SmartLabelFor(model => model.PaymentStatusId) + + @Html.DropDownList("PaymentStatusId", Model.AvailablePaymentStatuses, T("Admin.Common.All")) +
    + @Html.SmartLabelFor(model => model.ShippingStatusId) + + @Html.DropDownList("ShippingStatusId", Model.AvailableShippingStatuses, T("Admin.Common.All")) +
    +   + + +
    + +

    +
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByOrderTotal.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByOrderTotal.cshtml index 13c0f4570f..af0e21e9bb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByOrderTotal.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_ReportBestCustomersByOrderTotal.cshtml @@ -4,7 +4,7 @@ @using (Html.BeginForm()) { - +
    @@ -34,7 +34,7 @@ @Html.SmartLabelFor(model => model.PaymentStatusId) @@ -42,7 +42,7 @@ @Html.SmartLabelFor(model => model.ShippingStatusId) @@ -54,8 +54,9 @@
    @Html.SmartLabelFor(model => model.StartDate) @@ -26,7 +26,7 @@ @Html.SmartLabelFor(model => model.OrderStatusId) - @Html.DropDownList("OrderStatusId", Model.AvailableOrderStatuses) + @Html.DropDownList("OrderStatusId", Model.AvailableOrderStatuses, T("Admin.Common.All"))
    - @Html.DropDownList("PaymentStatusId", Model.AvailablePaymentStatuses) + @Html.DropDownList("PaymentStatusId", Model.AvailablePaymentStatuses, T("Admin.Common.All"))
    - @Html.DropDownList("ShippingStatusId", Model.AvailableShippingStatuses) + @Html.DropDownList("ShippingStatusId", Model.AvailableShippingStatuses, T("Admin.Common.All"))
    -

    -

    + +

    +
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/List.cshtml index fdcacfa7ea..96727f6ecb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/GiftCard/List.cshtml @@ -1,9 +1,6 @@ @model GiftCardListModel @using Telerik.Web.Mvc.UI @{ - var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - - //page title ViewBag.Title = T("Admin.GiftCards").Text; } @using (Html.BeginForm()) @@ -20,34 +17,36 @@ - - - - - - - - - - - + +
    - @Html.SmartLabelFor(model => model.ActivatedId) - - @Html.DropDownListFor(model => model.ActivatedId, Model.ActivatedList) -
    - @Html.SmartLabelFor(model => model.CouponCode) - - @Html.EditorFor(model => model.CouponCode) -
    + + + + + + + + + + + + - -
    + @Html.SmartLabelFor(model => model.ActivatedId) + + @Html.DropDownListFor(model => model.ActivatedId, Model.ActivatedList, T("Admin.Common.All")) +
    + @Html.SmartLabelFor(model => model.CouponCode) + + @Html.EditorFor(model => model.CouponCode) +
      - -
    -

    -

    + +
    + +

    +
    @@ -75,7 +74,7 @@ .HeaderTemplate("{0}".FormatWith(T("Admin.Common.Edit").Text)) .Filterable(false); }) - .Pageable(settings => settings.PageSize(gridPageSize).Position(GridPagerPosition.Both)) + .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) .DataBinding(dataBinding => dataBinding.Ajax().Select("GiftCardList", "GiftCard", Model)) .PreserveGridState() .EnableCustomBinding(true)) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Log/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Log/List.cshtml index fbd9a6d127..05d898f309 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Log/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Log/List.cshtml @@ -21,7 +21,8 @@ - + +
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml index c5f8fccc99..112515ff69 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/List.cshtml @@ -19,23 +19,35 @@ @Html.Widget("admin_button_toolbar_after") -
    @Html.SmartLabelFor(model => model.CreatedOnFrom) @@ -51,7 +52,7 @@ @Html.SmartLabelFor(model => model.LogLevelId) - @Html.DropDownList("LogLevelId", Model.AvailableLogLevels) + @Html.DropDownList("LogLevelId", Model.AvailableLogLevels, T("Admin.Common.All"))
    - - - - - + +
    - @Html.SmartLabelFor(model => model.SearchManufacturerName) - - @Html.EditorFor(model => Model.SearchManufacturerName) -
    + + + + + @if (Model.AvailableStores.Count > 1) + { + + + + + } + - - + +
    + @Html.SmartLabelFor(model => model.SearchManufacturerName) + + @Html.EditorFor(model => Model.SearchManufacturerName) +
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownListFor(model => model.SearchStoreId, Model.AvailableStores, T("Admin.Common.All")) +
      - -
    + +

    @@ -57,11 +69,9 @@ .HtmlAttributes(new { style = "text-align:center" }) .HeaderHtmlAttributes(new { style = "text-align:center" }); columns.Bound(x => x.Name) - .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) .ClientTemplate("\"><#= Name #>"); columns.Bound(x => x.Published) .Width(160) - .Template(item => @Html.SymbolForBool(item.Published)) .ClientTemplate(@Html.SymbolForBool("Published")) .Centered(); columns.Bound(x => x.DisplayOrder) @@ -69,8 +79,8 @@ .Centered(); columns.Bound(x => x.LimitedToStores) .Width(160) - .Template(item => @Html.SymbolForBool(item.LimitedToStores)) .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Hidden(Model.AvailableStores.Count <= 1) .Centered(); }) .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) @@ -129,7 +139,8 @@ function onDataBinding(e) { var searchModel = { - SearchManufacturerName: $('#@Html.FieldIdFor(model => model.SearchManufacturerName)').val() + SearchManufacturerName: $('#@Html.FieldIdFor(model => model.SearchManufacturerName)').val(), + SearchStoreId: $('#@Html.FieldIdFor(model => model.SearchStoreId)').val() }; e.data = searchModel; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/ProductAddPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/ProductAddPopup.cshtml index 15185b0812..d0864667b3 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/ProductAddPopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Manufacturer/ProductAddPopup.cshtml @@ -5,7 +5,6 @@ @{ var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - //page title ViewBag.Title = T("Admin.Catalog.Manufacturers.Products.AddNew").Text; } @using Telerik.Web.Mvc.UI; @@ -17,17 +16,16 @@ - if (ViewBag.RefreshPage == true) - { - - } + if (ViewBag.RefreshPage == true) + { + + } - - +
    @@ -49,7 +47,7 @@ @Html.SmartLabelFor(model => model.SearchManufacturerId) @@ -57,7 +55,7 @@ @Html.SmartLabelFor(model => model.SearchProductTypeId) @@ -69,8 +67,9 @@
    @Html.SmartLabelFor(model => model.SearchProductName) @@ -41,7 +39,7 @@ @Html.SmartLabelFor(model => model.SearchCategoryId) - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All").Text) + @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All"))
    - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All").Text) + @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All"))
    - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes) + @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes, T("Admin.Common.All"))
    -

    -

    + +

    + - - - - + @if (Model.AvailableStores.Count > 1) + { + + + + + }
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/List.cshtml index 29c941466b..ed38d5c49c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/MessageTemplate/List.cshtml @@ -16,26 +16,29 @@ - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) -
    -   - - -
    +@if (Model.AvailableStores.Count > 1) +{ + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
    +   + + +
    +}

    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml index dfcd240b18..aa82033bf7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/News/List.cshtml @@ -25,26 +25,29 @@ - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) -
    -   - - -
    +@if (Model.AvailableStores.Count > 1) +{ + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
    +   + + +
    +}

    @@ -81,6 +84,7 @@ columns.Bound(x => x.LimitedToStores) .Template(item => @Html.SymbolForBool(item.LimitedToStores)) .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Hidden(Model.AvailableStores.Count <= 1) .Centered(); columns.Bound(x => x.CreatedOn); }) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/NewsLetterSubscription/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/NewsLetterSubscription/List.cshtml index 5953d03c49..da8b2d4fd5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/NewsLetterSubscription/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/NewsLetterSubscription/List.cshtml @@ -24,14 +24,17 @@ @Html.EditorFor(model => Model.SearchEmail)
    - @Html.SmartLabelFor(model => model.StoreId) - - @Html.DropDownList("StoreId", Model.AvailableStores) -
    + @Html.SmartLabelFor(model => model.StoreId) + + @Html.DropDownList("StoreId", Model.AvailableStores, T("Admin.Common.All")) +
      @@ -64,6 +67,7 @@ .Centered() .Width(100); columns.Bound(x => x.StoreName) + .Hidden(Model.AvailableStores.Count <= 1) .ReadOnly(); columns.Bound(x => x.CreatedOn) .ReadOnly() diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/AddProductToOrder.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/AddProductToOrder.cshtml index 574db39957..2c99ed1a86 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/AddProductToOrder.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/AddProductToOrder.cshtml @@ -2,7 +2,6 @@ @{ var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - //page title ViewBag.Title = string.Format(T("Admin.Orders.Products.AddNew.Title1").Text, Model.OrderId); } @using Telerik.Web.Mvc.UI; @@ -19,7 +18,7 @@ @T("Admin.Orders.Products.AddNew.Note1") - +
    @@ -41,7 +40,7 @@ @Html.SmartLabelFor(model => model.SearchManufacturerId) @@ -49,7 +48,7 @@ @Html.SmartLabelFor(model => model.SearchProductTypeId) @@ -63,7 +62,9 @@
    @Html.SmartLabelFor(model => model.SearchProductName) @@ -33,7 +32,7 @@ @Html.SmartLabelFor(model => model.SearchCategoryId) - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All").Text) + @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All"))
    - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All").Text) + @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All"))
    - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes) + @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes, T("Admin.Common.All"))
    +

    +
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersReport.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersReport.cshtml index c6fb84b696..e035acfa1e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersReport.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/BestsellersReport.cshtml @@ -1,7 +1,6 @@ @model BestsellersReportModel @using Telerik.Web.Mvc.UI @{ - //page title ViewBag.Title = T("Admin.SalesReport.Bestsellers").Text; } @using (Html.BeginForm()) @@ -14,7 +13,8 @@
    - + +
    @@ -44,7 +44,7 @@ @Html.SmartLabelFor(model => model.PaymentStatusId) @@ -56,8 +56,9 @@
    @Html.SmartLabelFor(model => model.StartDate) @@ -36,7 +36,7 @@ @Html.SmartLabelFor(model => model.OrderStatusId) - @Html.DropDownList("OrderStatusId", Model.AvailableOrderStatuses) + @Html.DropDownList("OrderStatusId", Model.AvailableOrderStatuses, T("Admin.Common.All"))
    - @Html.DropDownList("PaymentStatusId", Model.AvailablePaymentStatuses) + @Html.DropDownList("PaymentStatusId", Model.AvailablePaymentStatuses, T("Admin.Common.All"))
    -

    -

    + +

    + - @if (Model.StoreCount > 1) + @if (Model.AvailableStores.Count > 1) {
    @@ -106,5 +107,4 @@ } - } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml index 47e12fdb99..af55c9bde6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/List.cshtml @@ -97,7 +97,7 @@ @Html.DropDownList("ShippingStatusIds", Model.AvailableShippingStatuses, null, new { multiple = "multiple" })
    @@ -176,7 +176,7 @@ columns.Bound(x => x.CustomerName); columns.Bound(x => x.CustomerEmail); columns.Bound(x => x.StoreName) - .Hidden(Model.StoreCount <= 1); + .Hidden(Model.AvailableStores.Count <= 1); columns.Bound(x => x.CreatedOn); columns.Bound(x => x.OrderTotal) .Width(180) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/AssociatedProductAddPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/AssociatedProductAddPopup.cshtml index b5fd79b0e4..a46a602417 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/AssociatedProductAddPopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/AssociatedProductAddPopup.cshtml @@ -27,7 +27,7 @@ } - +
    @@ -49,23 +49,26 @@ @Html.SmartLabelFor(model => model.SearchManufacturerId) - - - - + @if (Model.AvailableStores.Count > 1) + { + + + + + } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/BulkEdit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/BulkEdit.cshtml index 6eb786b6ba..ebdea889fa 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/BulkEdit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/BulkEdit.cshtml @@ -3,7 +3,6 @@ @{ var allString = T("Admin.Common.All").Text; - //page title ViewBag.Title = T("Admin.Catalog.BulkEdit").Text; } @using (Html.BeginForm()) @@ -38,7 +37,7 @@ @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes, allString) - @if (Model.StoreCount > 1) + @if (Model.AvailableStores.Count > 1) {
    @Html.SmartLabelFor(model => model.SearchProductName) @@ -41,7 +41,7 @@ @Html.SmartLabelFor(model => model.SearchCategoryId) - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All").Text) + @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All"))
    - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) + @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All"))
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
    @Html.SmartLabelFor(model => model.SearchProductTypeId) - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes) + @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes, T("Admin.Common.All"))
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/BundleItemAddPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/BundleItemAddPopup.cshtml index b31b86f3a2..490d650ce4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/BundleItemAddPopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/BundleItemAddPopup.cshtml @@ -47,7 +47,7 @@ } - +
    @@ -69,23 +69,26 @@ @Html.SmartLabelFor(model => model.SearchManufacturerId) - - - - + @if (Model.AvailableStores.Count > 1) + { + + + + + } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/CrossSellProductAddPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/CrossSellProductAddPopup.cshtml index ac06795dec..31f0e00244 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/CrossSellProductAddPopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/CrossSellProductAddPopup.cshtml @@ -5,7 +5,6 @@ @{ var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - //page title ViewBag.Title = T("Admin.Catalog.Products.CrossSells.AddNew").Text; } @using Telerik.Web.Mvc.UI; @@ -27,7 +26,7 @@ } -
    @Html.SmartLabelFor(model => model.SearchProductName) @@ -61,7 +61,7 @@ @Html.SmartLabelFor(model => model.SearchCategoryId) - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All").Text) + @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All"))
    - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) + @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All"))
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
    @Html.SmartLabelFor(model => model.SearchProductTypeId) - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes) + @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes, T("Admin.Common.All"))
    +
    @@ -49,23 +48,26 @@ @Html.SmartLabelFor(model => model.SearchManufacturerId) - - - - + @if (Model.AvailableStores.Count > 1) + { + + + + + } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml index feb7fe9c84..1b18b8a636 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/List.cshtml @@ -44,7 +44,7 @@ @Html.DropDownListFor(model => model.SearchProductTypeId, Model.AvailableProductTypes, allString) - @if (Model.StoreCount > 1) + @if (Model.AvailableStores.Count > 1) { + + + + } + @if (Model.StoreCount > 1) + { + + + + + }
    @Html.SmartLabelFor(model => model.SearchProductName) @@ -41,7 +40,7 @@ @Html.SmartLabelFor(model => model.SearchCategoryId) - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All").Text) + @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All"))
    - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) + @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All"))
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
    @Html.SmartLabelFor(model => model.SearchProductTypeId) - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes) + @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes, T("Admin.Common.All"))
    @@ -170,7 +170,7 @@ .Sortable(false) .Template(item => @Html.SymbolForBool(item.LimitedToStores)) .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) - .Hidden(Model.StoreCount <= 1) + .Hidden(Model.AvailableStores.Count <= 1) .Centered(); columns.Bound(x => x.CreatedOn); columns.Bound(x => x.UpdatedOn) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/ProductAttributeValueLinkagePopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/ProductAttributeValueLinkagePopup.cshtml index 0ef7426f3c..62cde68c17 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/ProductAttributeValueLinkagePopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/ProductAttributeValueLinkagePopup.cshtml @@ -34,7 +34,7 @@ @Html.HiddenFor(model => model.ProductId) - +
    @@ -56,23 +56,26 @@ @Html.SmartLabelFor(model => model.SearchManufacturerId) - - - - + @if (Model.AvailableStores.Count > 1) + { + + + + + } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/RelatedProductAddPopup.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/RelatedProductAddPopup.cshtml index 7d5db715ca..a9a5b0b701 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/RelatedProductAddPopup.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/RelatedProductAddPopup.cshtml @@ -5,7 +5,6 @@ @{ var gridPageSize = EngineContext.Current.Resolve().GridPageSize; - //page title ViewBag.Title = T("Admin.Catalog.Products.RelatedProducts.AddNew").Text; } @using Telerik.Web.Mvc.UI; @@ -26,8 +25,7 @@ } - -
    @Html.SmartLabelFor(model => model.SearchProductName) @@ -48,7 +48,7 @@ @Html.SmartLabelFor(model => model.SearchCategoryId) - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All").Text) + @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All"))
    - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) + @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All"))
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
    @Html.SmartLabelFor(model => model.SearchProductTypeId) - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes) + @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes, T("Admin.Common.All"))
    +
    @@ -49,23 +47,26 @@ @Html.SmartLabelFor(model => model.SearchManufacturerId) - - - - + @if (Model.AvailableStores.Count > 1) + { + + + + + } @@ -77,6 +78,7 @@
    @Html.SmartLabelFor(model => model.SearchProductName) @@ -41,7 +39,7 @@ @Html.SmartLabelFor(model => model.SearchCategoryId) - @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All").Text) + @Html.DropDownList("SearchCategoryId", Model.AvailableCategories, T("Admin.Common.All"))
    - @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All").Text) -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) + @Html.DropDownList("SearchManufacturerId", Model.AvailableManufacturers, T("Admin.Common.All"))
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
    @Html.SmartLabelFor(model => model.SearchProductTypeId) - @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes) + @Html.DropDownList("SearchProductTypeId", Model.AvailableProductTypes, T("Admin.Common.All"))
    +

    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/List.cshtml index e8f2359885..c9a62c9b5c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ReturnRequest/List.cshtml @@ -13,83 +13,88 @@ -
    - - - - - - - - - - - - - +
    - @Html.SmartLabelFor(model => model.SearchId) - - @Html.EditorFor(model => Model.SearchId) -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownListFor(model => model.SearchStoreId, Model.AvailableStores) -
    - @Html.SmartLabelFor(model => model.SearchReturnRequestStatusId) - - @Html.DropDownListFor(model => model.SearchReturnRequestStatusId, Model.AvailableReturnRequestStatus) -
    + + + + + @if (Model.AvailableStores.Count > 1) + { + + + + + } + + + + + - - + +
    + @Html.SmartLabelFor(model => model.SearchId) + + @Html.EditorFor(model => Model.SearchId) +
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownListFor(model => model.SearchStoreId, Model.AvailableStores, T("Admin.Common.All")) +
    + @Html.SmartLabelFor(model => model.SearchReturnRequestStatusId) + + @Html.DropDownListFor(model => model.SearchReturnRequestStatusId, Model.AvailableReturnRequestStatus, T("Admin.Common.All")) +
      - -
    + +
    +

    @(Html.Telerik().Grid() - .Name("returnrequests-grid") - .Columns(columns => - { + .Name("returnrequests-grid") + .Columns(columns => + { columns.Bound(x => x.Id) .Centered() .Width(80); columns.Bound(x => x.ProductName) .Template(x => @Html.LabeledProductName(x.ProductId, x.ProductName, x.ProductTypeName, x.ProductTypeLabelHint)) .ClientTemplate(@Html.LabeledProductName("ProductId", "ProductName")); - columns.Bound(x => x.Quantity) + columns.Bound(x => x.Quantity) .Width(80) - .Centered(); - columns.Bound(x => x.CustomerId) - .Template(x => Html.ActionLink(T("Admin.Common.View").Text, "Edit", "Customer", new { id = x.CustomerId }, new { })) + .Centered(); + columns.Bound(x => x.CustomerId) + .Template(x => Html.ActionLink(T("Admin.Common.View").Text, "Edit", "Customer", new { id = x.CustomerId }, new { })) .ClientTemplate("\"><#= CustomerFullName #>"); - columns.Bound(x => x.OrderId) + columns.Bound(x => x.OrderId) .Width(80) - .Centered() - .Template(x => Html.ActionLink(T("Admin.Common.View").Text, "Edit", "Order", new { id = x.OrderId }, new { })) + .Centered() + .Template(x => Html.ActionLink(T("Admin.Common.View").Text, "Edit", "Order", new { id = x.OrderId }, new { })) .ClientTemplate("\"><#= OrderId #>"); columns.Bound(x => x.ReturnRequestStatusStr) - .Width(180); - columns.Bound(x => x.CreatedOn) .Width(180); - columns.Bound(x => x.StoreName); - columns.Bound(x => x.Id) + columns.Bound(x => x.CreatedOn) + .Width(180); + columns.Bound(x => x.StoreName) + .Hidden(Model.AvailableStores.Count <= 1); + columns.Bound(x => x.Id) .Width(100) - .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); - }) + .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); + }) .Pageable(settings => settings.PageSize(Model.GridPageSize).Position(GridPagerPosition.Both)) - .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "ReturnRequest")) + .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "ReturnRequest")) .ClientEvents(events => events.OnDataBinding("returnRequestGrid_onDataBinding")) .PreserveGridState() - .EnableCustomBinding(true)) + .EnableCustomBinding(true))
    diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml index 08de2f50ab..18fda90102 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Topic/List.cshtml @@ -16,26 +16,29 @@ - - - - - - - - - -
    - @Html.SmartLabelFor(model => model.SearchStoreId) - - @Html.DropDownList("SearchStoreId", Model.AvailableStores) -
    -   - - -
    +@if (Model.AvailableStores.Count > 1) +{ + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.SearchStoreId) + + @Html.DropDownList("SearchStoreId", Model.AvailableStores, T("Admin.Common.All")) +
    +   + + +
    +}

    From 49da71aa6d88631916c7e9bf681732af73ff0f24 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 7 Jan 2016 18:56:27 +0100 Subject: [PATCH 386/874] Updated Google Analytics admin instructions Bulk update of string resources may fail because of outdated EfRepository.AutoCommitEnabledInternal state --- .../Localization/LocalizationService.cs | 9 +-------- .../Importer/NewsLetterSubscriptionImporter.cs | 4 +--- .../Localization/resources.de-de.xml | 11 ++++++----- .../Localization/resources.en-us.xml | 12 ++++++------ 4 files changed, 14 insertions(+), 22 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs b/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs index 31f83eb7dc..8610072e92 100644 --- a/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs +++ b/src/Libraries/SmartStore.Services/Localization/LocalizationService.cs @@ -437,7 +437,6 @@ public virtual void ImportResourcesFromXml( foreach (var xel in nodes.Cast()) { - string name = xel.GetAttribute("Name").TrimSafe(); string value = ""; var valueNode = xel.SelectSingleNode("Value"); @@ -488,13 +487,7 @@ public virtual void ImportResourcesFromXml( _lsrRepository.InsertRange(toAdd, 500); toAdd.Clear(); - _lsrRepository.AutoCommitEnabled = null; - toUpdate.Each(x => - { - _lsrRepository.Update(x); - }); - - _lsrRepository.Context.SaveChanges(); + _lsrRepository.UpdateRange(toUpdate); toUpdate.Clear(); //clear cache diff --git a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs index 5bf83c4755..17f3c10ed0 100644 --- a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs +++ b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs @@ -104,9 +104,7 @@ public void Execute(IImportExecuteContext context) toAdd.Clear(); // update modified subscribers - _subscriptionRepository.AutoCommitEnabled = null; - toUpdate.Each(x => _subscriptionRepository.Update(x)); - _subscriptionRepository.Context.SaveChanges(); + _subscriptionRepository.UpdateRange(toUpdate); toUpdate.Clear(); } catch (Exception exception) diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.de-de.xml b/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.de-de.xml index 627698df96..d7f6055f92 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.de-de.xml @@ -5,13 +5,14 @@ Google Analytics ist ein kostenloser Statistikdienst von Google. Der Dienst erfasst Statistiken über Besucher und Ecommerce-Konversion auf Ihrer Webseite

    +

    Google Analytics ist ein kostenloser Statistikdienst von Google. Der Dienst erfasst Statistiken über Besucher und Ecommerce-Konversion auf Ihrer Webseite.

    Führen Sie die folgenden Schritte aus um Google Analytics auf Ihrer Webseite einzubinden:

      -
    • erstellen Sie hier einen "Google Analytics"-Account und folgen Sie dem Wizard um Ihre Webseite zuzufügen
    • -
    • Kopieren Sie die "Google Analytics"-ID in das entspechende Feld in folgendem Formular
    • -
    • Kopieren Sie den Tracking-Code in die "Tracking-Code"-Box in folgendem Formular
    • -
    • KLicken Sie den "Speichern"-Button und Google Analytics wird in Ihre Webseite integriert
    • +
    • Erstellen Sie hier einen Google-Analytics-Account und folgen Sie dem Wizard um Ihre Webseite zuzufügen.
    • +
    • Kopieren Sie die Google-Analytics-ID in das entspechende Feld in folgendem Formular.
    • +
    • Kopieren Sie den Tracking-Code in die Tracking-Code-Box in folgendem Formular.
    • +
    • Klicken Sie den Speichern-Button.
    • +
    • Aktivieren Sie das Google Analytics Widget unter CMS > Widgets und Google Analytics wird in Ihre Webseite integriert.
    ]]>
    diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.en-us.xml b/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.en-us.xml index 2ac97f8895..d54877d501 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.GoogleAnalytics/Localization/resources.en-us.xml @@ -5,14 +5,14 @@ Google Analytics is a free website stats tool from Google. It keeps track of statistics - about the visitors and ecommerce conversion on your website.

    +

    Google Analytics is a free website statistics tool from Google. It keeps track of statistics about the visitors and ecommerce conversion on your website.

    Follow the next steps to enable Google Analytics integration:

      -
    • Create a Google Analytics account and follow the wizard to add your website
    • -
    • Copy the Google Analytics ID into the 'ID' box below
    • -
    • Copy the tracking code from Google Analytics into the 'Tracking Code' box below
    • -
    • Click the 'Save' button below and Google Analytics will be integrated into your store
    • +
    • Create a Google Analytics account and follow the wizard to add your website.
    • +
    • Copy the Google Analytics ID into the ID field below.
    • +
    • Copy the tracking code from Google Analytics into the Tracking Code field below.
    • +
    • Click the Save button below.
    • +
    • Activate the Google Analytics widget under CMS > Widgets to integrate Google Analytics into your store.
    ]]>
    From df31f4f375026c4129096ca0cc0cecdfbb7a1863 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 7 Jan 2016 20:22:45 +0100 Subject: [PATCH 387/874] TaskScheduler: logging the sweep call for debugging puposes (temp only) --- .../SmartStore.Core/Extensions/ConversionExtensions.cs | 6 +++++- src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs | 9 ++++++++- .../Controllers/TaskSchedulerController.cs | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Extensions/ConversionExtensions.cs b/src/Libraries/SmartStore.Core/Extensions/ConversionExtensions.cs index 64297abe9d..387bc9a614 100644 --- a/src/Libraries/SmartStore.Core/Extensions/ConversionExtensions.cs +++ b/src/Libraries/SmartStore.Core/Extensions/ConversionExtensions.cs @@ -239,7 +239,11 @@ public static string AsString(this Stream stream, Encoding encoding) // convert stream to string string result; - stream.Position = 0; + + if (stream.CanSeek) + { + stream.Position = 0; + } using (StreamReader sr = new StreamReader(stream, encoding)) { diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs index 22cecb7046..50b4df021b 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs @@ -173,8 +173,15 @@ protected internal virtual void CallEndpoint(string url) } else { + var response = t.Result; + + using (var logger = new TraceLogger()) + { + logger.Debug("TaskScheduler Sweep called successfully: {0}".FormatCurrent(response.GetResponseStream().AsString())); + } + _errCount = 0; - t.Result.Dispose(); + response.Dispose(); } }); } diff --git a/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs b/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs index 9b7b6763c2..860da5fa85 100644 --- a/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs @@ -65,7 +65,7 @@ public ActionResult Sweep() } } - return Content("{0} tasks executed".FormatInvariant(count)); + return Content("{0} of {1} pending tasks executed".FormatInvariant(count, pendingTasks.Count)); } [HttpPost] From d401828451b56e35817f1b01770bf533cf9d0e1c Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Fri, 8 Jan 2016 14:30:31 +0100 Subject: [PATCH 388/874] Performance: eliminated sending of the whole product model to data grid via json response in several locations --- .../Controllers/CategoryController.cs | 9 ++- .../Controllers/ManufacturerController.cs | 9 ++- .../Controllers/ProductController.cs | 64 +++++++++++++++++-- 3 files changed, 73 insertions(+), 9 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs index a1b8d695d6..245cce40b5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CategoryController.cs @@ -911,7 +911,14 @@ public ActionResult ProductAddPopupList(GridCommand command, CategoryModel.AddCa gridModel.Data = products.Select(x => { - var productModel = x.ToModel(); + var productModel = new ProductModel + { + Sku = x.Sku, + Published = x.Published, + ProductTypeLabelHint = x.ProductTypeLabelHint, + Name = x.Name, + Id = x.Id + }; productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); return productModel; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs index 2e7460bff4..51f74261f5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ManufacturerController.cs @@ -596,7 +596,14 @@ public ActionResult ProductAddPopupList(GridCommand command, ManufacturerModel.A gridModel.Data = products.Select(x => { - var productModel = x.ToModel(); + var productModel = new ProductModel + { + Sku = x.Sku, + Published = x.Published, + ProductTypeLabelHint = x.ProductTypeLabelHint, + Name = x.Name, + Id = x.Id + }; productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); return productModel; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 09139d349c..bb8c302ba9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -943,8 +943,18 @@ public ActionResult ProductList(GridCommand command, ProductListModel model) gridModel.Data = products.Select(x => { - var productModel = x.ToModel(); - productModel.FullDescription = ""; // Perf + var productModel = new ProductModel + { + Sku = x.Sku, + Published = x.Published, + ProductTypeLabelHint = x.ProductTypeLabelHint, + Name = x.Name, + Id = x.Id, + StockQuantity = x.StockQuantity, + Price = x.Price, + LimitedToStores = x.LimitedToStores + }; + PrepareProductPictureThumbnailModel(productModel, x); productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); @@ -1654,7 +1664,15 @@ public ActionResult RelatedProductAddPopupList(GridCommand command, ProductModel gridModel.Data = products.Select(x => { - var productModel = x.ToModel(); + var productModel = new ProductModel + { + Sku = x.Sku, + Published = x.Published, + ProductTypeLabelHint = x.ProductTypeLabelHint, + Name = x.Name, + Id = x.Id + }; + productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); return productModel; @@ -1876,7 +1894,15 @@ public ActionResult CrossSellProductAddPopupList(GridCommand command, ProductMod gridModel.Data = products.Select(x => { - var productModel = x.ToModel(); + var productModel = new ProductModel + { + Sku = x.Sku, + Published = x.Published, + ProductTypeLabelHint = x.ProductTypeLabelHint, + Name = x.Name, + Id = x.Id + }; + productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); return productModel; @@ -2095,7 +2121,15 @@ public ActionResult AssociatedProductAddPopupList(GridCommand command, ProductMo gridModel.Data = products.Select(x => { - var productModel = x.ToModel(); + var productModel = new ProductModel + { + Sku = x.Sku, + Published = x.Published, + ProductTypeLabelHint = x.ProductTypeLabelHint, + Name = x.Name, + Id = x.Id + }; + productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); return productModel; @@ -2411,7 +2445,15 @@ public ActionResult BundleItemAddPopupList(GridCommand command, ProductModel.Add gridModel.Data = products.Select(x => { - var productModel = x.ToModel(); + var productModel = new ProductModel + { + Sku = x.Sku, + Published = x.Published, + ProductTypeLabelHint = x.ProductTypeLabelHint, + Name = x.Name, + Id = x.Id + }; + productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); productModel.ProductSelectCheckboxClass = (!x.CanBeBundleItem() ? " hide" : ""); @@ -3611,7 +3653,15 @@ public ActionResult ProductAttributeValueLinkagePopupList(GridCommand command, P gridModel.Data = products.Select(x => { - var productModel = x.ToModel(); + var productModel = new ProductModel + { + Sku = x.Sku, + Published = x.Published, + ProductTypeLabelHint = x.ProductTypeLabelHint, + Name = x.Name, + Id = x.Id + }; + productModel.ProductTypeName = x.GetProductTypeLabel(_localizationService); return productModel; From 6530f97a5ab382a831c056d9f02ed8176bfd9429 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 8 Jan 2016 19:38:35 +0100 Subject: [PATCH 389/874] Removed TaskScheduler sweep logging --- .../SmartStore.Services/Tasks/TaskScheduler.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs index 50b4df021b..8cb0fbc0d7 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs @@ -173,14 +173,14 @@ protected internal virtual void CallEndpoint(string url) } else { + _errCount = 0; var response = t.Result; - using (var logger = new TraceLogger()) - { - logger.Debug("TaskScheduler Sweep called successfully: {0}".FormatCurrent(response.GetResponseStream().AsString())); - } + //using (var logger = new TraceLogger()) + //{ + // logger.Debug("TaskScheduler Sweep called successfully: {0}".FormatCurrent(response.GetResponseStream().AsString())); + //} - _errCount = 0; response.Dispose(); } }); From 94cc64cd43133ea5f0a0d36b25d2d53181369d7b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 9 Jan 2016 12:55:50 +0100 Subject: [PATCH 390/874] Resolves #760 Setting to set a customer role for new registered users --- changelog.md | 1 + .../Domain/Customers/CustomerSettings.cs | 9 +- .../201512151526290_ImportFramework.cs | 22 +++ .../Customers/CustomerRegistrationService.cs | 148 +++++++++--------- .../Controllers/SettingController.cs | 26 +-- .../Settings/CustomerUserSettingsModel.cs | 10 +- .../Views/Setting/CustomerUser.cshtml | 11 +- .../CustomerRegistrationServiceTests.cs | 6 +- 8 files changed, 141 insertions(+), 92 deletions(-) diff --git a/changelog.md b/changelog.md index f6fa16e556..6a82d8e636 100644 --- a/changelog.md +++ b/changelog.md @@ -33,6 +33,7 @@ * #841 Activity log for deleting an order * More settings to control creation of SEO names * GMC feed: Supporting fields multipack, bundle, adult, energy efficiency class and custom label (0 to 4) +* #760 Setting to set a customer role for new registered users ### Improvements * (Perf) Implemented static caches for URL aliases and localized properties. Increases app startup and request speed by up to 30%. diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs index 83380a63a3..d0dbf678e5 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs @@ -151,7 +151,6 @@ public CustomerSettings() /// public bool StoreLastVisitedPage { get; set; } - #region Form fields /// @@ -246,8 +245,12 @@ public CustomerSettings() #endregion - // codehint: sm-add (no ui, only db edit) public string PrefillLoginUsername { get; set; } public string PrefillLoginPwd { get; set; } - } + + /// + /// Identifier of a customer role that new registered customers will be assigned to + /// + public int RegisterCustomerRoleId { get; set; } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs index 7f2dec3086..2699566e17 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs @@ -316,6 +316,28 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.System.Warnings.DigitsOnly", "Please enter digits only.", "Bitte nur Ziffern eingeben."); + + builder.AddOrUpdate("Account.Register.Errors.CannotRegisterSearchEngine", + "A search engine can't be registered.", + "Eine Suchmaschine kann nicht registriert werden."); + + builder.AddOrUpdate("Account.Register.Errors.CannotRegisterTaskAccount", + "A background task account can't be registered.", + "Das Konto einer geplanten Aufgabe kann nicht registriert werden."); + + builder.AddOrUpdate("Account.Register.Errors.AlreadyRegistered", + "The customer is already registered.", + "Der Kunde ist bereits registriert."); + + builder.AddOrUpdate("Admin.Customers.CustomerRoles.CannotFoundRole", + "The customer role \"{0}\" cannot be found.", + "Die Kundengruppe \"{0}\" wurde nicht gefunden."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.RegisterCustomerRole", + "Customer role at registrations", + "Kundengruppe bei Registrierungen", + "Specifies a customer role that will be assigned to newly registered customers.", + "Legt eine Kundengruppe fest, die neu registrierten Kunden zugeordnet wird."); } } } diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerRegistrationService.cs b/src/Libraries/SmartStore.Services/Customers/CustomerRegistrationService.cs index 1615a2ce96..49fb4587aa 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerRegistrationService.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerRegistrationService.cs @@ -3,23 +3,22 @@ using SmartStore.Core; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Events; -using SmartStore.Services.Localization; +using SmartStore.Core.Localization; using SmartStore.Services.Messages; using SmartStore.Services.Security; namespace SmartStore.Services.Customers { - /// - /// Customer registration service - /// - public partial class CustomerRegistrationService : ICustomerRegistrationService + /// + /// Customer registration service + /// + public partial class CustomerRegistrationService : ICustomerRegistrationService { #region Fields private readonly ICustomerService _customerService; private readonly IEncryptionService _encryptionService; private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; - private readonly ILocalizationService _localizationService; private readonly RewardPointsSettings _rewardPointsSettings; private readonly CustomerSettings _customerSettings; private readonly IStoreContext _storeContext; @@ -35,31 +34,33 @@ public partial class CustomerRegistrationService : ICustomerRegistrationService public CustomerRegistrationService(ICustomerService customerService, IEncryptionService encryptionService, INewsLetterSubscriptionService newsLetterSubscriptionService, - ILocalizationService localizationService, RewardPointsSettings rewardPointsSettings, CustomerSettings customerSettings, IStoreContext storeContext, IEventPublisher eventPublisher) { this._customerService = customerService; this._encryptionService = encryptionService; this._newsLetterSubscriptionService = newsLetterSubscriptionService; - this._localizationService = localizationService; this._rewardPointsSettings = rewardPointsSettings; this._customerSettings = customerSettings; this._storeContext = storeContext; this._eventPublisher = eventPublisher; - } - #endregion + T = NullLocalizer.Instance; + } - #region Methods + #endregion - /// - /// Validate customer - /// - /// Username or email - /// Password - /// Result - public virtual bool ValidateCustomer(string usernameOrEmail, string password) + public Localizer T { get; set; } + + #region Methods + + /// + /// Validate customer + /// + /// Username or email + /// Password + /// Result + public virtual bool ValidateCustomer(string usernameOrEmail, string password) { Customer customer = null; if (_customerSettings.UsernamesEnabled) @@ -112,48 +113,46 @@ public virtual bool ValidateCustomer(string usernameOrEmail, string password) /// Result public virtual CustomerRegistrationResult RegisterCustomer(CustomerRegistrationRequest request) { - if (request == null) - throw new ArgumentNullException("request"); - - if (request.Customer == null) - throw new ArgumentException("Can't load current customer"); + Guard.ArgumentNotNull(() => request); + Guard.ArgumentNotNull(() => request.Customer); var result = new CustomerRegistrationResult(); + if (request.Customer.IsSearchEngineAccount()) { - result.AddError("Search engine can't be registered"); + result.AddError(T("Account.Register.Errors.CannotRegisterSearchEngine")); return result; } if (request.Customer.IsBackgroundTaskAccount()) { - result.AddError("Background task account can't be registered"); + result.AddError(T("Account.Register.Errors.CannotRegisterTaskAccount")); return result; } if (request.Customer.IsRegistered()) { - result.AddError("Current customer is already registered"); + result.AddError(T("Account.Register.Errors.AlreadyRegistered")); return result; } if (String.IsNullOrEmpty(request.Email)) { - result.AddError(_localizationService.GetResource("Account.Register.Errors.EmailIsNotProvided")); + result.AddError(T("Account.Register.Errors.EmailIsNotProvided")); return result; } if (!request.Email.IsEmail()) { - result.AddError(_localizationService.GetResource("Common.WrongEmail")); + result.AddError(T("Common.WrongEmail")); return result; } if (String.IsNullOrWhiteSpace(request.Password)) { - result.AddError(_localizationService.GetResource("Account.Register.Errors.PasswordIsNotProvided")); + result.AddError(T("Account.Register.Errors.PasswordIsNotProvided")); return result; } if (_customerSettings.UsernamesEnabled) { if (String.IsNullOrEmpty(request.Username)) { - result.AddError(_localizationService.GetResource("Account.Register.Errors.UsernameIsNotProvided")); + result.AddError(T("Account.Register.Errors.UsernameIsNotProvided")); return result; } } @@ -161,14 +160,14 @@ public virtual CustomerRegistrationResult RegisterCustomer(CustomerRegistrationR //validate unique user if (_customerService.GetCustomerByEmail(request.Email) != null) { - result.AddError(_localizationService.GetResource("Account.Register.Errors.EmailAlreadyExists")); + result.AddError(T("Account.Register.Errors.EmailAlreadyExists")); return result; } if (_customerSettings.UsernamesEnabled) { if (_customerService.GetCustomerByUsername(request.Username) != null) { - result.AddError(_localizationService.GetResource("Account.Register.Errors.UsernameAlreadyExists")); + result.AddError(T("Account.Register.Errors.UsernameAlreadyExists")); return result; } } @@ -180,45 +179,52 @@ public virtual CustomerRegistrationResult RegisterCustomer(CustomerRegistrationR switch (request.PasswordFormat) { - case PasswordFormat.Clear: - { - request.Customer.Password = request.Password; - } - break; - case PasswordFormat.Encrypted: - { - request.Customer.Password = _encryptionService.EncryptText(request.Password); - } - break; - case PasswordFormat.Hashed: - { - string saltKey = _encryptionService.CreateSaltKey(5); - request.Customer.PasswordSalt = saltKey; - request.Customer.Password = _encryptionService.CreatePasswordHash(request.Password, saltKey, _customerSettings.HashedPasswordFormat); - } - break; - default: - break; + case PasswordFormat.Clear: + request.Customer.Password = request.Password; + break; + case PasswordFormat.Encrypted: + request.Customer.Password = _encryptionService.EncryptText(request.Password); + break; + case PasswordFormat.Hashed: + string saltKey = _encryptionService.CreateSaltKey(5); + request.Customer.PasswordSalt = saltKey; + request.Customer.Password = _encryptionService.CreatePasswordHash(request.Password, saltKey, _customerSettings.HashedPasswordFormat); + break; } request.Customer.Active = request.IsApproved; - - //add to 'Registered' role - var registeredRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered); - if (registeredRole == null) - throw new SmartException("'Registered' role could not be loaded"); + + if (_customerSettings.RegisterCustomerRoleId != 0) + { + var customerRole = _customerService.GetCustomerRoleById(_customerSettings.RegisterCustomerRoleId); + request.Customer.CustomerRoles.Add(customerRole); + } + + //add to 'Registered' role + var registeredRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered); + if (registeredRole == null) + { + throw new SmartException(T("Admin.Customers.CustomerRoles.CannotFoundRole", "Registered")); + } + request.Customer.CustomerRoles.Add(registeredRole); + //remove from 'Guests' role var guestRole = request.Customer.CustomerRoles.FirstOrDefault(cr => cr.SystemName == SystemCustomerRoleNames.Guests); - if (guestRole != null) - request.Customer.CustomerRoles.Remove(guestRole); + if (guestRole != null) + { + request.Customer.CustomerRoles.Remove(guestRole); + } - //Add reward points for customer registration (if enabled) - if (_rewardPointsSettings.Enabled && _rewardPointsSettings.PointsForRegistration > 0) - request.Customer.AddRewardPointsHistoryEntry(_rewardPointsSettings.PointsForRegistration, _localizationService.GetResource("RewardPoints.Message.RegisteredAsCustomer")); + //Add reward points for customer registration (if enabled) + if (_rewardPointsSettings.Enabled && _rewardPointsSettings.PointsForRegistration > 0) + { + request.Customer.AddRewardPointsHistoryEntry(_rewardPointsSettings.PointsForRegistration, T("RewardPoints.Message.RegisteredAsCustomer")); + } _customerService.UpdateCustomer(request.Customer); - _eventPublisher.Publish(new CustomerRegisteredEvent{ Customer = request.Customer }); + _eventPublisher.Publish(new CustomerRegisteredEvent { Customer = request.Customer }); + return result; } @@ -235,19 +241,19 @@ public virtual PasswordChangeResult ChangePassword(ChangePasswordRequest request var result = new PasswordChangeResult(); if (String.IsNullOrWhiteSpace(request.Email)) { - result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.EmailIsNotProvided")); + result.AddError(T("Account.ChangePassword.Errors.EmailIsNotProvided")); return result; } if (String.IsNullOrWhiteSpace(request.NewPassword)) { - result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.PasswordIsNotProvided")); + result.AddError(T("Account.ChangePassword.Errors.PasswordIsNotProvided")); return result; } var customer = _customerService.GetCustomerByEmail(request.Email); if (customer == null) { - result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.EmailNotFound")); + result.AddError(T("Account.ChangePassword.Errors.EmailNotFound")); return result; } @@ -272,7 +278,7 @@ public virtual PasswordChangeResult ChangePassword(ChangePasswordRequest request bool oldPasswordIsValid = oldPwd == customer.Password; if (!oldPasswordIsValid) - result.AddError(_localizationService.GetResource("Account.ChangePassword.Errors.OldPasswordDoesntMatch")); + result.AddError(T("Account.ChangePassword.Errors.OldPasswordDoesntMatch")); if (oldPasswordIsValid) requestIsValid = true; @@ -327,14 +333,14 @@ public virtual void SetEmail(Customer customer, string newEmail) string oldEmail = customer.Email; if (!newEmail.IsEmail()) - throw new SmartException(_localizationService.GetResource("Account.EmailUsernameErrors.NewEmailIsNotValid")); + throw new SmartException(T("Account.EmailUsernameErrors.NewEmailIsNotValid")); if (newEmail.Length > 100) - throw new SmartException(_localizationService.GetResource("Account.EmailUsernameErrors.EmailTooLong")); + throw new SmartException(T("Account.EmailUsernameErrors.EmailTooLong")); var customer2 = _customerService.GetCustomerByEmail(newEmail); if (customer2 != null && customer.Id != customer2.Id) - throw new SmartException(_localizationService.GetResource("Account.EmailUsernameErrors.EmailAlreadyExists")); + throw new SmartException(T("Account.EmailUsernameErrors.EmailAlreadyExists")); customer.Email = newEmail; _customerService.UpdateCustomer(customer); @@ -370,11 +376,11 @@ public virtual void SetUsername(Customer customer, string newUsername) newUsername = newUsername.Trim(); if (newUsername.Length > 100) - throw new SmartException(_localizationService.GetResource("Account.EmailUsernameErrors.UsernameTooLong")); + throw new SmartException(T("Account.EmailUsernameErrors.UsernameTooLong")); var user2 = _customerService.GetCustomerByUsername(newUsername); if (user2 != null && customer.Id != user2.Id) - throw new SmartException(_localizationService.GetResource("Account.EmailUsernameErrors.UsernameAlreadyExists")); + throw new SmartException(T("Account.EmailUsernameErrors.UsernameAlreadyExists")); customer.Username = newUsername; _customerService.UpdateCustomer(customer); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs index 85ea07067b..47df2024dc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs @@ -902,13 +902,15 @@ public ActionResult CustomerUser() var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); StoreDependingSettings.CreateViewDataObject(storeScope); + var allCustomerRoles = _customerService.GetAllCustomerRoles(true); + var customerSettings = _services.Settings.LoadSetting(storeScope); var addressSettings = _services.Settings.LoadSetting(storeScope); var dateTimeSettings = _services.Settings.LoadSetting(storeScope); var externalAuthenticationSettings = _services.Settings.LoadSetting(storeScope); - //merge settings - var model = new CustomerUserSettingsModel(); + //merge settings + var model = new CustomerUserSettingsModel(); model.CustomerSettings = customerSettings.ToModel(); StoreDependingSettings.GetOverrideKeys(customerSettings, model.CustomerSettings, storeScope, _services.Settings, false); @@ -919,14 +921,15 @@ public ActionResult CustomerUser() model.DateTimeSettings.AllowCustomersToSetTimeZone = dateTimeSettings.AllowCustomersToSetTimeZone; model.DateTimeSettings.DefaultStoreTimeZoneId = _dateTimeHelper.DefaultStoreTimeZone.Id; + foreach (TimeZoneInfo timeZone in _dateTimeHelper.GetSystemTimeZones()) { - model.DateTimeSettings.AvailableTimeZones.Add(new SelectListItem() - { - Text = timeZone.DisplayName, - Value = timeZone.Id, - Selected = timeZone.Id.Equals(_dateTimeHelper.DefaultStoreTimeZone.Id, StringComparison.InvariantCultureIgnoreCase) - }); + model.DateTimeSettings.AvailableTimeZones.Add(new SelectListItem + { + Text = timeZone.DisplayName, + Value = timeZone.Id, + Selected = timeZone.Id.Equals(_dateTimeHelper.DefaultStoreTimeZone.Id, StringComparison.InvariantCultureIgnoreCase) + }); } StoreDependingSettings.GetOverrideKeys(dateTimeSettings, model.DateTimeSettings, storeScope, _services.Settings, false); @@ -938,7 +941,12 @@ public ActionResult CustomerUser() model.CustomerSettings.AvailableCustomerNumberMethods = customerSettings.CustomerNumberMethod.ToSelectList(); model.CustomerSettings.AvailableCustomerNumberVisibilities = customerSettings.CustomerNumberVisibility.ToSelectList(); - return View(model); + model.CustomerSettings.AvailableRegisterCustomerRoles = allCustomerRoles + .Where(x => x.SystemName != SystemCustomerRoleNames.Registered && x.SystemName != SystemCustomerRoleNames.Guests) + .Select(x => new SelectListItem { Text = x.Name, Value = x.Id.ToString() }) + .ToList(); + + return View(model); } [HttpPost] diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs index 1ad5872150..2e076d6471 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs @@ -29,8 +29,9 @@ public partial class CustomerSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.DefaultSortOrderMode")] public CustomerNumberMethod CustomerNumberMethod { get; set; } public SelectList AvailableCustomerNumberMethods { get; set; } + public IList AvailableRegisterCustomerRoles { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.DefaultSortOrderMode")] + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.DefaultSortOrderMode")] public CustomerNumberVisibility CustomerNumberVisibility { get; set; } public SelectList AvailableCustomerNumberVisibilities { get; set; } @@ -43,7 +44,10 @@ public partial class CustomerSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.UserRegistrationType")] public int UserRegistrationType { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.AllowCustomersToUploadAvatars")] + [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.RegisterCustomerRole")] + public int RegisterCustomerRoleId { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.AllowCustomersToUploadAvatars")] public bool AllowCustomersToUploadAvatars { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.DefaultAvatarEnabled")] @@ -131,7 +135,7 @@ public partial class CustomerSettingsModel public bool FaxEnabled { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.FaxRequired")] public bool FaxRequired { get; set; } - } + } public partial class AddressSettingsModel { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml index b22985819c..d3c493aad1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml @@ -3,7 +3,6 @@ @using SmartStore.Core.Domain.Customers; @using SmartStore.Core.Domain.Security; @{ - //page title ViewBag.Title = T("Admin.Configuration.Settings.CustomerUser").Text; } @using (Html.BeginForm()) @@ -128,6 +127,16 @@ @Html.ValidationMessageFor(model => model.CustomerSettings.UserRegistrationType)
    + @Html.SmartLabelFor(model => model.CustomerSettings.RegisterCustomerRoleId) + + @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.RegisterCustomerRoleId) + @Html.DropDownListFor(model => model.CustomerSettings.RegisterCustomerRoleId, Model.CustomerSettings.AvailableRegisterCustomerRoles, T("Common.Unspecified")) + @Html.ValidationMessageFor(model => model.CustomerSettings.RegisterCustomerRoleId) +
    @Html.SmartLabelFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars) diff --git a/src/Tests/SmartStore.Services.Tests/Customers/CustomerRegistrationServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Customers/CustomerRegistrationServiceTests.cs index b356946e7c..990c27b02f 100644 --- a/src/Tests/SmartStore.Services.Tests/Customers/CustomerRegistrationServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Customers/CustomerRegistrationServiceTests.cs @@ -11,7 +11,6 @@ using SmartStore.Core.Events; using SmartStore.Services.Common; using SmartStore.Services.Customers; -using SmartStore.Services.Localization; using SmartStore.Services.Messages; using SmartStore.Services.Security; using SmartStore.Tests; @@ -29,7 +28,6 @@ public class CustomerRegistrationServiceTests : ServiceTest IEncryptionService _encryptionService; ICustomerService _customerService; ICustomerRegistrationService _customerRegistrationService; - ILocalizationService _localizationService; CustomerSettings _customerSettings; INewsLetterSubscriptionService _newsLetterSubscriptionService; IEventPublisher _eventPublisher; @@ -117,15 +115,13 @@ public class CustomerRegistrationServiceTests : ServiceTest _genericAttributeService = MockRepository.GenerateMock(); _newsLetterSubscriptionService = MockRepository.GenerateMock(); - _localizationService = MockRepository.GenerateMock(); _storeContext = MockRepository.GenerateMock(); _customerService = new CustomerService(new NullCache(), _customerRepo, _customerRoleRepo, _genericAttributeRepo, _rewardPointsHistoryRepo, _genericAttributeService, _eventPublisher, _rewardPointsSettings); _customerRegistrationService = new CustomerRegistrationService(_customerService, - _encryptionService, _newsLetterSubscriptionService, _localizationService, - _rewardPointsSettings, _customerSettings, _storeContext, _eventPublisher); + _encryptionService, _newsLetterSubscriptionService, _rewardPointsSettings, _customerSettings, _storeContext, _eventPublisher); } //[Test] From 019a4d24f9f72f7f45cfb798b19b102e30017efe Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 10 Jan 2016 11:49:19 +0100 Subject: [PATCH 391/874] Resolves #800 Multi-store: Option to display all orders of all stores for customer in frontend --- changelog.md | 1 + .../Domain/Orders/OrderSettings.cs | 6 +++- .../201512151526290_ImportFramework.cs | 9 ++++++ .../Controllers/SettingController.cs | 5 ++- .../Infrastructure/AutoMapperStartupTask.cs | 1 + .../Models/Settings/OrderSettingsModel.cs | 4 +++ .../Administration/Views/Setting/Order.cshtml | 12 +++++++ .../Controllers/CustomerController.cs | 31 ++++++++++++------- 8 files changed, 55 insertions(+), 14 deletions(-) diff --git a/changelog.md b/changelog.md index 6a82d8e636..519c0f8906 100644 --- a/changelog.md +++ b/changelog.md @@ -34,6 +34,7 @@ * More settings to control creation of SEO names * GMC feed: Supporting fields multipack, bundle, adult, energy efficiency class and custom label (0 to 4) * #760 Setting to set a customer role for new registered users +* #800 Multi-store: Option to display all orders of all stores for customer in frontend ### Improvements * (Perf) Implemented static caches for URL aliases and localized properties. Increases app startup and request speed by up to 30%. diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/OrderSettings.cs b/src/Libraries/SmartStore.Core/Domain/Orders/OrderSettings.cs index 7a55c36f70..996271de79 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/OrderSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/OrderSettings.cs @@ -83,5 +83,9 @@ public OrderSettings() /// public int MinimumOrderPlacementInterval { get; set; } - } + /// + /// Gets or sets a value indicating whether to display all orders of all stores to a customer + /// + public bool DisplayOrdersOfAllStores { get; set; } + } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs index 2699566e17..767af5b90c 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs @@ -338,6 +338,15 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Kundengruppe bei Registrierungen", "Specifies a customer role that will be assigned to newly registered customers.", "Legt eine Kundengruppe fest, die neu registrierten Kunden zugeordnet wird."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Order.DisplayOrdersOfAllStores", + "Display orders of all stores", + "Auftrge aller Shops anzeigen", + "Specifies whether to display the orders of all stores to the customer. If this option is disabled, only the orders of the current store are displayed.", + "Legt fest, ob dem Kunden die Auftrge aller Shops angezeigt werden sollen. Ist diese Option deaktiviert, so werden nur die Auftrge des aktuellen Shops angezeigt."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Order.GiftCards_Deactivated") + .Value("de", "Geschenkgutschein wird deaktiviert, wenn Auftragsstatus..."); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs index 47df2024dc..1d9becf2f3 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs @@ -685,13 +685,16 @@ public ActionResult Order() //load settings for a chosen store scope var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); var orderSettings = _services.Settings.LoadSetting(storeScope); - var store = (storeScope == 0 ? _services.StoreContext.CurrentStore : _services.StoreService.GetStoreById(storeScope)); + + var allStores = _services.StoreService.GetAllStores(); + var store = (storeScope == 0 ? _services.StoreContext.CurrentStore : allStores.FirstOrDefault(x => x.Id == storeScope)); var model = orderSettings.ToModel(); StoreDependingSettings.GetOverrideKeys(orderSettings, model, storeScope, _services.Settings); model.PrimaryStoreCurrencyCode = store.PrimaryStoreCurrency.CurrencyCode; + model.StoreCount = allStores.Count; //gift card activation/deactivation model.GiftCards_Activated_OrderStatuses = OrderStatus.Pending.ToSelectList(false).ToList(); diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs index 9c6a24d877..3cbb5afe21 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs @@ -640,6 +640,7 @@ public void Execute() .ForMember(dest => dest.GiftCards_Activated_OrderStatuses, mo => mo.Ignore()) .ForMember(dest => dest.GiftCards_Deactivated_OrderStatuses, mo => mo.Ignore()) .ForMember(dest => dest.PrimaryStoreCurrencyCode, mo => mo.Ignore()) + .ForMember(dest => dest.StoreCount, mo => mo.Ignore()) .ForMember(dest => dest.OrderIdent, mo => mo.Ignore()) .ForMember(dest => dest.Locales, mo => mo.Ignore()); Mapper.CreateMap() diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/OrderSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/OrderSettingsModel.cs index ea2bc65410..c54a264eb5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/OrderSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/OrderSettingsModel.cs @@ -58,10 +58,14 @@ public OrderSettingsModel() public IList GiftCards_Deactivated_OrderStatuses { get; set; } public string PrimaryStoreCurrencyCode { get; set; } + public int StoreCount { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.Order.OrderIdent")] public int? OrderIdent { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Order.DisplayOrdersOfAllStores")] + public bool DisplayOrdersOfAllStores { get; set; } + public IList Locales { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml index 3c29b263a1..df566da1ed 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml @@ -116,6 +116,18 @@
    + @Html.SmartLabelFor(model => model.DisplayOrdersOfAllStores) + + @Html.SettingEditorFor(model => model.DisplayOrdersOfAllStores) + @Html.ValidationMessageFor(model => model.DisplayOrdersOfAllStores) +
    } diff --git a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs index 1c9fa88791..e6b81ab62f 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs @@ -360,11 +360,14 @@ protected CustomerOrderListModel PrepareCustomerOrderListModel(Customer customer var model = new CustomerOrderListModel(); model.NavigationModel = GetCustomerNavigationModel(customer); model.NavigationModel.SelectedTab = CustomerNavigationEnum.Orders; - var orders = _orderService.SearchOrders(_storeContext.CurrentStore.Id, customer.Id, - null, null, null, null, null, null, null, null, 0, int.MaxValue); + + var storeScope = (_orderSettings.DisplayOrdersOfAllStores ? 0 : _storeContext.CurrentStore.Id); + + var orders = _orderService.SearchOrders(storeScope, customer.Id, null, null, null, null, null, null, null, null, 0, int.MaxValue); + foreach (var order in orders) { - var orderModel = new CustomerOrderListModel.OrderDetailsModel() + var orderModel = new CustomerOrderListModel.OrderDetailsModel { Id = order.Id, OrderNumber = order.GetOrderNumber(), @@ -372,6 +375,7 @@ protected CustomerOrderListModel PrepareCustomerOrderListModel(Customer customer OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), IsReturnRequestAllowed = _orderProcessingService.IsReturnRequestAllowed(order) }; + var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); orderModel.OrderTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage); @@ -382,7 +386,7 @@ protected CustomerOrderListModel PrepareCustomerOrderListModel(Customer customer foreach (var recurringPayment in recurringPayments) { - var recurringPaymentModel = new CustomerOrderListModel.RecurringOrderModel() + var recurringPaymentModel = new CustomerOrderListModel.RecurringOrderModel { Id = recurringPayment.Id, StartDate = _dateTimeHelper.ConvertToUserTime(recurringPayment.StartDateUtc, DateTimeKind.Utc).ToString(), @@ -1291,6 +1295,7 @@ public ActionResult Orders() var customer = _workContext.CurrentCustomer; var model = PrepareCustomerOrderListModel(customer); + return View(model); } @@ -1303,9 +1308,13 @@ public ActionResult CancelRecurringPayment(FormCollection form) //get recurring payment identifier int recurringPaymentId = 0; - foreach (var formValue in form.AllKeys) - if (formValue.StartsWith("cancelRecurringPayment", StringComparison.InvariantCultureIgnoreCase)) - recurringPaymentId = Convert.ToInt32(formValue.Substring("cancelRecurringPayment".Length)); + foreach (var formValue in form.AllKeys) + { + if (formValue.StartsWith("cancelRecurringPayment", StringComparison.InvariantCultureIgnoreCase)) + { + recurringPaymentId = Convert.ToInt32(formValue.Substring("cancelRecurringPayment".Length)); + } + } var recurringPayment = _orderService.GetRecurringPaymentById(recurringPaymentId); if (recurringPayment == null) @@ -1323,11 +1332,9 @@ public ActionResult CancelRecurringPayment(FormCollection form) return View(model); } - else - { - return RedirectToAction("Orders"); - } - } + + return RedirectToAction("Orders"); + } #endregion From 480cb8f4d23dd7e6379bf01eed349d47aadebabc Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 11 Jan 2016 13:29:59 +0100 Subject: [PATCH 392/874] Resolves #790 Improve language editing --- changelog.md | 1 + .../201512151526290_ImportFramework.cs | 10 + .../Administration/Content/admin.less | 15 ++ .../Controllers/LanguageController.cs | 195 +++++++++--------- .../Infrastructure/AutoMapperStartupTask.cs | 4 +- .../Models/Localization/LanguageModel.cs | 13 +- .../Localization/LanguageValidator.cs | 29 +-- .../Administration/Views/Language/List.cshtml | 7 +- .../Views/Language/_CreateOrUpdate.cshtml | 86 ++++---- 9 files changed, 205 insertions(+), 155 deletions(-) diff --git a/changelog.md b/changelog.md index 519c0f8906..f8dec5cd62 100644 --- a/changelog.md +++ b/changelog.md @@ -70,6 +70,7 @@ * Product grid sortable by name, price and created on * #26 Display company or name in order list * Added inline editing of country grid +* #790 Improved language editing ### Bugfixes * #523 Redirecting to payment provider performed by core instead of plugin diff --git a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs index 767af5b90c..f281df8a32 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201512151526290_ImportFramework.cs @@ -347,6 +347,16 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Settings.Order.GiftCards_Deactivated") .Value("de", "Geschenkgutschein wird deaktiviert, wenn Auftragsstatus..."); + + builder.AddOrUpdate("Admin.Configuration.Languages.Fields.UniqueSeoCode.Required", + "Please select a SEO language code.", + "Bitte legen Sie einen SEO Sprach-Code fest."); + + builder.AddOrUpdate("Admin.Configuration.Languages.Fields.FlagImageFileName", + "Flag image", + "Flaggenbild", + "Specifies the flag image. The files for the flag images must be stored in /Content/Images/flags/.", + "Legt das Flaggenbild fest. Die Dateien der Flaggenbilder mssen in /Content/Images/flags/ liegen."); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index eff6584861..26c3604e2a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -432,6 +432,21 @@ td.adminData > input[type="checkbox"] { min-width: 314px; /* ensures more clean look & feel */ } +.select2-image-item { + img { + margin-bottom: 3px; + } + span:not(.select2-icon) { + padding-left: 5px; + } + .icon-container { + float: left; + display: inline-block; + width: 16px; + min-width: 16px; + max-width: 16px; + } +} /* select2 with large, templated items -------------------------------------------------------------- */ diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs index 02587df34f..20824005a4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/LanguageController.cs @@ -1,15 +1,17 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Localization; -using SmartStore.Core; using SmartStore.Core.Data; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Plugins; +using SmartStore.Services; +using SmartStore.Services.Directory; using SmartStore.Services.Localization; using SmartStore.Services.Security; using SmartStore.Services.Stores; @@ -28,74 +30,95 @@ public partial class LanguageController : AdminControllerBase #region Fields private readonly ILanguageService _languageService; - private readonly ILocalizationService _localizationService; - private readonly IStoreService _storeService; private readonly IStoreMappingService _storeMappingService; - private readonly IPermissionService _permissionService; - private readonly IWebHelper _webHelper; private readonly AdminAreaSettings _adminAreaSettings; private readonly IPluginFinder _pluginFinder; + private readonly ICountryService _countryService; + private readonly ICommonServices _services; #endregion #region Constructors public LanguageController(ILanguageService languageService, - ILocalizationService localizationService, - IStoreService storeService, IStoreMappingService storeMappingService, - IPermissionService permissionService, - IWebHelper webHelper, AdminAreaSettings adminAreaSettings, - IPluginFinder pluginFinder) + IPluginFinder pluginFinder, + ICountryService countryService, + ICommonServices services) { - this._localizationService = localizationService; this._languageService = languageService; - this._storeService = storeService; this._storeMappingService = storeMappingService; - this._permissionService = permissionService; - this._webHelper = webHelper; this._adminAreaSettings = adminAreaSettings; this._pluginFinder = pluginFinder; + this._countryService = countryService; + this._services = services; } - #endregion - + #endregion + #region Utilities - [NonAction] - private void PrepareFlagsModel(LanguageModel model) - { - if (model == null) - throw new ArgumentNullException("model"); + private void PrepareLanguageModel(LanguageModel model, Language language, bool excludeProperties) + { + var languageId = _services.WorkContext.WorkingLanguage.Id; - model.FlagFileNames = System.IO.Directory - .EnumerateFiles(_webHelper.MapPath("~/Content/Images/flags/"), "*.png", SearchOption.TopDirectoryOnly) - .Select(System.IO.Path.GetFileName) - .ToList(); - } + var allCultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures) + .OrderBy(x => x.DisplayName) + .ToList(); - [NonAction] - private void PrepareStoresMappingModel(LanguageModel model, Language language, bool excludeProperties) - { - if (model == null) - throw new ArgumentNullException("model"); + var allCountryNames = _countryService.GetAllCountries(true) + .ToDictionary(x => x.TwoLetterIsoCode.EmptyNull().ToLower(), x => x.GetLocalized(y => y.Name, languageId, true, false)); - model.AvailableStores = _storeService - .GetAllStores() - .Select(s => s.ToModel()) + model.AvailableCultures = allCultures + .Select(x => new SelectListItem { Text = "{0} [{1}]".FormatInvariant(x.DisplayName, x.IetfLanguageTag), Value = x.IetfLanguageTag }) .ToList(); - if (!excludeProperties) + + model.AvailableTwoLetterLanguageCodes = new List(); + model.AvailableFlags = new List(); + + foreach (var item in allCultures) { - if (language != null) - { - model.SelectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(language); - } - else + if (!model.AvailableTwoLetterLanguageCodes.Any(x => x.Value.IsCaseInsensitiveEqual(item.TwoLetterISOLanguageName))) { - model.SelectedStoreIds = new int[0]; + // display language name is not provided by net framework + var index = item.DisplayName.EmptyNull().IndexOf(" ("); + + if (index == -1) + index = item.DisplayName.EmptyNull().IndexOf(" ["); + + var displayName = "{0} [{1}]".FormatInvariant( + index == -1 ? item.DisplayName : item.DisplayName.Substring(0, index), + item.TwoLetterISOLanguageName); + + model.AvailableTwoLetterLanguageCodes.Add(new SelectListItem { Text = displayName, Value = item.TwoLetterISOLanguageName }); } } + + foreach (var path in Directory.EnumerateFiles(_services.WebHelper.MapPath("~/Content/Images/flags/"), "*.png", SearchOption.TopDirectoryOnly)) + { + var name = Path.GetFileNameWithoutExtension(path).EmptyNull().ToLower(); + string countryDescription = null; + + if (allCountryNames.ContainsKey(name)) + countryDescription = "{0} [{1}]".FormatInvariant(allCountryNames[name], name); + + if (countryDescription.IsEmpty()) + countryDescription = name; + + model.AvailableFlags.Add(new SelectListItem { Text = countryDescription, Value = Path.GetFileName(path) }); + } + + model.AvailableFlags = model.AvailableFlags.OrderBy(x => x.Text).ToList(); + + model.AvailableStores = _services.StoreService.GetAllStores() + .Select(s => s.ToModel()) + .ToList(); + + if (!excludeProperties) + { + model.SelectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(language); + } } #endregion @@ -109,15 +132,17 @@ public ActionResult Index() public ActionResult List() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); var languages = _languageService.GetAllLanguages(true); + var gridModel = new GridModel { Data = languages.Select(x => x.ToModel()), Total = languages.Count() }; + return View(gridModel); } @@ -126,7 +151,7 @@ public ActionResult List(GridCommand command) { var model = new GridModel(); - if (_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) { var languages = _languageService.GetAllLanguages(true); @@ -148,26 +173,20 @@ public ActionResult List(GridCommand command) public ActionResult Create() { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); var model = new LanguageModel(); - //flags - PrepareFlagsModel(model); - - //Stores - PrepareStoresMappingModel(model, null, false); + PrepareLanguageModel(model, null, false); - //default values - //model.Published = true; return View(model); } [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Create(LanguageModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); if (ModelState.IsValid) @@ -183,18 +202,14 @@ public ActionResult Create(LanguageModel model, bool continueEditing) foreach (var plugin in plugins) { - _localizationService.ImportPluginResourcesFromXml(plugin, null, false, filterLanguages); + _services.Localization.ImportPluginResourcesFromXml(plugin, null, false, filterLanguages); } - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Languages.Added")); + NotifySuccess(T("Admin.Configuration.Languages.Added")); return continueEditing ? RedirectToAction("Edit", new { id = language.Id }) : RedirectToAction("List"); } - //flags - PrepareFlagsModel(model); - - //Stores - PrepareStoresMappingModel(model, null, true); + PrepareLanguageModel(model, null, true); //If we got this far, something failed, redisplay form return View(model); @@ -202,7 +217,7 @@ public ActionResult Create(LanguageModel model, bool continueEditing) public ActionResult Edit(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); var language = _languageService.GetLanguageById(id); @@ -214,11 +229,7 @@ public ActionResult Edit(int id) var model = language.ToModel(); - //flags - PrepareFlagsModel(model); - - //Stores - PrepareStoresMappingModel(model, language, false); + PrepareLanguageModel(model, language, false); return View(model); } @@ -226,7 +237,7 @@ public ActionResult Edit(int id) [HttpPost, ParameterBasedOnFormName("save-continue", "continueEditing")] public ActionResult Edit(LanguageModel model, bool continueEditing) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); var language = _languageService.GetLanguageById(model.Id); @@ -252,15 +263,11 @@ public ActionResult Edit(LanguageModel model, bool continueEditing) _storeMappingService.SaveStoreMappings(language, model.SelectedStoreIds); //notification - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Languages.Updated")); + NotifySuccess(T("Admin.Configuration.Languages.Updated")); return continueEditing ? RedirectToAction("Edit", new { id = language.Id }) : RedirectToAction("List"); } - //flags - PrepareFlagsModel(model); - - //Stores - PrepareStoresMappingModel(model, language, true); + PrepareLanguageModel(model, language, true); //If we got this far, something failed, redisplay form return View(model); @@ -269,7 +276,7 @@ public ActionResult Edit(LanguageModel model, bool continueEditing) [HttpPost] public ActionResult Delete(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); var language = _languageService.GetLanguageById(id); @@ -288,7 +295,7 @@ public ActionResult Delete(int id) _languageService.DeleteLanguage(language); //notification - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Languages.Deleted")); + NotifySuccess(T("Admin.Configuration.Languages.Deleted")); return RedirectToAction("List"); } @@ -298,7 +305,7 @@ public ActionResult Delete(int id) public ActionResult Resources(int languageId) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); ViewBag.AllLanguages = _languageService.GetAllLanguages(true) @@ -312,7 +319,7 @@ public ActionResult Resources(int languageId) ViewBag.LanguageId = languageId; ViewBag.LanguageName = language.Name; - var resources = _localizationService + var resources = _services.Localization .GetResourceValues(languageId, true) .Where(x => x.Key != "!!___EOF___!!" && x.Value != null) .OrderBy(x => x.Key) @@ -339,11 +346,11 @@ public ActionResult Resources(int languageId, GridCommand command) { var model = new GridModel(); - if (_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) { var language = _languageService.GetLanguageById(languageId); - var resources = _localizationService + var resources = _services.Localization .GetResourceValues(languageId, true) .OrderBy(x => x.Key) .Where(x => x.Key != "!!___EOF___!!" && x.Value != null) @@ -377,7 +384,7 @@ public ActionResult Resources(int languageId, GridCommand command) [GridAction(EnableCustomBinding = true)] public ActionResult ResourceUpdate(LanguageResourceModel model, GridCommand command) { - if (_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) { if (model.Name != null) model.Name = model.Name.Trim(); @@ -390,11 +397,11 @@ public ActionResult ResourceUpdate(LanguageResourceModel model, GridCommand comm return Content(modelStateErrors.FirstOrDefault()); } - var resource = _localizationService.GetLocaleStringResourceById(model.Id); + var resource = _services.Localization.GetLocaleStringResourceById(model.Id); // if the resourceName changed, ensure it isn't being used by another resource if (!resource.ResourceName.Equals(model.Name, StringComparison.InvariantCultureIgnoreCase)) { - var res = _localizationService.GetLocaleStringResourceByName(model.Name, model.LanguageId, false); + var res = _services.Localization.GetLocaleStringResourceByName(model.Name, model.LanguageId, false); if (res != null && res.Id != resource.Id) { return Content(T("Admin.Configuration.Languages.Resources.NameAlreadyExists", res.ResourceName)); @@ -405,7 +412,7 @@ public ActionResult ResourceUpdate(LanguageResourceModel model, GridCommand comm resource.ResourceValue = model.Value; resource.IsTouched = true; - _localizationService.UpdateLocaleStringResource(resource); + _services.Localization.UpdateLocaleStringResource(resource); } return Resources(model.LanguageId, command); @@ -414,7 +421,7 @@ public ActionResult ResourceUpdate(LanguageResourceModel model, GridCommand comm [GridAction(EnableCustomBinding = true)] public ActionResult ResourceAdd(int id, LanguageResourceModel model, GridCommand command) { - if (_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) { if (model.Name != null) model.Name = model.Name.Trim(); @@ -427,7 +434,7 @@ public ActionResult ResourceAdd(int id, LanguageResourceModel model, GridCommand return Content(modelStateErrors.FirstOrDefault()); } - var res = _localizationService.GetLocaleStringResourceByName(model.Name, model.LanguageId, false); + var res = _services.Localization.GetLocaleStringResourceByName(model.Name, model.LanguageId, false); if (res == null) { var resource = new LocaleStringResource { LanguageId = id }; @@ -435,7 +442,7 @@ public ActionResult ResourceAdd(int id, LanguageResourceModel model, GridCommand resource.ResourceValue = model.Value; resource.IsTouched = true; - _localizationService.InsertLocaleStringResource(resource); + _services.Localization.InsertLocaleStringResource(resource); } else { @@ -449,11 +456,11 @@ public ActionResult ResourceAdd(int id, LanguageResourceModel model, GridCommand [GridAction(EnableCustomBinding = true)] public ActionResult ResourceDelete(int id, int languageId, GridCommand command) { - if (_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) { - var resource = _localizationService.GetLocaleStringResourceById(id); + var resource = _services.Localization.GetLocaleStringResourceById(id); - _localizationService.DeleteLocaleStringResource(resource); + _services.Localization.DeleteLocaleStringResource(resource); } return Resources(languageId, command); @@ -465,7 +472,7 @@ public ActionResult ResourceDelete(int id, int languageId, GridCommand command) public ActionResult ExportXml(int id) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); var language = _languageService.GetLanguageById(id); @@ -474,7 +481,7 @@ public ActionResult ExportXml(int id) try { - var xml = _localizationService.ExportResourcesToXml(language); + var xml = _services.Localization.ExportResourcesToXml(language); return new XmlDownloadResult(xml, "language-pack-{0}.xml".FormatInvariant(language.UniqueSeoCode)); } catch (Exception exc) @@ -487,7 +494,7 @@ public ActionResult ExportXml(int id) [HttpPost] public ActionResult ImportXml(int id, FormCollection form, ImportModeFlags mode, bool updateTouched) { - if (!_permissionService.Authorize(StandardPermissionProvider.ManageLanguages)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageLanguages)) return AccessDeniedView(); var language = _languageService.GetLanguageById(id); @@ -502,15 +509,15 @@ public ActionResult ImportXml(int id, FormCollection form, ImportModeFlags mode, var file = Request.Files["importxmlfile"]; if (file != null && file.ContentLength > 0) { - _localizationService.ImportResourcesFromXml(language, file.InputStream.AsString(), mode: mode, updateTouchedResources: updateTouched); + _services.Localization.ImportResourcesFromXml(language, file.InputStream.AsString(), mode: mode, updateTouchedResources: updateTouched); } else { - NotifyError(_localizationService.GetResource("Admin.Common.UploadFile")); + NotifyError(T("Admin.Common.UploadFile")); return RedirectToAction("Edit", new { id = language.Id }); } - NotifySuccess(_localizationService.GetResource("Admin.Configuration.Languages.Imported")); + NotifySuccess(T("Admin.Configuration.Languages.Imported")); return RedirectToAction("Edit", new { id = language.Id }); } catch (Exception exc) diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs index 3cbb5afe21..cf6652c228 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs @@ -129,7 +129,9 @@ public void Execute() .ForMember(dest => dest.Country, mo => mo.Ignore()); //language Mapper.CreateMap() - .ForMember(dest => dest.FlagFileNames, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableFlags, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableCultures, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableTwoLetterLanguageCodes, mo => mo.Ignore()) .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()); Mapper.CreateMap() diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Localization/LanguageModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Localization/LanguageModel.cs index b720c69d5f..28d3dfaf30 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Localization/LanguageModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Localization/LanguageModel.cs @@ -8,7 +8,7 @@ namespace SmartStore.Admin.Models.Localization { - [Validator(typeof(LanguageValidator))] + [Validator(typeof(LanguageValidator))] public class LanguageModel : EntityModelBase { public LanguageModel() @@ -23,18 +23,21 @@ public LanguageModel() [SmartResourceDisplayName("Admin.Configuration.Languages.Fields.LanguageCulture")] [AllowHtml] public string LanguageCulture { get; set; } + public List AvailableCultures { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Languages.Fields.UniqueSeoCode")] + [SmartResourceDisplayName("Admin.Configuration.Languages.Fields.UniqueSeoCode")] [AllowHtml] public string UniqueSeoCode { get; set; } + public List AvailableTwoLetterLanguageCodes { get; set; } - //flags - [SmartResourceDisplayName("Admin.Configuration.Languages.Fields.FlagImageFileName")] + //flags + [SmartResourceDisplayName("Admin.Configuration.Languages.Fields.FlagImageFileName")] [AllowHtml] public string FlagImageFileName { get; set; } public IList FlagFileNames { get; set; } + public List AvailableFlags { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Languages.Fields.Rtl")] + [SmartResourceDisplayName("Admin.Configuration.Languages.Fields.Rtl")] public bool Rtl { get; set; } [SmartResourceDisplayName("Admin.Configuration.Languages.Fields.Published")] diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/Localization/LanguageValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/Localization/LanguageValidator.cs index ea6ba1314e..891c093efc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Validators/Localization/LanguageValidator.cs +++ b/src/Presentation/SmartStore.Web/Administration/Validators/Localization/LanguageValidator.cs @@ -9,29 +9,32 @@ public partial class LanguageValidator : AbstractValidator { public LanguageValidator(ILocalizationService localizationService) { - RuleFor(x => x.Name).NotEmpty().WithMessage(localizationService.GetResource("Admin.Configuration.Languages.Fields.Name.Required")); + RuleFor(x => x.Name) + .NotEmpty() + .WithMessage(localizationService.GetResource("Admin.Configuration.Languages.Fields.Name.Required")); + RuleFor(x => x.LanguageCulture) .Must(x => - { - try - { - var culture = new CultureInfo(x); - return culture != null; - } - catch - { - return false; - } - }) + { + try + { + var culture = new CultureInfo(x); + return culture != null; + } + catch + { + return false; + } + }) .WithMessage(localizationService.GetResource("Admin.Configuration.Languages.Fields.LanguageCulture.Validation")); RuleFor(x => x.UniqueSeoCode) .NotNull() .WithMessage(localizationService.GetResource("Admin.Configuration.Languages.Fields.UniqueSeoCode.Required")); + RuleFor(x => x.UniqueSeoCode) .Length(2) .WithMessage(localizationService.GetResource("Admin.Configuration.Languages.Fields.UniqueSeoCode.Length")); - } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Language/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Language/List.cshtml index 362dd5f5a6..91c1fe8140 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Language/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Language/List.cshtml @@ -1,7 +1,6 @@ @model Telerik.Web.Mvc.GridModel @using Telerik.Web.Mvc.UI @{ - //page title ViewBag.Title = T("Admin.Configuration.Languages").Text; }
    @@ -24,7 +23,7 @@ columns.Bound(x => x.Name) .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })); columns.Bound(x => x.LanguageCulture) - .Width(150) + .Width(200) .Centered(); columns.Template( @
    @@ -34,10 +33,10 @@ .Centered() .Title(T("Admin.Configuration.Languages.Resources.View").Text); columns.Bound(x => x.DisplayOrder) - .Width(100) + .Width(200) .Centered(); columns.Bound(x => x.Published) - .Width(100) + .Width(200) .Template(item => @Html.SymbolForBool(item.Published)) .ClientTemplate(@Html.SymbolForBool("Published")) .Centered(); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Language/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Language/_CreateOrUpdate.cshtml index 621e963481..7f393dd8c5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Language/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Language/_CreateOrUpdate.cshtml @@ -1,9 +1,7 @@ -@model LanguageModel +@using SmartStore.Web.Framework.UI; +@model LanguageModel -@using Telerik.Web.Mvc.UI; -@using SmartStore.Web.Framework.UI; - -@Html.ValidationSummary(true) +@Html.ValidationSummary(false) @Html.HiddenFor(model => model.Id) @Html.SmartStore().TabStrip().Name("language-edit").Style(TabsStyle.Tabs).Position(TabsPosition.Top).Items(x => { @@ -16,20 +14,7 @@ @helper TabInfo() { - var cultures = System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.SpecificCultures).ToList(); - - - +
    @@ -54,7 +38,7 @@ @Html.SmartLabelFor(model => model.UniqueSeoCode) @@ -63,20 +47,15 @@ @Html.SmartLabelFor(model => model.FlagImageFileName) @@ -118,9 +97,40 @@ toggleStoreMapping(); - $("#pnlAllFlags a.flag").click(function () { - $("#FlagImageFileName").val($(this).data("filename")); + // init flags select + $('#LanguageEditTable').find('select[name=FlagImageFileName]').select2({ + allowClear: true, + minimumResultsForSearch: 16, + formatResult: flagSelectItemFormatting, + formatSelection: flagSelectItemFormatting }); + + function flagSelectItemFormatting(item) { + var flagUrl = '@Url.Content("~/Content/Images/flags/")'; + + try { + if (item.text.length > 0) { + var option = $(item.element), + html = '
    '; + + // item.element is undefined for selection formatting + if (option.length === 0) { + option = $('select[name="FlagImageFileName"]').find('option[value="' + item.id + '"]'); + } + + if (option.length !== 0) { + html += '
    '; + } + + html += '' + item.text + '
    '; + + return html; + } + } + catch (e) { } + + return item.text; + } }); @@ -132,8 +142,8 @@ $('#pnl-available-stores').hide(); } } - +
    @Html.SmartLabelFor(model => model.Name) @@ -44,8 +29,7 @@ @Html.SmartLabelFor(model => model.LanguageCulture) - @Html.DropDownListFor(x => x.LanguageCulture, cultures.Select(x => - new SelectListItem { Value = x.IetfLanguageTag, Text = string.Format("{0} [{1}]", x.DisplayName, x.IetfLanguageTag) })) + @Html.DropDownListFor(x => x.LanguageCulture, Model.AvailableCultures) @Html.ValidationMessageFor(model => model.LanguageCulture)
    - @Html.EditorFor(model => model.UniqueSeoCode) + @Html.DropDownListFor(x => x.UniqueSeoCode, Model.AvailableTwoLetterLanguageCodes) @Html.ValidationMessageFor(model => model.UniqueSeoCode)
    - @Html.EditorFor(model => model.FlagImageFileName) + @Html.ValidationMessageFor(model => model.FlagImageFileName) -
    - @T("Admin.Common.Show") -
    From c1a189e89bb48c296704eaecd1b2380f2a472385 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 11 Jan 2016 14:54:42 +0100 Subject: [PATCH 393/874] Suppresses opening of a non existing link in a new window according to forum post 47526 --- .../SmartStore.Web/Views/Checkout/Confirm.cshtml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml index fed6c42b8a..30d4231846 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml @@ -8,8 +8,8 @@ //title Html.AddTitleParts(T("PageTitle.Checkout").Text); - string termsLink = ""; - string disclaimerLink = ""; + string termsLink = ""; + string disclaimerLink = ""; string terms = string.Format(T("Checkout.TermsOfService.IAccept"), termsLink, "", disclaimerLink); } @section orderProgress{ @@ -38,7 +38,7 @@ $("#terms-of-service-modal .modal-body").html('