From 1146c4c9976ef71c55c83458d32d4d85cb3b2916 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 4 Feb 2016 13:13:34 +0100 Subject: [PATCH 001/423] Changed a string resource --- .../SmartStore.PayPal/Localization/resources.de-de.xml | 4 ++-- .../SmartStore.PayPal/Localization/resources.en-us.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index c8eb366fe2..d73638c505 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -296,10 +296,10 @@ Zusätzliche prozentuale Gebühr zum Gesamtbetrag. Es wird ein fester Wert verwendet, falls diese Option nicht aktiviert ist. - Produktbezeichnung und Gesamtbetrag an PayPal übermitteln + Produktbezeichnungen und Einzelpreise übermitteln - Aktivieren Sie diese Option, falls die Produktbezeichnung und der Gesamtbetrag an PayPal übermitteln werden sollen. + Aktivieren Sie diese Option, falls Produktbezeichnungen und Einzelpreise an PayPal übermittelt werden sollen. IPN aktivieren diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index ab7184f3bb..27a688c5f6 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -335,10 +335,10 @@ Determines whether to apply a percentage additional fee to the order total. If not enabled, a fixed value is used. - Pass product names and order totals to PayPal + Transmit product names and unit prices - Check the box if product names and order totals should be passed to PayPal. + Check the box if product names and unit prices should be transmitted to PayPal. Enable IPN From ed01c620205341c01ef8e8f441d252b652e5ba57 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 4 Feb 2016 19:11:43 +0100 Subject: [PATCH 002/423] Resolves #771 About page links - out of date --- .../Administration/Views/Home/About.cshtml | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Home/About.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Home/About.cshtml index 066d52d62c..d8f6f806a1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Home/About.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Home/About.cshtml @@ -49,16 +49,16 @@
  • @MITLicense()
  • @Apache2License()
  • @@ -175,19 +175,19 @@
  • @MITLicense()
  • @MITLicense()
  • @@ -211,7 +211,7 @@
  • - NuGet + NuGet
    @Apache2License()
  • @@ -266,12 +266,12 @@
  • - @GPLv3License() + @Apache2License()
  • From 9d4ede2e09cd80b8262baa2cb4fcbef379f8e503 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 4 Feb 2016 20:44:13 +0100 Subject: [PATCH 003/423] Product filter: Specification attributes are sorted by display order rather than alphabetically by name --- changelog.md | 1 + .../SmartStore.Services/Filter/FilterCriteria.cs | 1 + src/Libraries/SmartStore.Services/Filter/FilterService.cs | 8 ++++++-- .../SmartStore.Web/Views/Filter/Products.cshtml | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index b12f3191b9..0c47b7433f 100644 --- a/changelog.md +++ b/changelog.md @@ -79,6 +79,7 @@ * #843 Implement a product picker * #850 Use new product picker for selecting required products * Trusted Shops: badge will be displayed in mobile themes, payment info link replaced compare list link in footer +* Product filter: Specification attributes are sorted by display order rather than alphabetically by name ### Bugfixes * #523 Redirecting to payment provider performed by core instead of plugin diff --git a/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs b/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs index b567453f1b..23960ad3d9 100644 --- a/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs +++ b/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs @@ -38,6 +38,7 @@ public class FilterCriteria : IComparable // Metadata public int MatchCount { get; set; } + public int DisplayOrder { get; set; } public bool IsInactive { get; set; } public string NameLocalized { get; set; } public string ValueLocalized { get; set; } diff --git a/src/Libraries/SmartStore.Services/Filter/FilterService.cs b/src/Libraries/SmartStore.Services/Filter/FilterService.cs index 6187db1d44..1ebbc5c4ef 100644 --- a/src/Libraries/SmartStore.Services/Filter/FilterService.cs +++ b/src/Libraries/SmartStore.Services/Filter/FilterService.cs @@ -288,7 +288,6 @@ private List ProductFilterableSpecAttributes(FilterProductContex from p in query from sa in p.ProductSpecificationAttributes where sa.AllowFiltering - orderby sa.DisplayOrder select sa.SpecificationAttributeOption; if (attributeName.HasValue()) @@ -301,13 +300,18 @@ from a in attributes { Name = g.FirstOrDefault().SpecificationAttribute.Name, Value = g.FirstOrDefault().Name, + DisplayOrder = g.FirstOrDefault().SpecificationAttribute.DisplayOrder, ID = g.Key.Id, PId = g.FirstOrDefault().SpecificationAttribute.Id, MatchCount = g.Count() }; - var lst = grouped.OrderByDescending(a => a.MatchCount).ToList(); + var lst = grouped + .OrderBy(a => a.DisplayOrder) + .ThenByDescending(a => a.MatchCount) + .ToList(); + int languageId = _services.WorkContext.WorkingLanguage.Id; lst.ForEach(c => diff --git a/src/Presentation/SmartStore.Web/Views/Filter/Products.cshtml b/src/Presentation/SmartStore.Web/Views/Filter/Products.cshtml index d4ba5db947..0cd18a8d77 100644 --- a/src/Presentation/SmartStore.Web/Views/Filter/Products.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Filter/Products.cshtml @@ -69,7 +69,7 @@ else @FilterGroup(Model.Context.Criteria.Where(c => c.Entity.IsCaseInsensitiveEqual("Manufacturer")), T("Products.Manufacturer"), true) - foreach (var grp in Model.Context.Criteria.Where(c => c.Entity.IsCaseInsensitiveEqual(FilterService.ShortcutSpecAttribute)).GroupBy(c => c.Name).OrderBy(g => g.Key)) + foreach (var grp in Model.Context.Criteria.Where(c => c.Entity.IsCaseInsensitiveEqual(FilterService.ShortcutSpecAttribute)).GroupBy(c => c.Name)) { var first = grp.FirstOrDefault(); bool isActive = (Model.Context.Criteria.FirstOrDefault(c => c.Entity.IsCaseInsensitiveEqual(FilterService.ShortcutSpecAttribute) && c.Name.IsCaseInsensitiveEqual(grp.Key) && !c.IsInactive) != null); From b41d3abf55900142fd2c827b29e7da7b6e380fd9 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 4 Feb 2016 20:53:22 +0100 Subject: [PATCH 004/423] FastProperty.IsSequenceType should ignore string types --- src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs b/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs index 9711e8e55d..068b84f008 100644 --- a/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs +++ b/src/Libraries/SmartStore.Core/ComponentModel/FastProperty.cs @@ -96,7 +96,7 @@ public bool IsSequenceType { if (!_isSequenceType.HasValue) { - _isSequenceType = Property.PropertyType.IsSubClass(typeof(IEnumerable<>)); + _isSequenceType = Property.PropertyType != typeof(string) && Property.PropertyType.IsSubClass(typeof(IEnumerable<>)); } return _isSequenceType.Value; } From 6fe619425e967456dd8c94e29c89dac0bfd55010 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 4 Feb 2016 21:18:47 +0100 Subject: [PATCH 005/423] Optimized SettingService sequence type handling --- .../Configuration/SettingService.cs | 60 ++++++++++++------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs index 5a03d72ea6..aa688a4e96 100644 --- a/src/Libraries/SmartStore.Services/Configuration/SettingService.cs +++ b/src/Libraries/SmartStore.Services/Configuration/SettingService.cs @@ -10,6 +10,7 @@ using System.Linq.Expressions; using System.Reflection; using SmartStore.ComponentModel; +using System.Collections; namespace SmartStore.Services.Configuration { @@ -309,33 +310,46 @@ public virtual bool SettingExists(T settings, continue; var key = typeof(T).Name + "." + prop.Name; - //load by store + // load by store string setting = GetSettingByKey(key, storeId: storeId, loadSharedValueIfNotFound: true); - if (setting == null && !fastProp.IsSequenceType) + if (setting == null) { - #region Obsolete ('EnumerableConverter' can handle this case now) - //if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(List<>)) - //{ - // // convenience: don't return null for simple list types - // var listArg = prop.PropertyType.GetGenericArguments()[0]; - // object list = null; - - // if (listArg == typeof(int)) - // list = new List(); - // else if (listArg == typeof(decimal)) - // list = new List(); - // else if (listArg == typeof(string)) - // list = new List(); - - // if (list != null) - // { - // fastProp.SetValue(settings, list); - // } - //} - #endregion + if (fastProp.IsSequenceType) + { + if ((fastProp.GetValue(settings) as IEnumerable) != null) + { + // Instance of IEnumerable<> was already created, most likely in the constructor of the settings concrete class. + // In this case we shouldn't let the EnumerableConverter create a new instance but keep this one. + continue; + } + } + else + { + #region Obsolete ('EnumerableConverter' can handle this case now) + //if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(List<>)) + //{ + // // convenience: don't return null for simple list types + // var listArg = prop.PropertyType.GetGenericArguments()[0]; + // object list = null; + + // if (listArg == typeof(int)) + // list = new List(); + // else if (listArg == typeof(decimal)) + // list = new List(); + // else if (listArg == typeof(string)) + // list = new List(); + + // if (list != null) + // { + // fastProp.SetValue(settings, list); + // } + //} + #endregion + + continue; + } - continue; } var converter = TypeConverterFactory.GetConverter(prop.PropertyType); From c91a007b472ef04d38aec70930e7386666246d96 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 5 Feb 2016 16:40:02 +0100 Subject: [PATCH 006/423] Localized some hard coded strings Some code cleanup for OrderProcessingService --- .../201601262000441_ImportFramework1.cs | 124 +++ .../Orders/IOrderService.cs | 28 +- .../Orders/OrderProcessingService.cs | 793 +++++++----------- .../Orders/OrderService.cs | 48 +- .../Payments/PaymentService.cs | 48 +- 5 files changed, 493 insertions(+), 548 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs index 44b374d1a1..2d82cfce7a 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs @@ -138,6 +138,130 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Account.CustomerOrders.NotYourOrder", "This is not your order.", "Dieser Auftrag konnte Ihnen nicht zugeordnet werden."); + + builder.AddOrUpdate("Payment.CouldNotLoadMethod", + "The payment method could not be loaded.", + "Die Zahlungsart konnte nicht geladen werden."); + + builder.AddOrUpdate("Payment.MethodNotAvailable", + "The payment method is not available.", + "Die Zahlungsart steht nicht zur Verfgung."); + + builder.AddOrUpdate("Payment.RecurringPaymentNotSupported", + "Recurring payments are not supported by selected payment method.", + "Wiederkehrende Zahlungen sind fr die gewhlte Zahlungsart nicht mglich."); + + builder.AddOrUpdate("Payment.RecurringPaymentNotActive", + "Recurring payment is not active.", + "Wiederkehrende Zahlung ist inaktiv."); + + builder.AddOrUpdate("Payment.RecurringPaymentTypeUnknown", + "The recurring payment type is not supported.", + "Der Typ von wiederkehrender Zahlung wird nicht untersttzt."); + + builder.AddOrUpdate("Payment.CannotCalculateNextPaymentDate", + "The next payment date could not be calculated.", + "Das Datum der nchsten Zahlung kann nicht ermittelt werden."); + + builder.AddOrUpdate("Order.InitialOrderDoesNotExistForRecurringPayment", + "No initial order exists for the recurring payment.", + "Fr die wiederkehrende Zahlung existiert kein Ausgangsauftrag."); + + builder.AddOrUpdate("Order.CannotCalculateShippingTotal", + "The shipping total could not be calculated.", + "Die Versandkosten konnten nicht berechnet werden."); + + builder.AddOrUpdate("Order.CannotCalculateOrderTotal", + "The order total could not be calculated.", + "Die Auftragssumme konnte nicht berechnet werden."); + + builder.AddOrUpdate("Order.BillingAddressMissing", + "The billing address is missing.", + "Die Rechnungsanschrift fehlt."); + + builder.AddOrUpdate("Order.ShippingAddressMissing", + "The shipping address is missing.", + "Die Lieferanschrift fehlt."); + + builder.AddOrUpdate("Order.CountryNotAllowedForBilling", + "The country '{0}' is not allowed for billing.", + "Eine Rechnungslegung ist fr das Land '{0}' unzulssig."); + + builder.AddOrUpdate("Order.CountryNotAllowedForShipping", + "The country '{0}' is not allowed for shipping.", + "Ein Versand ist fr das Land '{0}' unzulssig."); + + builder.AddOrUpdate("Order.NoRecurringProducts", + "There are no recurring products.", + "Keine wiederkehrenden Produkte."); + + builder.AddOrUpdate("Order.DoesNotExist", + "The order does not exist.", + "Der Auftrag existiert nicht."); + + builder.AddOrUpdate("Order.CannotCancel", + "Cannot cancel order.", + "Der Auftrag kann nicht storniert werden."); + + builder.AddOrUpdate("Order.CannotMarkCompleted", + "Cannot mark order as completed.", + "Der Auftrag kann nicht als abgeschlossen markiert werden."); + + builder.AddOrUpdate("Order.CannotCapture", + "Cannot capture order.", + "Der Auftrag kann nicht gebucht werden."); + + builder.AddOrUpdate("Order.CannotMarkPaid", + "Cannot mark order as paid.", + "Der Auftrag kann nicht als bezahlt markiert werden."); + + builder.AddOrUpdate("Order.CannotRefund", + "Cannot do refund for order.", + "Eine Rckerstattung ist fr diesen Auftrag nicht mglich."); + + builder.AddOrUpdate("Order.CannotPartialRefund", + "Cannot do partial refund for order.", + "Eine Teilrckerstattung ist fr diesen Auftrag nicht mglich."); + + builder.AddOrUpdate("Order.CannotVoid", + "Cannot do void for order.", + "Eine Stornierung dieses Auftrages ist nicht mglich."); + + builder.AddOrUpdate("Shipment.AlreadyShipped", + "This shipment is already shipped.", + "Diese Sendung wird bereits ausgeliefert."); + + builder.AddOrUpdate("Shipment.AlreadyDelivered", + "This shipment is already delivered.", + "Diese Sendung wird bereits zugestellt."); + + builder.AddOrUpdate("Customer.DoesNotExist", + "The customer does not exist.", + "Der Kunde existiert nicht."); + + builder.AddOrUpdate("Checkout.AnonymousNotAllowed", + "An anonymous checkout is not allowed.", + "Ein anonymer Checkout ist nicht zulssig."); + + builder.AddOrUpdate("Common.Error.InvalidEmail", + "The email address is not valid.", + "Die E-Mail-Adresse ist ungltig."); + + builder.AddOrUpdate("Admin.OrderNotice.RecurringPaymentCancellationError", + "Unable to cancel recurring payment for order {0}.", + "Es ist ein Fehler bei der Stornierung einer wiederkehrenden Zahlung fr Auftrag {0} aufgetreten."); + + builder.AddOrUpdate("Admin.OrderNotice.OrderRefundError", + "Unable to refund order {0}.", + "Es ist ein Fehler bei einer Rckerstattung zu Auftrag {0} aufgetreten."); + + builder.AddOrUpdate("Admin.OrderNotice.OrderPartiallyRefundError", + "Unable to partially refund order {0}.", + "Es ist ein Fehler bei einer Teilerstattung zu Auftrag {0} aufgetreten."); + + builder.AddOrUpdate("Admin.OrderNotice.OrderVoidError", + "Unable to void payment transaction of order {0}.", + "Es ist ein Fehler bei der Stornierung einer Zahlungstransaktion zu Auftrag {0} aufgetreten."); } } } diff --git a/src/Libraries/SmartStore.Services/Orders/IOrderService.cs b/src/Libraries/SmartStore.Services/Orders/IOrderService.cs index 039beeef57..9fca02b17f 100644 --- a/src/Libraries/SmartStore.Services/Orders/IOrderService.cs +++ b/src/Libraries/SmartStore.Services/Orders/IOrderService.cs @@ -164,17 +164,25 @@ IPagedList SearchOrders(int storeId, int customerId, DateTime? startTime, /// Payment method system name /// Order Order GetOrderByAuthorizationTransactionIdAndPaymentMethod(string authorizationTransactionId, string paymentMethodSystemName); - - #endregion - #region Orders items - - /// - /// Gets an order item - /// - /// Order item identifier - /// Order item - OrderItem GetOrderItemById(int orderItemId); + /// + /// Shortcut to add an order + /// + /// Order + /// Order note + /// Whether to display the note to the customer + void AddOrderNote(Order order, string note, bool displayToCustomer = false); + + #endregion + + #region Orders items + + /// + /// Gets an order item + /// + /// Order item identifier + /// Order item + OrderItem GetOrderItemById(int orderItemId); /// /// Gets an order item diff --git a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs index 7c0e0f4bf6..538f8c580f 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Text; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Common; @@ -16,6 +15,7 @@ using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Tax; using SmartStore.Core.Events; +using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Affiliates; @@ -33,10 +33,10 @@ namespace SmartStore.Services.Orders { - /// - /// Order processing service - /// - public partial class OrderProcessingService : IOrderProcessingService + /// + /// Order processing service + /// + public partial class OrderProcessingService : IOrderProcessingService { #region Fields @@ -192,21 +192,31 @@ public OrderProcessingService(IOrderService orderService, this._localizationSettings = localizationSettings; this._currencySettings = currencySettings; this._shoppingCartSettings = shoppingCartSettings; - } - #endregion + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } - #region Utilities + #endregion + + #region Utilities private decimal Round(decimal value) { return (_shoppingCartSettings.RoundPricesDuringCalculation ? Math.Round(value, 2) : value); } - private string TNote(string resKey) - { - return _localizationService.GetResource("Admin.OrderNotice." + resKey); - } + private void ProcessErrors(Order order, IList errors, string messageKey) + { + if (errors.Any()) + { + var msg = string.Concat(T(messageKey, order.GetOrderNumber()), " ", string.Join(" ", errors)); + + _orderService.AddOrderNote(order, msg); + _logger.InsertLog(LogLevel.Error, msg, msg); + } + } /// /// Award reward points @@ -239,8 +249,9 @@ protected void AwardRewardPoints(Order order, decimal? amount = null) return; //add reward points - order.Customer.AddRewardPointsHistoryEntry(points, string.Format(_localizationService.GetResource("RewardPoints.Message.EarnedForOrder"), order.GetOrderNumber())); + order.Customer.AddRewardPointsHistoryEntry(points, T("RewardPoints.Message.EarnedForOrder", order.GetOrderNumber())); order.RewardPointsWereAdded = true; + _orderService.UpdateOrder(order); } @@ -277,7 +288,7 @@ protected void ReduceRewardPoints(Order order, decimal? amount = null) return; //reduce reward points - order.Customer.AddRewardPointsHistoryEntry(-points, string.Format(_localizationService.GetResource("RewardPoints.Message.ReducedForOrder"), order.GetOrderNumber())); + order.Customer.AddRewardPointsHistoryEntry(-points, string.Format(T("RewardPoints.Message.ReducedForOrder"), order.GetOrderNumber())); if (!order.RewardPointsRemaining.HasValue) order.RewardPointsRemaining = (int)Math.Round(order.OrderTotal / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); @@ -295,34 +306,42 @@ protected void ReduceRewardPoints(Order order, decimal? amount = null) protected void SetActivatedValueForPurchasedGiftCards(Order order, bool activate) { var giftCards = _giftCardService.GetAllGiftCards(order.Id, null, null, !activate); + foreach (var gc in giftCards) { if (activate) { //activate - bool isRecipientNotified = gc.IsRecipientNotified; + var isRecipientNotified = gc.IsRecipientNotified; + if (gc.GiftCardType == GiftCardType.Virtual) { //send email for virtual gift card - if (!String.IsNullOrEmpty(gc.RecipientEmail) && - !String.IsNullOrEmpty(gc.SenderEmail)) + if (!String.IsNullOrEmpty(gc.RecipientEmail) && !String.IsNullOrEmpty(gc.SenderEmail)) { var customerLang = _languageService.GetLanguageById(order.CustomerLanguageId); if (customerLang == null) customerLang = _languageService.GetAllLanguages().FirstOrDefault(); - int queuedEmailId = _workflowMessageService.SendGiftCardNotification(gc, customerLang.Id); - if (queuedEmailId > 0) - isRecipientNotified = true; + + var queuedEmailId = _workflowMessageService.SendGiftCardNotification(gc, customerLang.Id); + + if (queuedEmailId > 0) + { + isRecipientNotified = true; + } } } + gc.IsGiftCardActivated = true; gc.IsRecipientNotified = isRecipientNotified; + _giftCardService.UpdateGiftCard(gc); } else { //deactivate gc.IsGiftCardActivated = false; + _giftCardService.UpdateGiftCard(gc); } } @@ -347,15 +366,7 @@ protected void SetOrderStatus(Order order, OrderStatus os, bool notifyCustomer) order.OrderStatusId = (int)os; _orderService.UpdateOrder(order); - //order notes, notifications - order.OrderNotes.Add(new OrderNote - { - Note = string.Format(TNote("OrderStatusChanged"), os.GetLocalizedEnum(_localizationService)), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderStatusChanged", os.GetLocalizedEnum(_localizationService))); if (prevOrderStatus != OrderStatus.Complete && os == OrderStatus.Complete && notifyCustomer) { @@ -363,13 +374,7 @@ protected void SetOrderStatus(Order order, OrderStatus os, bool notifyCustomer) int orderCompletedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderCompletedCustomerNotification(order, order.CustomerLanguageId); if (orderCompletedCustomerNotificationQueuedEmailId > 0) { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format(TNote("CustomerCompletedEmailQueued"), orderCompletedCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerCompletedEmailQueued", orderCompletedCustomerNotificationQueuedEmailId)); } } @@ -379,13 +384,7 @@ protected void SetOrderStatus(Order order, OrderStatus os, bool notifyCustomer) int orderCancelledCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderCancelledCustomerNotification(order, order.CustomerLanguageId); if (orderCancelledCustomerNotificationQueuedEmailId > 0) { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format(TNote("CustomerCancelledEmailQueued"), orderCancelledCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerCancelledEmailQueued", orderCancelledCustomerNotificationQueuedEmailId)); } } @@ -400,15 +399,13 @@ protected void SetOrderStatus(Order order, OrderStatus os, bool notifyCustomer) } //gift cards activation - if (_orderSettings.GiftCards_Activated_OrderStatusId > 0 && - _orderSettings.GiftCards_Activated_OrderStatusId == (int)order.OrderStatus) + if (_orderSettings.GiftCards_Activated_OrderStatusId > 0 && _orderSettings.GiftCards_Activated_OrderStatusId == (int)order.OrderStatus) { SetActivatedValueForPurchasedGiftCards(order, true); } //gift cards deactivation - if (_orderSettings.GiftCards_Deactivated_OrderStatusId > 0 && - _orderSettings.GiftCards_Deactivated_OrderStatusId == (int)order.OrderStatus) + if (_orderSettings.GiftCards_Deactivated_OrderStatusId > 0 && _orderSettings.GiftCards_Deactivated_OrderStatusId == (int)order.OrderStatus) { SetActivatedValueForPurchasedGiftCards(order, false); } @@ -485,11 +482,11 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR #region Order details (customer, totals) //Recurring orders. Load initial order - Order initialOrder = _orderService.GetOrderById(processPaymentRequest.InitialOrderId); + var initialOrder = _orderService.GetOrderById(processPaymentRequest.InitialOrderId); if (processPaymentRequest.IsRecurringPayment) { if (initialOrder == null) - throw new ArgumentException("Initial order is not set for recurring payment"); + throw new ArgumentException(T("Order.InitialOrderDoesNotExistForRecurringPayment")); processPaymentRequest.PaymentMethodSystemName = initialOrder.PaymentMethodSystemName; } @@ -497,13 +494,15 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR //customer var customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); if (customer == null) - throw new ArgumentException("Customer is not set"); + throw new ArgumentException(T("Customer.DoesNotExist")); //affilites - int affiliateId = 0; + var affiliateId = 0; var affiliate = _affiliateService.GetAffiliateById(customer.AffiliateId); if (affiliate != null && affiliate.Active && !affiliate.Deleted) + { affiliateId = affiliate.Id; + } //customer currency string customerCurrencyCode = ""; @@ -528,19 +527,21 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR Language customerLanguage = null; if (!processPaymentRequest.IsRecurringPayment) { - customerLanguage = _languageService.GetLanguageById(customer.GetAttribute( - SystemCustomerAttributeNames.LanguageId, processPaymentRequest.StoreId)); + customerLanguage = _languageService.GetLanguageById(customer.GetAttribute(SystemCustomerAttributeNames.LanguageId, processPaymentRequest.StoreId)); } else { customerLanguage = _languageService.GetLanguageById(initialOrder.CustomerLanguageId); } - if (customerLanguage == null || !customerLanguage.Published) - customerLanguage = _workContext.WorkingLanguage; + + if (customerLanguage == null || !customerLanguage.Published) + { + customerLanguage = _workContext.WorkingLanguage; + } //check whether customer is guest if (customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) - throw new SmartException("Anonymous checkout is not allowed"); + throw new SmartException(T("Checkout.AnonymousNotAllowed")); var storeId = _storeContext.CurrentStore.Id; @@ -552,21 +553,12 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, processPaymentRequest.StoreId); if (cart.Count == 0) - throw new SmartException("Cart is empty"); + throw new SmartException(T("ShoppingCart.CartIsEmpty")); - //validate the entire shopping cart - var warnings = _shoppingCartService.GetShoppingCartWarnings(cart, - customer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes), true); + //validate the entire shopping cart + var warnings = _shoppingCartService.GetShoppingCartWarnings(cart, customer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes), true); if (warnings.Count > 0) - { - var warningsSb = new StringBuilder(); - foreach (string warning in warnings) - { - warningsSb.Append(warning); - warningsSb.Append(";"); - } - throw new SmartException(warningsSb.ToString()); - } + throw new SmartException(string.Join(" ", warnings)); //validate individual cart items foreach (var sci in cart) @@ -574,33 +566,27 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR var sciWarnings = _shoppingCartService.GetShoppingCartItemWarnings(customer, sci.Item.ShoppingCartType, sci.Item.Product, processPaymentRequest.StoreId, sci.Item.AttributesXml, sci.Item.CustomerEnteredPrice, sci.Item.Quantity, false, childItems: sci.ChildItems); + if (sciWarnings.Count > 0) - { - var warningsSb = new StringBuilder(); - foreach (string warning in sciWarnings) - { - warningsSb.Append(warning); - warningsSb.Append(";"); - } - throw new SmartException(warningsSb.ToString()); - } + throw new SmartException(string.Join(" ", sciWarnings)); } } //min totals validation if (!processPaymentRequest.IsRecurringPayment) { - bool minOrderSubtotalAmountOk = ValidateMinOrderSubtotalAmount(cart); + var minOrderSubtotalAmountOk = ValidateMinOrderSubtotalAmount(cart); if (!minOrderSubtotalAmountOk) { - decimal minOrderSubtotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderSubtotalAmount, _workContext.WorkingCurrency); - throw new SmartException(string.Format(_localizationService.GetResource("Checkout.MinOrderSubtotalAmount"), _priceFormatter.FormatPrice(minOrderSubtotalAmount, true, false))); + var minOrderSubtotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderSubtotalAmount, _workContext.WorkingCurrency); + throw new SmartException(T("Checkout.MinOrderSubtotalAmount", _priceFormatter.FormatPrice(minOrderSubtotalAmount, true, false))); } - bool minOrderTotalAmountOk = ValidateMinOrderTotalAmount(cart); + + var minOrderTotalAmountOk = ValidateMinOrderTotalAmount(cart); if (!minOrderTotalAmountOk) { - decimal minOrderTotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderTotalAmount, _workContext.WorkingCurrency); - throw new SmartException(string.Format(_localizationService.GetResource("Checkout.MinOrderTotalAmount"), _priceFormatter.FormatPrice(minOrderTotalAmount, true, false))); + var minOrderTotalAmount = _currencyService.ConvertFromPrimaryStoreCurrency(_orderSettings.MinOrderTotalAmount, _workContext.WorkingCurrency); + throw new SmartException(T("Checkout.MinOrderTotalAmount", _priceFormatter.FormatPrice(minOrderTotalAmount, true, false))); } } @@ -710,7 +696,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR orderShippingTotalInclTax = _orderTotalCalculationService.GetShoppingCartShippingTotal(cart, true, out orderShippingTaxRate, out shippingTotalDiscount); orderShippingTotalExclTax = _orderTotalCalculationService.GetShoppingCartShippingTotal(cart, false); if (!orderShippingTotalInclTax.HasValue || !orderShippingTotalExclTax.HasValue) - throw new SmartException("Shipping total couldn't be calculated"); + throw new SmartException(T("Order.CannotCalculateShippingTotal")); if (shippingTotalDiscount != null && !appliedDiscounts.Any(x => x.Id == shippingTotalDiscount.Id)) appliedDiscounts.Add(shippingTotalDiscount); @@ -749,7 +735,9 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR //VAT number var customerVatStatus = (VatNumberStatus)customer.GetAttribute(SystemCustomerAttributeNames.VatNumberStatusId); if (_taxSettings.EuVatEnabled && customerVatStatus == VatNumberStatus.Valid) + { vatNumber = customer.GetAttribute(SystemCustomerAttributeNames.VatNumber); + } //tax rates foreach (var kvp in taxRatesDictionary) @@ -777,14 +765,16 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR { Discount orderAppliedDiscount = null; orderTotal = _orderTotalCalculationService.GetShoppingCartTotal(cart, - out orderDiscountAmount, out orderAppliedDiscount, out appliedGiftCards, - out redeemedRewardPoints, out redeemedRewardPointsAmount); + out orderDiscountAmount, out orderAppliedDiscount, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount); + if (!orderTotal.HasValue) - throw new SmartException("Order total couldn't be calculated"); + throw new SmartException(T("Order.CannotCalculateOrderTotal")); - //discount history - if (orderAppliedDiscount != null && !appliedDiscounts.Any(x => x.Id == orderAppliedDiscount.Id)) - appliedDiscounts.Add(orderAppliedDiscount); + //discount history + if (orderAppliedDiscount != null && !appliedDiscounts.Any(x => x.Id == orderAppliedDiscount.Id)) + { + appliedDiscounts.Add(orderAppliedDiscount); + } } else { @@ -803,7 +793,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR if (!preProcessPaymentResult.Success) { result.Errors.AddRange(preProcessPaymentResult.Errors); - result.Errors.Add(_localizationService.GetResource("Common.Error.PreProcessPayment")); + result.Errors.Add(T("Common.Error.PreProcessPayment")); return result; } @@ -811,23 +801,23 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR if (!processPaymentRequest.IsRecurringPayment) { if (customer.BillingAddress == null) - throw new SmartException("Billing address is not provided"); + throw new SmartException(T("Order.BillingAddressMissing")); if (!customer.BillingAddress.Email.IsEmail()) - throw new SmartException("Email is not valid"); + throw new SmartException(T("Common.Error.InvalidEmail")); billingAddress = (Address)customer.BillingAddress.Clone(); } else { if (initialOrder.BillingAddress == null) - throw new SmartException("Billing address is not available"); + throw new SmartException(T("Order.BillingAddressMissing")); billingAddress = (Address)initialOrder.BillingAddress.Clone(); } if (billingAddress.Country != null && !billingAddress.Country.AllowsBilling) - throw new SmartException(string.Format("Country '{0}' is not allowed for billing", billingAddress.Country.Name)); + throw new SmartException(T("Order.CountryNotAllowedForBilling", billingAddress.Country.Name)); Address shippingAddress = null; if (shoppingCartRequiresShipping) @@ -835,23 +825,23 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR if (!processPaymentRequest.IsRecurringPayment) { if (customer.ShippingAddress == null) - throw new SmartException("Shipping address is not provided"); + throw new SmartException(T("Order.ShippingAddressMissing")); if (!customer.ShippingAddress.Email.IsEmail()) - throw new SmartException("Email is not valid"); + throw new SmartException(T("Common.Error.InvalidEmail")); shippingAddress = (Address)customer.ShippingAddress.Clone(); } else { if (initialOrder.ShippingAddress == null) - throw new SmartException("Shipping address is not available"); + throw new SmartException(T("Order.ShippingAddressMissing")); shippingAddress = (Address)initialOrder.ShippingAddress.Clone(); } if (shippingAddress.Country != null && !shippingAddress.Country.AllowsShipping) - throw new SmartException(string.Format("Country '{0}' is not allowed for shipping", shippingAddress.Country.Name)); + throw new SmartException(T("Order.CountryNotAllowedForShipping", shippingAddress.Country.Name)); } #endregion @@ -859,9 +849,11 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR #region Payment workflow //skip payment workflow if order total equals zero - bool skipPaymentWorkflow = false; - if (orderTotal.Value == decimal.Zero) - skipPaymentWorkflow = true; + var skipPaymentWorkflow = false; + if (orderTotal.Value == decimal.Zero) + { + skipPaymentWorkflow = true; + } //payment workflow Provider paymentMethod = null; @@ -869,11 +861,11 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR { paymentMethod = _paymentService.LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + throw new SmartException(T("Payment.CouldNotLoadMethod")); //ensure that payment method is active if (!paymentMethod.IsPaymentMethodActive(_paymentSettings)) - throw new SmartException("Payment method is not active"); + throw new SmartException(T("Payment.MethodNotAvailable")); } else { @@ -881,18 +873,20 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR } //recurring or standard shopping cart? - bool isRecurringShoppingCart = false; + var isRecurringShoppingCart = false; if (!processPaymentRequest.IsRecurringPayment) { isRecurringShoppingCart = cart.IsRecurring(); if (isRecurringShoppingCart) { - int recurringCycleLength = 0; + var recurringCycleLength = 0; + var recurringTotalCycles = 0; RecurringProductCyclePeriod recurringCyclePeriod; - int recurringTotalCycles = 0; string recurringCyclesError = cart.GetRecurringCycleInfo(_localizationService, out recurringCycleLength, out recurringCyclePeriod, out recurringTotalCycles); + if (!string.IsNullOrEmpty(recurringCyclesError)) throw new SmartException(recurringCyclesError); + processPaymentRequest.RecurringCycleLength = recurringCycleLength; processPaymentRequest.RecurringCyclePeriod = recurringCyclePeriod; processPaymentRequest.RecurringTotalCycles = recurringTotalCycles; @@ -916,13 +910,13 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR switch (recurringPaymentType) { case RecurringPaymentType.NotSupported: - throw new SmartException("Recurring payments are not supported by selected payment method"); + throw new SmartException(T("Payment.RecurringPaymentNotSupported")); case RecurringPaymentType.Manual: case RecurringPaymentType.Automatic: processPaymentResult = _paymentService.ProcessRecurringPayment(processPaymentRequest); break; default: - throw new SmartException("Not supported recurring payment type"); + throw new SmartException(T("Payment.RecurringPaymentTypeUnknown")); } } else @@ -941,6 +935,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR processPaymentRequest.CreditCardNumber = initialOrder.AllowStoringCreditCardNumber ? _encryptionService.DecryptText(initialOrder.CardNumber) : ""; //MaskedCreditCardNumber processPaymentRequest.CreditCardCvv2 = initialOrder.AllowStoringCreditCardNumber ? _encryptionService.DecryptText(initialOrder.CardCvv2) : ""; + try { processPaymentRequest.CreditCardExpireMonth = initialOrder.AllowStoringCreditCardNumber ? Convert.ToInt32(_encryptionService.DecryptText(initialOrder.CardExpirationMonth)) : 0; @@ -952,7 +947,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR switch (recurringPaymentType) { case RecurringPaymentType.NotSupported: - throw new SmartException("Recurring payments are not supported by selected payment method"); + throw new SmartException(T("Payment.RecurringPaymentNotSupported")); case RecurringPaymentType.Manual: processPaymentResult = _paymentService.ProcessRecurringPayment(processPaymentRequest); break; @@ -961,26 +956,25 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR processPaymentResult = new ProcessPaymentResult(); break; default: - throw new SmartException("Not supported recurring payment type"); - } + throw new SmartException(T("Payment.RecurringPaymentTypeUnknown")); + } } else { - throw new SmartException("No recurring products"); + throw new SmartException(T("Order.NoRecurringProducts")); } } } else { - //payment is not required - if (processPaymentResult == null) - processPaymentResult = new ProcessPaymentResult(); + //payment is not required + if (processPaymentResult == null) + { + processPaymentResult = new ProcessPaymentResult(); + } processPaymentResult.NewPaymentStatus = PaymentStatus.Paid; } - if (processPaymentResult == null) - throw new SmartException("processPaymentResult is not available"); - #endregion if (processPaymentResult.Success) @@ -992,10 +986,12 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR #region Save order details var shippingStatus = ShippingStatus.NotYetShipped; - if (!shoppingCartRequiresShipping) - shippingStatus = ShippingStatus.ShippingNotRequired; + if (!shoppingCartRequiresShipping) + { + shippingStatus = ShippingStatus.ShippingNotRequired; + } - var order = new Order() + var order = new Order { StoreId = processPaymentRequest.StoreId, OrderGuid = processPaymentRequest.OrderGuid, @@ -1086,12 +1082,14 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR decimal discountAmount = _priceCalculationService.GetDiscountAmount(sc, out scDiscount); decimal discountAmountInclTax = _taxService.GetProductPrice(sc.Item.Product, discountAmount, true, customer, out taxRate); decimal discountAmountExclTax = _taxService.GetProductPrice(sc.Item.Product, discountAmount, false, customer, out taxRate); - + if (scDiscount != null && !appliedDiscounts.Any(x => x.Id == scDiscount.Id)) - appliedDiscounts.Add(scDiscount); + { + appliedDiscounts.Add(scDiscount); + } //attributes - string attributeDescription = _productAttributeFormatter.FormatAttributes(sc.Item.Product, sc.Item.AttributesXml, customer); + var attributeDescription = _productAttributeFormatter.FormatAttributes(sc.Item.Product, sc.Item.AttributesXml, customer); var itemWeight = _shippingService.GetShoppingCartItemWeight(sc); @@ -1124,9 +1122,9 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR foreach (var childItem in sc.ChildItems) { - decimal bundleItemSubTotal = _taxService.GetProductPrice(childItem.Item.Product, _priceCalculationService.GetSubTotal(childItem, true), out taxRate); + var bundleItemSubTotal = _taxService.GetProductPrice(childItem.Item.Product, _priceCalculationService.GetSubTotal(childItem, true), out taxRate); - string attributesInfo = _productAttributeFormatter.FormatAttributes(childItem.Item.Product, childItem.Item.AttributesXml, order.Customer, + var attributesInfo = _productAttributeFormatter.FormatAttributes(childItem.Item.Product, childItem.Item.AttributesXml, order.Customer, renderPrices: false, allowHyperlinks: false); childItem.BundleItemData.ToOrderData(listBundleData, bundleItemSubTotal, childItem.Item.AttributesXml, attributesInfo); @@ -1141,11 +1139,10 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR //gift cards if (sc.Item.Product.IsGiftCard) { - string giftCardRecipientName, giftCardRecipientEmail, - giftCardSenderName, giftCardSenderEmail, giftCardMessage; + string giftCardRecipientName, giftCardRecipientEmail, giftCardSenderName, giftCardSenderEmail, giftCardMessage; + _productAttributeParser.GetGiftCardAttribute(sc.Item.AttributesXml, - out giftCardRecipientName, out giftCardRecipientEmail, - out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + out giftCardRecipientName, out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); for (int i = 0; i < sc.Item.Quantity; i++) { @@ -1168,7 +1165,6 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR } } - //inventory _productService.AdjustInventory(sc, true); } @@ -1182,7 +1178,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR foreach (var orderItem in initialOrderItems) { //save item - var newOrderItem = new OrderItem() + var newOrderItem = new OrderItem { OrderItemGuid = Guid.NewGuid(), Order = order, @@ -1213,12 +1209,11 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR string giftCardRecipientName, giftCardRecipientEmail, giftCardSenderName, giftCardSenderEmail, giftCardMessage; _productAttributeParser.GetGiftCardAttribute(orderItem.AttributesXml, - out giftCardRecipientName, out giftCardRecipientEmail, - out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + out giftCardRecipientName, out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); for (int i = 0; i < orderItem.Quantity; i++) { - var gc = new GiftCard() + var gc = new GiftCard { GiftCardType = orderItem.Product.GiftCardType, PurchasedWithOrderItem = newOrderItem, @@ -1237,7 +1232,6 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR } } - //inventory _productService.AdjustInventory(orderItem, true, orderItem.Quantity); } } @@ -1247,7 +1241,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR { foreach (var discount in appliedDiscounts) { - var duh = new DiscountUsageHistory() + var duh = new DiscountUsageHistory { Discount = discount, Order = order, @@ -1262,8 +1256,8 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR { foreach (var agc in appliedGiftCards) { - decimal amountUsed = agc.AmountCanBeUsed; - var gcuh = new GiftCardUsageHistory() + var amountUsed = agc.AmountCanBeUsed; + var gcuh = new GiftCardUsageHistory { GiftCard = agc.GiftCard, UsedWithOrder = order, @@ -1279,9 +1273,10 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR if (redeemedRewardPointsAmount > decimal.Zero) { customer.AddRewardPointsHistoryEntry(-redeemedRewardPoints, - string.Format(_localizationService.GetResource("RewardPoints.Message.RedeemedForOrder", order.CustomerLanguageId), order.GetOrderNumber()), + _localizationService.GetResource("RewardPoints.Message.RedeemedForOrder", order.CustomerLanguageId).FormatInvariant(order.GetOrderNumber()), order, - redeemedRewardPointsAmount); + redeemedRewardPointsAmount); + _customerService.UpdateCustomer(customer); } @@ -1289,7 +1284,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR if (!processPaymentRequest.IsRecurringPayment && isRecurringShoppingCart) { //create recurring payment (the first payment) - var rp = new RecurringPayment() + var rp = new RecurringPayment { CycleLength = processPaymentRequest.RecurringCycleLength, CyclePeriod = processPaymentRequest.RecurringCyclePeriod, @@ -1301,7 +1296,6 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR }; _orderService.InsertRecurringPayment(rp); - var recurringPaymentType = _paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName); switch (recurringPaymentType) { @@ -1333,50 +1327,34 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR } } - #endregion + #endregion - #region Notifications, notes and attributes - - //notes, messages - order.OrderNotes.Add(new OrderNote - { - Note = TNote("OrderPlaced"), - DisplayToCustomer = false, - CreatedOnUtc = utcNow - }); - _orderService.UpdateOrder(order); + #region Notifications, notes and attributes + + //notes, messages + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderPlaced")); //send email notifications int orderPlacedStoreOwnerNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedStoreOwnerNotification(order, _localizationSettings.DefaultAdminLanguageId); if (orderPlacedStoreOwnerNotificationQueuedEmailId > 0) { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format(TNote("MerchantEmailQueued"), orderPlacedStoreOwnerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = utcNow - }); - _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.MerchantEmailQueued", orderPlacedStoreOwnerNotificationQueuedEmailId)); } int orderPlacedCustomerNotificationQueuedEmailId = _workflowMessageService.SendOrderPlacedCustomerNotification(order, order.CustomerLanguageId); if (orderPlacedCustomerNotificationQueuedEmailId > 0) { - order.OrderNotes.Add(new OrderNote - { - Note = string.Format(TNote("CustomerEmailQueued"), orderPlacedCustomerNotificationQueuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = utcNow - }); - _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerEmailQueued", orderPlacedCustomerNotificationQueuedEmailId)); } //check order status CheckOrderStatus(order); - //reset checkout data - if (!processPaymentRequest.IsRecurringPayment) + //reset checkout data + if (!processPaymentRequest.IsRecurringPayment) + { _customerService.ResetCheckoutData(customer, processPaymentRequest.StoreId, clearCouponCodes: true, clearCheckoutAttributes: true); + } // check for generic attributes to be inserted automatically foreach (var customProperty in processPaymentRequest.CustomProperties.Where(x => x.Key.HasValue() && x.Value.AutoCreateGenericAttribute)) @@ -1392,10 +1370,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR if (!processPaymentRequest.IsRecurringPayment) { - _customerActivityService.InsertActivity( - "PublicStore.PlaceOrder", - _localizationService.GetResource("ActivityLog.PublicStore.PlaceOrder"), - order.GetOrderNumber()); + _customerActivityService.InsertActivity("PublicStore.PlaceOrder", T("ActivityLog.PublicStore.PlaceOrder"), order.GetOrderNumber()); } //raise event @@ -1403,13 +1378,16 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR { _eventPublisher.PublishOrderPaid(order); } + #endregion } } else { - foreach (var paymentError in processPaymentResult.Errors) - result.AddError(paymentError); + foreach (var paymentError in processPaymentResult.Errors) + { + result.AddError(paymentError); + } } } catch (Exception exc) @@ -1452,14 +1430,8 @@ public virtual void DeleteOrder(Order order) _productService.AdjustInventory(orderItem, false, orderItem.Quantity); } - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = TNote("OrderDeleted"), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + //add a note + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderDeleted")); //now delete an order _orderService.DeleteOrder(order); @@ -1474,25 +1446,25 @@ public virtual void ProcessNextRecurringPayment(RecurringPayment recurringPaymen { if (recurringPayment == null) throw new ArgumentNullException("recurringPayment"); + try { if (!recurringPayment.IsActive) - throw new SmartException("Recurring payment is not active"); + throw new SmartException(T("Payment.RecurringPaymentNotActive")); var initialOrder = recurringPayment.InitialOrder; if (initialOrder == null) - throw new SmartException("Initial order could not be loaded"); + throw new SmartException(T("Order.InitialOrderDoesNotExistForRecurringPayment")); var customer = initialOrder.Customer; if (customer == null) - throw new SmartException("Customer could not be loaded"); + throw new SmartException(T("Customer.DoesNotExist")); var nextPaymentDate = recurringPayment.NextPaymentDate; if (!nextPaymentDate.HasValue) - throw new SmartException("Next payment date could not be calculated"); + throw new SmartException(T("Payment.CannotCalculateNextPaymentDate")); - //payment info - var paymentInfo = new ProcessPaymentRequest() + var paymentInfo = new ProcessPaymentRequest { StoreId = initialOrder.StoreId, CustomerId = customer.Id, @@ -1506,35 +1478,30 @@ public virtual void ProcessNextRecurringPayment(RecurringPayment recurringPaymen //place a new order var result = this.PlaceOrder(paymentInfo, new Dictionary()); + if (result.Success) { if (result.PlacedOrder == null) - throw new SmartException("Placed order could not be loaded"); + throw new SmartException(T("Order.DoesNotExist")); - var rph = new RecurringPaymentHistory() + var rph = new RecurringPaymentHistory { RecurringPayment = recurringPayment, CreatedOnUtc = DateTime.UtcNow, - OrderId = result.PlacedOrder.Id, + OrderId = result.PlacedOrder.Id }; + recurringPayment.RecurringPaymentHistory.Add(rph); _orderService.UpdateRecurringPayment(recurringPayment); } - else - { - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; - } - throw new SmartException(error); + else if (result.Errors.Count > 0) + { + throw new SmartException(string.Join(" ", result.Errors)); } } - catch (Exception exc) + catch (Exception exception) { - _logger.Error(string.Format("Error while processing recurring order. {0}", exc.Message), exc); + _logger.ErrorsAll(exception); throw; } } @@ -1550,69 +1517,42 @@ public virtual IList CancelRecurringPayment(RecurringPayment recurringPa var initialOrder = recurringPayment.InitialOrder; if (initialOrder == null) - return new List() { "Initial order could not be loaded" }; - + return new List { T("Order.InitialOrderDoesNotExistForRecurringPayment") }; var request = new CancelRecurringPaymentRequest(); CancelRecurringPaymentResult result = null; + try { request.Order = initialOrder; - result = _paymentService.CancelRecurringPayment(request); + + result = _paymentService.CancelRecurringPayment(request); + if (result.Success) { //update recurring payment recurringPayment.IsActive = false; _orderService.UpdateRecurringPayment(recurringPayment); - - //add a note - initialOrder.OrderNotes.Add(new OrderNote() - { - Note = TNote("RecurringPaymentCancelled"), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(initialOrder); + _orderService.AddOrderNote(initialOrder, T("Admin.OrderNotice.RecurringPaymentCancelled")); //notify a store owner - _workflowMessageService - .SendRecurringPaymentCancelledStoreOwnerNotification(recurringPayment, - _localizationSettings.DefaultAdminLanguageId); + _workflowMessageService.SendRecurringPaymentCancelledStoreOwnerNotification(recurringPayment, _localizationSettings.DefaultAdminLanguageId); } } - catch (Exception exc) + catch (Exception exception) { - if (result == null) - result = new CancelRecurringPaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); + if (result == null) + { + result = new CancelRecurringPaymentResult(); + } + + result.AddError(exception.ToAllMessages()); } + ProcessErrors(initialOrder, result.Errors, "Admin.OrderNotice.RecurringPaymentCancellationError"); - //process errors - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; - } - if (!String.IsNullOrEmpty(error)) - { - //add a note - initialOrder.OrderNotes.Add(new OrderNote() - { - Note = string.Format(TNote("RecurringPaymentCancellationError"), error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(initialOrder); - - //log it - string logError = string.Format("Error cancelling recurring payment. Order #{0}. Error: {1}", initialOrder.Id, error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; + return result.Errors; } /// @@ -1666,10 +1606,10 @@ public virtual void Ship(Shipment shipment, bool notifyCustomer) var order = _orderService.GetOrderById(shipment.OrderId); if (order == null) - throw new Exception("Order cannot be loaded"); + throw new SmartException(T("Order.DoesNotExist")); if (shipment.ShippedDateUtc.HasValue) - throw new Exception("This shipment is already shipped"); + throw new SmartException(T("Shipment.AlreadyShipped")); shipment.ShippedDateUtc = DateTime.UtcNow; _shipmentService.UpdateShipment(shipment); @@ -1679,30 +1619,18 @@ public virtual void Ship(Shipment shipment, bool notifyCustomer) order.ShippingStatusId = (int)ShippingStatus.PartiallyShipped; else order.ShippingStatusId = (int)ShippingStatus.Shipped; - _orderService.UpdateOrder(order); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(TNote("ShipmentSent"), shipment.Id), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.ShipmentSent", shipment.Id)); + if (notifyCustomer) { //notify customer int queuedEmailId = _workflowMessageService.SendShipmentSentCustomerNotification(shipment, order.CustomerLanguageId); if (queuedEmailId > 0) { - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(TNote("CustomerShippedEmailQueued"), queuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerShippedEmailQueued", queuedEmailId)); } } @@ -1722,40 +1650,30 @@ public virtual void Deliver(Shipment shipment, bool notifyCustomer) var order = shipment.Order; if (order == null) - throw new Exception("Order cannot be loaded"); + throw new SmartException(T("Order.DoesNotExist")); - if (shipment.DeliveryDateUtc.HasValue) - throw new Exception("This shipment is already delivered"); + if (shipment.DeliveryDateUtc.HasValue) + throw new SmartException(T("Shipment.AlreadyDelivered")); shipment.DeliveryDateUtc = DateTime.UtcNow; _shipmentService.UpdateShipment(shipment); - if (!order.HasItemsToAddToShipment() && !order.HasItemsToShip() && !order.HasItemsToDeliver()) - order.ShippingStatusId = (int)ShippingStatus.Delivered; - _orderService.UpdateOrder(order); + if (!order.HasItemsToAddToShipment() && !order.HasItemsToShip() && !order.HasItemsToDeliver()) + { + order.ShippingStatusId = (int)ShippingStatus.Delivered; + } - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(TNote("ShipmentDelivered"), shipment.Id), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.ShipmentDelivered", shipment.Id)); + if (notifyCustomer) { //send email notification int queuedEmailId = _workflowMessageService.SendShipmentDeliveredCustomerNotification(shipment, order.CustomerLanguageId); if (queuedEmailId > 0) { - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(TNote("CustomerDeliveredEmailQueued"), queuedEmailId), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerDeliveredEmailQueued", queuedEmailId)); } } @@ -1792,19 +1710,12 @@ public virtual void CancelOrder(Order order, bool notifyCustomer) throw new ArgumentNullException("order"); if (!CanCancelOrder(order)) - throw new SmartException("Cannot do cancel for order."); + throw new SmartException(T("Order.CannotCancel")); //Cancel order SetOrderStatus(order, OrderStatus.Cancelled, notifyCustomer); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = TNote("OrderCancelled"), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderCancelled")); //cancel recurring payments var recurringPayments = _orderService.SearchRecurringPayments(0, 0, order.Id, null); @@ -1915,14 +1826,7 @@ public virtual void MarkAsAuthorized(Order order) order.PaymentStatusId = (int)PaymentStatus.Authorized; _orderService.UpdateOrder(order); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = TNote("OrderMarkedAsAuthorized"), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderMarkedAsAuthorized")); //check order status CheckOrderStatus(order); @@ -1949,7 +1853,7 @@ public virtual bool CanCompleteOrder(Order order) public virtual void CompleteOrder(Order order) { if (!CanCompleteOrder(order)) - throw new SmartException("You can't mark this order as completed"); + throw new SmartException(T("Order.CannotMarkCompleted")); if (CanMarkOrderAsPaid(order)) { @@ -1977,12 +1881,10 @@ public virtual bool CanCapture(Order order) if (order == null) throw new ArgumentNullException("order"); - if (order.OrderStatus == OrderStatus.Cancelled || - order.OrderStatus == OrderStatus.Pending) + if (order.OrderStatus == OrderStatus.Cancelled || order.OrderStatus == OrderStatus.Pending) return false; - if (order.PaymentStatus == PaymentStatus.Authorized && - _paymentService.SupportCapture(order.PaymentMethodSystemName)) + if (order.PaymentStatus == PaymentStatus.Authorized && _paymentService.SupportCapture(order.PaymentMethodSystemName)) return true; return false; @@ -1999,7 +1901,7 @@ public virtual IList Capture(Order order) throw new ArgumentNullException("order"); if (!CanCapture(order)) - throw new SmartException("Cannot do capture for order."); + throw new SmartException(T("Order.CannotCapture")); var request = new CapturePaymentRequest(); CapturePaymentResult result = null; @@ -2012,24 +1914,20 @@ public virtual IList Capture(Order order) if (result.Success) { var paidDate = order.PaidDateUtc; - if (result.NewPaymentStatus == PaymentStatus.Paid) - paidDate = DateTime.UtcNow; + if (result.NewPaymentStatus == PaymentStatus.Paid) + { + paidDate = DateTime.UtcNow; + } order.CaptureTransactionId = result.CaptureTransactionId; order.CaptureTransactionResult = result.CaptureTransactionResult; order.PaymentStatus = result.NewPaymentStatus; order.PaidDateUtc = paidDate; - _orderService.UpdateOrder(order); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = TNote("OrderCaptured"), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderCaptured")); + CheckOrderStatus(order); //raise event @@ -2039,38 +1937,19 @@ public virtual IList Capture(Order order) } } } - catch (Exception exc) + catch (Exception exception) { - if (result == null) - result = new CapturePaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } + if (result == null) + { + result = new CapturePaymentResult(); + } + result.AddError(exception.ToAllMessages()); + } - //process errors - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; - } - if (!String.IsNullOrEmpty(error)) - { - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format("Unable to capture order. {0}", error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + ProcessErrors(order, result.Errors, "Admin.OrderNotice.OrderCaptureError"); - //log it - string logError = string.Format(TNote("OrderCaptureError"), order.GetOrderNumber(), error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; + return result.Errors; } /// @@ -2104,20 +1983,14 @@ public virtual void MarkOrderAsPaid(Order order) throw new ArgumentNullException("order"); if (!CanMarkOrderAsPaid(order)) - throw new SmartException("You can't mark this order as paid"); + throw new SmartException(T("Order.CannotMarkPaid")); order.PaymentStatusId = (int)PaymentStatus.Paid; order.PaidDateUtc = DateTime.UtcNow; - _orderService.UpdateOrder(order); - //add a note - order.OrderNotes.Add(new OrderNote - { - Note = TNote("OrderMarkedAsPaid"), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + _orderService.UpdateOrder(order); + + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderMarkedAsPaid")); CheckOrderStatus(order); @@ -2165,7 +2038,7 @@ public virtual IList Refund(Order order) throw new ArgumentNullException("order"); if (!CanRefund(order)) - throw new SmartException("Cannot do refund for order."); + throw new SmartException(T("Order.CannotRefund")); var request = new RefundPaymentRequest(); RefundPaymentResult result = null; @@ -2174,7 +2047,9 @@ public virtual IList Refund(Order order) request.Order = order; request.AmountToRefund = order.OrderTotal; request.IsPartialRefund = false; - result = _paymentService.Refund(request); + + result = _paymentService.Refund(request); + if (result.Success) { //total amount refunded @@ -2183,53 +2058,29 @@ public virtual IList Refund(Order order) //update order info order.RefundedAmount = totalAmountRefunded; order.PaymentStatus = result.NewPaymentStatus; - _orderService.UpdateOrder(order); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(TNote("OrderRefunded"), _priceFormatter.FormatPrice(request.AmountToRefund, true, false)), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderRefunded", _priceFormatter.FormatPrice(request.AmountToRefund, true, false))); + //check order status CheckOrderStatus(order); } } - catch (Exception exc) + catch (Exception exception) { - if (result == null) - result = new RefundPaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } + if (result == null) + { + result = new RefundPaymentResult(); + } - //process errors - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; + result.AddError(exception.ToAllMessages()); } - if (!String.IsNullOrEmpty(error)) - { - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(TNote("OrderRefundError"), error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); - //log it - string logError = string.Format("Error refunding order '{0}'. Error: {1}", order.GetOrderNumber(), error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; + ProcessErrors(order, result.Errors, "Admin.OrderNotice.OrderRefundError"); + + return result.Errors; } /// @@ -2265,7 +2116,7 @@ public virtual void RefundOffline(Order order) throw new ArgumentNullException("order"); if (!CanRefundOffline(order)) - throw new SmartException("You can't refund this order"); + throw new SmartException(T("Order.CannotRefund")); //amout to refund decimal amountToRefund = order.OrderTotal; @@ -2276,17 +2127,11 @@ public virtual void RefundOffline(Order order) //update order info order.RefundedAmount = totalAmountRefunded; order.PaymentStatus = PaymentStatus.Refunded; - _orderService.UpdateOrder(order); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(TNote("OrderMarkedAsRefunded"), _priceFormatter.FormatPrice(amountToRefund, true, false)), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderMarkedAsRefunded", _priceFormatter.FormatPrice(amountToRefund, true, false))); + //check order status CheckOrderStatus(order); } @@ -2336,10 +2181,11 @@ public virtual IList PartiallyRefund(Order order, decimal amountToRefund throw new ArgumentNullException("order"); if (!CanPartiallyRefund(order, amountToRefund)) - throw new SmartException("Cannot do partial refund for order."); + throw new SmartException(T("Order.CannotPartialRefund")); var request = new RefundPaymentRequest(); RefundPaymentResult result = null; + try { request.Order = order; @@ -2356,53 +2202,28 @@ public virtual IList PartiallyRefund(Order order, decimal amountToRefund //update order info order.RefundedAmount = totalAmountRefunded; order.PaymentStatus = result.NewPaymentStatus; - _orderService.UpdateOrder(order); - - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(TNote("OrderPartiallyRefunded"), _priceFormatter.FormatPrice(amountToRefund, true, false)), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderPartiallyRefunded", _priceFormatter.FormatPrice(amountToRefund, true, false))); + //check order status CheckOrderStatus(order); } } - catch (Exception exc) + catch (Exception exception) { - if (result == null) - result = new RefundPaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } + if (result == null) + { + result = new RefundPaymentResult(); + } - //process errors - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; - } - if (!String.IsNullOrEmpty(error)) - { - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(TNote("OrderPartiallyRefundError"), error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + result.AddError(exception.ToAllMessages()); + } - //log it - string logError = string.Format("Error refunding order '{0}'. Error: {1}", order.GetOrderNumber(), error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } - return result.Errors; + ProcessErrors(order, result.Errors, "Admin.OrderNotice.OrderPartiallyRefundError"); + + return result.Errors; } /// @@ -2448,7 +2269,7 @@ public virtual void PartiallyRefundOffline(Order order, decimal amountToRefund) throw new ArgumentNullException("order"); if (!CanPartiallyRefundOffline(order, amountToRefund)) - throw new SmartException("You can't partially refund (offline) this order"); + throw new SmartException(T("Order.CannotPartialRefund")); //total amount refunded decimal totalAmountRefunded = order.RefundedAmount + amountToRefund; @@ -2457,17 +2278,11 @@ public virtual void PartiallyRefundOffline(Order order, decimal amountToRefund) order.RefundedAmount = totalAmountRefunded; //if (order.OrderTotal == totalAmountRefunded), then set order.PaymentStatus = PaymentStatus.Refunded; order.PaymentStatus = PaymentStatus.PartiallyRefunded; - _orderService.UpdateOrder(order); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(TNote("OrderMarkedAsPartiallyRefunded"), _priceFormatter.FormatPrice(amountToRefund, true, false)), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderMarkedAsPartiallyRefunded", _priceFormatter.FormatPrice(amountToRefund, true, false))); + //check order status CheckOrderStatus(order); } @@ -2509,10 +2324,11 @@ public virtual IList Void(Order order) throw new ArgumentNullException("order"); if (!CanVoid(order)) - throw new SmartException("Cannot do void for order."); + throw new SmartException(T("Order.CannotVoid")); var request = new VoidPaymentRequest(); VoidPaymentResult result = null; + try { request.Order = order; @@ -2522,51 +2338,27 @@ public virtual IList Void(Order order) { //update order info order.PaymentStatus = result.NewPaymentStatus; - _orderService.UpdateOrder(order); - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = TNote("OrderVoided"), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderVoided")); + //check order status CheckOrderStatus(order); } } - catch (Exception exc) + catch (Exception exception) { - if (result == null) - result = new VoidPaymentResult(); - result.AddError(string.Format("Error: {0}. Full exception: {1}", exc.Message, exc.ToString())); - } + if (result == null) + { + result = new VoidPaymentResult(); + } - //process errors - string error = ""; - for (int i = 0; i < result.Errors.Count; i++) - { - error += string.Format("Error {0}: {1}", i, result.Errors[i]); - if (i != result.Errors.Count - 1) - error += ". "; - } - if (!String.IsNullOrEmpty(error)) - { - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = string.Format(TNote("OrderVoidError"), error), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); - _orderService.UpdateOrder(order); + result.AddError(exception.ToAllMessages()); + } + + ProcessErrors(order, result.Errors, "Admin.OrderNotice.OrderVoidError"); - //log it - string logError = string.Format("Error voiding order '{0}'. Error: {1}", order.GetOrderNumber(), error); - _logger.InsertLog(LogLevel.Error, logError, logError); - } return result.Errors; } @@ -2603,20 +2395,14 @@ public virtual void VoidOffline(Order order) throw new ArgumentNullException("order"); if (!CanVoidOffline(order)) - throw new SmartException("You can't void this order"); + throw new SmartException(T("Order.CannotVoid")); - order.PaymentStatusId = (int)PaymentStatus.Voided; - _orderService.UpdateOrder(order); + order.PaymentStatusId = (int)PaymentStatus.Voided; - //add a note - order.OrderNotes.Add(new OrderNote() - { - Note = TNote("OrderMarkedAsVoided"), - DisplayToCustomer = false, - CreatedOnUtc = DateTime.UtcNow - }); _orderService.UpdateOrder(order); + _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderMarkedAsVoided")); + //check orer status CheckOrderStatus(order); } @@ -2634,7 +2420,7 @@ public virtual void ReOrder(Order order) foreach (var orderItem in order.OrderItems) { - bool isBundle = (orderItem.Product.ProductType == ProductType.BundledProduct); + var isBundle = (orderItem.Product.ProductType == ProductType.BundledProduct); var addToCartContext = new AddToCartContext(); @@ -2673,7 +2459,8 @@ public virtual bool IsReturnRequestAllowed(Order order) if (order.OrderStatus != OrderStatus.Complete) return false; - bool numberOfDaysReturnRequestAvailableValid = false; + var numberOfDaysReturnRequestAvailableValid = false; + if (_orderSettings.NumberOfDaysReturnRequestAvailable == 0) { numberOfDaysReturnRequestAvailableValid = true; @@ -2700,14 +2487,13 @@ public virtual bool ValidateMinOrderSubtotalAmount(IList 0 && _orderSettings.MinOrderSubtotalAmount > decimal.Zero) { - //subtotal decimal orderSubTotalDiscountAmountBase = decimal.Zero; Discount orderSubTotalAppliedDiscount = null; decimal subTotalWithoutDiscountBase = decimal.Zero; decimal subTotalWithDiscountBase = decimal.Zero; + _orderTotalCalculationService.GetShoppingCartSubTotal(cart, - out orderSubTotalDiscountAmountBase, out orderSubTotalAppliedDiscount, - out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); + out orderSubTotalDiscountAmountBase, out orderSubTotalAppliedDiscount, out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); if (subTotalWithoutDiscountBase < _orderSettings.MinOrderSubtotalAmount) return false; @@ -2729,6 +2515,7 @@ public virtual bool ValidateMinOrderTotalAmount(IList if (cart.Count > 0 && _orderSettings.MinOrderTotalAmount > decimal.Zero) { decimal? shoppingCartTotalBase = _orderTotalCalculationService.GetShoppingCartTotal(cart); + if (shoppingCartTotalBase.HasValue && shoppingCartTotalBase.Value < _orderSettings.MinOrderTotalAmount) return false; } @@ -2771,6 +2558,7 @@ public virtual Shipment AddShipment(Order order, string trackingNumber, Dictiona { if (!totalWeight.HasValue) totalWeight = 0; + totalWeight += orderItemTotalWeight.Value; } @@ -2800,6 +2588,7 @@ public virtual Shipment AddShipment(Order order, string trackingNumber, Dictiona if (shipment != null && shipment.ShipmentItems.Count > 0) { shipment.TotalWeight = totalWeight; + _shipmentService.InsertShipment(shipment); return shipment; diff --git a/src/Libraries/SmartStore.Services/Orders/OrderService.cs b/src/Libraries/SmartStore.Services/Orders/OrderService.cs index c54392d8c4..8455602e8f 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderService.cs @@ -13,10 +13,10 @@ namespace SmartStore.Services.Orders { - /// - /// Order service - /// - public partial class OrderService : IOrderService + /// + /// Order service + /// + public partial class OrderService : IOrderService { #region Fields @@ -392,8 +392,7 @@ public virtual void DeleteOrderNote(OrderNote orderNote) /// Authorization transaction ID /// Payment method system name /// Order - public virtual Order GetOrderByAuthorizationTransactionIdAndPaymentMethod(string authorizationTransactionId, - string paymentMethodSystemName) + public virtual Order GetOrderByAuthorizationTransactionIdAndPaymentMethod(string authorizationTransactionId, string paymentMethodSystemName) { var query = _orderRepository.Table; if (!String.IsNullOrWhiteSpace(authorizationTransactionId)) @@ -406,17 +405,34 @@ public virtual Order GetOrderByAuthorizationTransactionIdAndPaymentMethod(string var order = query.FirstOrDefault(); return order; } - - #endregion - - #region Order items - /// - /// Gets an Order item - /// - /// Order item identifier - /// Order item - public virtual OrderItem GetOrderItemById(int orderItemId) + public virtual void AddOrderNote(Order order, string note, bool displayToCustomer = false) + { + Guard.ArgumentNotNull(() => order); + + if (note.HasValue()) + { + order.OrderNotes.Add(new OrderNote + { + Note = note, + DisplayToCustomer = displayToCustomer, + CreatedOnUtc = DateTime.UtcNow + }); + + UpdateOrder(order); + } + } + + #endregion + + #region Order items + + /// + /// Gets an Order item + /// + /// Order item identifier + /// Order item + public virtual OrderItem GetOrderItemById(int orderItemId) { if (orderItemId == 0) return null; diff --git a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs index 496ed410ac..cbb6f569a9 100644 --- a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs +++ b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs @@ -7,6 +7,7 @@ using SmartStore.Core.Domain.Payments; using SmartStore.Core.Events; using SmartStore.Core.Infrastructure; +using SmartStore.Core.Localization; using SmartStore.Core.Plugins; namespace SmartStore.Services.Payments @@ -59,21 +60,25 @@ public PaymentService( _providerManager = providerManager; _services = services; _typeFinder = typeFinder; - } - #endregion + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + #endregion #region Methods /// - /// Load active payment methods - /// + /// Load active payment methods + /// /// Filter payment methods by customer and apply payment method restrictions; null to load all records /// Filter payment methods by cart amount; null to load all records /// Filter payment methods by store identifier; pass 0 to load all records /// Filter payment methods by payment method types /// Provide a fallback payment method if none is active - /// Payment methods + /// Payment methods public virtual IEnumerable> LoadActivePaymentMethods( Customer customer = null, IList cart = null, @@ -253,7 +258,7 @@ public virtual PreProcessPaymentResult PreProcessPayment(ProcessPaymentRequest p { var paymentMethod = LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + throw new SmartException(T("Payment.CouldNotLoadMethod")); return paymentMethod.Value.PreProcessPayment(processPaymentRequest); } @@ -282,9 +287,12 @@ public virtual ProcessPaymentResult ProcessPayment(ProcessPaymentRequest process processPaymentRequest.CreditCardNumber = processPaymentRequest.CreditCardNumber.Replace(" ", ""); processPaymentRequest.CreditCardNumber = processPaymentRequest.CreditCardNumber.Replace("-", ""); } - var paymentMethod = LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); - if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + + var paymentMethod = LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); + + if (paymentMethod == null) + throw new SmartException(T("Payment.CouldNotLoadMethod")); + return paymentMethod.Value.ProcessPayment(processPaymentRequest); } } @@ -300,7 +308,7 @@ public virtual void PostProcessPayment(PostProcessPaymentRequest postProcessPaym { var paymentMethod = LoadPaymentMethodBySystemName(postProcessPaymentRequest.Order.PaymentMethodSystemName); if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + throw new SmartException(T("Payment.CouldNotLoadMethod")); paymentMethod.Value.PostProcessPayment(postProcessPaymentRequest); } @@ -377,7 +385,7 @@ public virtual CapturePaymentResult Capture(CapturePaymentRequest capturePayment { var paymentMethod = LoadPaymentMethodBySystemName(capturePaymentRequest.Order.PaymentMethodSystemName); if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + throw new SmartException(T("Payment.CouldNotLoadMethod")); try { @@ -386,7 +394,7 @@ public virtual CapturePaymentResult Capture(CapturePaymentRequest capturePayment catch (NotSupportedException) { var result = new CapturePaymentResult(); - result.AddError(_services.Localization.GetResource("Common.Payment.NoCaptureSupport")); + result.AddError(T("Common.Payment.NoCaptureSupport")); return result; } catch @@ -432,7 +440,7 @@ public virtual RefundPaymentResult Refund(RefundPaymentRequest refundPaymentRequ { var paymentMethod = LoadPaymentMethodBySystemName(refundPaymentRequest.Order.PaymentMethodSystemName); if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + throw new SmartException(T("Payment.CouldNotLoadMethod")); try { @@ -441,7 +449,7 @@ public virtual RefundPaymentResult Refund(RefundPaymentRequest refundPaymentRequ catch (NotSupportedException) { var result = new RefundPaymentResult(); - result.AddError(_services.Localization.GetResource("Common.Payment.NoRefundSupport")); + result.AddError(T("Common.Payment.NoRefundSupport")); return result; } catch @@ -474,7 +482,7 @@ public virtual VoidPaymentResult Void(VoidPaymentRequest voidPaymentRequest) { var paymentMethod = LoadPaymentMethodBySystemName(voidPaymentRequest.Order.PaymentMethodSystemName); if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + throw new SmartException(T("Payment.CouldNotLoadMethod")); try { @@ -483,7 +491,7 @@ public virtual VoidPaymentResult Void(VoidPaymentRequest voidPaymentRequest) catch (NotSupportedException) { var result = new VoidPaymentResult(); - result.AddError(_services.Localization.GetResource("Common.Payment.NoVoidSupport")); + result.AddError(T("Common.Payment.NoVoidSupport")); return result; } catch @@ -526,7 +534,7 @@ public virtual ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentReques { var paymentMethod = LoadPaymentMethodBySystemName(processPaymentRequest.PaymentMethodSystemName); if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + throw new SmartException(T("Payment.CouldNotLoadMethod")); try { @@ -535,7 +543,7 @@ public virtual ProcessPaymentResult ProcessRecurringPayment(ProcessPaymentReques catch (NotSupportedException) { var result = new ProcessPaymentResult(); - result.AddError(_services.Localization.GetResource("Common.Payment.NoRecurringPaymentSupport")); + result.AddError(T("Common.Payment.NoRecurringPaymentSupport")); return result; } catch @@ -557,7 +565,7 @@ public virtual CancelRecurringPaymentResult CancelRecurringPayment(CancelRecurri var paymentMethod = LoadPaymentMethodBySystemName(cancelPaymentRequest.Order.PaymentMethodSystemName); if (paymentMethod == null) - throw new SmartException("Payment method couldn't be loaded"); + throw new SmartException(T("Payment.CouldNotLoadMethod")); try { @@ -566,7 +574,7 @@ public virtual CancelRecurringPaymentResult CancelRecurringPayment(CancelRecurri catch (NotSupportedException) { var result = new CancelRecurringPaymentResult(); - result.AddError(_services.Localization.GetResource("Common.Payment.NoRecurringPaymentSupport")); + result.AddError(T("Common.Payment.NoRecurringPaymentSupport")); return result; } catch From 88fca1e160d56c59a371bf1fa882719f60794b1f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 6 Feb 2016 11:54:46 +0100 Subject: [PATCH 007/423] Product filter: Option to sort filter results by their display order rather than by number of matches --- changelog.md | 1 + .../Domain/Catalog/CatalogSettings.cs | 8 ++- .../201601262000441_ImportFramework1.cs | 6 +++ .../Filter/FilterCriteria.cs | 1 + .../Filter/FilterService.cs | 51 +++++++++++++------ .../Models/Settings/CatalogSettingsModel.cs | 3 ++ .../Views/Setting/Catalog.cshtml | 49 ++++++++++-------- 7 files changed, 83 insertions(+), 36 deletions(-) diff --git a/changelog.md b/changelog.md index 0c47b7433f..8ade03b5b7 100644 --- a/changelog.md +++ b/changelog.md @@ -40,6 +40,7 @@ * #436 Make %Order.Product(s)% token to link the product detail page and a add product thumbnail * #339 Meta robots setting for page indexing of search engines * PayPal Direct and Express: Option for API security protocol +* Product filter: Option to sort filter results by their display order rather than by number of matches ### 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/Catalog/CatalogSettings.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs index b5930ea2b6..4db06d260d 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs @@ -33,6 +33,7 @@ public CatalogSettings() CompareProductsEnabled = true; FilterEnabled = true; MaxFilterItemsToDisplay = 4; + SortFilterResultsByMatches = true; SubCategoryDisplayType = SubCategoryDisplayType.AboveProductList; ProductSearchAutoCompleteEnabled = true; ShowProductImagesInSearchAutoComplete = true; @@ -175,7 +176,12 @@ public CatalogSettings() /// Gets or sets a value indicating whether all filter criterias should be expanded /// public bool ExpandAllFilterCriteria { get; set; } - + + /// + /// Gets or sets a value indicating whether filter results should be sorted by matches + /// + public bool SortFilterResultsByMatches { get; set; } + /// /// Gets or sets a value indicating whether and where to display a list of subcategories /// diff --git a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs index 2d82cfce7a..c9ad88f38a 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs @@ -262,6 +262,12 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.OrderNotice.OrderVoidError", "Unable to void payment transaction of order {0}.", "Es ist ein Fehler bei der Stornierung einer Zahlungstransaktion zu Auftrag {0} aufgetreten."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.SortFilterResultsByMatches", + "Sort filter results by number of matches", + "Filterergebnisse nach Trefferanzahl sortieren", + "Specifies to sort filter results by number of matches in descending order. If this option is deactivated then the result is sorted by the display order of the values.", + "Legt fest, das Filterergebnisse absteigend nach der Anzahl an bereinstimmungen sortiert werden. Ist diese Option deaktiviert, so wird in der fr die Werte festgelegten Reihenfolge sortiert."); } } } diff --git a/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs b/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs index 23960ad3d9..6bbd9756f9 100644 --- a/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs +++ b/src/Libraries/SmartStore.Services/Filter/FilterCriteria.cs @@ -39,6 +39,7 @@ public class FilterCriteria : IComparable // Metadata public int MatchCount { get; set; } public int DisplayOrder { get; set; } + public int DisplayOrderValues { get; set; } public bool IsInactive { get; set; } public string NameLocalized { get; set; } public string ValueLocalized { get; set; } diff --git a/src/Libraries/SmartStore.Services/Filter/FilterService.cs b/src/Libraries/SmartStore.Services/Filter/FilterService.cs index 1ebbc5c4ef..d3e1ceeaac 100644 --- a/src/Libraries/SmartStore.Services/Filter/FilterService.cs +++ b/src/Libraries/SmartStore.Services/Filter/FilterService.cs @@ -11,7 +11,6 @@ using SmartStore.Core.Infrastructure; using SmartStore.Services.Catalog; using SmartStore.Services.Localization; -using SmartStore.Utilities; namespace SmartStore.Services.Filter { @@ -254,19 +253,28 @@ from pm in p.ProductManufacturers var grouped = from m in manus - orderby m.DisplayOrder group m by m.Id into grp orderby grp.Key select new FilterCriteria { MatchCount = grp.Count(), - Value = grp.FirstOrDefault().Name + Value = grp.FirstOrDefault().Name, + DisplayOrder = grp.FirstOrDefault().DisplayOrder }; - grouped = grouped.OrderByDescending(m => m.MatchCount); + if (_catalogSettings.SortFilterResultsByMatches) + { + grouped = grouped.OrderByDescending(m => m.MatchCount); + } + else + { + grouped = grouped.OrderBy(m => m.DisplayOrder); + } if (!getAll) + { grouped = grouped.Take(_catalogSettings.MaxFilterItemsToDisplay); + } var lst = grouped.ToList(); @@ -282,6 +290,8 @@ orderby grp.Key private List ProductFilterableSpecAttributes(FilterProductContext context, string attributeName = null) { + List criterias = null; + var languageId = _services.WorkContext.WorkingLanguage.Id; var query = ProductFilter(context); var attributes = @@ -291,7 +301,9 @@ where sa.AllowFiltering select sa.SpecificationAttributeOption; if (attributeName.HasValue()) + { attributes = attributes.Where(a => a.SpecificationAttribute.Name == attributeName); + } var grouped = from a in attributes @@ -300,21 +312,30 @@ from a in attributes { Name = g.FirstOrDefault().SpecificationAttribute.Name, Value = g.FirstOrDefault().Name, - DisplayOrder = g.FirstOrDefault().SpecificationAttribute.DisplayOrder, ID = g.Key.Id, PId = g.FirstOrDefault().SpecificationAttribute.Id, - MatchCount = g.Count() + MatchCount = g.Count(), + DisplayOrder = g.FirstOrDefault().SpecificationAttribute.DisplayOrder, + DisplayOrderValues = g.FirstOrDefault().DisplayOrder }; + if (_catalogSettings.SortFilterResultsByMatches) + { + criterias = grouped + .OrderBy(a => a.DisplayOrder) + .ThenByDescending(a => a.MatchCount) + .ThenBy(a => a.DisplayOrderValues) + .ToList(); + } + else + { + criterias = grouped + .OrderBy(a => a.DisplayOrder) + .ThenBy(a => a.DisplayOrderValues) + .ToList(); + } - var lst = grouped - .OrderBy(a => a.DisplayOrder) - .ThenByDescending(a => a.MatchCount) - .ToList(); - - int languageId = _services.WorkContext.WorkingLanguage.Id; - - lst.ForEach(c => + criterias.ForEach(c => { c.Entity = ShortcutSpecAttribute; c.IsInactive = true; @@ -326,7 +347,7 @@ from a in attributes c.ValueLocalized = _localizedEntityService.GetLocalizedValue(languageId, c.ID.Value, "SpecificationAttributeOption", "Name"); }); - return lst; + return criterias; } public virtual List Deserialize(string jsonData) diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs index 0dde9d1fb4..375245d768 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs @@ -109,6 +109,9 @@ public CatalogSettingsModel() [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.ExpandAllFilterCriteria")] public bool ExpandAllFilterCriteria { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.SortFilterResultsByMatches")] + public bool SortFilterResultsByMatches { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.SubCategoryDisplayType")] public SubCategoryDisplayType SubCategoryDisplayType { get; set; } public SelectList AvailableSubCategoryDisplayTypes { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml index 6ce64dd6b7..8095ae1255 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Catalog.cshtml @@ -30,7 +30,10 @@ $("#@Html.FieldIdFor(model => model.ProductSearchAutoCompleteEnabled)").click(toggleProductSearchAutoComplete); $("#@Html.FieldIdFor(model => model.CompareProductsEnabled)").click(toggleCompareProducts); $("#@Html.FieldIdFor(model => model.EnableHtmlTextCollapser)").click(toggleHtmlTextCollapsedHeight); - $("#@Html.FieldIdFor(model => model.FilterEnabled)").click(toggleFilterEnabled); + + $("#@Html.FieldIdFor(model => model.FilterEnabled)").change(function () { + $('#ProductFilterTable').find('.product-filter-option').toggle($(this).is(':checked')); + }).trigger('change'); toggleShowCategoryProductNumberIncludingSubcategories(); toggleEmailAFriend(); @@ -43,7 +46,6 @@ toggleProductSearchAutoComplete(); toggleCompareProducts(); toggleHtmlTextCollapsedHeight(); - toggleFilterEnabled(); toggleManufacturersOnHomepage(); }); @@ -157,16 +159,6 @@ $('#pnlHtmlTextCollapsedHeight').hide(); } } - - function toggleFilterEnabled() { - if ($('#@Html.FieldIdFor(model => model.FilterEnabled)').is(':checked')) { - $('#pnlMaxFilterItemsToDisplay').show(); - $('#pnlExpandAllFilterCriteria').show(); - } else { - $('#pnlMaxFilterItemsToDisplay').hide(); - $('#pnlExpandAllFilterCriteria').hide(); - } - } @Html.Action("StoreScopeConfiguration", "Setting") @@ -434,7 +426,26 @@ @Html.ValidationMessageFor(model => model.CategoryBreadcrumbEnabled) + + + @Html.SmartLabelFor(model => model.SubCategoryDisplayType) + + + @Html.SettingOverrideCheckbox(model => Model.SubCategoryDisplayType) + @Html.DropDownListFor(model => model.SubCategoryDisplayType, Model.AvailableSubCategoryDisplayTypes) + @Html.ValidationMessageFor(model => model.SubCategoryDisplayType) + + + + + + + - + @@ -453,7 +464,7 @@ @Html.ValidationMessageFor(model => model.MaxFilterItemsToDisplay) - + @@ -462,15 +473,13 @@ @Html.ValidationMessageFor(model => model.ExpandAllFilterCriteria) - - +
    +
    +
    @T("Filtering.FilterResults")
    +
    +
    @Html.SmartLabelFor(model => model.FilterEnabled) @@ -444,7 +455,7 @@ @Html.ValidationMessageFor(model => model.FilterEnabled)
    @Html.SmartLabelFor(model => model.MaxFilterItemsToDisplay)
    @Html.SmartLabelFor(model => model.ExpandAllFilterCriteria)
    - @Html.SmartLabelFor(model => model.SubCategoryDisplayType) + @Html.SmartLabelFor(model => model.SortFilterResultsByMatches) - @Html.SettingOverrideCheckbox(model => Model.SubCategoryDisplayType) - @Html.DropDownListFor(model => model.SubCategoryDisplayType, Model.AvailableSubCategoryDisplayTypes) - @Html.ValidationMessageFor(model => model.SubCategoryDisplayType) + @Html.SettingEditorFor(model => model.SortFilterResultsByMatches) + @Html.ValidationMessageFor(model => model.SortFilterResultsByMatches)
    From 513edf709ec4f4a07149982c84ff0af1217cedcf Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 6 Feb 2016 14:07:44 +0100 Subject: [PATCH 008/423] Unit testing --- .../Infrastructure/AutoMapperStartupTask.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs index cf6652c228..1d1a7130e8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs @@ -133,7 +133,8 @@ public void Execute() .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()); + .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) + .ForMember(dest => dest.FlagFileNames, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.LocaleStringResources, mo => mo.Ignore()); //email account @@ -199,7 +200,8 @@ public void Execute() .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) - .ForMember(dest => dest.UpdatedOn, mo => mo.Ignore()); + .ForMember(dest => dest.UpdatedOn, mo => mo.Ignore()) + .ForMember(dest => dest.GridPageSize, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.HasDiscountsApplied, mo => mo.Ignore()) .ForMember(dest => dest.CreatedOnUtc, mo => mo.Ignore()) @@ -215,7 +217,8 @@ public void Execute() .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()) .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) .ForMember(dest => dest.CreatedOn, mo => mo.Ignore()) - .ForMember(dest => dest.UpdatedOn, mo => mo.Ignore()); + .ForMember(dest => dest.UpdatedOn, mo => mo.Ignore()) + .ForMember(dest => dest.GridPageSize, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.CreatedOnUtc, mo => mo.Ignore()) .ForMember(dest => dest.UpdatedOnUtc, mo => mo.Ignore()) @@ -662,8 +665,9 @@ public void Execute() .ForMember(dest => dest.AutoCompleteSearchThumbPictureSize, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.AvailableCustomerNumberMethods, mo => mo.Ignore()) - .ForMember(dest => dest.AvailableCustomerNumberVisibilities, mo => mo.Ignore()); - Mapper.CreateMap() + .ForMember(dest => dest.AvailableCustomerNumberVisibilities, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableRegisterCustomerRoles, mo => mo.Ignore()); + Mapper.CreateMap() .ForMember(dest => dest.HashedPasswordFormat, mo => mo.Ignore()) .ForMember(dest => dest.PasswordMinLength, mo => mo.Ignore()) .ForMember(dest => dest.AvatarMaximumSizeBytes, mo => mo.Ignore()) From ad9b559dd16ce88e03ebc48c4c1ea943f8e4098f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 7 Feb 2016 11:28:40 +0100 Subject: [PATCH 009/423] Minor refactoring --- .../Localization/LocalizationHelper.cs | 16 ++++++++++++ .../Controllers/CommonController.cs | 25 +------------------ 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Localization/LocalizationHelper.cs b/src/Libraries/SmartStore.Core/Localization/LocalizationHelper.cs index 118128c58e..ed9beed2ad 100644 --- a/src/Libraries/SmartStore.Core/Localization/LocalizationHelper.cs +++ b/src/Libraries/SmartStore.Core/Localization/LocalizationHelper.cs @@ -32,5 +32,21 @@ public static string GetLanguageNativeName(string locale) return null; } + + public static string GetCurrencySymbol(string locale) + { + try + { + if (locale.HasValue()) + { + var info = new RegionInfo(locale); + if (info != null) + return info.CurrencySymbol; + } + } + catch { } + + return null; + } } } diff --git a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs index a0ef70f734..2a62c28163 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Drawing; -using System.Globalization; using System.Linq; using System.Text; using System.Web.Mvc; @@ -227,7 +226,7 @@ protected CurrencySelectorModel PrepareCurrencySelectorModel() Id = x.Id, Name = x.GetLocalized(y => y.Name), ISOCode = x.CurrencyCode, - Symbol = GetCurrencySymbol(x.DisplayLocale) ?? x.CurrencyCode + Symbol = LocalizationHelper.GetCurrencySymbol(x.DisplayLocale) ?? x.CurrencyCode }) .ToList(); return result; @@ -241,28 +240,6 @@ protected CurrencySelectorModel PrepareCurrencySelectorModel() return model; } - // TODO: Zentral auslagern - private static string GetCurrencySymbol(string locale) - { - try - { - if (!string.IsNullOrEmpty(locale)) - { - var info = new RegionInfo(locale); - if (info == null) - { - return null; - } - return info.CurrencySymbol; - } - return null; - } - catch - { - return null; - } - } - [NonAction] protected TaxTypeSelectorModel PrepareTaxTypeSelectorModel() { From c0a8bd603478cf8ecc57b3ae5431165e7754a636 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 8 Feb 2016 07:31:08 +0100 Subject: [PATCH 010/423] Localized some hard coded strings --- .../201601262000441_ImportFramework1.cs | 28 ++ .../Orders/AppliedGiftCard.cs | 3 - .../Orders/OrderProcessingService.cs | 4 +- .../Orders/OrderTotalCalculationService.cs | 45 ++-- .../Orders/ShoppingCartService.cs | 243 ++++++++++-------- .../Payments/PaymentService.cs | 4 +- .../Shipping/ShippingService.cs | 41 +-- .../Controllers/OrderController.cs | 2 +- .../Controllers/ProductController.cs | 12 +- .../Controllers/CatalogController.cs | 2 +- .../Controllers/ProductController.cs | 12 +- .../Controllers/ShoppingCartController.cs | 2 +- .../Orders/OrderProcessingServiceTests.cs | 1 - .../OrderTotalCalculationServiceTests.cs | 4 - .../Shipping/ShippingServiceTests.cs | 4 - 15 files changed, 232 insertions(+), 175 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs index c9ad88f38a..b15e96101a 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs @@ -139,6 +139,14 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "This is not your order.", "Dieser Auftrag konnte Ihnen nicht zugeordnet werden."); + builder.AddOrUpdate("Shipping.CouldNotLoadMethod", + "The shipping rate computation method could not be loaded.", + "Die Berechnungsmethode fr Versandkosten konnte nicht geladen werden."); + + builder.AddOrUpdate("Shipping.OneActiveMethodProviderRequired", + "At least one shipping rate computation method provider is required to be active.", + "Mindestens ein Provider zur Berechnung von Versandkosten muss aktiviert sein."); + builder.AddOrUpdate("Payment.CouldNotLoadMethod", "The payment method could not be loaded.", "Die Zahlungsart konnte nicht geladen werden."); @@ -147,6 +155,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "The payment method is not available.", "Die Zahlungsart steht nicht zur Verfgung."); + builder.AddOrUpdate("Payment.OneActiveMethodProviderRequired", + "At least one payment method provider is required to be active.", + "Mindestens ein Zahlungsart-Provider muss aktiviert sein."); + builder.AddOrUpdate("Payment.RecurringPaymentNotSupported", "Recurring payments are not supported by selected payment method.", "Wiederkehrende Zahlungen sind fr die gewhlte Zahlungsart nicht mglich."); @@ -268,6 +280,22 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Filterergebnisse nach Trefferanzahl sortieren", "Specifies to sort filter results by number of matches in descending order. If this option is deactivated then the result is sorted by the display order of the values.", "Legt fest, das Filterergebnisse absteigend nach der Anzahl an bereinstimmungen sortiert werden. Ist diese Option deaktiviert, so wird in der fr die Werte festgelegten Reihenfolge sortiert."); + + builder.AddOrUpdate("Wishlist.IsDisabled", + "The wishlist is disabled.", + "Die Wunschliste ist deaktiviert."); + + builder.AddOrUpdate("ShoppingCart.IsDisabled", + "The shoping cart is disabled.", + "Der Warenkorb ist deaktiviert."); + + builder.AddOrUpdate("Products.NotFound", + "The product {0} was not found.", + "Das Produkt {0} wurde nicht gefunden."); + + builder.AddOrUpdate("Products.Variants.NotFound", + "The product variant {0} was not found.", + "Die Produktvariante wurde {0} nicht gefunden."); } } } diff --git a/src/Libraries/SmartStore.Services/Orders/AppliedGiftCard.cs b/src/Libraries/SmartStore.Services/Orders/AppliedGiftCard.cs index ea8cdd8be8..4318b43cd6 100644 --- a/src/Libraries/SmartStore.Services/Orders/AppliedGiftCard.cs +++ b/src/Libraries/SmartStore.Services/Orders/AppliedGiftCard.cs @@ -1,6 +1,3 @@ - - - using SmartStore.Core.Domain.Orders; namespace SmartStore.Services.Orders diff --git a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs index 538f8c580f..4ca617a354 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs @@ -288,7 +288,7 @@ protected void ReduceRewardPoints(Order order, decimal? amount = null) return; //reduce reward points - order.Customer.AddRewardPointsHistoryEntry(-points, string.Format(T("RewardPoints.Message.ReducedForOrder"), order.GetOrderNumber())); + order.Customer.AddRewardPointsHistoryEntry(-points, T("RewardPoints.Message.ReducedForOrder", order.GetOrderNumber())); if (!order.RewardPointsRemaining.HasValue) order.RewardPointsRemaining = (int)Math.Round(order.OrderTotal / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); @@ -1370,7 +1370,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR if (!processPaymentRequest.IsRecurringPayment) { - _customerActivityService.InsertActivity("PublicStore.PlaceOrder", T("ActivityLog.PublicStore.PlaceOrder"), order.GetOrderNumber()); + _customerActivityService.InsertActivity("PublicStore.PlaceOrder", T("ActivityLog.PublicStore.PlaceOrder", order.GetOrderNumber())); } //raise event diff --git a/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs b/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs index 3e48061380..4a92fd4fd2 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs @@ -9,6 +9,7 @@ using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Tax; +using SmartStore.Core.Localization; using SmartStore.Core.Plugins; using SmartStore.Services.Catalog; using SmartStore.Services.Common; @@ -19,10 +20,10 @@ namespace SmartStore.Services.Orders { - /// - /// Order service - /// - public partial class OrderTotalCalculationService : IOrderTotalCalculationService + /// + /// Order service + /// + public partial class OrderTotalCalculationService : IOrderTotalCalculationService { #region Fields @@ -98,20 +99,24 @@ public OrderTotalCalculationService(IWorkContext workContext, this._shippingSettings = shippingSettings; this._shoppingCartSettings = shoppingCartSettings; this._catalogSettings = catalogSettings; - } - #endregion + T = NullLocalizer.Instance; + } - #region Methods + public Localizer T { get; set; } - /// - /// Gets shopping cart subtotal - /// - /// Cart - /// Applied discount amount - /// Applied discount - /// Sub total (without discount) - /// Sub total (with discount) + #endregion + + #region Methods + + /// + /// Gets shopping cart subtotal + /// + /// Cart + /// Applied discount amount + /// Applied discount + /// Sub total (without discount) + /// Sub total (with discount) public virtual void GetShoppingCartSubTotal(IList cart, out decimal discountAmount, out Discount appliedDiscount, out decimal subTotalWithoutDiscount, out decimal subTotalWithDiscount) @@ -613,7 +618,7 @@ public virtual decimal AdjustShippingRate(decimal shippingRate, IList cart, out So int redeemedRewardPoints = 0; decimal redeemedRewardPointsAmount = decimal.Zero; List appliedGiftCards = null; + return GetShoppingCartTotal(cart, out discountAmount, out appliedDiscount, - out appliedGiftCards, - out redeemedRewardPoints, out redeemedRewardPointsAmount, ignoreRewardPonts, usePaymentMethodAdditionalFee); + out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount, ignoreRewardPonts, usePaymentMethodAdditionalFee); } /// @@ -899,9 +904,7 @@ public virtual decimal GetTaxTotal(IList cart, out So decimal subTotalWithoutDiscountBase = decimal.Zero; decimal subTotalWithDiscountBase = decimal.Zero; - GetShoppingCartSubTotal(cart, false, - out orderSubTotalDiscountAmount, out orderSubTotalAppliedDiscount, - out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); + GetShoppingCartSubTotal(cart, false, out orderSubTotalDiscountAmount, out orderSubTotalAppliedDiscount, out subTotalWithoutDiscountBase, out subTotalWithDiscountBase); //subtotal with discount subtotalBase = subTotalWithDiscountBase; diff --git a/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs b/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs index ceec6e4ee5..259b323910 100644 --- a/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs +++ b/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs @@ -8,6 +8,7 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Events; +using SmartStore.Core.Localization; using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; @@ -19,10 +20,10 @@ namespace SmartStore.Services.Orders { - /// - /// Shopping cart service - /// - public partial class ShoppingCartService : IShoppingCartService + /// + /// Shopping cart service + /// + public partial class ShoppingCartService : IShoppingCartService { #region Fields @@ -112,20 +113,24 @@ public ShoppingCartService( this._genericAttributeService = genericAttributeService; this._downloadService = downloadService; this._catalogSettings = catalogSettings; - } - #endregion + T = NullLocalizer.Instance; + } - #region Methods + public Localizer T { get; set; } - /// - /// Delete shopping cart item - /// - /// Shopping cart item - /// A value indicating whether to reset checkout data - /// A value indicating whether to ensure that only active checkout attributes are attached to the current customer + #endregion + + #region Methods + + /// + /// Delete shopping cart item + /// + /// Shopping cart item + /// A value indicating whether to reset checkout data + /// A value indicating whether to ensure that only active checkout attributes are attached to the current customer /// A value indicating whether to delete child cart items - public virtual void DeleteShoppingCartItem(ShoppingCartItem shoppingCartItem, bool resetCheckoutData = true, + public virtual void DeleteShoppingCartItem(ShoppingCartItem shoppingCartItem, bool resetCheckoutData = true, bool ensureOnlyActiveCheckoutAttributes = false, bool deleteChildCartItems = true) { if (shoppingCartItem == null) @@ -271,17 +276,17 @@ public virtual IList GetRequiredProductWarnings(Customer customer, //don't display specific errors from 'addToCartWarnings' variable //display only generic error - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.RequiredProductWarning"), rp.GetLocalized(x => x.Name))); + warnings.Add(T("ShoppingCart.RequiredProductWarning", rp.GetLocalized(x => x.Name))); } } else { - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.RequiredProductWarning"), rp.GetLocalized(x => x.Name))); + warnings.Add(T("ShoppingCart.RequiredProductWarning", rp.GetLocalized(x => x.Name))); } } else { - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.RequiredProductWarning"), rp.GetLocalized(x => x.Name))); + warnings.Add(T("ShoppingCart.RequiredProductWarning", rp.GetLocalized(x => x.Name))); } } } @@ -314,57 +319,57 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart //deleted? if (product.Deleted) { - warnings.Add(_localizationService.GetResource("ShoppingCart.ProductDeleted")); + warnings.Add(T("ShoppingCart.ProductDeleted")); return warnings; } // check if the product type is available for order if (product.ProductType == ProductType.GroupedProduct) { - warnings.Add(_localizationService.GetResource("ShoppingCart.ProductNotAvailableForOrder")); + warnings.Add(T("ShoppingCart.ProductNotAvailableForOrder")); } // validate bundle if (product.ProductType == ProductType.BundledProduct) { if (product.BundlePerItemPricing && customerEnteredPrice != decimal.Zero) - warnings.Add(_localizationService.GetResource("ShoppingCart.Bundle.NoCustomerEnteredPrice")); + warnings.Add(T("ShoppingCart.Bundle.NoCustomerEnteredPrice")); } //published? if (!product.Published) { - warnings.Add(_localizationService.GetResource("ShoppingCart.ProductUnpublished")); + warnings.Add(T("ShoppingCart.ProductUnpublished")); } //ACL if (!_aclService.Authorize(product, customer)) { - warnings.Add(_localizationService.GetResource("ShoppingCart.ProductUnpublished")); + warnings.Add(T("ShoppingCart.ProductUnpublished")); } //Store mapping if (!_storeMappingService.Authorize(product, _storeContext.CurrentStore.Id)) { - warnings.Add(_localizationService.GetResource("ShoppingCart.ProductUnpublished")); + warnings.Add(T("ShoppingCart.ProductUnpublished")); } //disabled "add to cart" button if (shoppingCartType == ShoppingCartType.ShoppingCart && product.DisableBuyButton) { - warnings.Add(_localizationService.GetResource("ShoppingCart.BuyingDisabled")); + warnings.Add(T("ShoppingCart.BuyingDisabled")); } //disabled "add to wishlist" button if (shoppingCartType == ShoppingCartType.Wishlist && product.DisableWishlistButton) { - warnings.Add(_localizationService.GetResource("ShoppingCart.WishlistDisabled")); + warnings.Add(T("ShoppingCart.WishlistDisabled")); } //call for price if (shoppingCartType == ShoppingCartType.ShoppingCart && product.CallForPrice) { - warnings.Add(_localizationService.GetResource("Products.CallForPrice")); + warnings.Add(T("Products.CallForPrice")); } //customer entered price @@ -373,11 +378,13 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart if (customerEnteredPrice < product.MinimumCustomerEnteredPrice || customerEnteredPrice > product.MaximumCustomerEnteredPrice) { - decimal minimumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.MinimumCustomerEnteredPrice, _workContext.WorkingCurrency); - decimal maximumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.MaximumCustomerEnteredPrice, _workContext.WorkingCurrency); - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.CustomerEnteredPrice.RangeError"), - _priceFormatter.FormatPrice(minimumCustomerEnteredPrice, true, false), - _priceFormatter.FormatPrice(maximumCustomerEnteredPrice, true, false))); + var minimumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.MinimumCustomerEnteredPrice, _workContext.WorkingCurrency); + var maximumCustomerEnteredPrice = _currencyService.ConvertFromPrimaryStoreCurrency(product.MaximumCustomerEnteredPrice, _workContext.WorkingCurrency); + + warnings.Add(T("ShoppingCart.CustomerEnteredPrice.RangeError", + _priceFormatter.FormatPrice(minimumCustomerEnteredPrice, true, false), + _priceFormatter.FormatPrice(maximumCustomerEnteredPrice, true, false)) + ); } } @@ -385,18 +392,20 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart var hasQtyWarnings = false; if (quantity < product.OrderMinimumQuantity) { - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.MinimumQuantity"), product.OrderMinimumQuantity)); + warnings.Add(T("ShoppingCart.MinimumQuantity", product.OrderMinimumQuantity)); hasQtyWarnings = true; } + if (quantity > product.OrderMaximumQuantity) { - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.MaximumQuantity"), product.OrderMaximumQuantity)); + warnings.Add(T("ShoppingCart.MaximumQuantity", product.OrderMaximumQuantity)); hasQtyWarnings = true; } + var allowedQuantities = product.ParseAllowedQuatities(); if (allowedQuantities.Length > 0 && !allowedQuantities.Contains(quantity)) { - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.AllowedQuantities"), string.Join(", ", allowedQuantities))); + warnings.Add(T("ShoppingCart.AllowedQuantities", string.Join(", ", allowedQuantities))); } var validateOutOfStock = shoppingCartType == ShoppingCartType.ShoppingCart || !_shoppingCartSettings.AllowOutOfStockItemsToBeAddedToWishlist; @@ -414,11 +423,12 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart { if (product.StockQuantity < quantity) { - int maximumQuantityCanBeAdded = product.StockQuantity; + var maximumQuantityCanBeAdded = product.StockQuantity; + if (maximumQuantityCanBeAdded <= 0) - warnings.Add(_localizationService.GetResource("ShoppingCart.OutOfStock")); + warnings.Add(T("ShoppingCart.OutOfStock")); else - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.QuantityExceedsStock"), maximumQuantityCanBeAdded)); + warnings.Add(T("ShoppingCart.QuantityExceedsStock", maximumQuantityCanBeAdded)); } } } @@ -434,9 +444,9 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart int maximumQuantityCanBeAdded = combination.StockQuantity; if (maximumQuantityCanBeAdded <= 0) - warnings.Add(_localizationService.GetResource("ShoppingCart.OutOfStock")); + warnings.Add(T("ShoppingCart.OutOfStock")); else - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.QuantityExceedsStock"), maximumQuantityCanBeAdded)); + warnings.Add(T("ShoppingCart.QuantityExceedsStock", maximumQuantityCanBeAdded)); } } } @@ -447,14 +457,14 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart } //availability dates - bool availableStartDateError = false; + var availableStartDateError = false; if (product.AvailableStartDateTimeUtc.HasValue) { DateTime now = DateTime.UtcNow; DateTime availableStartDateTime = DateTime.SpecifyKind(product.AvailableStartDateTimeUtc.Value, DateTimeKind.Utc); if (availableStartDateTime.CompareTo(now) > 0) { - warnings.Add(_localizationService.GetResource("ShoppingCart.NotAvailable")); + warnings.Add(T("ShoppingCart.NotAvailable")); availableStartDateError = true; } } @@ -464,7 +474,7 @@ public virtual IList GetStandardWarnings(Customer customer, ShoppingCart DateTime availableEndDateTime = DateTime.SpecifyKind(product.AvailableEndDateTimeUtc.Value, DateTimeKind.Utc); if (availableEndDateTime.CompareTo(now) < 0) { - warnings.Add(_localizationService.GetResource("ShoppingCart.NotAvailable")); + warnings.Add(T("ShoppingCart.NotAvailable")); } } return warnings; @@ -497,7 +507,7 @@ public virtual IList GetShoppingCartItemAttributeWarnings( if (pv1 == null || pv1.Id != product.Id) { - warnings.Add(_localizationService.GetResource("ShoppingCart.AttributeError")); + warnings.Add(T("ShoppingCart.AttributeError")); return warnings; } } @@ -533,8 +543,7 @@ public virtual IList GetShoppingCartItemAttributeWarnings( if (!found) { - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.SelectAttribute"), - pva2.TextPrompt.HasValue() ? pva2.TextPrompt : pva2.ProductAttribute.GetLocalized(a => a.Name))); + warnings.Add(T("ShoppingCart.SelectAttribute", pva2.TextPrompt.HasValue() ? pva2.TextPrompt : pva2.ProductAttribute.GetLocalized(a => a.Name))); } } } @@ -549,7 +558,7 @@ public virtual IList GetShoppingCartItemAttributeWarnings( if (combination != null && !combination.IsActive) { - warnings.Add(_localizationService.GetResource("ShoppingCart.NotAvailable")); + warnings.Add(T("ShoppingCart.NotAvailable")); } } @@ -568,18 +577,16 @@ public virtual IList GetShoppingCartItemAttributeWarnings( foreach (var linkageWarning in linkageWarnings) { - string msg = _localizationService.GetResource("ShoppingCart.ProductLinkageAttributeWarning").FormatWith( + warnings.Add(T("ShoppingCart.ProductLinkageAttributeWarning", pvaValue.ProductVariantAttribute.ProductAttribute.GetLocalized(a => a.Name), pvaValue.GetLocalized(a => a.Name), - linkageWarning); - - warnings.Add(msg); + linkageWarning) + ); } } else { - string msg = _localizationService.GetResource("ShoppingCart.ProductLinkageProductNotLoading").FormatWith(pvaValue.LinkedProductId); - warnings.Add(msg); + warnings.Add(T("ShoppingCart.ProductLinkageProductNotLoading", pvaValue.LinkedProductId)); } } } @@ -668,27 +675,34 @@ public virtual IList GetShoppingCartItemGiftCardWarnings(ShoppingCartTyp string giftCardMessage = string.Empty; _productAttributeParser.GetGiftCardAttribute(selectedAttributes, - out giftCardRecipientName, out giftCardRecipientEmail, - out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); + out giftCardRecipientName, out giftCardRecipientEmail, out giftCardSenderName, out giftCardSenderEmail, out giftCardMessage); - if (String.IsNullOrEmpty(giftCardRecipientName)) - warnings.Add(_localizationService.GetResource("ShoppingCart.RecipientNameError")); + if (String.IsNullOrEmpty(giftCardRecipientName)) + { + warnings.Add(T("ShoppingCart.RecipientNameError")); + } if (product.GiftCardType == GiftCardType.Virtual) { - //validate for virtual gift cards only + //validate for virtual gift cards only if (String.IsNullOrEmpty(giftCardRecipientEmail) || !giftCardRecipientEmail.IsEmail()) - warnings.Add(_localizationService.GetResource("ShoppingCart.RecipientEmailError")); + { + warnings.Add(T("ShoppingCart.RecipientEmailError")); + } } - if (String.IsNullOrEmpty(giftCardSenderName)) - warnings.Add(_localizationService.GetResource("ShoppingCart.SenderNameError")); + if (String.IsNullOrEmpty(giftCardSenderName)) + { + warnings.Add(T("ShoppingCart.SenderNameError")); + } if (product.GiftCardType == GiftCardType.Virtual) { - //validate for virtual gift cards only + //validate for virtual gift cards only if (String.IsNullOrEmpty(giftCardSenderEmail) || !giftCardSenderEmail.IsEmail()) - warnings.Add(_localizationService.GetResource("ShoppingCart.SenderEmailError")); + { + warnings.Add(T("ShoppingCart.SenderEmailError")); + } } } @@ -707,19 +721,27 @@ public virtual IList GetBundleItemWarnings(ProductBundleItem bundleItem) if (bundleItem != null) { - string name = bundleItem.GetLocalizedName(); + var name = bundleItem.GetLocalizedName(); if (!bundleItem.Published) - warnings.Add(_localizationService.GetResource("ShoppingCart.Bundle.BundleItemUnpublished").FormatWith(name)); + { + warnings.Add(T("ShoppingCart.Bundle.BundleItemUnpublished", name)); + } if (bundleItem.ProductId == 0 || bundleItem.BundleProductId == 0 || bundleItem.Product == null || bundleItem.BundleProduct == null) - warnings.Add(_localizationService.GetResource("ShoppingCart.Bundle.MissingProduct").FormatWith(name)); + { + warnings.Add(T("ShoppingCart.Bundle.MissingProduct", name)); + } if (bundleItem.Quantity <= 0) - warnings.Add(_localizationService.GetResource("ShoppingCart.Bundle.Quantity").FormatWith(name)); + { + warnings.Add(T("ShoppingCart.Bundle.Quantity", name)); + } if (bundleItem.Product.IsDownload || bundleItem.Product.IsRecurring) - warnings.Add(_localizationService.GetResource("ShoppingCart.Bundle.ProductResrictions").FormatWith(name)); + { + warnings.Add(T("ShoppingCart.Bundle.ProductResrictions", name)); + } } return warnings; @@ -819,7 +841,7 @@ public virtual IList GetShoppingCartWarnings(IList GetShoppingCartWarnings(IList GetShoppingCartWarnings(IList GetShoppingCartWarnings(IList GetShoppingCartWarnings(IList a.TextPrompt))) warnings.Add(ca2.GetLocalized(a => a.TextPrompt)); else - warnings.Add(string.Format(_localizationService.GetResource("ShoppingCart.SelectAttribute"), ca2.GetLocalized(a => a.Name))); + warnings.Add(T("ShoppingCart.SelectAttribute", ca2.GetLocalized(a => a.Name))); } } } @@ -927,7 +955,7 @@ public virtual OrganizedShoppingCartItem FindShoppingCartItemInTheCart(IList AddToCart(Customer customer, Product product, Shoppi if (cartType == ShoppingCartType.ShoppingCart && !_permissionService.Authorize(StandardPermissionProvider.EnableShoppingCart, customer)) { - warnings.Add("Shopping cart is disabled"); + warnings.Add(T("ShoppingCart.IsDisabled")); return warnings; } if (cartType == ShoppingCartType.Wishlist && !_permissionService.Authorize(StandardPermissionProvider.EnableWishlist, customer)) { - warnings.Add("Wishlist is disabled"); + warnings.Add(T("Wishlist.IsDisabled")); return warnings; } if (quantity <= 0) { - warnings.Add(_localizationService.GetResource("ShoppingCart.QuantityShouldPositive")); + warnings.Add(T("ShoppingCart.QuantityShouldPositive")); return warnings; } //if (parentItemId.HasValue && (parentItemId.Value == 0 || bundleItem == null || bundleItem.Id == 0)) //{ - // warnings.Add(_localizationService.GetResource("ShoppingCart.Bundle.BundleItemNotFound").FormatWith(bundleItem.GetLocalizedName())); + // warnings.Add(T("ShoppingCart.Bundle.BundleItemNotFound", bundleItem.GetLocalizedName())); // return warnings; //} @@ -1066,17 +1098,17 @@ public virtual List AddToCart(Customer customer, Product product, Shoppi //maximum items validation if (cartType == ShoppingCartType.ShoppingCart && cart.Count >= _shoppingCartSettings.MaximumShoppingCartItems) { - warnings.Add(_localizationService.GetResource("ShoppingCart.MaximumShoppingCartItems")); + warnings.Add(T("ShoppingCart.MaximumShoppingCartItems")); return warnings; } else if (cartType == ShoppingCartType.Wishlist && cart.Count >= _shoppingCartSettings.MaximumWishlistItems) { - warnings.Add(_localizationService.GetResource("ShoppingCart.MaximumWishlistItems")); + warnings.Add(T("ShoppingCart.MaximumWishlistItems")); return warnings; } var now = DateTime.UtcNow; - var cartItem = new ShoppingCartItem() + var cartItem = new ShoppingCartItem { ShoppingCartType = cartType, StoreId = storeId, @@ -1090,8 +1122,9 @@ public virtual List AddToCart(Customer customer, Product product, Shoppi }; if (bundleItem != null) + { cartItem.BundleItemId = bundleItem.Id; - + } if (ctx == null) { @@ -1137,7 +1170,7 @@ public virtual void AddToCart(AddToCartContext ctx) _downloadService, _catalogSettings, null, ctx.Warnings, true, ctx.BundleItemId); if (ctx.Product.ProductType == ProductType.BundledProduct && ctx.Attributes.HasValue()) - ctx.Warnings.Add(_localizationService.GetResource("ShoppingCart.Bundle.NoAttributes")); + ctx.Warnings.Add(T("ShoppingCart.Bundle.NoAttributes")); if (ctx.Product.IsGiftCard) ctx.Attributes = ctx.AttributeForm.AddGiftCardAttribute(ctx.Attributes, ctx.Product.Id, _productAttributeParser, ctx.BundleItemId); diff --git a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs index cbb6f569a9..5d82085595 100644 --- a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs +++ b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs @@ -133,7 +133,7 @@ public virtual IEnumerable> LoadActivePaymentMethods( else { if (DataSettings.DatabaseIsInstalled()) - throw Error.Application("At least one payment method provider is required to be active."); + throw new SmartException(T("Payment.OneActiveMethodProviderRequired")); } } @@ -512,6 +512,7 @@ public virtual RecurringPaymentType GetRecurringPaymentType(string paymentMethod var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); if (paymentMethod == null) return RecurringPaymentType.NotSupported; + return paymentMethod.Value.RecurringPaymentType; } @@ -595,6 +596,7 @@ public virtual PaymentMethodType GetPaymentMethodType(string paymentMethodSystem var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); if (paymentMethod == null) return PaymentMethodType.Unknown; + return paymentMethod.Value.PaymentMethodType; } diff --git a/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs b/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs index 981e64523f..51f0a22966 100644 --- a/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs +++ b/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs @@ -9,12 +9,12 @@ using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Events; using SmartStore.Core.Infrastructure; +using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Configuration; -using SmartStore.Services.Localization; using SmartStore.Services.Orders; namespace SmartStore.Services.Shipping @@ -32,7 +32,6 @@ public partial class ShippingService : IShippingService private readonly IProductService _productService; private readonly ICheckoutAttributeParser _checkoutAttributeParser; private readonly IGenericAttributeService _genericAttributeService; - private readonly ILocalizationService _localizationService; private readonly ShippingSettings _shippingSettings; private readonly IEventPublisher _eventPublisher; private readonly ShoppingCartSettings _shoppingCartSettings; @@ -67,7 +66,6 @@ public ShippingService( IProductService productService, ICheckoutAttributeParser checkoutAttributeParser, IGenericAttributeService genericAttributeService, - ILocalizationService localizationService, ShippingSettings shippingSettings, IEventPublisher eventPublisher, ShoppingCartSettings shoppingCartSettings, @@ -81,26 +79,29 @@ public ShippingService( this._productService = productService; this._checkoutAttributeParser = checkoutAttributeParser; this._genericAttributeService = genericAttributeService; - this._localizationService = localizationService; this._shippingSettings = shippingSettings; this._eventPublisher = eventPublisher; this._shoppingCartSettings = shoppingCartSettings; this._settingService = settingService; this._providerManager = providerManager; this._typeFinder = typeFinder; + + T = NullLocalizer.Instance; } - #endregion - - #region Methods + public Localizer T { get; set; } - #region Shipping rate computation methods + #endregion - /// - /// Load active shipping rate computation methods - /// + #region Methods + + #region Shipping rate computation methods + + /// + /// Load active shipping rate computation methods + /// /// Load records allows only in specified store; pass 0 to load all records - /// Shipping rate computation methods + /// Shipping rate computation methods public virtual IEnumerable> LoadActiveShippingRateComputationMethods(int storeId = 0) { var allMethods = LoadAllShippingRateComputationMethods(storeId); @@ -126,7 +127,7 @@ public virtual IEnumerable> LoadActiveS else { if (DataSettings.DatabaseIsInstalled()) - throw Error.Application("At least one shipping method provider is required to be active."); + throw new SmartException(T("Shipping.OneActiveMethodProviderRequired")); } } @@ -389,7 +390,7 @@ public virtual GetShippingOptionResponse GetShippingOptions(IList 0 && result.Errors.Count > 0) result.Errors.Clear(); } - - //no shipping options loaded - if (result.ShippingOptions.Count == 0 && result.Errors.Count == 0) - result.Errors.Add(_localizationService.GetResource("Checkout.ShippingOptionCouldNotBeLoaded")); + + //no shipping options loaded + if (result.ShippingOptions.Count == 0 && result.Errors.Count == 0) + { + result.Errors.Add(T("Checkout.ShippingOptionCouldNotBeLoaded")); + } return result; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs index 37041f72f6..a5799a0b87 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/OrderController.cs @@ -584,7 +584,7 @@ protected OrderModel.AddOrderProductModel.ProductDetailsModel PrepareAddProductT { var product = _productService.GetProductById(productId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", productId)); var customer = _workContext.CurrentCustomer; // TODO: we need a customer representing entity instance for backend work var order = _orderService.GetOrderById(orderId); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index cca81b8cf3..de56f83cce 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -2162,7 +2162,7 @@ public ActionResult ProductPictureAdd(int pictureId, int displayOrder, int produ var product = _productService.GetProductById(productId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", productId)); var productPicture = new ProductPicture { @@ -2940,11 +2940,11 @@ public ActionResult EditAttributeValues(int productVariantAttributeId) var pva = _productAttributeService.GetProductVariantAttributeById(productVariantAttributeId); if (pva == null) - throw new ArgumentException("No product variant attribute found with the specified id"); + throw new ArgumentException(T("Products.Variants.NotFound", productVariantAttributeId)); var product = _productService.GetProductById(pva.ProductId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", pva.ProductId)); var model = new ProductModel.ProductVariantAttributeValueListModel() { @@ -3027,7 +3027,7 @@ public ActionResult ProductAttributeValueCreatePopup(int productAttributeAttribu var pva = _productAttributeService.GetProductVariantAttributeById(productAttributeAttributeId); if (pva == null) - throw new ArgumentException("No product variant attribute found with the specified id"); + throw new ArgumentException(T("Products.Variants.NotFound", productAttributeAttributeId)); var model = new ProductModel.ProductVariantAttributeValueModel() { @@ -3539,7 +3539,7 @@ public ActionResult CreateAllAttributeCombinations(ProductVariantAttributeCombin var product = _productService.GetProductById(productId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", productId)); _productAttributeService.CreateAllProductVariantAttributeCombinations(product); @@ -3557,7 +3557,7 @@ public ActionResult DeleteAllAttributeCombinations(ProductVariantAttributeCombin var product = _productService.GetProductById(productId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", productId)); _pvacRepository.DeleteAll(x => x.ProductId == product.Id); diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs index 979d767497..74f5d54f7f 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs @@ -365,7 +365,7 @@ public ActionResult ProductBreadcrumb(int productId) { var product = _productService.GetProductById(productId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", productId)); if (!_catalogSettings.CategoryBreadcrumbEnabled) return Content(""); diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index 6f52543374..6bd64d3cd9 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -355,7 +355,7 @@ public ActionResult ReviewOverview(int id) { var product = _productService.GetProductById(id); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", id)); var model = new ProductReviewOverviewModel() { @@ -372,7 +372,7 @@ public ActionResult ProductSpecifications(int productId) { var product = _productService.GetProductById(productId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", productId)); var model = _helper.PrepareProductSpecificationModel(product); @@ -403,7 +403,7 @@ public ActionResult ProductTierPrices(int productId) var product = _productService.GetProductById(productId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", productId)); if (!product.HasTierPrices) return Content(""); //no tier prices @@ -522,7 +522,7 @@ public ActionResult BackInStockSubscribePopup(int id /* productId */) { var product = _productService.GetProductById(id); if (product == null || product.Deleted) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", id)); var model = new BackInStockSubscribeModel(); model.ProductId = product.Id; @@ -551,7 +551,7 @@ public ActionResult BackInStockSubscribePopupPOST(int id /* productId */) { var product = _productService.GetProductById(id); if (product == null || product.Deleted) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", id)); if (!_services.WorkContext.CurrentCustomer.IsRegistered()) return Content(T("BackInStockSubscriptions.OnlyRegistered")); @@ -762,7 +762,7 @@ public ActionResult ProductTags(int productId) { var product = _productService.GetProductById(productId); if (product == null) - throw new ArgumentException("No product found with the specified id"); + throw new ArgumentException(T("Products.NotFound", productId)); var cacheKey = string.Format(ModelCacheEventConsumer.PRODUCTTAG_BY_PRODUCT_MODEL_KEY, product.Id, _services.WorkContext.WorkingLanguage.Id, _services.StoreContext.CurrentStore.Id); var cacheModel = _services.Cache.Get(cacheKey, () => diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index 789da467c5..2f25ae9568 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -1195,7 +1195,7 @@ public ActionResult AddProductSimple(int productId, bool forceredirection = fals return Json(new { success = false, - message = "No product found with the specified ID" + message = T("Products.NotFound", productId) }); } diff --git a/src/Tests/SmartStore.Services.Tests/Orders/OrderProcessingServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Orders/OrderProcessingServiceTests.cs index f800f24852..b6ed53c97e 100644 --- a/src/Tests/SmartStore.Services.Tests/Orders/OrderProcessingServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Orders/OrderProcessingServiceTests.cs @@ -135,7 +135,6 @@ public class OrderProcessingServiceTests : ServiceTest _productService, _checkoutAttributeParser, _genericAttributeService, - _localizationService, _shippingSettings, _eventPublisher, _shoppingCartSettings, diff --git a/src/Tests/SmartStore.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs index 532849c90b..1f5e26956f 100644 --- a/src/Tests/SmartStore.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Orders/OrderTotalCalculationServiceTests.cs @@ -22,7 +22,6 @@ using SmartStore.Services.Configuration; using SmartStore.Services.Directory; using SmartStore.Services.Discounts; -using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Orders; using SmartStore.Services.Shipping; @@ -53,7 +52,6 @@ public class OrderTotalCalculationServiceTests : ServiceTest IOrderTotalCalculationService _orderTotalCalcService; IAddressService _addressService; ShippingSettings _shippingSettings; - ILocalizationService _localizationService; ILogger _logger; IRepository _shippingMethodRepository; ShoppingCartSettings _shoppingCartSettings; @@ -91,7 +89,6 @@ public class OrderTotalCalculationServiceTests : ServiceTest _eventPublisher = MockRepository.GenerateMock(); _eventPublisher.Expect(x => x.Publish(Arg.Is.Anything)); - _localizationService = MockRepository.GenerateMock(); _settingService = MockRepository.GenerateMock(); _typeFinder = MockRepository.GenerateMock(); @@ -109,7 +106,6 @@ public class OrderTotalCalculationServiceTests : ServiceTest _productService, _checkoutAttributeParser, _genericAttributeService, - _localizationService, _shippingSettings, _eventPublisher, _shoppingCartSettings, diff --git a/src/Tests/SmartStore.Services.Tests/Shipping/ShippingServiceTests.cs b/src/Tests/SmartStore.Services.Tests/Shipping/ShippingServiceTests.cs index 47b1dec995..96d8595160 100644 --- a/src/Tests/SmartStore.Services.Tests/Shipping/ShippingServiceTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Shipping/ShippingServiceTests.cs @@ -12,7 +12,6 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Configuration; -using SmartStore.Services.Localization; using SmartStore.Services.Orders; using SmartStore.Services.Shipping; using SmartStore.Tests; @@ -29,7 +28,6 @@ public class ShippingServiceTests : ServiceTest ICheckoutAttributeParser _checkoutAttributeParser; ShippingSettings _shippingSettings; IEventPublisher _eventPublisher; - ILocalizationService _localizationService; IGenericAttributeService _genericAttributeService; IShippingService _shippingService; ShoppingCartSettings _shoppingCartSettings; @@ -52,7 +50,6 @@ public class ShippingServiceTests : ServiceTest _eventPublisher = MockRepository.GenerateMock(); _eventPublisher.Expect(x => x.Publish(Arg.Is.Anything)); - _localizationService = MockRepository.GenerateMock(); _genericAttributeService = MockRepository.GenerateMock(); _settingService = MockRepository.GenerateMock(); _typeFinder = MockRepository.GenerateMock(); @@ -65,7 +62,6 @@ public class ShippingServiceTests : ServiceTest _productService, _checkoutAttributeParser, _genericAttributeService, - _localizationService, _shippingSettings, _eventPublisher, _shoppingCartSettings, From 59d8711b1d7748be0a808c43df5b3531e08e0a08 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 9 Feb 2016 11:40:30 +0100 Subject: [PATCH 011/423] Confirm order: Ensure a friendly message if the payment failed --- .../201601262000441_ImportFramework1.cs | 4 +++ .../Orders/OrderProcessingService.cs | 2 ++ .../Services/PayPalHelper.cs | 25 ++++++++++++------- .../Controllers/CheckoutController.cs | 2 +- .../Views/Checkout/Confirm.Mobile.cshtml | 6 ++--- .../Views/Checkout/Confirm.cshtml | 2 +- 6 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs index b15e96101a..59826c2e3b 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs @@ -175,6 +175,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "The next payment date could not be calculated.", "Das Datum der nchsten Zahlung kann nicht ermittelt werden."); + builder.AddOrUpdate("Payment.PayingFailed", + "Unfortunately we can not handle this purchasing via your preferred payment method. Please select an alternate payment option to complete your order.", + "Leider knnen wir diesen Einkauf nicht ber die gewnschte Zahlungsart abwickeln. Bitte whlen Sie eine alternative Zahlungsoption aus, um Ihre Bestellung abzuschlieen."); + builder.AddOrUpdate("Order.InitialOrderDoesNotExistForRecurringPayment", "No initial order exists for the recurring payment.", "Fr die wiederkehrende Zahlung existiert kein Ausgangsauftrag."); diff --git a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs index 4ca617a354..dd832a61c1 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs @@ -1384,6 +1384,8 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR } else { + result.AddError(T("Payment.PayingFailed")); + foreach (var paymentError in processPaymentResult.Errors) { result.AddError(paymentError); diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalHelper.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalHelper.cs index 7c2a6d90de..1f057f46ef 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalHelper.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalHelper.cs @@ -74,8 +74,9 @@ public static PaymentStatus GetPaymentStatus(string paymentStatus, string pendin /// True - response OK; otherwise, false public static bool CheckSuccess(ILocalizationService localization, AbstractResponseType abstractResponse, out string errorMsg) { - bool success = false; - StringBuilder sb = new StringBuilder(); + var success = false; + var sb = new StringBuilder(); + switch (abstractResponse.Ack) { case AckCodeType.Success: @@ -85,19 +86,25 @@ public static bool CheckSuccess(ILocalizationService localization, AbstractRespo default: break; } + if (null != abstractResponse.Errors) { foreach (ErrorType errorType in abstractResponse.Errors) { - if (sb.Length <= 0) - { + if (errorType.ShortMessage.IsEmpty()) + continue; + + if (sb.Length > 0) sb.Append(Environment.NewLine); - } - 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); - } + + sb.Append("{0}: {1}".FormatInvariant(localization.GetResource("Admin.System.Log.Fields.ShortMessage"), errorType.ShortMessage)); + sb.AppendLine(" ({0}).".FormatInvariant(errorType.ErrorCode)); + + if (errorType.LongMessage.HasValue() && errorType.LongMessage != errorType.ShortMessage) + sb.AppendLine("{0}: {1}".FormatInvariant(localization.GetResource("Admin.System.Log.Fields.FullMessage"), errorType.LongMessage)); + } } + errorMsg = sb.ToString(); return success; } diff --git a/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs b/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs index e0ec2e2fd1..519b5c5d44 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs @@ -855,7 +855,7 @@ public ActionResult ConfirmOrder(FormCollection form) } else { - model.Warnings.AddRange(placeOrderResult.Errors); + model.Warnings.AddRange(placeOrderResult.Errors.Select(x => HtmlUtils.ConvertPlainTextToHtml(x))); } } catch (Exception exc) diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml index bd9f678373..ce45bc8284 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml @@ -27,9 +27,9 @@
      @for (int i = 0; i < Model.Warnings.Count; i++) - { -
    • @Model.Warnings[i]
    • - } + { +
    • @Html.Raw(Model.Warnings[i])
    • + }
    } diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml index 6257d2c92c..d750403536 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml @@ -76,7 +76,7 @@
      @for (int i = 0; i < Model.Warnings.Count; i++) { -
    • @Model.Warnings[i]
    • +
    • @Html.Raw(Model.Warnings[i])
    • }
    From f11e2c223c6843ebef65436f1197d495f8873f6f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 9 Feb 2016 20:49:26 +0100 Subject: [PATCH 012/423] Localized some hard coded strings --- .../201601262000441_ImportFramework1.cs | 28 +++- .../Messages/WorkflowMessageService.cs | 19 ++- .../Orders/OrderProcessingService.cs | 6 +- .../Controllers/InstallController.cs | 23 ++- .../Controllers/NewsletterController.cs | 143 +++++++++--------- .../Controllers/OrderController.cs | 85 ++++++----- .../Controllers/PollController.cs | 67 ++++---- .../Controllers/ProductController.cs | 4 +- 8 files changed, 208 insertions(+), 167 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs index 59826c2e3b..2feb789226 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs @@ -211,9 +211,9 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "There are no recurring products.", "Keine wiederkehrenden Produkte."); - builder.AddOrUpdate("Order.DoesNotExist", - "The order does not exist.", - "Der Auftrag existiert nicht."); + builder.AddOrUpdate("Order.NotFound", + "The order {0} was not found.", + "Der Auftrag {0} wurde nicht gefunden."); builder.AddOrUpdate("Order.CannotCancel", "Cannot cancel order.", @@ -263,6 +263,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "The email address is not valid.", "Die E-Mail-Adresse ist ungltig."); + builder.AddOrUpdate("Common.Error.NoActiveLanguage", + "No active language could be loaded.", + "Es wurde keine aktive Sprache gefunden."); + builder.AddOrUpdate("Admin.OrderNotice.RecurringPaymentCancellationError", "Unable to cancel recurring payment for order {0}.", "Es ist ein Fehler bei der Stornierung einer wiederkehrenden Zahlung fr Auftrag {0} aufgetreten."); @@ -299,7 +303,23 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Products.Variants.NotFound", "The product variant {0} was not found.", - "Die Produktvariante wurde {0} nicht gefunden."); + "Die Produktvariante {0} wurde nicht gefunden."); + + builder.AddOrUpdate("Reviews.NotFound", + "The product review {0} was not found.", + "Die Produktbewertung {0} wurde nicht gefunden."); + + builder.AddOrUpdate("Polls.AnswerNotFound", + "The poll answer {0} was not found.", + "Eine Umfrageantwort {0} wurde nicht gefunden."); + + builder.AddOrUpdate("Polls.NotAvailable", + "The poll is not available.", + "Die Umfrage ist nicht verfgbar."); + + builder.AddOrUpdate("Install.LanguageNotRegistered", + "The install language '{0}' is not registered.", + "Die Installationssprache '{0}' ist nicht registriert."); } } } diff --git a/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs b/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs index a806bc68e8..1f2746245e 100644 --- a/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs +++ b/src/Libraries/SmartStore.Services/Messages/WorkflowMessageService.cs @@ -15,6 +15,7 @@ using SmartStore.Core.Domain.Shipping; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; +using SmartStore.Core.Localization; using SmartStore.Services.Customers; using SmartStore.Services.Localization; using SmartStore.Services.Media; @@ -72,13 +73,17 @@ public WorkflowMessageService( this._workContext = workContext; this._httpRequest = httpRequest; this._downloadServioce = downloadServioce; - } - #endregion + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + #endregion - #region Utilities + #region Utilities - protected int SendNotification( + protected int SendNotification( MessageTemplate messageTemplate, EmailAccount emailAccount, int languageId, @@ -250,7 +255,7 @@ protected Language EnsureLanguageIsActive(int languageId, int storeId) } if (language == null) - throw new Exception("No active language could be loaded"); + throw new SmartException(T("Common.Error.NoActiveLanguage")); return language; } @@ -493,7 +498,7 @@ public virtual int SendShipmentSentCustomerNotification(Shipment shipment, int l var order = shipment.Order; if (order == null) - throw new Exception("Order cannot be loaded"); + throw new SmartException(T("Order.NotFound", shipment.OrderId)); var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; var language = EnsureLanguageIsActive(languageId, store.Id); @@ -532,7 +537,7 @@ public virtual int SendShipmentDeliveredCustomerNotification(Shipment shipment, var order = shipment.Order; if (order == null) - throw new Exception("Order cannot be loaded"); + throw new SmartException(T("Order.NotFound", shipment.OrderId)); var store = _storeService.GetStoreById(order.StoreId) ?? _storeContext.CurrentStore; var language = EnsureLanguageIsActive(languageId, store.Id); diff --git a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs index dd832a61c1..9210d60a57 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs @@ -1484,7 +1484,7 @@ public virtual void ProcessNextRecurringPayment(RecurringPayment recurringPaymen if (result.Success) { if (result.PlacedOrder == null) - throw new SmartException(T("Order.DoesNotExist")); + throw new SmartException(T("Order.NotFound", "".NaIfEmpty())); var rph = new RecurringPaymentHistory { @@ -1608,7 +1608,7 @@ public virtual void Ship(Shipment shipment, bool notifyCustomer) var order = _orderService.GetOrderById(shipment.OrderId); if (order == null) - throw new SmartException(T("Order.DoesNotExist")); + throw new SmartException(T("Order.NotFound", shipment.OrderId)); if (shipment.ShippedDateUtc.HasValue) throw new SmartException(T("Shipment.AlreadyShipped")); @@ -1652,7 +1652,7 @@ public virtual void Deliver(Shipment shipment, bool notifyCustomer) var order = shipment.Order; if (order == null) - throw new SmartException(T("Order.DoesNotExist")); + throw new SmartException(T("Order.NotFound", shipment.OrderId)); if (shipment.DeliveryDateUtc.HasValue) throw new SmartException(T("Shipment.AlreadyDelivered")); diff --git a/src/Presentation/SmartStore.Web/Controllers/InstallController.cs b/src/Presentation/SmartStore.Web/Controllers/InstallController.cs index e0bef97946..46b37c5dd1 100644 --- a/src/Presentation/SmartStore.Web/Controllers/InstallController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/InstallController.cs @@ -1,33 +1,28 @@ -using Autofac; -using System; +using System; using System.Collections.Generic; +using System.Data.Entity; using System.Data.SqlClient; using System.Linq; using System.Security.Principal; using System.Threading; using System.Threading.Tasks; -using System.Web; -using System.Web.SessionState; -using System.Web.Caching; using System.Web.Hosting; using System.Web.Mvc; -using System.ComponentModel.Composition; +using System.Web.SessionState; +using Autofac; using SmartStore.Core; -using SmartStore.Core.Caching; +using SmartStore.Core.Async; using SmartStore.Core.Data; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Infrastructure; using SmartStore.Core.Plugins; +using SmartStore.Data; +using SmartStore.Data.Setup; using SmartStore.Services.Security; +using SmartStore.Utilities; using SmartStore.Web.Framework.Security; using SmartStore.Web.Infrastructure.Installation; using SmartStore.Web.Models.Install; -using SmartStore.Core.Async; -using System.Data.Entity; -using SmartStore.Data; -using SmartStore.Data.Setup; -using System.Configuration; -using SmartStore.Utilities; namespace SmartStore.Web.Controllers { @@ -480,7 +475,7 @@ protected virtual InstallationResult InstallCore(ILifetimeScope scope, InstallMo { return UpdateResult(x => { - x.Errors.Add(string.Format("The install language '{0}' is not registered", model.PrimaryLanguage)); + x.Errors.Add(_locService.GetResource("Install.LanguageNotRegistered").FormatInvariant(model.PrimaryLanguage)); x.Completed = true; x.Success = false; x.RedirectUrl = null; diff --git a/src/Presentation/SmartStore.Web/Controllers/NewsletterController.cs b/src/Presentation/SmartStore.Web/Controllers/NewsletterController.cs index ad148aeb33..d41e2a40b0 100644 --- a/src/Presentation/SmartStore.Web/Controllers/NewsletterController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/NewsletterController.cs @@ -3,16 +3,14 @@ using SmartStore.Core; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Messages; -using SmartStore.Services.Localization; using SmartStore.Services.Messages; -using SmartStore.Web.Models.Newsletter; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Models.Newsletter; namespace SmartStore.Web.Controllers { - public partial class NewsletterController : PublicControllerBase + public partial class NewsletterController : PublicControllerBase { - private readonly ILocalizationService _localizationService; private readonly IWorkContext _workContext; private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; private readonly IWorkflowMessageService _workflowMessageService; @@ -20,12 +18,13 @@ public partial class NewsletterController : PublicControllerBase private readonly CustomerSettings _customerSettings; - public NewsletterController(ILocalizationService localizationService, - IWorkContext workContext, INewsLetterSubscriptionService newsLetterSubscriptionService, - IWorkflowMessageService workflowMessageService, CustomerSettings customerSettings, + public NewsletterController( + IWorkContext workContext, + INewsLetterSubscriptionService newsLetterSubscriptionService, + IWorkflowMessageService workflowMessageService, + CustomerSettings customerSettings, IStoreContext storeContext) { - this._localizationService = localizationService; this._workContext = workContext; this._newsLetterSubscriptionService = newsLetterSubscriptionService; this._workflowMessageService = workflowMessageService; @@ -47,56 +46,59 @@ public ActionResult NewsletterBox() public ActionResult Subscribe(bool subscribe, string email) { string result; - bool success = false; + var success = false; if (!email.IsEmail()) - result = _localizationService.GetResource("Newsletter.Email.Wrong"); - else - { - //subscribe/unsubscribe - email = email.Trim(); - - var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(email, _storeContext.CurrentStore.Id); - if (subscription != null) - { - if (subscribe) - { - if (!subscription.Active) - { - _workflowMessageService.SendNewsLetterSubscriptionActivationMessage(subscription, _workContext.WorkingLanguage.Id); - } - result = _localizationService.GetResource("Newsletter.SubscribeEmailSent"); - } - else - { - if (subscription.Active) - { - _workflowMessageService.SendNewsLetterSubscriptionDeactivationMessage(subscription, _workContext.WorkingLanguage.Id); - } - result = _localizationService.GetResource("Newsletter.UnsubscribeEmailSent"); - } - } - else if (subscribe) - { - subscription = new NewsLetterSubscription() - { - NewsLetterSubscriptionGuid = Guid.NewGuid(), - Email = email, - Active = false, - CreatedOnUtc = DateTime.UtcNow, + { + result = T("Newsletter.Email.Wrong"); + } + else + { + //subscribe/unsubscribe + email = email.Trim(); + + var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(email, _storeContext.CurrentStore.Id); + if (subscription != null) + { + if (subscribe) + { + if (!subscription.Active) + { + _workflowMessageService.SendNewsLetterSubscriptionActivationMessage(subscription, _workContext.WorkingLanguage.Id); + } + result = T("Newsletter.SubscribeEmailSent"); + } + else + { + if (subscription.Active) + { + _workflowMessageService.SendNewsLetterSubscriptionDeactivationMessage(subscription, _workContext.WorkingLanguage.Id); + } + result = T("Newsletter.UnsubscribeEmailSent"); + } + } + else if (subscribe) + { + subscription = new NewsLetterSubscription + { + NewsLetterSubscriptionGuid = Guid.NewGuid(), + Email = email, + Active = false, + CreatedOnUtc = DateTime.UtcNow, StoreId = _storeContext.CurrentStore.Id - }; - _newsLetterSubscriptionService.InsertNewsLetterSubscription(subscription); - _workflowMessageService.SendNewsLetterSubscriptionActivationMessage(subscription, _workContext.WorkingLanguage.Id); - - result = _localizationService.GetResource("Newsletter.SubscribeEmailSent"); - } - else - { - result = _localizationService.GetResource("Newsletter.UnsubscribeEmailSent"); - } - success = true; - } + }; + + _newsLetterSubscriptionService.InsertNewsLetterSubscription(subscription); + _workflowMessageService.SendNewsLetterSubscriptionActivationMessage(subscription, _workContext.WorkingLanguage.Id); + + result = T("Newsletter.SubscribeEmailSent"); + } + else + { + result = T("Newsletter.UnsubscribeEmailSent"); + } + success = true; + } return Json(new { @@ -108,25 +110,26 @@ public ActionResult Subscribe(bool subscribe, string email) public ActionResult SubscriptionActivation(Guid token, bool active) { var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByGuid(token); - if (subscription == null) + if (subscription == null) + { return HttpNotFound(); + } var model = new SubscriptionActivationModel(); - if (active) - { - subscription.Active = active; - _newsLetterSubscriptionService.UpdateNewsLetterSubscription(subscription); - } - else - _newsLetterSubscriptionService.DeleteNewsLetterSubscription(subscription); - - if (active) - model.Result = _localizationService.GetResource("Newsletter.ResultActivated"); - else - model.Result = _localizationService.GetResource("Newsletter.ResultDeactivated"); - - return View(model); + if (active) + { + subscription.Active = active; + _newsLetterSubscriptionService.UpdateNewsLetterSubscription(subscription); + } + else + { + _newsLetterSubscriptionService.DeleteNewsLetterSubscription(subscription); + } + + model.Result = T(active ? "Newsletter.ResultActivated" : "Newsletter.ResultDeactivated"); + + return View(model); } } } diff --git a/src/Presentation/SmartStore.Web/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Controllers/OrderController.cs index 8254820961..b3acce902b 100644 --- a/src/Presentation/SmartStore.Web/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/OrderController.cs @@ -111,6 +111,7 @@ protected OrderDetailsModel PrepareOrderDetailsModel(Order order) throw new ArgumentNullException("order"); var store = _storeService.GetStoreById(order.StoreId) ?? _services.StoreContext.CurrentStore; + var language = _services.WorkContext.WorkingLanguage; var orderSettings = _services.Settings.LoadSetting(store.Id); var catalogSettings = _services.Settings.LoadSetting(store.Id); @@ -184,56 +185,62 @@ protected OrderDetailsModel PrepareOrderDetailsModel(Order order) case TaxDisplayType.ExcludingTax: { //order subtotal - var orderSubtotalExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalExclTax, order.CurrencyRate); - model.OrderSubtotal = _priceFormatter.FormatPrice(orderSubtotalExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, false, false); + var orderSubtotalExclTax = _currencyService.ConvertCurrency(order.OrderSubtotalExclTax, order.CurrencyRate); + model.OrderSubtotal = _priceFormatter.FormatPrice(orderSubtotalExclTax, true, order.CustomerCurrencyCode, language, false, false); //discount (applied to order subtotal) - var orderSubTotalDiscountExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountExclTax, order.CurrencyRate); - if (orderSubTotalDiscountExclTaxInCustomerCurrency > decimal.Zero) + var orderSubTotalDiscountExclTax = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountExclTax, order.CurrencyRate); + if (orderSubTotalDiscountExclTax > decimal.Zero) { - model.OrderSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountExclTaxInCustomerCurrency, true, - order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, false, false); + model.OrderSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountExclTax, true, order.CustomerCurrencyCode, language, false, false); } //order shipping - var orderShippingExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingExclTax, order.CurrencyRate); - model.OrderShipping = _priceFormatter.FormatShippingPrice(orderShippingExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, false, false); + var orderShippingExclTax = _currencyService.ConvertCurrency(order.OrderShippingExclTax, order.CurrencyRate); + model.OrderShipping = _priceFormatter.FormatShippingPrice(orderShippingExclTax, true, order.CustomerCurrencyCode, language, false, false); + //payment method additional fee - var paymentMethodAdditionalFeeExclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeExclTax, order.CurrencyRate); - if (paymentMethodAdditionalFeeExclTaxInCustomerCurrency != decimal.Zero) - model.PaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeExclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, false, false); + var paymentMethodAdditionalFeeExclTax = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeExclTax, order.CurrencyRate); + if (paymentMethodAdditionalFeeExclTax != decimal.Zero) + { + model.PaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeExclTax, true, order.CustomerCurrencyCode, + language, false, false); + } } break; case TaxDisplayType.IncludingTax: { //order subtotal - var orderSubtotalInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubtotalInclTax, order.CurrencyRate); - model.OrderSubtotal = _priceFormatter.FormatPrice(orderSubtotalInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, true, false); + var orderSubtotalInclTax = _currencyService.ConvertCurrency(order.OrderSubtotalInclTax, order.CurrencyRate); + model.OrderSubtotal = _priceFormatter.FormatPrice(orderSubtotalInclTax, true, order.CustomerCurrencyCode, language, true, false); //discount (applied to order subtotal) - var orderSubTotalDiscountInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountInclTax, order.CurrencyRate); - if (orderSubTotalDiscountInclTaxInCustomerCurrency > decimal.Zero) + var orderSubTotalDiscountInclTax = _currencyService.ConvertCurrency(order.OrderSubTotalDiscountInclTax, order.CurrencyRate); + if (orderSubTotalDiscountInclTax > decimal.Zero) { - model.OrderSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountInclTaxInCustomerCurrency, true, - order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, true, false); + model.OrderSubTotalDiscount = _priceFormatter.FormatPrice(-orderSubTotalDiscountInclTax, true, order.CustomerCurrencyCode, language, true, false); } //order shipping - var orderShippingInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderShippingInclTax, order.CurrencyRate); - model.OrderShipping = _priceFormatter.FormatShippingPrice(orderShippingInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, true, false); + var orderShippingInclTax = _currencyService.ConvertCurrency(order.OrderShippingInclTax, order.CurrencyRate); + model.OrderShipping = _priceFormatter.FormatShippingPrice(orderShippingInclTax, true, order.CustomerCurrencyCode, language, true, false); //payment method additional fee - var paymentMethodAdditionalFeeInclTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeInclTax, order.CurrencyRate); - if (paymentMethodAdditionalFeeInclTaxInCustomerCurrency != decimal.Zero) - model.PaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeInclTaxInCustomerCurrency, true, order.CustomerCurrencyCode, _services.WorkContext.WorkingLanguage, true, false); + var paymentMethodAdditionalFeeInclTax = _currencyService.ConvertCurrency(order.PaymentMethodAdditionalFeeInclTax, order.CurrencyRate); + if (paymentMethodAdditionalFeeInclTax != decimal.Zero) + { + model.PaymentMethodAdditionalFee = _priceFormatter.FormatPaymentMethodAdditionalFee(paymentMethodAdditionalFeeInclTax, true, order.CustomerCurrencyCode, + language, true, false); + } } break; } //tax - bool displayTax = true; - bool displayTaxRates = true; + var displayTax = true; + var displayTaxRates = true; + if (taxSettings.HideTaxInOrderSummary && order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax) { displayTax = false; @@ -252,31 +259,34 @@ protected OrderDetailsModel PrepareOrderDetailsModel(Order order) displayTax = !displayTaxRates; var orderTaxInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTax, order.CurrencyRate); - //TODO pass languageId to _priceFormatter.FormatPrice - model.Tax = _priceFormatter.FormatPrice(orderTaxInCustomerCurrency, true, order.CustomerCurrencyCode, false, _services.WorkContext.WorkingLanguage); + + model.Tax = _priceFormatter.FormatPrice(orderTaxInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); foreach (var tr in order.TaxRatesDictionary) { var rate = _priceFormatter.FormatTaxRate(tr.Key); - var labelKey = "ShoppingCart.Totals.TaxRateLine" + (_services.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax ? "Incl" : "Excl"); - model.TaxRates.Add(new OrderDetailsModel.TaxRate() + //var labelKey = "ShoppingCart.Totals.TaxRateLine" + (_services.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax ? "Incl" : "Excl"); + var labelKey = (_services.WorkContext.TaxDisplayType == TaxDisplayType.IncludingTax ? "ShoppingCart.Totals.TaxRateLineIncl" : "ShoppingCart.Totals.TaxRateLineExcl"); + + model.TaxRates.Add(new OrderDetailsModel.TaxRate { Rate = rate, Label = T(labelKey).Text.FormatCurrent(rate), - //TODO pass languageId to _priceFormatter.FormatPrice - Value = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(tr.Value, order.CurrencyRate), true, order.CustomerCurrencyCode, false, _services.WorkContext.WorkingLanguage), + Value = _priceFormatter.FormatPrice(_currencyService.ConvertCurrency(tr.Value, order.CurrencyRate), true, order.CustomerCurrencyCode, false, language), }); } } } + model.DisplayTaxRates = displayTaxRates; model.DisplayTax = displayTax; //discount (applied to order total) var orderDiscountInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderDiscount, order.CurrencyRate); - if (orderDiscountInCustomerCurrency > decimal.Zero) - model.OrderTotalDiscount = _priceFormatter.FormatPrice(-orderDiscountInCustomerCurrency, true, order.CustomerCurrencyCode, false, _services.WorkContext.WorkingLanguage); - + if (orderDiscountInCustomerCurrency > decimal.Zero) + { + model.OrderTotalDiscount = _priceFormatter.FormatPrice(-orderDiscountInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); + } //gift cards foreach (var gcuh in order.GiftCardUsageHistory) @@ -284,7 +294,7 @@ protected OrderDetailsModel PrepareOrderDetailsModel(Order order) model.GiftCards.Add(new OrderDetailsModel.GiftCard { CouponCode = gcuh.GiftCard.GiftCardCouponCode, - Amount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(gcuh.UsedValue, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, _services.WorkContext.WorkingLanguage), + Amount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(gcuh.UsedValue, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, language), }); } @@ -292,12 +302,13 @@ protected OrderDetailsModel PrepareOrderDetailsModel(Order order) if (order.RedeemedRewardPointsEntry != null) { model.RedeemedRewardPoints = -order.RedeemedRewardPointsEntry.Points; - model.RedeemedRewardPointsAmount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(order.RedeemedRewardPointsEntry.UsedAmount, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, _services.WorkContext.WorkingLanguage); + model.RedeemedRewardPointsAmount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(order.RedeemedRewardPointsEntry.UsedAmount, order.CurrencyRate)), + true, order.CustomerCurrencyCode, false, language); } //total var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); - model.OrderTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, _services.WorkContext.WorkingLanguage); + model.OrderTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, language); //checkout attributes model.CheckoutAttributeInfo = HtmlUtils.ConvertPlainTextToTable(HtmlUtils.ConvertHtmlToPlainText(order.CheckoutAttributeDescription)); @@ -338,7 +349,7 @@ protected ShipmentDetailsModel PrepareShipmentDetailsModel(Shipment shipment) var order = shipment.Order; if (order == null) - throw new Exception("order cannot be loaded"); + throw new SmartException(T("Order.NotFound", shipment.OrderId)); var store = _storeService.GetStoreById(order.StoreId) ?? _services.StoreContext.CurrentStore; var catalogSettings = _services.Settings.LoadSetting(store.Id); diff --git a/src/Presentation/SmartStore.Web/Controllers/PollController.cs b/src/Presentation/SmartStore.Web/Controllers/PollController.cs index 6b51058139..1919207834 100644 --- a/src/Presentation/SmartStore.Web/Controllers/PollController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/PollController.cs @@ -6,7 +6,6 @@ using SmartStore.Core.Caching; using SmartStore.Core.Domain.Polls; using SmartStore.Services.Customers; -using SmartStore.Services.Localization; using SmartStore.Services.Polls; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Infrastructure.Cache; @@ -14,11 +13,10 @@ namespace SmartStore.Web.Controllers { - public partial class PollController : PublicControllerBase + public partial class PollController : PublicControllerBase { #region Fields - private readonly ILocalizationService _localizationService; private readonly IWorkContext _workContext; private readonly IPollService _pollService; private readonly IWebHelper _webHelper; @@ -29,12 +27,13 @@ public partial class PollController : PublicControllerBase #region Constructors - public PollController(ILocalizationService localizationService, - IWorkContext workContext, IPollService pollService, - IWebHelper webHelper, ICacheManager cacheManager, + public PollController( + IWorkContext workContext, + IPollService pollService, + IWebHelper webHelper, + ICacheManager cacheManager, IStoreContext storeContext) { - this._localizationService = localizationService; this._workContext = workContext; this._pollService = pollService; this._webHelper = webHelper; @@ -49,18 +48,23 @@ public PollController(ILocalizationService localizationService, [NonAction] protected PollModel PreparePollModel(Poll poll, bool setAlreadyVotedProperty) { - var model = new PollModel() + var model = new PollModel { Id = poll.Id, AlreadyVoted = setAlreadyVotedProperty && _pollService.AlreadyVoted(poll.Id, _workContext.CurrentCustomer.Id), Name = poll.Name }; + var answers = poll.PollAnswers.OrderBy(x => x.DisplayOrder); - foreach (var answer in answers) - model.TotalVotes += answer.NumberOfVotes; + + foreach (var answer in answers) + { + model.TotalVotes += answer.NumberOfVotes; + } + foreach (var pa in answers) { - model.Answers.Add(new PollAnswerModel() + model.Answers.Add(new PollAnswerModel { Id = pa.Id, Name = pa.Name, @@ -88,10 +92,11 @@ public ActionResult PollBlock(string systemKeyword) var poll = _pollService.GetPollBySystemKeyword(systemKeyword, _workContext.WorkingLanguage.Id); if (poll == null) - return new PollModel() { Id = 0 }; //we do not cache nulls. that's why let's return an empty record (ID = 0) + return new PollModel { Id = 0 }; //we do not cache nulls. that's why let's return an empty record (ID = 0) return PreparePollModel(poll, false); }); + if (cachedModel == null || cachedModel.Id == 0) return Content(""); @@ -108,30 +113,29 @@ public ActionResult PollBlock(string systemKeyword) public ActionResult Vote(int pollAnswerId) { var pollAnswer = _pollService.GetPollAnswerById(pollAnswerId); - if (pollAnswer == null) - return Json(new - { - error = "No poll answer found with the specified id", - }); + + if (pollAnswer == null) + { + return Json(new { error = T("Polls.AnswerNotFound", pollAnswerId).Text }); + } var poll = pollAnswer.Poll; - if (!poll.Published) - return Json(new - { - error = "Poll is not available", - }); - if (_workContext.CurrentCustomer.IsGuest() && !poll.AllowGuestsToVote) - return Json(new - { - error = _localizationService.GetResource("Polls.OnlyRegisteredUsersVote"), - }); + if (!poll.Published) + { + return Json(new { error = T("Polls.NotAvailable").Text }); + } + + if (_workContext.CurrentCustomer.IsGuest() && !poll.AllowGuestsToVote) + { + return Json(new { error = T("Polls.OnlyRegisteredUsersVote").Text }); + } bool alreadyVoted = _pollService.AlreadyVoted(poll.Id, _workContext.CurrentCustomer.Id); if (!alreadyVoted) { //vote - pollAnswer.PollVotingRecords.Add(new PollVotingRecord() + pollAnswer.PollVotingRecords.Add(new PollVotingRecord { PollAnswerId = pollAnswer.Id, CustomerId = _workContext.CurrentCustomer.Id, @@ -140,8 +144,10 @@ public ActionResult Vote(int pollAnswerId) CreatedOnUtc = DateTime.UtcNow, UpdatedOnUtc = DateTime.UtcNow, }); + //update totals pollAnswer.NumberOfVotes = pollAnswer.PollVotingRecords.Count; + _pollService.UpdatePoll(poll); } @@ -161,12 +167,14 @@ public ActionResult HomePagePolls() .Select(x => PreparePollModel(x, false)) .ToList(); }); + //"AlreadyVoted" property of "PollModel" object depends on the current customer. Let's update it. //But first we need to clone the cached model (the updated one should not be cached) var model = new List(); + foreach (var p in cachedModel) { - var pollModel = (PollModel) p.Clone(); + var pollModel = (PollModel)p.Clone(); pollModel.AlreadyVoted = _pollService.AlreadyVoted(pollModel.Id, _workContext.CurrentCustomer.Id); model.Add(pollModel); } @@ -178,6 +186,5 @@ public ActionResult HomePagePolls() } #endregion - } } diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index 6bd64d3cd9..2e796fcfce 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -893,7 +893,7 @@ public ActionResult SetReviewHelpfulness(int productReviewId, bool washelpful) { var productReview = _customerContentService.GetCustomerContentById(productReviewId) as ProductReview; if (productReview == null) - throw new ArgumentException("No product review found with the specified id"); + throw new ArgumentException(T("Reviews.NotFound", productReviewId)); if (_services.WorkContext.CurrentCustomer.IsGuest() && !_catalogSettings.AllowAnonymousUsersToReviewProduct) { @@ -1032,7 +1032,7 @@ public ActionResult AskQuestionSend(ProductAskQuestionModel model, bool captchaV } else { - ModelState.AddModelError("", "Fehler beim Versenden der Email. Bitte versuchen Sie es später erneut."); + ModelState.AddModelError("", T("Common.Error.SendMail")); } } From 64b1673cb28c1546d3bb43f5f010456615c21ea8 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 10 Feb 2016 12:46:23 +0100 Subject: [PATCH 013/423] Fixed: Deletion of a customer could delete all newsletter subscriptions --- changelog.md | 1 + .../Messages/NewsLetterSubscriptionService.cs | 3 ++- .../Controllers/CustomerController.cs | 25 +++++++++++-------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/changelog.md b/changelog.md index 8ade03b5b7..2b5067eadf 100644 --- a/changelog.md +++ b/changelog.md @@ -101,6 +101,7 @@ * Product import: Store mappings were not applied when inserting new records * Faulty permission handling in ajax grid actions (no message, infinite loading icon) * Grouped products: Display order was not correct +* Deletion of a customer could delete all newsletter subscriptions ## SmartStore.NET 2.2.2 diff --git a/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs b/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs index 997a70f1ee..fe238a9530 100644 --- a/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs +++ b/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs @@ -114,7 +114,8 @@ public void UpdateNewsLetterSubscription(NewsLetterSubscription newsLetterSubscr /// if set to true [publish subscription events]. public virtual void DeleteNewsLetterSubscription(NewsLetterSubscription newsLetterSubscription, bool publishSubscriptionEvents = true) { - if (newsLetterSubscription == null) throw new ArgumentNullException("newsLetterSubscription"); + if (newsLetterSubscription == null) + throw new ArgumentNullException("newsLetterSubscription"); _subscriptionRepository.Delete(newsLetterSubscription); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs index e79a61221b..fb6fcf84ed 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CustomerController.cs @@ -1038,30 +1038,33 @@ public ActionResult Delete(int id) var customer = _customerService.GetCustomerById(id); if (customer == null) - //No customer found with the specified id return RedirectToAction("List"); try { _customerService.DeleteCustomer(customer); - //remove newsletter subscriptions (if exists) - var subscriptions = _newsLetterSubscriptionService.GetAllNewsLetterSubscriptions(customer.Email, 0, int.MaxValue, true); - - foreach (var subscription in subscriptions) + if (customer.Email.HasValue()) { - _newsLetterSubscriptionService.DeleteNewsLetterSubscription(subscription); + foreach (var store in _storeService.GetAllStores()) + { + var subscription = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(customer.Email, store.Id); + if (subscription != null) + { + _newsLetterSubscriptionService.DeleteNewsLetterSubscription(subscription); + } + } } - //activity log - _customerActivityService.InsertActivity("DeleteCustomer", _localizationService.GetResource("ActivityLog.DeleteCustomer"), customer.Id); + _customerActivityService.InsertActivity("DeleteCustomer", T("ActivityLog.DeleteCustomer", customer.Id)); + + NotifySuccess(T("Admin.Customers.Customers.Deleted")); - NotifySuccess(_localizationService.GetResource("Admin.Customers.Customers.Deleted")); return RedirectToAction("List"); } - catch (Exception exc) + catch (Exception exception) { - NotifyError(exc.Message); + NotifyError(exception.Message); return RedirectToAction("Edit", new { id = customer.Id }); } } From eaab4be5ea542bff1d24907ee6c5a3a8f372358b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 11 Feb 2016 11:48:24 +0100 Subject: [PATCH 014/423] Some improvements on import column mapping --- .../Controllers/ImportController.cs | 4 +- .../Views/Import/_ColumnMappings.cshtml | 10 +-- .../Views/Import/_CreateOrUpdate.cshtml | 63 +++++++++---------- 3 files changed, 37 insertions(+), 40 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index 82bceb25be..38734f0f64 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -100,14 +100,14 @@ private void PrepareProfileModel(ImportProfileModel model, ImportProfile profile { var csvConverter = new CsvConfigurationConverter(); csvConfiguration = csvConverter.ConvertFrom(profile.FileTypeConfiguration) ?? CsvConfiguration.ExcelFriendlyConfiguration; + + model.CsvConfiguration = new CsvConfigurationModel(csvConfiguration); } else { csvConfiguration = CsvConfiguration.ExcelFriendlyConfiguration; } - model.CsvConfiguration = new CsvConfigurationModel(csvConfiguration); - // column mapping model.AvailableSourceColumns = new List(); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml index d25d0dd4a4..b33b0ae412 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml @@ -41,7 +41,7 @@ - @foreach (var column in Model.AvailableSourceColumns) { @@ -50,13 +50,13 @@ - + - - @@ -106,7 +106,7 @@ @(mapping.Column.HasValue() ? "" : "disabled='disabled'") style="width: 97%;" /> - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_CreateOrUpdate.cshtml index 31534c8da5..d73c87f4b0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_CreateOrUpdate.cshtml @@ -229,33 +229,23 @@ else if (Model.ExistingFileNames.Count > 1) // mapping row handling $('#ImportColumnMappings').on('change', '.mapping-control-property', function () { - var row = $(this).closest('tr'), - property = $(this).select2('val'), - hasProperty = !_.isEmpty(property); + var row = $(this).closest('tr'); if (row.hasClass('column-mapping-new')) { - row.find('.mapping-control-column').select2('val', property).select2(hasProperty ? 'enable' : 'disable').prop('disabled', !hasProperty); + var hasColumn = !_.isEmpty(row.find('.mapping-control-column').select2('val')), + hasProperty = !_.isEmpty($(this).select2('val')); - var hasColumn = !_.isEmpty(row.find('.mapping-control-column').select2('val')); - row.find('.mapping-control-default').prop('disabled', !(hasProperty && hasColumn)); - row.find('.mapping-button-add').toggle(hasProperty); - } - else { - var hasColumn = !_.isEmpty(row.find('.mapping-control-column').select2('val')); - if (!hasColumn) { - row.find('.mapping-control-column').select2('val', property); - } + row.find('.mapping-button-add').prop('disabled', !(hasProperty && hasColumn)); } }).on('change', '.mapping-control-column', function () { - var row = $(this).closest('tr'), - hasColumn = !_.isEmpty($(this).select2('val')), - hasProperty = !_.isEmpty(row.find('.mapping-control-property').select2('val')); + var row = $(this).closest('tr'); if (row.hasClass('column-mapping-new')) { - row.find('.mapping-button-add').toggle(hasProperty); - } + var hasColumn = !_.isEmpty($(this).select2('val')), + hasProperty = !_.isEmpty(row.find('.mapping-control-property').select2('val')); - row.find('.mapping-control-default').prop('disabled', !(hasProperty && hasColumn)); + row.find('.mapping-button-add').prop('disabled', !(hasProperty && hasColumn)); + } }).on('click', '.mapping-button-add', function () { var row = $(this).closest('tr'), table = row.closest('table'), @@ -327,12 +317,13 @@ else if (Model.ExistingFileNames.Count > 1) try { if (item.text.length > 0) { var option = $(item.element), + isLocalized = (item.id.substring(item.id.length - 1) === ']'), html = ''; if (option.length === 0) { html += '
    '; html += ''; - html += ''; + html += ''; html += ''; } else { @@ -354,18 +345,20 @@ else if (Model.ExistingFileNames.Count > 1) html += (mappingIndex === -1 ? '
    ' : '
    '); html += ''; - html += (mappingIndex === -1 ? '' : ''); + html += '' : ' text-warning" />'); html += ''; } html += '' + item.id + ''; - html += ' 50) { - html += item.text + '">' + item.text.substring(0, 50) + '…'; + html += ' title="' + $('
    ').text(item.text).html() + '">' + item.text.substring(0, 50) + '…'; } else { - html += '">' + item.text; + html += '>' + item.text; } html += ''; html += '
    '; @@ -379,7 +372,7 @@ else if (Model.ExistingFileNames.Count > 1) function selectColumnFormatting(item, container, query) { try { - if (item.text.length > 0) { + if (item.id.length > 0) { var html = ''; if (item.element) { @@ -387,16 +380,20 @@ else if (Model.ExistingFileNames.Count > 1) } html += '
    '; - html += '' + item.id + ''; - html += '' + item.text.substring(0, 50) + '…'; - } - else { - html += '">' + item.text; + if (query) { + html += ' 50) { + html += ' title="' + $('
    ').text(item.text).html() + '">' + item.text.substring(0, 50) + '…'; + } + else { + html += '>' + item.text; + } + html += ''; } - html += ''; + + html += '' + item.id + ''; + html += '
    '; return html; } From 922b807be37a9bb3b3d8f82f6287d448e4124726 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 11 Feb 2016 19:37:47 +0100 Subject: [PATCH 015/423] Import column mapping: Less restrictive column versus entity property comparing --- .../Import/ColumnMapping/ColumnMap.cs | 4 +- .../Import/DataTable/LightweightDataTable.cs | 42 +++++++++++++++---- .../Import/ImportProfileService.cs | 2 +- .../Controllers/ImportController.cs | 34 +++++++++++---- .../Views/Import/_ColumnMappings.cshtml | 3 +- .../Views/Import/_CreateOrUpdate.cshtml | 2 +- 6 files changed, 67 insertions(+), 20 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs index 8ea5adaa60..813f29b021 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs @@ -103,7 +103,7 @@ public ColumnMappingValue GetMapping(string sourceColumn) return result; } - var crossPair = _map.FirstOrDefault(x => x.Value.Property != null && x.Value.Property == sourceColumn); + var crossPair = _map.FirstOrDefault(x => x.Value.Property.IsCaseInsensitiveEqual(sourceColumn)); if (crossPair.Key.HasValue()) { @@ -144,7 +144,7 @@ public string GetMappedProperty(string sourceColumn) return result.Property; } - var crossPair = _map.FirstOrDefault(x => x.Value.Property == sourceColumn); + var crossPair = _map.FirstOrDefault(x => x.Value.Property.IsCaseInsensitiveEqual(sourceColumn)); if (crossPair.Key.HasValue()) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataTable/LightweightDataTable.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataTable/LightweightDataTable.cs index dde6cb96d2..68cb99eb9c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataTable/LightweightDataTable.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataTable/LightweightDataTable.cs @@ -17,6 +17,7 @@ public class LightweightDataTable : IDataTable private readonly IList _columns; private readonly IList _rows; private readonly IDictionary _columnIndexes; + private readonly IDictionary _alternativeColumnIndexes; public LightweightDataTable(IList columns, IList data) { @@ -34,26 +35,42 @@ public LightweightDataTable(IList columns, IList data) _rows = new ReadOnlyCollection(rows); _columnIndexes = new Dictionary(StringComparer.OrdinalIgnoreCase); + _alternativeColumnIndexes = new Dictionary(StringComparer.OrdinalIgnoreCase); + for (var i = 0; i < columns.Count; i++) { - _columnIndexes[columns[i].Name] = i; + var name = columns[i].Name; + var alternativeName = GetAlternativeColumnNameFor(name); + + _columnIndexes[name] = i; + + if (!alternativeName.IsCaseInsensitiveEqual(name)) + _alternativeColumnIndexes[alternativeName] = i; } - } + } public bool HasColumn(string name) { - if (name.IsEmpty()) - return false; + if (name.HasValue()) + { + return (_columnIndexes.ContainsKey(name) || _alternativeColumnIndexes.ContainsKey(name)); + } - return _columnIndexes.ContainsKey(name); + return false; } public int GetColumnIndex(string name) { int index; - if (name.HasValue() && _columnIndexes.TryGetValue(name, out index)) - return index; + if (name.HasValue()) + { + if (_columnIndexes.TryGetValue(name, out index)) + return index; + + if (_alternativeColumnIndexes.TryGetValue(name, out index)) + return index; + } return -1; } @@ -74,6 +91,17 @@ public IList Rows } } + public static string GetAlternativeColumnNameFor(string name) + { + if (name.IsEmpty()) + return name; + + return name + .Replace(" ", "") + .Replace("-", "") + .Replace("_", ""); + } + public static IDataTable FromPostedFile( HttpPostedFileBase file, int skip = 0, diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs index 80b3c34ccd..bfabae1d11 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs @@ -262,7 +262,7 @@ public virtual Dictionary GetImportableEntityProperties(ImportEn foreach (ImportEntityType type in Enum.GetValues(typeof(ImportEntityType))) { EntitySet entitySet = null; - var dic = new Dictionary(); + var dic = new Dictionary(StringComparer.OrdinalIgnoreCase); try { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index 38734f0f64..52b5eb75a6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -151,7 +151,7 @@ private void PrepareProfileModel(ImportProfileModel model, ImportProfile profile if (x == "Id") item.Text = T("Admin.Common.Entity.Fields.Id"); else if (allProperties.ContainsKey(x)) - item.Text = allProperties[x]; + item.Text = allProperties[x]; return item; }) @@ -204,14 +204,34 @@ private void PrepareProfileModel(ImportProfileModel model, ImportProfile profile model.AvailableSourceColumns.Add(mapModel); // auto map where field equals property name - if (!hasStoredMappings && !model.ColumnMappings.Any(x => x.Column == column.Name) && allProperties.Any(x => x.Key == column.Name)) + if (!hasStoredMappings && !model.ColumnMappings.Any(x => x.Column == column.Name)) { - model.ColumnMappings.Add(new ColumnMappingItemModel + var kvp = allProperties.FirstOrDefault(x => x.Key.IsCaseInsensitiveEqual(column.Name)); + + if (kvp.Key.HasValue()) { - Column = column.Name, - Property = column.Name, - ColumnLocalized = (allProperties.ContainsKey(column.Name) ? allProperties[column.Name] : null) - }); + model.ColumnMappings.Add(new ColumnMappingItemModel + { + Column = column.Name, + Property = kvp.Key, + ColumnLocalized = kvp.Value + }); + } + else + { + var alternativeName = LightweightDataTable.GetAlternativeColumnNameFor(column.Name); + kvp = allProperties.FirstOrDefault(x => x.Key.IsCaseInsensitiveEqual(alternativeName)); + + if (kvp.Key.HasValue()) + { + model.ColumnMappings.Add(new ColumnMappingItemModel + { + Column = column.Name, + Property = kvp.Key, + ColumnLocalized = kvp.Value + }); + } + } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml index b33b0ae412..45f05961bb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml @@ -102,8 +102,7 @@ - + -
    @Html.Widget("searchbox") } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Views/Customer/_CheckUsernameAvailability.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/_CheckUsernameAvailability.cshtml index b79106ebb5..fffbbe4a6b 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/_CheckUsernameAvailability.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/_CheckUsernameAvailability.cshtml @@ -1,6 +1,6 @@  - - \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/EstimateShipping.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/EstimateShipping.cshtml index 361532f73a..b7840d8c70 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/EstimateShipping.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/EstimateShipping.cshtml @@ -12,7 +12,7 @@ var throbber = estimateBox.data("throbber"); if (!throbber) { - throbber = estimateBox.throbber({ white: true, small: true, show: false }).data("throbber"); + throbber = estimateBox.throbber({ white: true, small: true, message: '', show: false }).data("throbber"); } throbber.show(); diff --git a/src/Presentation/SmartStore.Web/Web.config b/src/Presentation/SmartStore.Web/Web.config index ddc61a9f9f..ea77417613 100644 --- a/src/Presentation/SmartStore.Web/Web.config +++ b/src/Presentation/SmartStore.Web/Web.config @@ -92,7 +92,7 @@ - + From 4cf8bdea04165e7d9ba1b7f28f3bb923d06fff0b Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 13 Feb 2016 01:47:18 +0100 Subject: [PATCH 024/423] When a user manually executes a scheduled task, IWorkContext.CurrentCusomer is always the user himself during task execution (not background task system account anymore) --- .../DataExchange/DataExportTask.cs | 4 ++-- .../DataExchange/Import/DataImportTask.cs | 4 ++-- .../SmartStore.Services/Tasks/TaskExecutor.cs | 18 ++++++++++++++++-- .../SmartStore.Services/Tasks/TaskScheduler.cs | 3 +-- .../Controllers/ExportController.cs | 2 +- .../Controllers/ImportController.cs | 2 +- .../Controllers/ScheduleTaskController.cs | 16 +++++++++++----- 7 files changed, 34 insertions(+), 15 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/DataExportTask.cs b/src/Libraries/SmartStore.Services/DataExchange/DataExportTask.cs index 213d23f387..c11ff5c2b9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DataExportTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DataExportTask.cs @@ -38,9 +38,9 @@ public void Execute(TaskExecutionContext ctx) ctx.SetProgress(val, max, msg, true); }; - if (ctx.Parameters.ContainsKey("CurrentCustomerId")) + if (ctx.Parameters.ContainsKey(TaskExecutor.CurrentCustomerIdParamName)) { - request.CustomerId = ctx.Parameters["CurrentCustomerId"].ToInt(); // do not use built-in background tasks customer + request.CustomerId = ctx.Parameters[TaskExecutor.CurrentCustomerIdParamName].ToInt(); // do not use built-in background tasks customer } if (ctx.Parameters.ContainsKey("SelectedIds")) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImportTask.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImportTask.cs index 253bd10b6f..dd75d7a96f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImportTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImportTask.cs @@ -28,9 +28,9 @@ public void Execute(TaskExecutionContext ctx) ctx.SetProgress(val, max, msg, true); }; - if (ctx.Parameters.ContainsKey("CurrentCustomerId")) + if (ctx.Parameters.ContainsKey(TaskExecutor.CurrentCustomerIdParamName)) { - request.CustomerId = ctx.Parameters["CurrentCustomerId"].ToInt(); // do not use built-in background tasks customer + request.CustomerId = ctx.Parameters[TaskExecutor.CurrentCustomerIdParamName].ToInt(); // do not use built-in background tasks customer } _importer.Import(request, ctx.CancellationToken); diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs index 18408a15c0..fc7ce73e42 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs @@ -24,6 +24,8 @@ public class TaskExecutor : ITaskExecutor private readonly Func _taskResolver; private readonly IComponentContext _componentContext; + public const string CurrentCustomerIdParamName = "CurrentCustomerId"; + public TaskExecutor( IScheduleTaskService scheduledTaskService, IDbContext dbContext, @@ -82,8 +84,20 @@ public void Execute( try { - // set background task system customer as current customer - var customer = _customerService.GetCustomerBySystemName(SystemCustomerNames.BackgroundTask); + Customer customer = null; + + // try virtualize current customer (which is necessary when user manually executes a task) + if (taskParameters != null && taskParameters.ContainsKey(CurrentCustomerIdParamName)) + { + customer = _customerService.GetCustomerById(taskParameters[CurrentCustomerIdParamName].ToInt()); + } + + if (customer == null) + { + // no virtualization: set background task system customer as current customer + customer = _customerService.GetCustomerBySystemName(SystemCustomerNames.BackgroundTask); + } + _workContext.CurrentCustomer = customer; // create task instance diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs index 8cb0fbc0d7..1997fa3e3f 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs @@ -14,7 +14,6 @@ namespace SmartStore.Services.Tasks { - public class DefaultTaskScheduler : DisposableObject, ITaskScheduler, IRegisteredObject { private bool _intervalFixed; @@ -108,7 +107,7 @@ public void RunSingleTask(int scheduleTaskId, IDictionary taskPa if (taskParameters != null && taskParameters.Any()) { - var qs = new QueryString(); + var qs = new QueryString(); taskParameters.Each(x => qs.Add(x.Key, x.Value)); query = qs.ToString(); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 421ddf014b..d6badaa1ea 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -1007,7 +1007,7 @@ public ActionResult Execute(int id, string selectedIds) return RedirectToAction("List"); var taskParams = new Dictionary(); - taskParams.Add("CurrentCustomerId", _services.WorkContext.CurrentCustomer.Id.ToString()); + taskParams.Add(TaskExecutor.CurrentCustomerIdParamName, _services.WorkContext.CurrentCustomer.Id.ToString()); if (selectedIds.HasValue()) taskParams.Add("SelectedIds", selectedIds); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index 17c82a0b10..d2927e33d1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -618,7 +618,7 @@ public ActionResult Execute(int id) return RedirectToAction("List"); var taskParams = new Dictionary(); - taskParams.Add("CurrentCustomerId", _services.WorkContext.CurrentCustomer.Id.ToString()); + taskParams.Add(TaskExecutor.CurrentCustomerIdParamName, _services.WorkContext.CurrentCustomer.Id.ToString()); _taskScheduler.RunSingleTask(profile.SchedulingTaskId, taskParams); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs index 1dfa73a544..4c64c167c9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using System.Collections.Generic; using System.Threading; using System.Web.Mvc; using SmartStore.Admin.Extensions; @@ -13,6 +14,7 @@ using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Filters; using SmartStore.Web.Framework.Security; +using SmartStore.Core; namespace SmartStore.Admin.Controllers { @@ -24,19 +26,22 @@ public partial class ScheduleTaskController : AdminControllerBase private readonly IPermissionService _permissionService; private readonly IDateTimeHelper _dateTimeHelper; private readonly ILocalizationService _localizationService; + private readonly IWorkContext _workContext; - public ScheduleTaskController( + public ScheduleTaskController( IScheduleTaskService scheduleTaskService, ITaskScheduler taskScheduler, IPermissionService permissionService, IDateTimeHelper dateTimeHelper, - ILocalizationService localizationService) + ILocalizationService localizationService, + IWorkContext workContext) { this._scheduleTaskService = scheduleTaskService; this._taskScheduler = taskScheduler; this._permissionService = permissionService; this._dateTimeHelper = dateTimeHelper; this._localizationService = localizationService; + this._workContext = workContext; } private bool IsTaskVisible(ScheduleTask task) @@ -137,9 +142,10 @@ public ActionResult RunJob(int id, string returnUrl = "") if (!_permissionService.Authorize(StandardPermissionProvider.ManageScheduleTasks)) return AccessDeniedView(); - returnUrl = returnUrl.NullEmpty() ?? Request.UrlReferrer.ToString(); + var taskParams = new Dictionary(); + taskParams[TaskExecutor.CurrentCustomerIdParamName] = _workContext.CurrentCustomer.Id.ToString(); - _taskScheduler.RunSingleTask(id); + _taskScheduler.RunSingleTask(id, taskParams); // The most tasks are completed rather quickly. Wait a while... var start = DateTime.UtcNow; @@ -166,7 +172,7 @@ public ActionResult RunJob(int id, string returnUrl = "") } } - return Redirect(returnUrl); + return Redirect(returnUrl.NullEmpty() ?? Request.UrlReferrer.ToString()); } public ActionResult CancelJob(int id /* scheduleTaskId */, string returnUrl = "") From a99b8a17b6c405c370b9867120ae997f2d484cf0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 13 Feb 2016 12:37:01 +0100 Subject: [PATCH 025/423] No permission check for background import\export tasks Removed obsolete customer id handling via import\export context object --- .../201601262000441_ImportFramework1.cs | 5 +-- .../Customers/Importer/CustomerImporter.cs | 2 +- .../DataExchange/DataExportTask.cs | 5 --- .../DataExchange/DataExporter.cs | 22 ++++++------- .../DataExchange/IDataExporter.cs | 1 - .../DataExchange/Import/DataImportTask.cs | 5 --- .../DataExchange/Import/DataImporter.cs | 31 +++++++++++-------- .../DataExchange/Import/IDataImporter.cs | 4 +-- .../Import/ImportExecuteContext.cs | 8 ----- .../Controllers/ExportController.cs | 4 +-- .../Controllers/ImportController.cs | 2 +- .../Controllers/ScheduleTaskController.cs | 5 +-- .../Views/Import/_CreateOrUpdate.cshtml | 2 +- .../Views/ScheduleTask/MinimalTask.cshtml | 30 ++++++++++++------ 14 files changed, 62 insertions(+), 64 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs index cdf51187ee..3cfbda89ee 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs @@ -47,11 +47,11 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Add import file...", "Importdatei hinzufgen..."); - builder.AddOrUpdate("Admin.System.ScheduleTasks.RunNow.Success.DataImportTask", + builder.AddOrUpdate("Admin.System.ScheduleTasks.RunNow.Progress.DataImportTask", "The task is now running in the background. You will receive an email as soon as it is completed. The progress can be tracked in the import profile list.", "Die Aufgabe wird jetzt im Hintergrund ausgefhrt. Sie erhalten eine E-Mail, sobald sie abgeschlossen ist. Den Fortschritt knnen Sie in der Importprofilliste verfolgen."); - builder.AddOrUpdate("Admin.System.ScheduleTasks.RunNow.Success.DataExportTask", + builder.AddOrUpdate("Admin.System.ScheduleTasks.RunNow.Progress.DataExportTask", "The task is now running in the background. You will receive an email as soon as it is completed. The progress can be tracked in the export profile list.", "Die Aufgabe wird jetzt im Hintergrund ausgefhrt. Sie erhalten eine E-Mail, sobald sie abgeschlossen ist. Den Fortschritt knnen Sie in der Exportprofilliste verfolgen."); @@ -69,6 +69,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Common.Updated", "Updated", "Aktualisiert"); builder.AddOrUpdate("Admin.Common.Warnings", "Warnings", "Warnungen"); builder.AddOrUpdate("Admin.Common.Errors", "Errors", "Fehler"); + builder.AddOrUpdate("Admin.Common.UnsupportedEntityType", "Unsupported entity type '{0}'", "Nicht untersttzter Entittstyp '{0}'"); builder.AddOrUpdate("Admin.DataExchange.Import.CompletedEmail.Body", "This is an automatic notification of store \"{0}\" about a recent data import. Summary:", diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 190bd5f70b..84b4b6fdf6 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -305,7 +305,7 @@ public static string[] DefaultKeyFields public void Execute(IImportExecuteContext context) { var utcNow = DateTime.UtcNow; - var customer = _customerService.GetCustomerById(context.CustomerId); + var customer = _services.WorkContext.CurrentCustomer; var allowManagingCustomerRoles = _services.Permissions.Authorize(StandardPermissionProvider.ManageCustomerRoles, customer); var allCustomerRoles = _customerService.GetAllCustomerRoles(true); diff --git a/src/Libraries/SmartStore.Services/DataExchange/DataExportTask.cs b/src/Libraries/SmartStore.Services/DataExchange/DataExportTask.cs index c11ff5c2b9..f32f06c625 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DataExportTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DataExportTask.cs @@ -38,11 +38,6 @@ public void Execute(TaskExecutionContext ctx) ctx.SetProgress(val, max, msg, true); }; - if (ctx.Parameters.ContainsKey(TaskExecutor.CurrentCustomerIdParamName)) - { - request.CustomerId = ctx.Parameters[TaskExecutor.CurrentCustomerIdParamName].ToInt(); // do not use built-in background tasks customer - } - if (ctx.Parameters.ContainsKey("SelectedIds")) { request.EntitiesToExport = ctx.Parameters["SelectedIds"] diff --git a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs index 45182c1452..31a3777a43 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs @@ -205,10 +205,10 @@ private bool HasPermission(DataExporterContext ctx) if (ctx.Request.HasPermission) return true; - if (ctx.Request.CustomerId == 0) - ctx.Request.CustomerId = _services.WorkContext.CurrentCustomer.Id; // fallback to background task system customer + var customer = _services.WorkContext.CurrentCustomer; - var customer = _customerService.GetCustomerById(ctx.Request.CustomerId); + if (customer.SystemName == SystemCustomerNames.BackgroundTask) + return true; if (ctx.Request.Provider.Value.EntityType == ExportEntityType.Product || ctx.Request.Provider.Value.EntityType == ExportEntityType.Category || @@ -865,9 +865,6 @@ 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.Request.CustomerId == 0) - ctx.Request.CustomerId = _services.WorkContext.CurrentCustomer.Id; - if (ctx.Projection.CurrencyId.HasValue) ctx.ContextCurrency = _currencyService.Value.GetCurrencyById(ctx.Projection.CurrencyId.Value); else @@ -961,13 +958,16 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) logHead.AppendLine("Export provider:\t{0} ({1})".FormatInvariant(ctx.Request.Provider.Metadata.FriendlyName, ctx.Request.Profile.ProviderSystemName)); var plugin = ctx.Request.Provider.Metadata.PluginDescriptor; - logHead.Append("Plugin:\t\t\t\t"); + logHead.Append("Plugin:\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.Request.Provider.Value.EntityType.ToString()); + logHead.AppendLine("Entity:\t\t\t" + ctx.Request.Provider.Value.EntityType.ToString()); var storeInfo = (ctx.Request.Profile.PerStore ? "{0} (Id {1})".FormatInvariant(ctx.Store.Name, ctx.Store.Id) : "All stores"); - logHead.Append("Store:\t\t\t\t" + storeInfo); + logHead.AppendLine("Store:\t\t\t" + storeInfo); + + var customer = _services.WorkContext.CurrentCustomer; + logHead.Append("Executed by:\t\t" + (customer.Email.HasValue() ? customer.Email : customer.SystemName)); ctx.Log.Information(logHead.ToString()); } @@ -1329,13 +1329,13 @@ public IList Preview(DataExportRequest request, int pageIndex, int? tot var unused = Init(ctx, totalRecords); if (!HasPermission(ctx)) - throw new SmartException("You do not have permission to perform the selected export"); + throw new SmartException(T("Admin.AccessDenied")); using (var segmenter = CreateSegmenter(ctx, pageIndex)) { if (segmenter == null) { - throw new SmartException("Unsupported entity type '{0}'".FormatInvariant(ctx.Request.Provider.Value.EntityType.ToString())); + throw new SmartException(T("Admin.Common.UnsupportedEntityType", ctx.Request.Provider.Value.EntityType.ToString())); } while (segmenter.HasData) diff --git a/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs index 97f7cec7f1..34b96faf24 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs @@ -42,7 +42,6 @@ public DataExportRequest(ExportProfile profile, Provider provid public ProgressValueSetter ProgressValueSetter { get; set; } - public int CustomerId { get; set; } public bool HasPermission { get; set; } public IList EntitiesToExport { get; set; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImportTask.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImportTask.cs index dd75d7a96f..a25235fe00 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImportTask.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImportTask.cs @@ -28,11 +28,6 @@ public void Execute(TaskExecutionContext ctx) ctx.SetProgress(val, max, msg, true); }; - if (ctx.Parameters.ContainsKey(TaskExecutor.CurrentCustomerIdParamName)) - { - request.CustomerId = ctx.Parameters[TaskExecutor.CurrentCustomerIdParamName].ToInt(); // do not use built-in background tasks customer - } - _importer.Import(request, ctx.CancellationToken); } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs index cbe1c82961..a2114c26eb 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs @@ -156,7 +156,13 @@ public DataImporter( private bool HasPermission(DataImporterContext ctx) { - var customer = _customerService.GetCustomerById(ctx.Request.CustomerId); + if (ctx.Request.HasPermission) + return true; + + var customer = _services.WorkContext.CurrentCustomer; + + if (customer.SystemName == SystemCustomerNames.BackgroundTask) + return true; if (ctx.Request.Profile.EntityType == ImportEntityType.Product || ctx.Request.Profile.EntityType == ImportEntityType.Category) return _services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog, customer); @@ -176,17 +182,17 @@ private void LogResult(DataImporterContext ctx) var sb = new StringBuilder(); sb.AppendLine(); - sb.AppendFormat("Started:\t\t\t{0}\r\n", result.StartDateUtc.ToLocalTime()); - sb.AppendFormat("Finished:\t\t\t{0}\r\n", result.EndDateUtc.ToLocalTime()); - sb.AppendFormat("Duration:\t\t\t{0}\r\n", (result.EndDateUtc - result.StartDateUtc).ToString("g")); + sb.AppendFormat("Started:\t\t{0}\r\n", result.StartDateUtc.ToLocalTime()); + sb.AppendFormat("Finished:\t\t{0}\r\n", result.EndDateUtc.ToLocalTime()); + sb.AppendFormat("Duration:\t\t{0}\r\n", (result.EndDateUtc - result.StartDateUtc).ToString("g")); sb.AppendLine(); - sb.AppendFormat("Total rows:\t\t\t{0}\r\n", result.TotalRecords); + sb.AppendFormat("Total rows:\t\t{0}\r\n", result.TotalRecords); sb.AppendFormat("Rows processed:\t\t{0}\r\n", result.AffectedRecords); sb.AppendFormat("Records imported:\t{0}\r\n", result.NewRecords); sb.AppendFormat("Records updated:\t{0}\r\n", result.ModifiedRecords); sb.AppendLine(); - sb.AppendFormat("Warnings:\t\t\t{0}\r\n", result.Warnings); - sb.AppendFormat("Errors:\t\t\t\t{0}", result.Errors); + sb.AppendFormat("Warnings:\t\t{0}\r\n", result.Warnings); + sb.AppendFormat("Errors:\t\t\t{0}", result.Errors); ctx.Log.Information(sb.ToString()); @@ -278,8 +284,11 @@ private void ImportCoreInner(DataImporterContext ctx, string filePath) logHead.Append("Import profile:\t\t" + ctx.Request.Profile.Name); logHead.AppendLine(ctx.Request.Profile.Id == 0 ? " (volatile)" : " (Id {0})".FormatInvariant(ctx.Request.Profile.Id)); - logHead.AppendLine("Entity:\t\t\t\t" + ctx.Request.Profile.EntityType.ToString()); - logHead.Append("File:\t\t\t\t" + Path.GetFileName(filePath)); + logHead.AppendLine("Entity:\t\t\t" + ctx.Request.Profile.EntityType.ToString()); + logHead.AppendLine("File:\t\t\t" + Path.GetFileName(filePath)); + + var customer = _services.WorkContext.CurrentCustomer; + logHead.Append("Executed by:\t\t" + (customer.Email.HasValue() ? customer.Email : customer.SystemName)); ctx.Log.Information(logHead.ToString()); } @@ -347,10 +356,6 @@ private void ImportCoreOuter(DataImporterContext ctx) { ctx.Log = logger; - if (ctx.Request.CustomerId == 0) - ctx.Request.CustomerId = _services.WorkContext.CurrentCustomer.Id; // fallback to system background task customer - - ctx.ExecuteContext.CustomerId = ctx.Request.CustomerId; ctx.ExecuteContext.UpdateOnly = ctx.Request.Profile.UpdateOnly; ctx.ExecuteContext.KeyFieldNames = ctx.Request.Profile.KeyFieldNames.SplitSafe(","); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/IDataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/IDataImporter.cs index 404e78f220..8c2fbebf73 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/IDataImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/IDataImporter.cs @@ -29,10 +29,10 @@ public DataImportRequest(ImportProfile profile) public ImportProfile Profile { get; private set; } - public int CustomerId { get; set; } - public ProgressValueSetter ProgressValueSetter { get; set; } + public bool HasPermission { get; set; } + public IList EntitiesToImport { get; set; } public IDictionary CustomData { get; private set; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index 9f5ba29006..1b731a94e3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using System.Threading; using SmartStore.Core; using SmartStore.Core.Domain.DataExchange; @@ -8,11 +7,6 @@ namespace SmartStore.Services.DataExchange.Import { public interface IImportExecuteContext { - /// - /// Context customer identifier - /// - int CustomerId { get; } - /// /// Whether to only update existing records /// @@ -78,8 +72,6 @@ public ImportExecuteContext( public ColumnMap ColumnMap { get; internal set; } - public int CustomerId { get; internal set; } - public bool UpdateOnly { get; internal set; } public string[] KeyFieldNames { get; internal set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index d6badaa1ea..3f0fc8e3ab 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -823,7 +823,7 @@ public ActionResult Preview(int id) [HttpPost, GridAction(EnableCustomBinding = true)] public ActionResult PreviewList(GridCommand command, int id, int totalRecords) { - if (_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageExports)) { NotifyAccessDenied(); @@ -1014,7 +1014,7 @@ public ActionResult Execute(int id, string selectedIds) _taskScheduler.RunSingleTask(profile.SchedulingTaskId, taskParams); - NotifyInfo(T("Admin.System.ScheduleTasks.RunNow.Success.DataExportTask")); + NotifyInfo(T("Admin.System.ScheduleTasks.RunNow.Progress.DataExportTask")); return RedirectToAction("List"); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index d2927e33d1..7154bdc645 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -622,7 +622,7 @@ public ActionResult Execute(int id) _taskScheduler.RunSingleTask(profile.SchedulingTaskId, taskParams); - NotifyInfo(T("Admin.System.ScheduleTasks.RunNow.Success.DataImportTask")); + NotifyInfo(T("Admin.System.ScheduleTasks.RunNow.Progress.DataImportTask")); return RedirectToAction("List"); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs index 4c64c167c9..0239e8603f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs @@ -69,7 +69,7 @@ private string GetTaskMessage(ScheduleTask task, string resourceKey) if (taskClassName.HasValue()) { - message = T(string.Concat(resourceKey, ".", taskClassName)); + message = _localizationService.GetResource(string.Concat(resourceKey, ".", taskClassName), returnEmptyIfNotFound: true); } if (message.IsEmpty()) @@ -274,7 +274,7 @@ public ActionResult FutureSchedules(string expression) } [ChildActionOnly] - public ActionResult MinimalTask(int taskId, string returnUrl /* mandatory on purpose */, bool cancellable = true) + public ActionResult MinimalTask(int taskId, string returnUrl /* mandatory on purpose */, bool cancellable = true, bool reloadPage = false) { var task = _scheduleTaskService.GetTaskById(taskId); if (task == null) @@ -285,6 +285,7 @@ public ActionResult MinimalTask(int taskId, string returnUrl /* mandatory on pur ViewBag.HasPermission = _permissionService.Authorize(StandardPermissionProvider.ManageScheduleTasks); ViewBag.ReturnUrl = returnUrl; ViewBag.Cancellable = cancellable; + ViewBag.ReloadPage = reloadPage; var model = task.ToScheduleTaskModel(_localizationService, _dateTimeHelper, Url); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_CreateOrUpdate.cshtml index 015a9e0131..3dcabee6f3 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_CreateOrUpdate.cshtml @@ -80,7 +80,7 @@ else if (Model.ExistingFileNames.Count > 1) @Html.SmartLabelFor(x => x.ScheduleTaskId) - @Html.Action("MinimalTask", "ScheduleTask", new { taskId = Model.ScheduleTaskId, returnUrl = Request.RawUrl, cancellable = true, area = "admin" }) + @Html.Action("MinimalTask", "ScheduleTask", new { taskId = Model.ScheduleTaskId, returnUrl = Request.RawUrl, cancellable = true, reloadPage = true, area = "admin" }) } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/MinimalTask.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/MinimalTask.cshtml index 5842a4185d..530a08f62e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/MinimalTask.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/MinimalTask.cshtml @@ -5,6 +5,7 @@ var returnUrl = (string)ViewBag.ReturnUrl; var hasPermission = ViewBag.HasPermission == true; var cancellable = ViewBag.Cancellable == true; + var reloadPage = ViewBag.ReloadPage == true; }
    @@ -33,16 +34,25 @@ onTaskCompleted: function (taskId, el) { el.prev().removeClass('hide'); // .task-info el.next().addClass('hide'); // .btn-cancel-task - $.ajax({ - cache: false, - global: false, - type: 'POST', - url: '@Url.Action("GetMinimalTaskWidget")', - data: { taskId: taskId, returnUrl: '@returnUrl' }, - success: function (data) { - el.prev().html(data); - } - }); + + @if (reloadPage) + { + location.href = '@returnUrl'; + } + else + { + $.ajax({ + cache: false, + global: false, + type: 'POST', + url: '@Url.Action("GetMinimalTaskWidget")', + data: { taskId: taskId, returnUrl: '@returnUrl' }, + success: function (data) { + el.prev().html(data); + } + }); + } + } }); }); From fad2a1aa1f4e17d409aea6ff79a9a2fdcac5c9f4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 13 Feb 2016 15:33:15 +0100 Subject: [PATCH 026/423] Resolves #854 Make 'Bottom Description' in Category edit page collapsible --- changelog.md | 1 + .../201601262000441_ImportFramework1.cs | 10 ++++++ .../Views/Category/_CreateOrUpdate.cshtml | 33 ++++++++++++++++--- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/changelog.md b/changelog.md index 2b5067eadf..7a32990db6 100644 --- a/changelog.md +++ b/changelog.md @@ -41,6 +41,7 @@ * #339 Meta robots setting for page indexing of search engines * PayPal Direct and Express: Option for API security protocol * Product filter: Option to sort filter results by their display order rather than by number of matches +* Elmar Shopinfo: Option to export delivery time as availability ### 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/201601262000441_ImportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs index 3cfbda89ee..38a30b9f4c 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs @@ -321,6 +321,16 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Install.LanguageNotRegistered", "The install language '{0}' is not registered.", "Die Installationssprache '{0}' ist nicht registriert."); + + builder.AddOrUpdate("Admin.Catalog.Categories.DescriptionToggle", + "Show other description", + "Andere Beschreibung anzeigen"); + + builder.AddOrUpdate("Admin.Catalog.Categories.Fields.Description", + "Top description", + "Obere Beschreibung", + "Description of the category that is displayed above products on the category page.", + "Beschreibung der Warengruppe, die auf der Warengruppenseite oberhalb der Produkte angezeigt wird."); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml index a38346c8c1..507546d242 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Category/_CreateOrUpdate.cshtml @@ -53,7 +53,7 @@ @Html.ValidationMessageFor(model => model.Locales[item].FullName) - + @Html.SmartLabelFor(model => model.Locales[item].Description) @@ -62,7 +62,7 @@ @Html.ValidationMessageFor(model => model.Locales[item].Description) - + @Html.SmartLabelFor(model => model.Locales[item].BottomDescription) @@ -71,6 +71,16 @@ @Html.ValidationMessageFor(model => model.Locales[item].BottomDescription) + + +   + + + + + @Html.HiddenFor(model => model.Locales[item].LanguageId) @@ -97,7 +107,7 @@ @Html.ValidationMessageFor(model => model.FullName) - + @Html.SmartLabelFor(model => model.Description) @@ -106,7 +116,7 @@ @Html.ValidationMessageFor(model => model.Description) - + @Html.SmartLabelFor(model => model.BottomDescription) @@ -115,6 +125,16 @@ @Html.ValidationMessageFor(model => model.BottomDescription) + + +   + + + + + )) @@ -563,6 +583,11 @@ $('.pnl-available-stores').toggle($('#@Html.FieldIdFor(model => model.LimitedToStores)').is(':checked')); }).trigger('change'); + // toggle top\bottom description + $('button.description-toggle-button').click(function() { + $('table').find('.description-container').toggle(); + }); + // add new product $(document).on('click', '#AddNewProductButton', function () { $({}).entityPicker('loadDialog', { From 37efceaec83d040328a9a15dbe4f9387300b09c5 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 13 Feb 2016 17:10:05 +0100 Subject: [PATCH 027/423] Resolves #856 Don't route topics which are excluded from sitemap --- changelog.md | 1 + src/Presentation/SmartStore.Web/Controllers/TopicController.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 7a32990db6..58e210096a 100644 --- a/changelog.md +++ b/changelog.md @@ -82,6 +82,7 @@ * #850 Use new product picker for selecting required products * Trusted Shops: badge will be displayed in mobile themes, payment info link replaced compare list link in footer * Product filter: Specification attributes are sorted by display order rather than alphabetically by name +* #856 Don't route topics which are excluded from sitemap ### Bugfixes * #523 Redirecting to payment provider performed by core instead of plugin diff --git a/src/Presentation/SmartStore.Web/Controllers/TopicController.cs b/src/Presentation/SmartStore.Web/Controllers/TopicController.cs index 7e861fac2e..5821d1658f 100644 --- a/src/Presentation/SmartStore.Web/Controllers/TopicController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/TopicController.cs @@ -79,8 +79,9 @@ public ActionResult TopicDetails(string systemName) var cacheKey = string.Format(ModelCacheEventConsumer.TOPIC_MODEL_KEY, systemName, _workContext.WorkingLanguage.Id, _storeContext.CurrentStore.Id); var cacheModel = _cacheManager.Get(cacheKey, () => PrepareTopicModel(systemName)); - if (cacheModel == null) + if (cacheModel == null || !cacheModel.IncludeInSitemap) return HttpNotFound(); + return View("TopicDetails", cacheModel); } From 243060f83c856a24c94dcfe794719c12396ea7f2 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 15 Feb 2016 10:40:55 +0100 Subject: [PATCH 028/423] Resolves #855 PayPal standard: Return URLs like PDTHandler require HTTPS --- changelog.md | 3 +- .../Controllers/PayPalDirectController.cs | 18 +- .../Controllers/PayPalExpressController.cs | 20 +- .../Controllers/PayPalStandardController.cs | 15 +- .../Filters/PayPalExpressCheckoutFilter.cs | 71 ++++--- .../Localization/resources.de-de.xml | 27 +-- .../Localization/resources.en-us.xml | 176 ++++++------------ .../Models/ApiConfigurationModels.cs | 16 +- .../PayPalStandardConfigurationModel.cs | 17 +- .../Providers/PayPalDirectProvider.cs | 3 +- .../Providers/PayPalExpressProvider.cs | 4 +- .../Providers/PayPalProviderBase.cs | 47 ----- .../Providers/PayPalStandardProvider.cs | 146 +++++---------- .../Services/PayPalHelper.cs | 69 ++++++- .../Services/PayPalProcessPaymentRequest.cs | 10 +- .../Services/PayPalStandardCore.cs | 6 +- .../Settings/PayPalSettings.cs | 21 ++- .../Views/PayPalStandard/Configure.cshtml | 8 + src/Plugins/SmartStore.PayPal/changelog.md | 8 +- 19 files changed, 293 insertions(+), 392 deletions(-) diff --git a/changelog.md b/changelog.md index 58e210096a..cbe2a94a26 100644 --- a/changelog.md +++ b/changelog.md @@ -39,7 +39,7 @@ * #451 Add message token for product shipping surcharge * #436 Make %Order.Product(s)% token to link the product detail page and a add product thumbnail * #339 Meta robots setting for page indexing of search engines -* PayPal Direct and Express: Option for API security protocol +* PayPal: Option for API security protocol * Product filter: Option to sort filter results by their display order rather than by number of matches * Elmar Shopinfo: Option to export delivery time as availability @@ -104,6 +104,7 @@ * Faulty permission handling in ajax grid actions (no message, infinite loading icon) * Grouped products: Display order was not correct * Deletion of a customer could delete all newsletter subscriptions +* PayPal: Fixed "The request was aborted: Could not create SSL/TLS secure channel." ## SmartStore.NET 2.2.2 diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs index cb9fc24024..63ff4d0368 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs @@ -92,7 +92,7 @@ public ActionResult Configure(PayPalDirectConfigurationModel model, FormCollecti _services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); _services.Settings.ClearCache(); - NotifySuccess(_services.Localization.GetResource("Plugins.Payments.PayPal.ConfigSaveNote")); + NotifySuccess(_services.Localization.GetResource("Admin.Common.DataSuccessfullySaved")); return Configure(); } @@ -180,9 +180,13 @@ public override IList ValidatePaymentForm(FormCollection form) }; var validationResult = validator.Validate(model); - if (!validationResult.IsValid) - foreach (var error in validationResult.Errors) - warnings.Add(error.ErrorMessage); + if (!validationResult.IsValid) + { + foreach (var error in validationResult.Errors) + { + warnings.Add(error.ErrorMessage); + } + } return warnings; } @@ -216,7 +220,7 @@ public ActionResult IPNHandler() Debug.WriteLine("PayPal Direct IPN: {0}".FormatWith(Request.ContentLength)); byte[] param = Request.BinaryRead(Request.ContentLength); - string strRequest = Encoding.ASCII.GetString(param); + var strRequest = Encoding.ASCII.GetString(param); Dictionary values; var provider = _paymentService.LoadPaymentMethodBySystemName("Payments.PayPalDirect", true); @@ -224,7 +228,9 @@ public ActionResult IPNHandler() if (processor == null) throw new SmartException(T("Plugins.Payments.PayPalDirect.NoModuleLoading")); - if (processor.VerifyIPN(strRequest, out values)) + var settings = _services.Settings.LoadSetting(); + + if (PayPalHelper.VerifyIPN(settings, strRequest, out values)) { #region values decimal total = decimal.Zero; diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs index 3cb9adc89b..68f057a1ee 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs @@ -5,7 +5,6 @@ using System.Text; using System.Web.Mvc; using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Logging; using SmartStore.Core.Domain.Orders; @@ -35,10 +34,8 @@ public class PayPalExpressController : PaymentControllerBase private readonly IOrderService _orderService; private readonly IOrderProcessingService _orderProcessingService; private readonly ILogger _logger; - private readonly PaymentSettings _paymentSettings; private readonly OrderSettings _orderSettings; private readonly ICurrencyService _currencyService; - private readonly CurrencySettings _currencySettings; private readonly IOrderTotalCalculationService _orderTotalCalculationService; private readonly ICustomerService _customerService; private readonly IGenericAttributeService _genericAttributeService; @@ -49,10 +46,8 @@ public PayPalExpressController( IOrderService orderService, IOrderProcessingService orderProcessingService, ILogger logger, - PaymentSettings paymentSettings, OrderSettings orderSettings, ICurrencyService currencyService, - CurrencySettings currencySettings, IOrderTotalCalculationService orderTotalCalculationService, ICustomerService customerService, IGenericAttributeService genericAttributeService, @@ -62,10 +57,8 @@ public PayPalExpressController( _orderService = orderService; _orderProcessingService = orderProcessingService; _logger = logger; - _paymentSettings = paymentSettings; _orderSettings = orderSettings; _currencyService = currencyService; - _currencySettings = currencySettings; _orderTotalCalculationService = orderTotalCalculationService; _customerService = customerService; _genericAttributeService = genericAttributeService; @@ -123,22 +116,23 @@ public ActionResult Configure(PayPalExpressConfigurationModel model, FormCollect _services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); _services.Settings.ClearCache(); - NotifySuccess(_services.Localization.GetResource("Plugins.Payments.PayPal.ConfigSaveNote")); + NotifySuccess(_services.Localization.GetResource("Admin.Common.DataSuccessfullySaved")); return Configure(); } public ActionResult PaymentInfo() { - var model = new PayPalExpressPaymentInfoModel(); model.CurrentPageIsBasket = PayPalHelper.CurrentPageIsBasket(this.ControllerContext.ParentActionViewContext.RequestContext.RouteData); if (model.CurrentPageIsBasket) { var culture = _services.WorkContext.WorkingLanguage.LanguageCulture; + var settings = _services.Settings.LoadSetting(_services.StoreContext.CurrentStore.Id); var buttonUrl = "https://www.paypalobjects.com/{0}/i/btn/btn_xpressCheckout.gif".FormatWith(culture.Replace("-", "_")); - model.SubmitButtonImageUrl = PayPalHelper.CheckIfButtonExists(buttonUrl); + + model.SubmitButtonImageUrl = PayPalHelper.CheckIfButtonExists(settings, buttonUrl); } return PartialView(model); @@ -148,7 +142,7 @@ public ActionResult PaymentInfo() public ActionResult IPNHandler() { byte[] param = Request.BinaryRead(Request.ContentLength); - string strRequest = Encoding.ASCII.GetString(param); + var strRequest = Encoding.ASCII.GetString(param); Dictionary values; var provider = _paymentService.LoadPaymentMethodBySystemName("Payments.PayPalExpress", true); @@ -156,7 +150,9 @@ public ActionResult IPNHandler() if (processor == null) throw new SmartException(T("PayPal Express module cannot be loaded")); - if (processor.VerifyIPN(strRequest, out values)) + var settings = _services.Settings.LoadSetting(); + + if (PayPalHelper.VerifyIPN(settings, strRequest, out values)) { #region values decimal total = decimal.Zero; diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs index fe150fa4e0..93667447cd 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs @@ -10,6 +10,7 @@ using SmartStore.Core.Domain.Payments; using SmartStore.Core.Logging; using SmartStore.PayPal.Models; +using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Services; using SmartStore.Services.Localization; @@ -67,7 +68,11 @@ public ActionResult Configure() model.Copy(settings, true); - var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + model.AvailableSecurityProtocols = PayPalHelper.GetSecurityProtocols() + .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) + .ToList(); + + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); storeDependingSettingHelper.GetOverrideKeys(settings, model, storeScope, _services.Settings); return View(model); @@ -93,7 +98,7 @@ public ActionResult Configure(PayPalStandardConfigurationModel model, FormCollec _services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); _services.Settings.ClearCache(); - NotifySuccess(_services.Localization.GetResource("Plugins.Payments.PayPal.ConfigSaveNote")); + NotifySuccess(_services.Localization.GetResource("Admin.Common.DataSuccessfullySaved")); return Configure(); } @@ -266,7 +271,7 @@ public ActionResult IPNHandler() Debug.WriteLine("PayPal Standard IPN: {0}".FormatInvariant(Request.ContentLength)); byte[] param = Request.BinaryRead(Request.ContentLength); - string strRequest = Encoding.ASCII.GetString(param); + var strRequest = Encoding.UTF8.GetString(param); Dictionary values; var provider = _paymentService.LoadPaymentMethodBySystemName("Payments.PayPalStandard", true); @@ -274,7 +279,9 @@ public ActionResult IPNHandler() if (processor == null) throw new SmartException(_localizationService.GetResource("Plugins.Payments.PayPalStandard.NoModuleLoading")); - if (processor.VerifyIPN(strRequest, out values)) + var settings = _services.Settings.LoadSetting(); + + if (PayPalHelper.VerifyIPN(settings, strRequest, out values)) { #region values diff --git a/src/Plugins/SmartStore.PayPal/Filters/PayPalExpressCheckoutFilter.cs b/src/Plugins/SmartStore.PayPal/Filters/PayPalExpressCheckoutFilter.cs index 8536350363..8f87c83a99 100644 --- a/src/Plugins/SmartStore.PayPal/Filters/PayPalExpressCheckoutFilter.cs +++ b/src/Plugins/SmartStore.PayPal/Filters/PayPalExpressCheckoutFilter.cs @@ -6,7 +6,6 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Services; using SmartStore.Services.Common; -using SmartStore.Services.Customers; using SmartStore.Services.Payments; namespace SmartStore.PayPal.Filters @@ -15,19 +14,18 @@ public class PayPalExpressCheckoutFilter : IActionFilter { private static readonly string[] s_interceptableActions = new string[] { "PaymentMethod" }; - private readonly IGenericAttributeService _genericAttributeService; - private readonly HttpContextBase _httpContext; - private readonly ICommonServices _services; - private readonly ICustomerService _customerService; + private readonly IGenericAttributeService _genericAttributeService; + private readonly HttpContextBase _httpContext; + private readonly ICommonServices _services; - public PayPalExpressCheckoutFilter(IGenericAttributeService genericAttributeService, - HttpContextBase httpContext, ICommonServices services, - ICustomerService customerService) + public PayPalExpressCheckoutFilter( + IGenericAttributeService genericAttributeService, + HttpContextBase httpContext, + ICommonServices services) { - _genericAttributeService = genericAttributeService; - _httpContext = httpContext; - _services = services; - _customerService = customerService; + _genericAttributeService = genericAttributeService; + _httpContext = httpContext; + _services = services; } private static bool IsInterceptableAction(string actionName) @@ -37,36 +35,37 @@ private static bool IsInterceptableAction(string actionName) public void OnActionExecuting(ActionExecutingContext filterContext) { - if (filterContext == null || filterContext.ActionDescriptor == null || filterContext.HttpContext == null || filterContext.HttpContext.Request == null) - return; + if (filterContext == null || filterContext.ActionDescriptor == null || filterContext.HttpContext == null || filterContext.HttpContext.Request == null) + return; - string actionName = filterContext.ActionDescriptor.ActionName; + string actionName = filterContext.ActionDescriptor.ActionName; - var store = _services.StoreContext.CurrentStore; - var customer = _services.WorkContext.CurrentCustomer; - - var attr = Convert.ToBoolean(filterContext.HttpContext.GetCheckoutState().CustomProperties["PayPalExpressButtonUsed"]); + var store = _services.StoreContext.CurrentStore; + var customer = _services.WorkContext.CurrentCustomer; - //verify paypalexpressprovider was used - if (attr == true) { - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, "Payments.PayPalExpress", store.Id); + var attr = Convert.ToBoolean(filterContext.HttpContext.GetCheckoutState().CustomProperties["PayPalExpressButtonUsed"]); - var paymentRequest = _httpContext.Session["OrderPaymentInfo"] as ProcessPaymentRequest; - if (paymentRequest == null) - { - _httpContext.Session["OrderPaymentInfo"] = new ProcessPaymentRequest(); - } + //verify paypalexpressprovider was used + if (attr == true) + { + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, "Payments.PayPalExpress", store.Id); - //delete property for backward navigation - _httpContext.GetCheckoutState().CustomProperties.Remove("PayPalExpressButtonUsed"); + var paymentRequest = _httpContext.Session["OrderPaymentInfo"] as ProcessPaymentRequest; + if (paymentRequest == null) + { + _httpContext.Session["OrderPaymentInfo"] = new ProcessPaymentRequest(); + } - filterContext.Result = new RedirectToRouteResult( - new RouteValueDictionary { - { "Controller", "Checkout" }, - { "Action", "Confirm" }, - { "area", null } - }); - } + //delete property for backward navigation + _httpContext.GetCheckoutState().CustomProperties.Remove("PayPalExpressButtonUsed"); + + filterContext.Result = new RedirectToRouteResult( + new RouteValueDictionary { + { "Controller", "Checkout" }, + { "Action", "Confirm" }, + { "area", null } + }); + } } public void OnActionExecuted(ActionExecutedContext filterContext) diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index d73638c505..b8cd7db49b 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -9,9 +9,6 @@ - - Ihre Einstellungen wurden erfolgreich gespeichert. - Sandbox benutzen @@ -63,7 +60,6 @@ - PayPal Express @@ -84,6 +80,7 @@
    1. In Ihr Premier- oder Business-Konto einloggen.
    2. Auf den Register Mein Profil klicken.
    3. +
    4. Unter Sprach-Kodierung > Weitere Einstellungen wählen Sie bitte UTF-8.
    5. Sofortige Zahlungsbestätigung klicken.
    6. Einstellungen für sofortige Zahlungsbestätigungen wählen klicken.
    7. Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalExpress/IPNHandler) eingeben.
    8. @@ -119,11 +116,9 @@ Autorisierung sofort, Abbuchung später - oder - Checkout-Button auf der Warenkorbseite aktivieren @@ -161,7 +156,6 @@ PayPal Direct - @@ -179,6 +173,7 @@
      1. In Ihr Premier- oder Business-Konto einloggen.
      2. Auf den Register Mein Profil klicken.
      3. +
      4. Unter Sprach-Kodierung > Weitere Einstellungen wählen Sie bitte UTF-8.
      5. Sofortige Zahlungsbestätigung klicken.
      6. Einstellungen für sofortige Zahlungsbestätigungen wählen klicken.
      7. Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalDirect/IPNHandler) eingeben.
      8. @@ -219,12 +214,11 @@ PayPal Standard - - Stellen Sie bitte sicher, dass PayPal die Primärwährung Ihres Shops unterstützt, falls Sie dieses Gateway benutzen!

        + Stellen Sie bitte sicher, dass PayPal die Primärwährung Ihres Shops unterstützt, falls Sie dieses Gateway benutzen.

        Sie müssen PDT (Payment Data Transfer) und die automatische Rückleitung in Ihrem PayPal-Profil aktivieren, um PDT nutzen zu können. Ferner benötigen Sie einen PDT-Identitäts-Token, der für jede PDT-Kommunikation mit PayPal erforderlich ist. Folgen Sie den Schritten, um Ihr Konto für PDT zu konfigurieren:

        @@ -247,7 +241,8 @@
        1. In Ihr Premier- oder Business-Konto einloggen.
        2. Auf den Register Mein Profil klicken.
        3. -
        4. Sofortige Zahlungsbestätigung klicken.
        5. +
        6. Unter Sprach-Kodierung > Weitere Einstellungen wählen Sie bitte UTF-8.
        7. +
        8. Zurück zu Mein Profil und auf Sofortige Zahlungsbestätigung klicken.
        9. Einstellungen für sofortige Zahlungsbestätigungen wählen klicken.
        10. Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalStandard/IPNHandler) eingeben.
        11. Speichern klicken. Danach sollten Sie eine Nachricht über die erfolgreiche Aktivierung von IPN erhalten.
        12. @@ -283,18 +278,6 @@ 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 - - - Zusätzliche Gebühren, die dem Kunden berechnet werden sollen. - - - Zusätzliche Gebühren (prozentual) - - - Zusätzliche prozentuale Gebühr zum Gesamtbetrag. Es wird ein fester Wert verwendet, falls diese Option nicht aktiviert ist. - Produktbezeichnungen und Einzelpreise übermitteln diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index 27a688c5f6..f4a32684b1 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -6,6 +6,59 @@ Provides the PayPal payment methods PayPal Express, PayPal Standard and PayPal Direct. + + + + + Use Sandbox + + + Check the box to enable Sandbox (testing environment). + + + Transaction mode + + + Specify the payment transaction mode. + + + Security protocol + + + Specifies the security protocol to use with the PayPal API. + + + API Account Name + + + Specify the API account name. + + + API Account Password + + + Specify the API account password. + + + Signature + + + Enter the signature. + + + Additional fee + + + Enter additional fee to charge your customers. + + + Additional fee. Use percentage. + + + Specifies whether to apply a percentage additional fee to the order total. A fixed value is used if not enabled. + + + PayPal Express @@ -28,62 +81,14 @@
          1. Log in to your Premier or Business account.
          2. Click the Profile subtab.
          3. +
          4. Click Language Encoding and More options and select UTF-8.
          5. Click Instant Payment Notification in the Selling Preferences column.
          6. Click the Edit IPN Settings button to update your settings.
          7. Select Receive IPN messages (enabled) and enter the URL of your IPN handler (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalExpress/IPNHandler).
          8. Click Save, and you should get a message that you have successfully activated IPN.
          ]]> -
          - - - Use Sandbox - - - Check the box to enable Sandbox (testing environment). - - - Transaction mode - - - Specify transaction mode. - - - Security protocol - - - Specifies the security protocol to use with the PayPal API. - - - API Account Name - - - Specify API account name. - - - API Account Password - - - Specify API account password. - - - Signature - - - Specify signature. - - - Additional fee - - - Enter additional fee to charge your customers. - - - Additional fee. Use percentage - - - Determines whether to apply a percentage additional fee to the order total. A fixed value is used if not enabled. - + PayPal Express module cannot be loaded. @@ -112,11 +117,9 @@ Authorize immediately, debit later - or - Activate checkout button in shopping cart @@ -153,7 +156,6 @@ PayPal Direct - @@ -171,6 +173,7 @@
          1. Log in to your Premier or Business account.
          2. Click the Profile subtab.
          3. +
          4. Click Language Encoding and More options and select UTF-8.
          5. Click Instant Payment Notification in the Selling Preferences column.
          6. Click the Edit IPN Settings button to update your settings.
          7. Select Receive IPN messages (enabled) and enter the URL of your IPN handler (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalDirect/IPNHandler).
          8. @@ -178,48 +181,6 @@
          ]]>
          - - Use Sandbox - - - Check the box to enable Sandbox (testing environment). - - - Transaction mode - - - Specify transaction mode. - - - API Account Name - - - Specify API account name. - - - API Account Password - - - Specify API account password. - - - Signature - - - Specify signature. - - - Additional fee - - - Enter additional fee to charge your customers. - - - Additional fee. Use percentage - - - Determines whether to apply a percentage additional fee to the order total. A fixed value is used if not enabled. - PayPal Direct module cannot be loaded. @@ -263,7 +224,7 @@ you send to PayPal. Follow these steps to configure your account for PDT:

          1. Log in to your PayPal account.
          2. -
          3. Click the Profile subtab.
          4. +
          5. Click the Profile subtab.
          6. Click Website Payment Preferences in the Seller Preferences column.
          7. Under Auto Return for Website Payments, click the On radio button.
          8. For the Return URL, enter the URL on your site that will receive the transaction ID posted by PayPal after a customer payment (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalStandard/PDTHandler).
          9. @@ -279,8 +240,9 @@ The second way is to configure your paypal account to activate this service; follow these steps:
            1. Log in to your Premier or Business account.
            2. -
            3. Click the Profile subtab.
            4. -
            5. Click Instant Payment Notification in the Selling Preferences column.
            6. +
            7. Click the Profile subtab.
            8. +
            9. Click Language Encoding and More options and select UTF-8.
            10. +
            11. Back to Profile click Instant Payment Notification in the Selling Preferences column.
            12. Click the Edit IPN Settings button to update your settings.
            13. Select Receive IPN messages (Enabled) and enter the URL of your IPN handler (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalStandard/IPNHandler).
            14. Click Save, and you should get a message that you have successfully activated IPN.
            15. @@ -292,12 +254,6 @@ After confirmation of the order, you will be redirected straight to PayPal. Please provide your data for online banking. - - Use Sandbox - - - Check the box to enable Sandbox (testing environment). - Business Email @@ -322,18 +278,6 @@ 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 - - - Enter additional fee to charge your customers. - - - Additional fee. Use percentage - - - Determines whether to apply a percentage additional fee to the order total. If not enabled, a fixed value is used. - Transmit product names and unit prices diff --git a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs index 40b55e50ed..07fd5e0592 100644 --- a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs +++ b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs @@ -47,9 +47,9 @@ public void Copy(PayPalDirectPaymentSettings settings, bool fromSettings) { if (fromSettings) { - UseSandbox = settings.UseSandbox; - TransactMode = Convert.ToInt32(settings.TransactMode); SecurityProtocol = settings.SecurityProtocol; + UseSandbox = settings.UseSandbox; + TransactMode = Convert.ToInt32(settings.TransactMode); ApiAccountName = settings.ApiAccountName; ApiAccountPassword = settings.ApiAccountPassword; Signature = settings.Signature; @@ -58,9 +58,9 @@ public void Copy(PayPalDirectPaymentSettings settings, bool fromSettings) } else { - settings.UseSandbox = UseSandbox; - settings.TransactMode = (TransactMode)TransactMode; settings.SecurityProtocol = SecurityProtocol; + settings.UseSandbox = UseSandbox; + settings.TransactMode = (TransactMode)TransactMode; settings.ApiAccountName = ApiAccountName; settings.ApiAccountPassword = ApiAccountPassword; settings.Signature = Signature; @@ -91,9 +91,9 @@ public void Copy(PayPalExpressPaymentSettings settings, bool fromSettings) { if (fromSettings) { - UseSandbox = settings.UseSandbox; - TransactMode = Convert.ToInt32(settings.TransactMode); SecurityProtocol = settings.SecurityProtocol; + UseSandbox = settings.UseSandbox; + TransactMode = Convert.ToInt32(settings.TransactMode); ApiAccountName = settings.ApiAccountName; ApiAccountPassword = settings.ApiAccountPassword; Signature = settings.Signature; @@ -107,9 +107,9 @@ public void Copy(PayPalExpressPaymentSettings settings, bool fromSettings) } else { - settings.UseSandbox = UseSandbox; - settings.TransactMode = (TransactMode)TransactMode; settings.SecurityProtocol = SecurityProtocol; + settings.UseSandbox = UseSandbox; + settings.TransactMode = (TransactMode)TransactMode; settings.ApiAccountName = ApiAccountName; settings.ApiAccountPassword = ApiAccountPassword; settings.Signature = Signature; diff --git a/src/Plugins/SmartStore.PayPal/Models/PayPalStandardConfigurationModel.cs b/src/Plugins/SmartStore.PayPal/Models/PayPalStandardConfigurationModel.cs index b9a6d1161b..2adc2a5cf0 100644 --- a/src/Plugins/SmartStore.PayPal/Models/PayPalStandardConfigurationModel.cs +++ b/src/Plugins/SmartStore.PayPal/Models/PayPalStandardConfigurationModel.cs @@ -1,4 +1,7 @@ -using SmartStore.PayPal.Settings; +using System.Collections.Generic; +using System.Net; +using System.Web.Mvc; +using SmartStore.PayPal.Settings; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; @@ -6,7 +9,11 @@ namespace SmartStore.PayPal.Models { public class PayPalStandardConfigurationModel : ModelBase { - [SmartResourceDisplayName("Plugins.Payments.PayPal.UseSandbox")] + [SmartResourceDisplayName("Plugins.Payments.PayPal.SecurityProtocol")] + public SecurityProtocolType? SecurityProtocol { get; set; } + public List AvailableSecurityProtocols { get; set; } + + [SmartResourceDisplayName("Plugins.Payments.PayPal.UseSandbox")] public bool UseSandbox { get; set; } [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.BusinessEmail")] @@ -21,10 +28,10 @@ public class PayPalStandardConfigurationModel : ModelBase [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.PdtValidateOnlyWarn")] public bool PdtValidateOnlyWarn { get; set; } - [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.AdditionalFee")] + [SmartResourceDisplayName("Plugins.Payments.PayPal.AdditionalFee")] public decimal AdditionalFee { get; set; } - [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.AdditionalFeePercentage")] + [SmartResourceDisplayName("Plugins.Payments.PayPal.AdditionalFeePercentage")] public bool AdditionalFeePercentage { get; set; } [SmartResourceDisplayName("Plugins.Payments.PayPalStandard.Fields.PassProductNamesAndTotals")] @@ -40,6 +47,7 @@ public void Copy(PayPalStandardPaymentSettings settings, bool fromSettings) { if (fromSettings) { + SecurityProtocol = settings.SecurityProtocol; UseSandbox = settings.UseSandbox; BusinessEmail = settings.BusinessEmail; PdtToken = settings.PdtToken; @@ -53,6 +61,7 @@ public void Copy(PayPalStandardPaymentSettings settings, bool fromSettings) } else { + settings.SecurityProtocol = SecurityProtocol; settings.UseSandbox = UseSandbox; settings.BusinessEmail = BusinessEmail; settings.PdtToken = PdtToken; diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalDirectProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalDirectProvider.cs index 0750acbeda..514d54e946 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalDirectProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalDirectProvider.cs @@ -31,8 +31,7 @@ public class PayPalDirectProvider : PayPalProviderBase diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalProviderBase.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalProviderBase.cs index 9f4e582421..aa8cfe8029 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalProviderBase.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalProviderBase.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Net; -using System.Text; -using System.Web; using System.Web.Routing; using SmartStore.Core.Configuration; using SmartStore.Core.Domain.Orders; @@ -69,50 +66,6 @@ protected PayPalAPISoapBinding GetApiService(TSetting settings) return service; } - - /// - /// Verifies IPN - /// - /// Form string - /// Values - /// Result - public bool VerifyIPN(string formString, out Dictionary values) - { - // settings: multistore context not possible here. we need the custom value to determine what store it is. - var settings = Services.Settings.LoadSetting(); - var req = (HttpWebRequest)WebRequest.Create(PayPalHelper.GetPaypalUrl(settings)); - - req.Method = "POST"; - req.ContentType = "application/x-www-form-urlencoded"; - req.UserAgent = HttpContext.Current.Request.UserAgent; - - string formContent = string.Format("{0}&cmd=_notify-validate", formString); - req.ContentLength = formContent.Length; - - using (var sw = new StreamWriter(req.GetRequestStream(), Encoding.ASCII)) - { - sw.Write(formContent); - } - - string response = null; - using (var sr = new StreamReader(req.GetResponse().GetResponseStream())) - { - response = HttpUtility.UrlDecode(sr.ReadToEnd()); - } - bool success = response.Trim().Equals("VERIFIED", StringComparison.OrdinalIgnoreCase); - - values = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (string l in formString.Split('&')) - { - string line = HttpUtility.UrlDecode(l).Trim(); - int equalPox = line.IndexOf('='); - if (equalPox >= 0) - values.Add(line.Substring(0, equalPox), line.Substring(equalPox + 1)); - } - - return success; - } - /// /// Gets additional handling fee /// diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs index 7f60018419..036489ebff 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs @@ -4,7 +4,6 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Net; using System.Text; using System.Web; using System.Web.Routing; @@ -27,7 +26,7 @@ namespace SmartStore.PayPal /// /// PayPalStandard provider /// - [SystemName("Payments.PayPalStandard")] + [SystemName("Payments.PayPalStandard")] [FriendlyName("PayPal Standard")] [DisplayOrder(2)] public partial class PayPalStandardProvider : PaymentPluginBase, IConfigurable @@ -191,11 +190,11 @@ public override void PostProcessPayment(PostProcessPaymentRequest postProcessPay if (cartTotal > postProcessPaymentRequest.Order.OrderTotal) { - /* Take the difference between what the order total is and what it should be and use that as the "discount". - * The difference equals the amount of the gift card and/or reward points used. - */ + // Take the difference between what the order total is and what it should be and use that as the "discount". + // The difference equals the amount of the gift card and/or reward points used. decimal discountTotal = cartTotal - postProcessPaymentRequest.Order.OrderTotal; discountTotal = Math.Round(discountTotal, 2); + //gift card or rewared point amount applied to cart in SmartStore.NET - shows in Paypal as "discount" builder.AppendFormat("&discount_amount_cart={0}", discountTotal.ToString("0.00", CultureInfo.InvariantCulture)); } @@ -230,8 +229,8 @@ public override void PostProcessPayment(PostProcessPaymentRequest postProcessPay builder.AppendFormat("&no_shipping=1", new object[0]); } - string returnUrl = _services.WebHelper.GetStoreLocation(false) + "Plugins/SmartStore.PayPal/PayPalStandard/PDTHandler"; - string cancelReturnUrl = _services.WebHelper.GetStoreLocation(false) + "Plugins/SmartStore.PayPal/PayPalStandard/CancelOrder"; + var returnUrl = _services.WebHelper.GetStoreLocation(store.SslEnabled) + "Plugins/SmartStore.PayPal/PayPalStandard/PDTHandler"; + var cancelReturnUrl = _services.WebHelper.GetStoreLocation(store.SslEnabled) + "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 +238,7 @@ public override void PostProcessPayment(PostProcessPaymentRequest postProcessPay { string ipnUrl; if (String.IsNullOrWhiteSpace(settings.IpnUrl)) - ipnUrl = _services.WebHelper.GetStoreLocation(false) + "Plugins/SmartStore.PayPal/PayPalStandard/IPNHandler"; + ipnUrl = _services.WebHelper.GetStoreLocation(store.SslEnabled) + "Plugins/SmartStore.PayPal/PayPalStandard/IPNHandler"; else ipnUrl = settings.IpnUrl; builder.AppendFormat("¬ify_url={0}", ipnUrl); @@ -326,22 +325,29 @@ public override decimal GetAdditionalHandlingFee(IListResult public bool GetPDTDetails(string tx, PayPalStandardPaymentSettings settings, out Dictionary values, out string response) { - var req = (HttpWebRequest)WebRequest.Create(PayPalHelper.GetPaypalUrl(settings)); - req.Method = "POST"; - req.ContentType = "application/x-www-form-urlencoded"; + var request = PayPalHelper.GetPayPalWebRequest(settings); + request.Method = "POST"; + request.ContentType = "application/x-www-form-urlencoded"; - string formContent = string.Format("cmd=_notify-synch&at={0}&tx={1}", settings.PdtToken, tx); - req.ContentLength = formContent.Length; + var formContent = string.Format("cmd=_notify-synch&at={0}&tx={1}", settings.PdtToken, tx); + request.ContentLength = formContent.Length; - using (var sw = new StreamWriter(req.GetRequestStream(), Encoding.ASCII)) - sw.Write(formContent); + using (var sw = new StreamWriter(request.GetRequestStream(), Encoding.ASCII)) + { + sw.Write(formContent); + } response = null; - using (var sr = new StreamReader(req.GetResponse().GetResponseStream())) - response = HttpUtility.UrlDecode(sr.ReadToEnd()); + using (var sr = new StreamReader(request.GetResponse().GetResponseStream())) + { + response = HttpUtility.UrlDecode(sr.ReadToEnd()); + } values = new Dictionary(StringComparer.OrdinalIgnoreCase); - bool firstLine = true, success = false; + + var firstLine = true; + var success = false; + foreach (string l in response.Split('\n')) { string line = l.Trim(); @@ -399,10 +405,10 @@ public List GetLineItems(PostProcessPaymentRequest postProcessPa var order = postProcessPaymentRequest.Order; var lst = new List(); - // order items - foreach (var orderItem in order.OrderItems) + // order items... checkout attributes are included in order total + foreach (var orderItem in order.OrderItems) { - var item = new PayPalLineItem() + var item = new PayPalLineItem { Type = PayPalItemType.CartItem, Name = orderItem.Product.GetLocalized(x => x.Name), @@ -414,30 +420,10 @@ public List GetLineItems(PostProcessPaymentRequest postProcessPa cartTotal += orderItem.PriceExclTax; } - // checkout attributes.... are included in order total - //foreach (var caValue in checkoutAttributeValues) - //{ - // var attributePrice = _taxService.GetCheckoutAttributePrice(caValue, false, order.Customer); - - // if (attributePrice > decimal.Zero && caValue.CheckoutAttribute != null) - // { - // var item = new PayPalLineItem() - // { - // Type = PayPalItemType.CheckoutAttribute, - // Name = caValue.CheckoutAttribute.GetLocalized(x => x.Name), - // Quantity = 1, - // Amount = attributePrice - // }; - // lst.Add(item); - - // cartTotal += attributePrice; - // } - //} - // shipping if (order.OrderShippingExclTax > decimal.Zero) { - var item = new PayPalLineItem() + var item = new PayPalLineItem { Type = PayPalItemType.Shipping, Name = T("Plugins.Payments.PayPalStandard.ShippingFee").Text, @@ -452,7 +438,7 @@ public List GetLineItems(PostProcessPaymentRequest postProcessPa // payment fee if (order.PaymentMethodAdditionalFeeExclTax > decimal.Zero) { - var item = new PayPalLineItem() + var item = new PayPalLineItem { Type = PayPalItemType.PaymentFee, Name = T("Plugins.Payments.PayPalStandard.PaymentMethodFee").Text, @@ -467,7 +453,7 @@ public List GetLineItems(PostProcessPaymentRequest postProcessPa // tax if (order.OrderTax > decimal.Zero) { - var item = new PayPalLineItem() + var item = new PayPalLineItem { Type = PayPalItemType.Tax, Name = T("Plugins.Payments.PayPalStandard.SalesTax").Text, @@ -501,17 +487,21 @@ public void AdjustLineItemAmounts(List paypalItems, PostProcessP if (cartItems.Count() <= 0) return; - decimal totalSmartStore = Math.Round(postProcessPaymentRequest.Order.OrderSubtotalExclTax, 2); - decimal totalPayPal = decimal.Zero; + //decimal totalSmartStore = Math.Round(postProcessPaymentRequest.Order.OrderSubtotalExclTax, 2); + decimal totalSmartStore = Math.Round(postProcessPaymentRequest.Order.OrderTotal, 2); + decimal totalPayPal = decimal.Zero; decimal delta, portion, rest; - // calculate what PayPal calculates - cartItems.Each(x => totalPayPal += (x.AmountRounded * x.Quantity)); - totalPayPal = Math.Round(totalPayPal, 2, MidpointRounding.AwayFromZero); + // calculate what PayPal calculates + //cartItems.Each(x => totalPayPal += (x.AmountRounded * x.Quantity)); + paypalItems.Each(x => totalPayPal += (x.AmountRounded * x.Quantity)); + totalPayPal = Math.Round(totalPayPal, 2, MidpointRounding.AwayFromZero); + + // calculate difference + delta = Math.Round(totalSmartStore - totalPayPal, 2); + //"SM: {0}, PP: {1}, delta: {2}".FormatInvariant(totalSmartStore, totalPayPal, delta).Dump(); - // calculate difference - delta = Math.Round(totalSmartStore - totalPayPal, 2); - if (delta == decimal.Zero) + if (delta == decimal.Zero) return; // prepare lines... only lines with quantity = 1 are adjustable. if there is no one, create one. @@ -542,56 +532,12 @@ public void AdjustLineItemAmounts(List paypalItems, PostProcessP restItem.Amount = restItem.Amount + rest; } - //"SM: {0}, PP: {1}, delta: {2} (portion: {3}, rest: {4})".FormatWith(totalSmartStore, totalPayPal, delta, portion, rest).Dump(); - } - catch (Exception exc) - { - _logger.Error(exc.Message, exc); - } - } - - - /// - /// Verifies IPN - /// - /// Form string - /// Values - /// Result - public bool VerifyIPN(string formString, out Dictionary values) - { - // settings: multistore context not possible here. we need the custom value to determine what store it is. - var settings = _services.Settings.LoadSetting(); - - var req = (HttpWebRequest)WebRequest.Create(PayPalHelper.GetPaypalUrl(settings)); - req.Method = "POST"; - req.ContentType = "application/x-www-form-urlencoded"; - req.UserAgent = HttpContext.Current.Request.UserAgent; - - string formContent = string.Format("{0}&cmd=_notify-validate", formString); - req.ContentLength = formContent.Length; - - using (var sw = new StreamWriter(req.GetRequestStream(), Encoding.ASCII)) - { - sw.Write(formContent); - } - - string response = null; - using (var sr = new StreamReader(req.GetResponse().GetResponseStream())) - { - response = HttpUtility.UrlDecode(sr.ReadToEnd()); - } - bool success = response.Trim().Equals("VERIFIED", StringComparison.OrdinalIgnoreCase); - - values = new Dictionary(StringComparer.OrdinalIgnoreCase); - foreach (string l in formString.Split('&')) + //"SM: {0}, PP: {1}, delta: {2} (portion: {3}, rest: {4})".FormatInvariant(totalSmartStore, totalPayPal, delta, portion, rest).Dump(); + } + catch (Exception exception) { - string line = HttpUtility.UrlDecode(l).Trim(); - int equalPox = line.IndexOf('='); - if (equalPox >= 0) - values.Add(line.Substring(0, equalPox), line.Substring(equalPox + 1)); + _logger.Error(exception.Message, exception); } - - return success; } /// diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalHelper.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalHelper.cs index 1f057f46ef..87ff90ca6d 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalHelper.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalHelper.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.IO; using System.Net; using System.Text; +using System.Web; using System.Web.Routing; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Payments; @@ -127,11 +129,16 @@ public static CurrencyCodeType GetPaypalCurrency(Currency currency) return currencyCodeType; } - public static string CheckIfButtonExists(string buttonUrl) - { - + public static string CheckIfButtonExists(PayPalExpressPaymentSettings settings, string buttonUrl) + { HttpWebResponse response = null; - var request = (HttpWebRequest)WebRequest.Create(buttonUrl); + + if (settings.SecurityProtocol.HasValue) + { + ServicePointManager.SecurityProtocol = settings.SecurityProtocol.Value; + } + + var request = (HttpWebRequest)WebRequest.Create(buttonUrl); request.Method = "HEAD"; try @@ -151,8 +158,6 @@ public static string CheckIfButtonExists(string buttonUrl) response.Close(); } } - - } public static bool CurrentPageIsBasket(RouteData routeData) @@ -172,7 +177,57 @@ public static string GetPaypalUrl(PayPalSettingsBase settings) "https://www.paypal.com/cgi-bin/webscr"; } - public static string GetApiVersion() + public static HttpWebRequest GetPayPalWebRequest(PayPalSettingsBase settings) + { + if (settings.SecurityProtocol.HasValue) + { + ServicePointManager.SecurityProtocol = settings.SecurityProtocol.Value; + } + + var request = (HttpWebRequest)WebRequest.Create(GetPaypalUrl(settings)); + return request; + } + + public static bool VerifyIPN(PayPalSettingsBase settings, string formString, out Dictionary values) + { + // settings: multistore context not possible here. we need the custom value to determine what store it is. + + var request = PayPalHelper.GetPayPalWebRequest(settings); + request.Method = "POST"; + request.ContentType = "application/x-www-form-urlencoded"; + request.UserAgent = HttpContext.Current.Request.UserAgent; + + var formContent = string.Format("{0}&cmd=_notify-validate", formString); + request.ContentLength = formContent.Length; + + using (var sw = new StreamWriter(request.GetRequestStream(), Encoding.ASCII)) + { + sw.Write(formContent); + } + + string response = null; + using (var sr = new StreamReader(request.GetResponse().GetResponseStream())) + { + response = HttpUtility.UrlDecode(sr.ReadToEnd()); + } + + var success = response.Trim().Equals("VERIFIED", StringComparison.OrdinalIgnoreCase); + + values = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var item in formString.SplitSafe("&")) + { + var line = HttpUtility.UrlDecode(item).TrimSafe(); + var equalIndex = line.IndexOf('='); + + if (equalIndex >= 0) + values.Add(line.Substring(0, equalIndex), line.Substring(equalIndex + 1)); + } + + return success; + } + + public static string GetApiVersion() { return "109"; } diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalProcessPaymentRequest.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalProcessPaymentRequest.cs index 22984ffeac..ff64026abf 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalProcessPaymentRequest.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalProcessPaymentRequest.cs @@ -1,16 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using SmartStore.Services.Payments; +using SmartStore.Services.Payments; namespace SmartStore.PayPal.Services { - public class PayPalProcessPaymentRequest : ProcessPaymentRequest + public class PayPalProcessPaymentRequest : ProcessPaymentRequest { /// /// Gets or sets an order Discount Amount /// public decimal Discount { get; set; } } -} +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalStandardCore.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalStandardCore.cs index eb7d70877d..9c68e538fe 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalStandardCore.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalStandardCore.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; namespace SmartStore.PayPal.Services { @@ -20,7 +19,7 @@ public decimal AmountRounded public PayPalLineItem Clone() { - var item = new PayPalLineItem() + var item = new PayPalLineItem { Type = this.Type, Name = this.Name, @@ -37,10 +36,9 @@ object ICloneable.Clone() } - public enum PayPalItemType : int + public enum PayPalItemType { CartItem = 0, - CheckoutAttribute, Shipping, PaymentFee, Tax diff --git a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs index fbee21e704..df30f3fcc6 100644 --- a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs +++ b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs @@ -5,29 +5,30 @@ namespace SmartStore.PayPal.Settings { public abstract class PayPalSettingsBase { - public bool UseSandbox { get; set; } + public PayPalSettingsBase() + { + SecurityProtocol = SecurityProtocolType.Tls12; + } + + public SecurityProtocolType? SecurityProtocol { get; set; } + + public bool UseSandbox { get; set; } + /// /// Gets or sets a value indicating whether to "additional fee" is specified as percentage. true - percentage, false - fixed value. /// + /// public bool AdditionalFeePercentage { get; set; } - /// - /// Additional fee - /// + public decimal AdditionalFee { get; set; } } public abstract class PayPalApiSettingsBase : PayPalSettingsBase { - public PayPalApiSettingsBase() - { - SecurityProtocol = SecurityProtocolType.Tls12; - } - public TransactMode TransactMode { get; set; } public string ApiAccountName { get; set; } public string ApiAccountPassword { get; set; } public string Signature { get; set; } - public SecurityProtocolType? SecurityProtocol { get; set; } } public class PayPalDirectPaymentSettings : PayPalApiSettingsBase, ISettings diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml index 70868da9e5..8c1b616cbb 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml @@ -24,6 +24,14 @@ @using (Html.BeginForm()) { + + + + + +
              + @Html.SmartLabelFor(model => model.SecurityProtocol) + + @Html.DropDownListFor(model => model.SecurityProtocol, Model.AvailableSecurityProtocols, T("Common.Unspecified")) +
              @Html.SmartLabelFor(model => model.UseSandbox) diff --git a/src/Plugins/SmartStore.PayPal/changelog.md b/src/Plugins/SmartStore.PayPal/changelog.md index a76b138768..c6157fa5c7 100644 --- a/src/Plugins/SmartStore.PayPal/changelog.md +++ b/src/Plugins/SmartStore.PayPal/changelog.md @@ -1,11 +1,13 @@ #Release Notes ##Paypal 2.2.0.4 -### New Features +###New Features * Option for API security protocol +###Bugfixes +* "The request was aborted: Could not create SSL/TLS secure channel." See https://devblog.paypal.com/upcoming-security-changes-notice/ ##Paypal 2.2.0.3 -### New Features +###New Features * Option to add order note when order total validation fails ##PayPal 2.2.0.2 @@ -13,7 +15,7 @@ * Redirecting to payment provider performed by core instead of plugin ##Paypal 2.2.0.1 -### New Features +###New Features * Supports order list label for new incoming IPNs ##Paypal 1.22 From 394d640e87dbc96c24033adb06aa6cab078ad125 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 15 Feb 2016 13:04:18 +0100 Subject: [PATCH 029/423] Resolves #851 Replace reCAPTCHA with "I'm not a robot" CAPTCHA --- changelog.md | 1 + .../201601262000441_ImportFramework1.cs | 8 ++ .../SmartStore.Web.Framework.csproj | 4 +- .../UI/Captcha/CaptchaValidatorAttribute.cs | 96 +++++++++++++------ .../UI/Captcha/HtmlExtensions.cs | 30 +++--- .../SmartStore.Web.Framework/packages.config | 1 - .../SmartStore.Web/SmartStore.Web.csproj | 3 - src/Presentation/SmartStore.Web/Web.config | 4 +- .../SmartStore.Web/packages.config | 1 - 9 files changed, 96 insertions(+), 52 deletions(-) diff --git a/changelog.md b/changelog.md index cbe2a94a26..bfa62a7127 100644 --- a/changelog.md +++ b/changelog.md @@ -83,6 +83,7 @@ * Trusted Shops: badge will be displayed in mobile themes, payment info link replaced compare list link in footer * Product filter: Specification attributes are sorted by display order rather than alphabetically by name * #856 Don't route topics which are excluded from sitemap +* #851 Replace reCAPTCHA with "I'm not a robot" CAPTCHA ### Bugfixes * #523 Redirecting to payment provider performed by core instead of plugin diff --git a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs index 38a30b9f4c..a50a01c797 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs @@ -331,6 +331,14 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Obere Beschreibung", "Description of the category that is displayed above products on the category page.", "Beschreibung der Warengruppe, die auf der Warengruppenseite oberhalb der Produkte angezeigt wird."); + + builder.AddOrUpdate("Common.CaptchaUnableToVerify", + "The API call to verify a CAPTCHA has failed.", + "Der API-Aufruf zur Prfung eines CAPTCHAs ist fehlgeschlagen."); + + builder.AddOrUpdate("Common.WrongCaptcha", + "Please confirm that you are not a \"robot\".", + "Bitte besttigen Sie, dass Sie kein \"Roboter\" sind."); } } } diff --git a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj index 05afb411d8..729a38d055 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj +++ b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj @@ -126,9 +126,6 @@ False ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll - - ..\..\packages\recaptcha.1.0.5.0\lib\.NetFramework 4.0\Recaptcha.dll - @@ -140,6 +137,7 @@ ..\..\packages\Microsoft.AspNet.WebApi.Client.5.1.2\lib\net45\System.Net.Http.Formatting.dll + False diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Captcha/CaptchaValidatorAttribute.cs b/src/Presentation/SmartStore.Web.Framework/UI/Captcha/CaptchaValidatorAttribute.cs index 94459d3067..050b694202 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Captcha/CaptchaValidatorAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Captcha/CaptchaValidatorAttribute.cs @@ -1,44 +1,82 @@ using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Json; +using System.Text; +using System.Web; using System.Web.Mvc; -using SmartStore.Core.Infrastructure; +using SmartStore.Core.Logging; +using SmartStore.Services.Localization; +using SmartStore.Utilities; namespace SmartStore.Web.Framework.UI.Captcha { - public class CaptchaValidatorAttribute : ActionFilterAttribute + public class CaptchaValidatorAttribute : ActionFilterAttribute { - private const string CHALLENGE_FIELD_KEY = "recaptcha_challenge_field"; - private const string RESPONSE_FIELD_KEY = "recaptcha_response_field"; - public Lazy CaptchaSettings { get; set; } + public Lazy Logger { get; set; } + public Lazy LocalizationService { get; set; } - public override void OnActionExecuting(ActionExecutingContext filterContext) + public override void OnActionExecuting(ActionExecutingContext filterContext) { - bool valid = false; - var captchaChallengeValue = filterContext.HttpContext.Request.Form[CHALLENGE_FIELD_KEY]; - var captchaResponseValue = filterContext.HttpContext.Request.Form[RESPONSE_FIELD_KEY]; - if (!string.IsNullOrEmpty(captchaChallengeValue) && !string.IsNullOrEmpty(captchaResponseValue)) - { + var valid = false; + + try + { var captchaSettings = CaptchaSettings.Value; - if (captchaSettings.Enabled) - { - //validate captcha - var captchaValidtor = new Recaptcha.RecaptchaValidator - { - PrivateKey = captchaSettings.ReCaptchaPrivateKey, - RemoteIP = filterContext.HttpContext.Request.UserHostAddress, - Challenge = captchaChallengeValue, - Response = captchaResponseValue - }; - - var recaptchaResponse = captchaValidtor.Validate(); - valid = recaptchaResponse.IsValid; - } - } - - //this will push the result value into a parameter in our Action - filterContext.ActionParameters["captchaValid"] = valid; + var verifyUrl = CommonHelper.GetAppSetting("g:RecaptchaVerifyUrl"); + var recaptchaResponse = filterContext.HttpContext.Request.Form["g-recaptcha-response"]; + + var url = "{0}?secret={1}&response={2}".FormatInvariant( + verifyUrl, + HttpUtility.UrlEncode(captchaSettings.ReCaptchaPrivateKey), + HttpUtility.UrlEncode(recaptchaResponse) + ); + + using (var client = new WebClient()) + { + var jsonResponse = client.DownloadString(url); + using (var memoryStream = new MemoryStream(Encoding.Unicode.GetBytes(jsonResponse))) + { + var serializer = new DataContractJsonSerializer(typeof(GoogleRecaptchaApiResponse)); + var result = serializer.ReadObject(memoryStream) as GoogleRecaptchaApiResponse; + + if (result == null) + { + Logger.Value.Error(LocalizationService.Value.GetResource("Common.CaptchaUnableToVerify")); + } + else + { + if (result.ErrorCodes == null) + { + valid = result.Success; + } + } + } + } + } + catch (Exception exception) + { + Logger.Value.ErrorsAll(exception); + } + + // this will push the result value into a parameter in our Action + filterContext.ActionParameters["captchaValid"] = valid; base.OnActionExecuting(filterContext); } } + + + [DataContract] + public class GoogleRecaptchaApiResponse + { + [DataMember(Name = "success")] + public bool Success { get; set; } + + [DataMember(Name = "error-codes")] + public List ErrorCodes { get; set; } + } } diff --git a/src/Presentation/SmartStore.Web.Framework/UI/Captcha/HtmlExtensions.cs b/src/Presentation/SmartStore.Web.Framework/UI/Captcha/HtmlExtensions.cs index d523811a9c..77d7b1cb7a 100644 --- a/src/Presentation/SmartStore.Web.Framework/UI/Captcha/HtmlExtensions.cs +++ b/src/Presentation/SmartStore.Web.Framework/UI/Captcha/HtmlExtensions.cs @@ -1,28 +1,30 @@ -using System.IO; +using System.Text; using System.Web.Mvc; -using System.Web.UI; using SmartStore.Core.Infrastructure; +using SmartStore.Utilities; namespace SmartStore.Web.Framework.UI.Captcha { - public static class HtmlExtensions + public static class HtmlExtensions { public static string GenerateCaptcha(this HtmlHelper helper) { + var sb = new StringBuilder(); var captchaSettings = EngineContext.Current.Resolve(); - var captchaControl = new Recaptcha.RecaptchaControl - { - ID = "recaptcha", - Theme = "white", - PublicKey = captchaSettings.ReCaptchaPublicKey, - PrivateKey = captchaSettings.ReCaptchaPrivateKey - }; + var widgetUrl = CommonHelper.GetAppSetting("g:RecaptchaWidgetUrl"); + var elementId = "GoogleRecaptchaWidget"; - var htmlWriter = new HtmlTextWriter(new StringWriter()); + sb.AppendLine(""); + sb.AppendLine("
              ".FormatInvariant(elementId)); + sb.AppendLine("".FormatInvariant(widgetUrl)); - captchaControl.RenderControl(htmlWriter); - - return htmlWriter.InnerWriter.ToString(); + return sb.ToString(); } } } diff --git a/src/Presentation/SmartStore.Web.Framework/packages.config b/src/Presentation/SmartStore.Web.Framework/packages.config index d105ec9f25..06b8ceab7c 100644 --- a/src/Presentation/SmartStore.Web.Framework/packages.config +++ b/src/Presentation/SmartStore.Web.Framework/packages.config @@ -25,7 +25,6 @@ - \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index d85cdaee1d..b7cd9cde97 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -138,9 +138,6 @@ False ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll
              - - ..\..\packages\recaptcha.1.0.5.0\lib\.NetFramework 4.0\Recaptcha.dll - diff --git a/src/Presentation/SmartStore.Web/Web.config b/src/Presentation/SmartStore.Web/Web.config index ea77417613..cb7537b427 100644 --- a/src/Presentation/SmartStore.Web/Web.config +++ b/src/Presentation/SmartStore.Web/Web.config @@ -1,4 +1,4 @@ - + @@ -83,6 +83,8 @@ + + + From bf87c9d65c4104accfc4f3093c62a4856ba3aba7 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 16 Feb 2016 00:06:04 +0100 Subject: [PATCH 035/423] Minor CSS enhancements --- changelog.md | 2 +- .../Administration/Content/admin.less | 9 ++++++--- .../Administration/Views/Export/List.cshtml | 14 +++++++------- .../Administration/Views/Import/List.cshtml | 4 ++-- .../Views/Common/EntityPickerList.cshtml | 2 +- 5 files changed, 17 insertions(+), 14 deletions(-) diff --git a/changelog.md b/changelog.md index ec52892d74..000e34fc02 100644 --- a/changelog.md +++ b/changelog.md @@ -13,7 +13,7 @@ * Attach order invoice PDF automatically to order notification emails * #526 Min/Max amount option for which the payment method should be offered during checkout * (Dev) New _SyncMapping_ service: enables easier entity synchronization with external systems -* #792 ViewEngine: Enable vbhtml views per configuration +* (Dev) #792 ViewEngine: Enable vbhtml views per configuration * #718 ShopConnector: Import option for "Published" and "Disable buy\wishlist button" * #702 Facebook and Twitter external authentication suitable for multi-stores * New scheduled task: Clear e-mail queue diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index 1e9b777e17..12e3c546e8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -902,12 +902,15 @@ td.adminSeparator hr { } .admin-table { + thead th { + text-transform: uppercase; + } + th, td { + padding: 12px; + } .disabled { .muted(); } - .centered { - text-align: center; - } .progress-info { min-width: 260px; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 00d5b4011c..9de32a6d36 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -27,11 +27,11 @@ - + - - + + @@ -41,20 +41,20 @@ @foreach (var profile in Model.OrderBy(x => x.Name)) { - - - + if (Model.ShowKeyFieldNote) + { + + + + } - if (Model.ShowKeyFieldNote) + if (Model.KeyFieldNames != null && Model.KeyFieldNames.Contains("Id", StringComparer.OrdinalIgnoreCase)) { + + + +
              @T("Common.Image") @T("Admin.DataExchange.Export.Name") @T("Admin.DataExchange.Export.EntityType")@T("Admin.DataExchange.Export.FileExtension")@T("Admin.DataExchange.Export.ExportFiles")@T("Admin.DataExchange.Export.FileExtension")@T("Admin.DataExchange.Export.ExportFiles") @T("Admin.System.ScheduleTasks.LastStart") @T("Admin.System.ScheduleTasks.NextRun") @T("Admin.Common.Actions")
              + @profile.Provider.FriendlyName - @profile.Name + @profile.Name
              @profile.ProviderSystemName
              @profile.Provider.EntityTypeName.NaIfEmpty() + @Html.IconForFileExtension(profile.Provider.FileExtension, true) + @Html.Partial("ProfileFileCount", profile.Details) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml index 2191b24194..5ecdd6ba5f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml @@ -24,7 +24,7 @@ @if(Model.Profiles.Any()) { - +
              @@ -41,7 +41,7 @@ { + + + +
              @T("Admin.DataExchange.Import.Name")
              - @profile.Name + @profile.Name @profile.EntityTypeName diff --git a/src/Presentation/SmartStore.Web/Views/Common/EntityPickerList.cshtml b/src/Presentation/SmartStore.Web/Views/Common/EntityPickerList.cshtml index 38f12511ee..e9262f1ca1 100644 --- a/src/Presentation/SmartStore.Web/Views/Common/EntityPickerList.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Common/EntityPickerList.cshtml @@ -17,7 +17,7 @@ @if (item.ImageUrl.HasValue()) {
              - +
              }
              From 9149c9c3a71faa09014564d3cef14bbd5f5eb7b0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 16 Feb 2016 13:23:33 +0100 Subject: [PATCH 036/423] Resolves #654 Place user agreement for downloadable files in checkout process --- changelog.md | 1 + .../201601262000441_ImportFramework1.cs | 12 ++++ .../Controllers/DownloadController.cs | 24 +++++++- .../Controllers/ShoppingCartController.cs | 4 +- .../Models/ShoppingCart/ShoppingCartModel.cs | 3 + .../Views/Checkout/Confirm.Mobile.cshtml | 17 +++++- .../Views/Checkout/Confirm.cshtml | 39 ++++++++----- .../ShoppingCart/OrderSummary.Mobile.cshtml | 26 ++++++++- .../Views/ShoppingCart/OrderSummary.cshtml | 55 ++++++++++++++++--- 9 files changed, 153 insertions(+), 28 deletions(-) diff --git a/changelog.md b/changelog.md index 000e34fc02..6bd8d990e0 100644 --- a/changelog.md +++ b/changelog.md @@ -43,6 +43,7 @@ * PayPal: Option for API security protocol * Product filter: Option to sort filter results by their display order rather than by number of matches * Elmar Shopinfo: Option to export delivery time as availability +* #654 Place user agreement for downloadable files in checkout process ### 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/201601262000441_ImportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs index a50a01c797..f56ba50ad2 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs @@ -339,6 +339,18 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Common.WrongCaptcha", "Please confirm that you are not a \"robot\".", "Bitte besttigen Sie, dass Sie kein \"Roboter\" sind."); + + builder.AddOrUpdate("DownloadableProducts.UserAgreementConfirmation", + "Yes, I agree to the user agreement for this product.", + "Ja, ich stimme der Nutzungsvereinbarung fr dieses Produkt zu."); + + builder.AddOrUpdate("DownloadableProducts.HasNoUserAgreement", + "The product has no user agreement.", + "Das Produkt besitzt keine Nutzungsvereinbarung."); + + builder.AddOrUpdate("Checkout.DownloadUserAgreement.PleaseAgree", + "Please agree to the user agreement for downloadable products.", + "Bitte stimmen Sie der Nutzungsvereinbarung fr herunterladbare Produkte zu."); } } } diff --git a/src/Presentation/SmartStore.Web/Controllers/DownloadController.cs b/src/Presentation/SmartStore.Web/Controllers/DownloadController.cs index 8d56ea3c12..308517f636 100644 --- a/src/Presentation/SmartStore.Web/Controllers/DownloadController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/DownloadController.cs @@ -1,9 +1,11 @@ using System; +using System.Web; using System.Web.Mvc; using SmartStore.Core; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Media; +using SmartStore.Core.Html; using SmartStore.Services.Catalog; using SmartStore.Services.Media; using SmartStore.Services.Orders; @@ -167,5 +169,25 @@ public ActionResult GetFileUpload(Guid downloadId) return GetFileContentResultFor(download, null); } - } + + public ActionResult GetUserAgreement(int productId, bool? asPlainText) + { + var product = _productService.GetProductById(productId); + if (product == null) + return HttpNotFound(); + + if (!product.IsDownload || !product.HasUserAgreement || product.UserAgreementText.IsEmpty()) + return Content(T("DownloadableProducts.HasNoUserAgreement")); + + if (asPlainText ?? false) + { + var agreement = HtmlUtils.ConvertHtmlToPlainText(product.UserAgreementText); + agreement = HtmlUtils.StripTags(HttpUtility.HtmlDecode(agreement)); + + return Content(agreement); + } + + return Content(product.UserAgreementText); + } + } } diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index 2f25ae9568..47049dacc2 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -240,7 +240,9 @@ private ShoppingCartModel.ShoppingCartItemModel PrepareShoppingCartItemModel(Org ShortDesc = product.GetLocalized(x => x.ShortDescription), ProductType = product.ProductType, BasePrice = product.GetBasePriceInfo(_localizationService, _priceFormatter, _currencyService, _taxService, _priceCalculationService, _workContext.WorkingCurrency), - Weight = product.Weight + Weight = product.Weight, + IsDownload = product.IsDownload, + HasUserAgreement = product.HasUserAgreement }; model.ProductUrl = GetProductUrlWithAttributes(sci, model.ProductSeName); diff --git a/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs b/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs index 7567b03e6c..d76808fed8 100644 --- a/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs +++ b/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs @@ -108,6 +108,9 @@ public ShoppingCartItemModel() public string BasePrice { get; set; } + public bool IsDownload { get; set; } + public bool HasUserAgreement { get; set; } + public bool BundlePerItemPricing { get; set; } public bool BundlePerItemShoppingCart { get; set; } public BundleItemModel BundleItem { get; set; } diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml index ce45bc8284..9783e3bbe3 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml @@ -75,8 +75,10 @@ +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.cshtml index fcaf07c168..fc696d7a6a 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.cshtml @@ -44,15 +44,12 @@
              } - @if (Model.DisplayDeliveryTime) + @if (Model.DisplayDeliveryTime && item.IsShipEnabled && (!String.IsNullOrEmpty(item.DeliveryTimeName) && !String.IsNullOrEmpty(item.DeliveryTimeHexValue))) {
              - @if ((!String.IsNullOrEmpty(item.DeliveryTimeName) && !String.IsNullOrEmpty(item.DeliveryTimeHexValue)) && item.IsShipEnabled) - { - @T("Products.DeliveryTime") - - @item.DeliveryTimeName - } + @T("Products.DeliveryTime") + + @item.DeliveryTimeName
              } @@ -68,6 +65,17 @@ @Html.Raw(item.RecurringInfo) } + @if (!Model.IsEditable && item.IsDownload && item.HasUserAgreement) + { +
              +
              + +
              +
              + } @if (item.Warnings.Count > 0) {
              @@ -126,8 +134,7 @@ @helper BundleProducts(ShoppingCartModel.ShoppingCartItemModel parentItem) { if (parentItem.ChildItems != null) - { - + {
                @@ -413,3 +420,33 @@ } + +@if (!Model.IsEditable && Model.Items.Any(x => x.IsDownload && x.HasUserAgreement)) +{ + + +} \ No newline at end of file From 94bbdc962021e8d4dc9b09e2ad1e064d00d2ae6b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 16 Feb 2016 15:30:30 +0100 Subject: [PATCH 037/423] Minor improvements to order confirmation page settings --- .../Domain/Orders/ShoppingCartSettings.cs | 16 ++++++--- .../201601262000441_ImportFramework1.cs | 23 +++++++++++++ .../Settings/ShoppingCartSettingsModel.cs | 5 ++- .../Views/Setting/ShoppingCart.cshtml | 34 ++++++++++++++----- .../Content/smartstore.entitypicker.css | 7 ++-- .../Controllers/ShoppingCartController.cs | 12 +++++-- .../Models/ShoppingCart/ShoppingCartModel.cs | 4 ++- 7 files changed, 80 insertions(+), 21 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs b/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs index 7398790f67..e64000ea1b 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs @@ -17,6 +17,7 @@ public ShoppingCartSettings() ShowDiscountBox = true; ShowGiftCardBox = true; ShowCommentBox = true; + ShowEsdRevocationWaiverBox = true; CrossSellsNumber = 8; EmailWishlistEnabled = true; MiniShoppingCartEnabled = true; @@ -97,11 +98,16 @@ public ShoppingCartSettings() /// Gets or sets a value indicating whether to show a comment box on shopping cart page /// public bool ShowCommentBox { get; set; } - - /// - /// Gets or sets a number of "Cross-sells" on shopping cart page - /// - public int CrossSellsNumber { get; set; } + + /// + /// Gets or sets a value indicating whether to show a revocation waiver checkbox box for ESD products + /// + public bool ShowEsdRevocationWaiverBox { get; set; } + + /// + /// Gets or sets a number of "Cross-sells" on shopping cart page + /// + public int CrossSellsNumber { get; set; } /// /// Gets or sets a value indicating whether "email a wishlist" feature is enabled diff --git a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs index f56ba50ad2..bdfd3cbf46 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs @@ -351,6 +351,29 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Checkout.DownloadUserAgreement.PleaseAgree", "Please agree to the user agreement for downloadable products.", "Bitte stimmen Sie der Nutzungsvereinbarung fr herunterladbare Produkte zu."); + + + builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.OrderConfirmationPage", + "Order confirmation page", + "Bestellabschlussseite"); + + builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.ShowEsdRevocationWaiverBox", + "Show revocation waiver box for electronic services", + "Widerrufsverzichtbox fr elektronische Leistungen anzeigen", + "Specifies whether the customer must agree a revocation waiver for electronic services on the order confirmation page.", + "Legt fest, ob der Kunde auf der Bestellabschlussseite einem Widerrufsverzicht fr elektronische Leistungen zustimmen muss."); + + builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.ShowCommentBox", + "Show comment box", + "Kommentarbox anzeigen", + "Specifies whether comment box is displayed on the order confirmation page.", + "Legt fest, ob der Kunde auf der Bestellabschlussseite einen Kommentar zu seiner Bestellung hinterlegen kann."); + + builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.ShowConfirmOrderLegalHint", + "Show legal hints in order summary", + "Rechtliche Hinweise in der Warenkorbbersicht anzeigen", + "Specifies whether to show hints in order summary on the confirm order page. This text can be altered in the language resources.", + "Legt fest, ob rechtliche Hinweise in der Warenkorbbersicht auf der Bestellabschluseite angezeigt werden. Dieser Text kann in den Sprachresourcen gendert werden."); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ShoppingCartSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ShoppingCartSettingsModel.cs index 34d639051b..df20029b62 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ShoppingCartSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ShoppingCartSettingsModel.cs @@ -76,7 +76,10 @@ public class ShoppingCartSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.ShowCommentBox")] public bool ShowCommentBox { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.RoundPricesDuringCalculation")] + [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.ShowEsdRevocationWaiverBox")] + public bool ShowEsdRevocationWaiverBox { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.RoundPricesDuringCalculation")] public bool RoundPricesDuringCalculation { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml index 34f62d4c96..6ee39a782a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml @@ -292,6 +292,22 @@ @helper TabCheckoutSettings() { + + + + + + + - - - - + + + +
              +
              +
              @T("Admin.Configuration.Settings.ShoppingCart.OrderConfirmationPage")
              +
              +
              + @Html.SmartLabelFor(model => model.ShowCommentBox) + + @Html.SettingEditorFor(model => model.ShowCommentBox) + @Html.ValidationMessageFor(model => model.ShowCommentBox) +
              @Html.SmartLabelFor(model => model.ShowConfirmOrderLegalHint) @@ -301,14 +317,14 @@ @Html.ValidationMessageFor(model => model.ShowConfirmOrderLegalHint)
              - @Html.SmartLabelFor(model => model.ShowCommentBox) - - @Html.SettingEditorFor(model => model.ShowCommentBox) - @Html.ValidationMessageFor(model => model.ShowCommentBox) -
              + @Html.SmartLabelFor(model => model.ShowEsdRevocationWaiverBox) + + @Html.SettingEditorFor(model => model.ShowEsdRevocationWaiverBox) + @Html.ValidationMessageFor(model => model.ShowEsdRevocationWaiverBox) +
              } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/smartstore.entitypicker.css b/src/Presentation/SmartStore.Web/Content/smartstore.entitypicker.css index 72f3a6f91c..00183c84dc 100644 --- a/src/Presentation/SmartStore.Web/Content/smartstore.entitypicker.css +++ b/src/Presentation/SmartStore.Web/Content/smartstore.entitypicker.css @@ -39,9 +39,12 @@ float: left; width: 65px; } +.entity-picker-list .img-polaroid { + padding: 2px; +} .entity-picker-list .thumb img { - max-width: 52px; - max-height: 52px; + max-width: 50px; + max-height: 50px; } .entity-picker-list .label { display: inline-block; diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index 47049dacc2..00928a51d7 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -637,12 +637,14 @@ protected void PrepareShoppingCartModel(ShoppingCartModel model, model.ShowProductImages = _shoppingCartSettings.ShowProductImagesOnShoppingCart; model.ShowProductBundleImages = _shoppingCartSettings.ShowProductBundleImagesOnShoppingCart; model.ShowSku = _catalogSettings.ShowProductSku; + var checkoutAttributesXml = _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes, _genericAttributeService); model.CheckoutAttributeInfo = HtmlUtils.ConvertPlainTextToTable(HtmlUtils.ConvertHtmlToPlainText( _checkoutAttributeFormatter.FormatAttributes(checkoutAttributesXml, _workContext.CurrentCustomer) )); //model.CheckoutAttributeInfo = _checkoutAttributeFormatter.FormatAttributes(_workContext.CurrentCustomer.CheckoutAttributes, _workContext.CurrentCustomer); //model.CheckoutAttributeInfo = _checkoutAttributeFormatter.FormatAttributes(_workContext.CurrentCustomer.CheckoutAttributes, _workContext.CurrentCustomer, "", false); + bool minOrderSubtotalAmountOk = _orderProcessingService.ValidateMinOrderSubtotalAmount(cart); if (!minOrderSubtotalAmountOk) { @@ -655,17 +657,21 @@ protected void PrepareShoppingCartModel(ShoppingCartModel model, model.DiscountBox.Display = _shoppingCartSettings.ShowDiscountBox; var discountCouponCode = _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.DiscountCouponCode); var discount = _discountService.GetDiscountByCouponCode(discountCouponCode); - if (discount != null && - discount.RequiresCouponCode && - _discountService.IsDiscountValid(discount, _workContext.CurrentCustomer)) + if (discount != null && discount.RequiresCouponCode && _discountService.IsDiscountValid(discount, _workContext.CurrentCustomer)) + { model.DiscountBox.CurrentCode = discount.CouponCode; + } + model.GiftCardBox.Display = _shoppingCartSettings.ShowGiftCardBox; model.DisplayCommentBox = _shoppingCartSettings.ShowCommentBox; + model.DisplayEsdRevocationWaiverBox = _shoppingCartSettings.ShowEsdRevocationWaiverBox; //cart warnings var cartWarnings = _shoppingCartService.GetShoppingCartWarnings(cart, checkoutAttributesXml, validateCheckoutAttributes); foreach (var warning in cartWarnings) + { model.Warnings.Add(warning); + } #endregion diff --git a/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs b/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs index d76808fed8..0d37bdb2be 100644 --- a/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs +++ b/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs @@ -51,9 +51,11 @@ public ShoppingCartModel() public bool DisplayCommentBox { get; set; } public string CustomerComment { get; set; } + public bool DisplayEsdRevocationWaiverBox { get; set; } + #region Nested Classes - public partial class ShoppingCartItemModel : EntityModelBase + public partial class ShoppingCartItemModel : EntityModelBase { public ShoppingCartItemModel() { From 089093fb1b65f38da8fb5681aade52d8eabc1c24 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 16 Feb 2016 18:02:21 +0100 Subject: [PATCH 038/423] Resolves #398 EU law: add 'revocation' form and revocation waiver for ESD --- changelog.md | 1 + .../201601262000441_ImportFramework1.cs | 9 +++++ .../Controllers/CheckoutController.cs | 1 + .../Controllers/DownloadController.cs | 2 +- .../Controllers/ShoppingCartController.cs | 3 +- .../Models/Checkout/CheckoutConfirmModel.cs | 1 + .../Models/ShoppingCart/ShoppingCartModel.cs | 2 ++ .../Views/Checkout/Confirm.Mobile.cshtml | 35 +++++++++++++++---- .../Views/Checkout/Confirm.cshtml | 22 ++++++++++-- .../ShoppingCart/OrderSummary.Mobile.cshtml | 9 +++++ .../Views/ShoppingCart/OrderSummary.cshtml | 11 ++++++ 11 files changed, 86 insertions(+), 10 deletions(-) diff --git a/changelog.md b/changelog.md index 6bd8d990e0..b9ca687ae2 100644 --- a/changelog.md +++ b/changelog.md @@ -44,6 +44,7 @@ * Product filter: Option to sort filter results by their display order rather than by number of matches * Elmar Shopinfo: Option to export delivery time as availability * #654 Place user agreement for downloadable files in checkout process +* #398 EU law: add 'revocation' form and revocation waiver for ESD ### 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/201601262000441_ImportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs index bdfd3cbf46..33980a4bd6 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs @@ -374,6 +374,15 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Rechtliche Hinweise in der Warenkorbbersicht anzeigen", "Specifies whether to show hints in order summary on the confirm order page. This text can be altered in the language resources.", "Legt fest, ob rechtliche Hinweise in der Warenkorbbersicht auf der Bestellabschluseite angezeigt werden. Dieser Text kann in den Sprachresourcen gendert werden."); + + + builder.AddOrUpdate("Checkout.EsdRevocationWaiverConfirmation", + "Yes, I want access to the digital content immediately and know that my right of revocation expires with the access.", + "Ja, ich mchte sofort Zugang zu dem digitalen Inhalt und wei, dass mein Widerrufsrecht mit dem Zugang erlischt."); + + builder.AddOrUpdate("Checkout.EsdRevocationWaiverConfirmation.PleaseAgree", + "Please confirm that you would like access to the digital content immediately.", + "Bitte besttigen Sie, dass Sie sofort Zugang zu dem digitalen Inhalt wnschen."); } } } diff --git a/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs b/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs index 519b5c5d44..cef4f0de15 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs @@ -360,6 +360,7 @@ protected CheckoutConfirmModel PrepareConfirmOrderModel(IList Warnings { get; set; } public bool ShowConfirmOrderLegalHint { get; set; } + public bool ShowEsdRevocationWaiverBox { get; set; } public bool BypassPaymentMethodInfo { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs b/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs index 0d37bdb2be..6a38af28ac 100644 --- a/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs +++ b/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs @@ -113,6 +113,8 @@ public ShoppingCartItemModel() public bool IsDownload { get; set; } public bool HasUserAgreement { get; set; } + public bool IsEsd { get; set; } + public bool BundlePerItemPricing { get; set; } public bool BundlePerItemShoppingCart { get; set; } public BundleItemModel BundleItem { get; set; } diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml index 9783e3bbe3..d8f5fc7079 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.Mobile.cshtml @@ -76,14 +76,16 @@ $(function () { $('.confirm-order-next-step-button').click(function () { var termOfServiceOk = true, - userAgreementsOk = true; + userAgreementsOk = true, + esdRevocationWaiverOk = true, + alertMessage; // terms of services @if (Model.TermsOfServiceEnabled) { if (!$('#termsofservice').is(':checked')) { - alert('@T("Checkout.TermsOfService.PleaseAccept")'); + alertMessage = '@T("Checkout.TermsOfService.PleaseAccept")'; termOfServiceOk = false; } else { @@ -96,12 +98,30 @@ $('ul.list-order-products').find('input[name^=AgreeUserAgreement]').each(function () { if (!$(this).is(':checked')) { userAgreementsOk = false; - alert('@T("Checkout.DownloadUserAgreement.PleaseAgree")'); + if (!alertMessage) { + alertMessage = '@T("Checkout.DownloadUserAgreement.PleaseAgree")'; + } return false; } }); - - if (termOfServiceOk && userAgreementsOk) { + + // agree esd revocation waiver + @if(Model.ShowEsdRevocationWaiverBox) + { + + $('ul.list-order-products').find('input[name^=AgreeEsdRevocationWaiver]').each(function () { + if (!$(this).is(':checked')) { + esdRevocationWaiverOk = false; + if (!alertMessage) { + alertMessage = '@T("Checkout.EsdRevocationWaiverConfirmation.PleaseAgree")'; + } + return false; + } + }); + + } + + if (termOfServiceOk && userAgreementsOk && esdRevocationWaiverOk) { var submitOrderEvent = jQuery.Event('submitOrder'); submitOrderEvent.isOrderValid = true; submitOrderEvent.isMobile = true; @@ -111,7 +131,10 @@ if (true === submitOrderEvent.isOrderValid) { $('#confirm-order-form').submit(); } - } + } + else if (alertMessage) { + alert(alertMessage); + } }); }); diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml index 22de1355ec..82c4444f62 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml @@ -112,7 +112,8 @@ var checkoutButton = $(".confirm-order-next-step-button"); checkoutButton.click(function () { var termOfServiceOk = true, - userAgreementsOk = true; + userAgreementsOk = true, + esdRevocationWaiverOk = true; $("#customercommenthidden").val($("#CustomerComment").val()); @@ -142,8 +143,25 @@ return false; } }); + + // agree esd revocation waiver + @if(Model.ShowEsdRevocationWaiverBox) + { + + $('table.table-order-products').find('input[name^=AgreeEsdRevocationWaiver]').each(function () { + if (!$(this).is(':checked')) { + esdRevocationWaiverOk = false; + displayNotification('@T("Checkout.EsdRevocationWaiverConfirmation.PleaseAgree").ToString().EncodeJsString('"', false)', 'error'); + if (termOfServiceOk) { + $.scrollTo($('table.table-order-products'), 800, { offset: -20 }); + } + return false; + } + }); + + } - if (termOfServiceOk && userAgreementsOk) { + if (termOfServiceOk && userAgreementsOk && esdRevocationWaiverOk) { var submitOrderEvent = jQuery.Event('submitOrder'); submitOrderEvent.isOrderValid = true; submitOrderEvent.isMobile = false; diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.Mobile.cshtml index d0daa2abe4..51e1f695ea 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.Mobile.cshtml @@ -164,6 +164,15 @@ } + @if (!Model.IsEditable && Model.DisplayEsdRevocationWaiverBox && item.IsEsd) + { +
              + +
              + } @if (Model.IsEditable) {
              diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.cshtml index fc696d7a6a..18f94761f9 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.cshtml @@ -76,6 +76,17 @@
              } + @if (!Model.IsEditable && Model.DisplayEsdRevocationWaiverBox && item.IsEsd) + { +
              +
              + +
              +
              + } @if (item.Warnings.Count > 0) {
              From bf0e249508d2b40e9a4ba01078330a1e549045ff Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 16 Feb 2016 21:01:21 +0100 Subject: [PATCH 039/423] Added data exchange settings page --- .../DataExchange/DataExchangeSettings.cs | 5 ++ .../201601262000441_ImportFramework1.cs | 14 +++ .../Controllers/SettingController.cs | 86 ++++++++++++++++--- .../Settings/DataExchangeSettingsModel.cs | 25 ++++++ .../Administration/SmartStore.Admin.csproj | 2 + .../Views/Setting/DataExchange.cshtml | 55 ++++++++++++ .../Administration/sitemap.config | 1 + 7 files changed, 175 insertions(+), 13 deletions(-) create mode 100644 src/Presentation/SmartStore.Web/Administration/Models/Settings/DataExchangeSettingsModel.cs create mode 100644 src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs index 742e379df5..446412b144 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs @@ -13,5 +13,10 @@ public DataExchangeSettings() /// The maximum length of file names (in characters) of files created by the export framework ///
              public int MaxFileNameLength { get; set; } + + /// + /// Relative path to a folder with images to be imported + /// + public string ImageImportFolder { get; set; } } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs index 33980a4bd6..cb21c1f500 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs @@ -70,6 +70,7 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Common.Warnings", "Warnings", "Warnungen"); builder.AddOrUpdate("Admin.Common.Errors", "Errors", "Fehler"); builder.AddOrUpdate("Admin.Common.UnsupportedEntityType", "Unsupported entity type '{0}'", "Nicht untersttzter Entittstyp '{0}'"); + builder.AddOrUpdate("Admin.Common.DataExchange", "Data exchange", "Datenaustausch"); builder.AddOrUpdate("Admin.DataExchange.Import.CompletedEmail.Body", "This is an automatic notification of store \"{0}\" about a recent data import. Summary:", @@ -383,6 +384,19 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Checkout.EsdRevocationWaiverConfirmation.PleaseAgree", "Please confirm that you would like access to the digital content immediately.", "Bitte besttigen Sie, dass Sie sofort Zugang zu dem digitalen Inhalt wnschen."); + + + builder.AddOrUpdate("Admin.Configuration.Settings.DataExchange.MaxFileNameLength", + "Maximum length of file and folder names", + "Maximale Lnge von Datei- und Ordnernamen", + "Specifies the maximum length of file and folder names created during an import or export.", + "Legt die maximale Lnge von Datei- und Ordnernamen fest, die im Rahmen eines Imports\\Exports erzeugt wurden."); + + builder.AddOrUpdate("Admin.Configuration.Settings.DataExchange.ImageImportFolder", + "Image folder (relative path)", + "Bilderordner (relativer Pfad)", + "Specifies a relative path to a folder with images to be imported (e.g. Content\\Images).", + "Legt einen relativen Pfad zu einem Ordner mit zu importierenden Bildern fest (z.B. Inhalt\\Bilder)."); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs index fa7aad7957..9650234c38 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs @@ -9,6 +9,7 @@ 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.Forums; using SmartStore.Core.Domain.Localization; @@ -113,7 +114,9 @@ public SettingController( this._services = services; } - #endregion  + #endregion + + #region Utilities private StoreDependingSettingHelper StoreDependingSettings { @@ -125,7 +128,20 @@ private StoreDependingSettingHelper StoreDependingSettings } } - #region Methods + private void NotifyModelStateErrors() + { + foreach (var modelState in ModelState.Values) + { + foreach (var error in modelState.Errors) + { + NotifyError(error.ErrorMessage); + } + } + } + + #endregion + + #region Methods [ChildActionOnly] public ActionResult StoreScopeConfiguration() @@ -666,11 +682,9 @@ public ActionResult RewardPoints(RewardPointsSettingsModel model, FormCollection } else { - //If we got this far, something failed, redisplay form - foreach (var modelState in ModelState.Values) - foreach (var error in modelState.Errors) - NotifyError(error.ErrorMessage); + NotifyModelStateErrors(); } + return RedirectToAction("RewardPoints"); } @@ -768,13 +782,9 @@ public ActionResult Order(OrderSettingsModel model, FormCollection form) } else { - //If we got this far, something failed, redisplay form - foreach (var modelState in ModelState.Values) - { - foreach (var error in modelState.Errors) - NotifyError(error.ErrorMessage); - } - } + NotifyModelStateErrors(); + } + return RedirectToAction("Order"); } @@ -1533,6 +1543,56 @@ public ActionResult TestSeoNameCreation(GeneralCommonSettingsModel model) return Content(result); } + public ActionResult DataExchange() + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) + return AccessDeniedView(); + + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var settings = _services.Settings.LoadSetting(storeScope); + + var model = new DataExchangeSettingsModel + { + MaxFileNameLength = settings.MaxFileNameLength, + ImageImportFolder = settings.ImageImportFolder + }; + + StoreDependingSettings.GetOverrideKeys(settings, model, storeScope, _services.Settings); + + return View(model); + } + + [HttpPost] + public ActionResult DataExchange(DataExchangeSettingsModel model, FormCollection form) + { + if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) + return AccessDeniedView(); + + if (ModelState.IsValid) + { + var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); + var settings = _services.Settings.LoadSetting(storeScope); + + settings.MaxFileNameLength = model.MaxFileNameLength; + settings.ImageImportFolder = model.ImageImportFolder; + + StoreDependingSettings.UpdateSettings(settings, form, storeScope, _services.Settings); + + _services.Settings.ClearCache(); + + _customerActivityService.InsertActivity("EditSettings", _services.Localization.GetResource("ActivityLog.EditSettings")); + + NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Updated")); + } + else + { + NotifyModelStateErrors(); + } + + return RedirectToAction("DataExchange"); + } + + //all settings public ActionResult AllSettings() { diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/DataExchangeSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/DataExchangeSettingsModel.cs new file mode 100644 index 0000000000..f69e72e6d4 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/DataExchangeSettingsModel.cs @@ -0,0 +1,25 @@ +using SmartStore.Web.Framework; + +namespace SmartStore.Admin.Models.Settings +{ + public partial class DataExchangeSettingsModel + { + #region General + + [SmartResourceDisplayName("Admin.Configuration.Settings.DataExchange.MaxFileNameLength")] + public int MaxFileNameLength { get; set; } + + #endregion + + #region Import + + [SmartResourceDisplayName("Admin.Configuration.Settings.DataExchange.ImageImportFolder")] + public string ImageImportFolder { get; set; } + + #endregion + + #region Export + + #endregion + } +} \ 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 aa8e1d79a0..e6f94f1ce8 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -270,6 +270,7 @@ + @@ -524,6 +525,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml new file mode 100644 index 0000000000..eeab6d754c --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml @@ -0,0 +1,55 @@ +@model DataExchangeSettingsModel +@{ + ViewBag.Title = T("Admin.Common.DataExchange").Text; +} +@using (Html.BeginForm()) +{ +
              +
              + + @T("Admin.Common.DataExchange") +
              +
              + +
              +
              + + @Html.Action("StoreScopeConfiguration", "Setting") + @Html.ValidationSummary(false) + + @(Html.SmartStore().TabStrip().Name("dataexchangesettings-edit").Items(x => + { + x.Add().Text(T("Admin.Common.General").Text).Content(@TabGeneral()).Selected(true); + x.Add().Text(T("Common.Import").Text).Content(@TabImport()); + })) +} + +@helper TabGeneral() +{ + + + + + +
              + @Html.SmartLabelFor(model => model.MaxFileNameLength) + + @Html.SettingEditorFor(model => model.MaxFileNameLength) + @Html.ValidationMessageFor(model => model.MaxFileNameLength) +
              +} + +@helper TabImport() +{ + + + + + +
              + @Html.SmartLabelFor(model => model.ImageImportFolder) + + @Html.SettingEditorFor(model => model.ImageImportFolder) + @Html.ValidationMessageFor(model => model.ImageImportFolder) +
              +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/sitemap.config b/src/Presentation/SmartStore.Web/Administration/sitemap.config index 62881a6d45..179ac3a5c4 100644 --- a/src/Presentation/SmartStore.Web/Administration/sitemap.config +++ b/src/Presentation/SmartStore.Web/Administration/sitemap.config @@ -94,6 +94,7 @@ + From e495aa8b0701c65e974e22294043a9aafaf016cb Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 17 Feb 2016 10:39:34 +0100 Subject: [PATCH 040/423] Minor change --- .../DataExchange/DataExporter.cs | 8 +-- .../Controllers/SettingController.cs | 4 +- .../Views/Setting/DataExchange.cshtml | 62 ++++++++----------- 3 files changed, 32 insertions(+), 42 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs index 31a3777a43..53cfc707b3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs @@ -79,7 +79,6 @@ public partial class DataExporter : IDataExporter private readonly Lazy> _subscriptionRepository; private readonly Lazy> _orderRepository; - private readonly Lazy _dataExchangeSettings; private readonly Lazy _mediaSettings; private readonly Lazy _contactDataSettings; @@ -116,7 +115,6 @@ public DataExporter( Lazy> customerRepository, Lazy> subscriptionRepository, Lazy> orderRepository, - Lazy dataExchangeSettings, Lazy mediaSettings, Lazy contactDataSettings) { @@ -154,7 +152,6 @@ public DataExporter( _subscriptionRepository = subscriptionRepository; _orderRepository = orderRepository; - _dataExchangeSettings = dataExchangeSettings; _mediaSettings = mediaSettings; _contactDataSettings = contactDataSettings; @@ -943,7 +940,8 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) if (ctx.ExecuteContext.Abort != DataExchangeAbortion.None) return; - int fileIndex = 0; + var fileIndex = 0; + var dataExchangeSettings = _services.Settings.LoadSetting(store.Id); ctx.Store = store; @@ -974,7 +972,7 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) ctx.ExecuteContext.Store = ToDynamic(ctx, ctx.Store); - ctx.ExecuteContext.MaxFileNameLength = _dataExchangeSettings.Value.MaxFileNameLength; + ctx.ExecuteContext.MaxFileNameLength = dataExchangeSettings.MaxFileNameLength; ctx.ExecuteContext.HasPublicDeployment = ctx.Request.Profile.Deployments.Any(x => x.IsPublic && x.DeploymentType == ExportDeploymentType.FileSystem); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs index 9650234c38..fa96fae19b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs @@ -1580,9 +1580,9 @@ public ActionResult DataExchange(DataExchangeSettingsModel model, FormCollection _services.Settings.ClearCache(); - _customerActivityService.InsertActivity("EditSettings", _services.Localization.GetResource("ActivityLog.EditSettings")); + _customerActivityService.InsertActivity("EditSettings", T("ActivityLog.EditSettings")); - NotifySuccess(_services.Localization.GetResource("Admin.Configuration.Updated")); + NotifySuccess(T("Admin.Configuration.Updated")); } else { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml index eeab6d754c..100dfd26e6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml @@ -16,40 +16,32 @@ @Html.Action("StoreScopeConfiguration", "Setting") @Html.ValidationSummary(false) - - @(Html.SmartStore().TabStrip().Name("dataexchangesettings-edit").Items(x => - { - x.Add().Text(T("Admin.Common.General").Text).Content(@TabGeneral()).Selected(true); - x.Add().Text(T("Common.Import").Text).Content(@TabImport()); - })) -} -@helper TabGeneral() -{ - - - - - -
              - @Html.SmartLabelFor(model => model.MaxFileNameLength) - - @Html.SettingEditorFor(model => model.MaxFileNameLength) - @Html.ValidationMessageFor(model => model.MaxFileNameLength) -
              + + + + + + + + + + + + +
              + @Html.SmartLabelFor(model => model.MaxFileNameLength) + + @Html.SettingEditorFor(model => model.MaxFileNameLength) + @Html.ValidationMessageFor(model => model.MaxFileNameLength) +
              +
              +
              @T("Common.Import")
              +
              +
              + @Html.SmartLabelFor(model => model.ImageImportFolder) + + @Html.SettingEditorFor(model => model.ImageImportFolder) + @Html.ValidationMessageFor(model => model.ImageImportFolder) +
              } - -@helper TabImport() -{ - - - - - -
              - @Html.SmartLabelFor(model => model.ImageImportFolder) - - @Html.SettingEditorFor(model => model.ImageImportFolder) - @Html.ValidationMessageFor(model => model.ImageImportFolder) -
              -} \ No newline at end of file From 2199ef2efaa0ac3cd2821d295ccfad1536e08438 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 18 Feb 2016 14:56:39 +0100 Subject: [PATCH 041/423] Resolves #738 Implement download of pictures via URLs in product import --- changelog.md | 1 + .../DataExchange/DataExchangeSettings.cs | 6 + .../SmartStore.Core/Logging/TraceLogger.cs | 22 +- .../Utilities/FileDownloadManager.cs | 110 ++++++--- .../201601262000441_ImportFramework1.cs | 6 + .../Catalog/Importer/ProductImporter.cs | 222 +++++++++++++++++- .../DataExchange/Import/DataImporter.cs | 14 +- .../Import/ImportExecuteContext.cs | 27 ++- .../Controllers/SettingController.cs | 4 +- .../Settings/DataExchangeSettingsModel.cs | 3 + .../Views/Setting/DataExchange.cshtml | 9 + 11 files changed, 362 insertions(+), 62 deletions(-) diff --git a/changelog.md b/changelog.md index b9ca687ae2..ce43f0987f 100644 --- a/changelog.md +++ b/changelog.md @@ -45,6 +45,7 @@ * Elmar Shopinfo: Option to export delivery time as availability * #654 Place user agreement for downloadable files in checkout process * #398 EU law: add 'revocation' form and revocation waiver for ESD +* #738 Implement download of pictures via URLs in product import ### 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/DataExchange/DataExchangeSettings.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs index 446412b144..4b0bc6d497 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/DataExchangeSettings.cs @@ -7,6 +7,7 @@ public class DataExchangeSettings : ISettings public DataExchangeSettings() { MaxFileNameLength = 50; + ImageDownloadTimeout = 10; } /// @@ -18,5 +19,10 @@ public DataExchangeSettings() /// Relative path to a folder with images to be imported /// public string ImageImportFolder { get; set; } + + /// + /// The timeout for image download per entity in minutes + /// + public int ImageDownloadTimeout { get; set; } } } diff --git a/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs b/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs index 20767d0095..3b8f7a4657 100644 --- a/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs +++ b/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs @@ -25,7 +25,7 @@ public TraceLogger(string fileName) _traceSource = new TraceSource("SmartStore"); _traceSource.Switch = new SourceSwitch("LogSwitch", "Error"); _traceSource.Listeners.Remove("Default"); - + var console = new ConsoleTraceListener(false); console.Filter = new EventTypeFilter(SourceLevels.All); console.Name = "console"; @@ -34,11 +34,15 @@ public TraceLogger(string fileName) textListener.Filter = new EventTypeFilter(SourceLevels.All); textListener.TraceOutputOptions = TraceOptions.DateTime; - // force UTF-8 encoding (even if the text just contains ANSI characters) - var append = File.Exists(fileName); + try + { + // force UTF-8 encoding (even if the text just contains ANSI characters) + var append = File.Exists(fileName); + _streamWriter = new StreamWriter(fileName, append, Encoding.UTF8); - _streamWriter = new StreamWriter(fileName, append, Encoding.UTF8); - textListener.Writer = _streamWriter; + textListener.Writer = _streamWriter; + } + catch (IOException) { } _traceSource.Listeners.Add(console); _traceSource.Listeners.Add(textListener); @@ -139,11 +143,13 @@ public void Flush() protected override void OnDispose(bool disposing) { _traceSource.Flush(); - - _streamWriter.Close(); _traceSource.Close(); - _streamWriter.Dispose(); + if (_streamWriter != null) + { + _streamWriter.Close(); + _streamWriter.Dispose(); + } } } } diff --git a/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs b/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs index 2fdd530c40..09f40c69d9 100644 --- a/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs +++ b/src/Libraries/SmartStore.Core/Utilities/FileDownloadManager.cs @@ -51,14 +51,32 @@ public FileDownloadResponse DownloadFile(string url, bool sendAuthCookie = false } using (var resp = (HttpWebResponse)req.GetResponse()) + using (var stream = resp.GetResponseStream()) { - using (var stream = resp.GetResponseStream()) + if (resp.StatusCode == HttpStatusCode.OK) { - if (resp.StatusCode == HttpStatusCode.OK) + var data = stream.ToByteArray(); + if (data != null && data.Length != 0) { - var data = stream.ToByteArray(); - var cd = new ContentDisposition(resp.Headers["Content-Disposition"]); - var fileName = cd.FileName; + string fileName = null; + + var contentDisposition = resp.Headers["Content-Disposition"]; + if (contentDisposition.HasValue()) + { + var cd = new ContentDisposition(contentDisposition); + fileName = cd.FileName; + } + + if (fileName.IsEmpty()) + { + try + { + var uri = new Uri(url); + fileName = Path.GetFileName(uri.LocalPath); + } + catch { } + } + return new FileDownloadResponse(data, fileName, resp.ContentType); } } @@ -79,31 +97,40 @@ public async Task DownloadAsync(FileDownloadManagerContext context, IEnumerable< private async Task DownloadFiles(FileDownloadManagerContext context, IEnumerable items) { - var client = new HttpClient(); - - client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue(); - client.DefaultRequestHeaders.CacheControl.NoCache = true; - client.DefaultRequestHeaders.Add("Connection", "Keep-alive"); + try + { + using (var client = new HttpClient()) + { + client.DefaultRequestHeaders.CacheControl = new CacheControlHeaderValue(); + client.DefaultRequestHeaders.CacheControl.NoCache = true; + client.DefaultRequestHeaders.Add("Connection", "Keep-alive"); - if (context.Timeout.TotalMilliseconds > 0 && context.Timeout != Timeout.InfiniteTimeSpan) - client.Timeout = context.Timeout; + if (context.Timeout.TotalMilliseconds > 0 && context.Timeout != Timeout.InfiniteTimeSpan) + client.Timeout = context.Timeout; - IEnumerable downloadTasksQuery = - from item in items - select ProcessUrl(context, client, item); + IEnumerable downloadTasksQuery = + from item in items + select ProcessUrl(context, client, item); - // now execute the bunch - List downloadTasks = downloadTasksQuery.ToList(); + // now execute the bunch + List downloadTasks = downloadTasksQuery.ToList(); - while (downloadTasks.Count > 0) - { - // identify the first task that completes - Task firstFinishedTask = await Task.WhenAny(downloadTasks); + while (downloadTasks.Count > 0) + { + // identify the first task that completes + Task firstFinishedTask = await Task.WhenAny(downloadTasks); - // process only once - downloadTasks.Remove(firstFinishedTask); + // process only once + downloadTasks.Remove(firstFinishedTask); - await firstFinishedTask; + await firstFinishedTask; + } + } + } + catch (Exception exception) + { + if (context.Logger != null) + context.Logger.ErrorsAll(exception); } } @@ -111,7 +138,10 @@ private async Task ProcessUrl(FileDownloadManagerContext context, HttpClient cli { try { - Task task = client.GetStreamAsync(item.Url); + //HttpResponseMessage response = await client.GetAsync(item.Url, HttpCompletionOption.ResponseHeadersRead); + //Task task = response.Content.ReadAsStreamAsync(); + + Task task = client.GetStreamAsync(item.Url); await task; int count; @@ -132,29 +162,31 @@ private async Task ProcessUrl(FileDownloadManagerContext context, HttpClient cli item.Success = (!task.IsFaulted && !canceled); } - catch (Exception exc) + catch (Exception exception) { - item.Success = false; - item.ErrorMessage = exc.ToAllMessages(); - - var webExc = exc.InnerException as WebException; - if (webExc != null) - item.ExceptionStatus = webExc.Status; + try + { + item.Success = false; + item.ErrorMessage = exception.ToAllMessages(); - if (context.Logger != null) - context.Logger.Error(item.ToString(), exc); + var webExc = exception.InnerException as WebException; + if (webExc != null) + item.ExceptionStatus = webExc.Status; + + if (context.Logger != null) + context.Logger.Error(item.ToString(), exception); + } + catch { } } } - } + public class FileDownloadResponse { public FileDownloadResponse(byte[] data, string fileName, string contentType) { Guard.ArgumentNotNull(() => data); - Guard.ArgumentNotEmpty(() => fileName); - Guard.ArgumentNotEmpty(() => contentType); this.Data = data; this.FileName = fileName; @@ -182,12 +214,12 @@ public class FileDownloadManagerContext /// /// Optional logger to log errors /// - public TraceLogger Logger { get; set; } + public ILogger Logger { get; set; } /// /// Cancellation token /// - public CancellationTokenSource CancellationToken { get; set; } + public CancellationToken CancellationToken { get; set; } /// /// Timeout for the HTTP client diff --git a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs index cb21c1f500..557a465ef3 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs @@ -397,6 +397,12 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Bilderordner (relativer Pfad)", "Specifies a relative path to a folder with images to be imported (e.g. Content\\Images).", "Legt einen relativen Pfad zu einem Ordner mit zu importierenden Bildern fest (z.B. Inhalt\\Bilder)."); + + builder.AddOrUpdate("Admin.Configuration.Settings.DataExchange.ImageDownloadTimeout", + "Timeout for image download (minutes)", + "Zeitlimit fr Bilder-Download (Minuten)", + "Specifies the timeout for the image download in minutes.", + "Legt das Zeitlimit fr den Bilder-Download in Minuten fest."); } } } diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index e94744b9a9..7c89b77931 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -8,6 +8,7 @@ using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Seo; using SmartStore.Core.Events; +using SmartStore.Core.IO; using SmartStore.Services.DataExchange.Import; using SmartStore.Services.Localization; using SmartStore.Services.Media; @@ -33,7 +34,9 @@ public class ProductImporter : IEntityImporter private readonly IProductService _productService; private readonly IUrlRecordService _urlRecordService; private readonly IStoreMappingService _storeMappingService; + private readonly FileDownloadManager _fileDownloadManager; private readonly SeoSettings _seoSettings; + private readonly DataExchangeSettings _dataExchangeSettings; public ProductImporter( IRepository productPictureRepository, @@ -50,7 +53,9 @@ public ProductImporter( IProductService productService, IUrlRecordService urlRecordService, IStoreMappingService storeMappingService, - SeoSettings seoSettings) + FileDownloadManager fileDownloadManager, + SeoSettings seoSettings, + DataExchangeSettings dataExchangeSettings) { _productPictureRepository = productPictureRepository; _productManufacturerRepository = productManufacturerRepository; @@ -66,7 +71,10 @@ public ProductImporter( _productService = productService; _urlRecordService = urlRecordService; _storeMappingService = storeMappingService; + _fileDownloadManager = fileDownloadManager; + _seoSettings = seoSettings; + _dataExchangeSettings = dataExchangeSettings; } private int? ZeroToNull(object value, CultureInfo culture) @@ -80,7 +88,168 @@ public ProductImporter( return (int?)null; } - private void ProcessProductPictures(IImportExecuteContext context, ImportRow[] batch) + private void ProcessProductPictures(IImportExecuteContext context, ProductImporterContext importerContext, ImportRow[] batch) + { + // true, cause pictures must be saved and assigned an id prior adding a mapping. + _productPictureRepository.AutoCommitEnabled = true; + + ProductPicture lastInserted = null; + var equalPictureId = 0; + + foreach (var row in batch) + { + var imageUrls = row.GetDataValue>("ImageUrls"); + if (imageUrls == null) + continue; + + var imageNumber = 0; + var displayOrder = -1; + var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); + var imageFiles = new List(); + + // collect required image file infos + foreach (var urlOrPath in imageUrls) + { + ++imageNumber; + var image = new FileDownloadManagerItem + { + Id = imageNumber, + DisplayOrder = imageNumber, + MimeType = MimeTypes.MapNameToMimeType(urlOrPath) + }; + + if (image.MimeType.IsEmpty()) + image.MimeType = "image/jpeg"; + + var extension = MimeTypes.MapMimeTypeToExtension(image.MimeType); + + if (extension.HasValue()) + { + if (urlOrPath.IsWebUrl()) + { + image.Url = urlOrPath; + image.FileName = "{0}-{1}".FormatInvariant(image.Id, seoName).ToValidFileName(); + image.Path = Path.Combine(importerContext.ImageDownloadFolder, image.FileName + extension.EnsureStartsWith(".")); + } + else if (Path.IsPathRooted(urlOrPath)) + { + image.Path = urlOrPath; + image.Success = true; + } + else + { + image.Path = Path.Combine(importerContext.ImageFolder, urlOrPath); + image.Success = true; + } + + imageFiles.Add(image); + } + } + + // download images + if (imageFiles.Any(x => x.Url.HasValue())) + { + foreach (var image in imageFiles.Where(x => x.Url.HasValue())) + { + try + { + var timeout = (_dataExchangeSettings.ImageDownloadTimeout * 60 * 1000); + var response = _fileDownloadManager.DownloadFile(image.Url, false, timeout > 0 ? timeout : (int?)null); + image.Success = (response != null); + + if (response != null) + { + File.WriteAllBytes(image.Path, response.Data); + } + } + catch (Exception exception) + { + context.Result.AddWarning(exception.ToAllMessages(), row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + } + } + + // async HttpClient deadlocks ITask. wrong synchronization context? + // async downloading in batch processing is inefficient cause only the image processing benefits from async, + // not the record processing itself. a per record processing would speed up the import. + + //Task imageDownload = _fileDownloadManager.DownloadAsync(importerContext.DownloaderContext, imageFiles.Where(x => x.Url.HasValue())); + + //if (imageDownload != null) + //{ + // imageDownload.Wait(); + //} + } + + // import images + foreach (var image in imageFiles.OrderBy(x => x.DisplayOrder)) + { + try + { + if ((image.Success ?? false) && File.Exists(image.Path)) + { + var pictureBinary = File.ReadAllBytes(image.Path); + + if (pictureBinary != null && pictureBinary.Length > 0) + { + var currentProductPictures = _productPictureRepository.TableUntracked.Expand(x => x.Picture) + .Where(x => x.ProductId == row.Entity.Id) + .ToList(); + + var currentPictures = currentProductPictures + .Select(x => x.Picture) + .ToList(); + + if (displayOrder == -1) + { + displayOrder = (currentProductPictures.Any() ? currentProductPictures.Select(x => x.DisplayOrder).Max() : 0); + } + + pictureBinary = _pictureService.ValidatePicture(pictureBinary); + pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); + + if (pictureBinary != null && pictureBinary.Length > 0) + { + // no equal picture found in sequence + var newPicture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false); + if (newPicture != null) + { + var mapping = new ProductPicture + { + ProductId = row.Entity.Id, + PictureId = newPicture.Id, + DisplayOrder = ++displayOrder + }; + + _productPictureRepository.Insert(mapping); + lastInserted = mapping; + } + } + else + { + context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + } + } + } + else if (image.Url.HasValue()) + { + context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + } + } + catch (Exception exception) + { + context.Result.AddWarning(exception.ToAllMessages(), row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + } + } + } + + // Perf: notify only about LAST insertion and update + if (lastInserted != null) + { + _services.EventPublisher.EntityInserted(lastInserted); + } + } + + private void ProcessProductPicturesOld(IImportExecuteContext context, ImportRow[] batch) { // true, cause pictures must be saved and assigned an id prior adding a mapping. _productPictureRepository.AutoCommitEnabled = true; @@ -382,7 +551,7 @@ private void ProcessStoreMappings(IImportExecuteContext context, ImportRow[] batch, DateTime utcNow) + private int ProcessProducts(IImportExecuteContext context, ProductImporterContext importerContext, ImportRow[] batch) { _productRepository.AutoCommitEnabled = true; @@ -534,8 +703,8 @@ private int ProcessProducts(IImportExecuteContext context, ImportRow[] row.SetProperty(context.Result, product, (x) => x.AvailableEndDateTimeUtc); row.SetProperty(context.Result, product, (x) => x.LimitedToStores); - row.SetProperty(context.Result, product, (x) => x.CreatedOnUtc, utcNow); - product.UpdatedOnUtc = utcNow; + row.SetProperty(context.Result, product, (x) => x.CreatedOnUtc, importerContext.UtcNow); + product.UpdatedOnUtc = importerContext.UtcNow; if (row.IsTransient) { @@ -554,9 +723,14 @@ private int ProcessProducts(IImportExecuteContext context, ImportRow[] // Perf: notify only about LAST insertion and update if (lastInserted != null) + { _services.EventPublisher.EntityInserted(lastInserted); + } + if (lastUpdated != null) + { _services.EventPublisher.EntityUpdated(lastUpdated); + } return num; } @@ -582,7 +756,7 @@ public void Execute(IImportExecuteContext context) using (var scope = new DbContextScope(ctx: _productRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { var segmenter = context.GetSegmenter(); - var utcNow = DateTime.UtcNow; + var importerContext = new ProductImporterContext(context, _dataExchangeSettings); context.Result.TotalRecords = segmenter.TotalRows; @@ -600,7 +774,7 @@ public void Execute(IImportExecuteContext context) // =========================================================================== try { - ProcessProducts(context, batch, utcNow); + ProcessProducts(context, importerContext, batch); } catch (Exception exception) { @@ -699,11 +873,11 @@ public void Execute(IImportExecuteContext context) // =========================================================================== // 6.) Import product picture mappings // =========================================================================== - if (segmenter.HasColumn("PictureThumbPaths")) + if (segmenter.HasColumn("ImageUrls")) { try { - ProcessProductPictures(context, batch); + ProcessProductPictures(context, importerContext, batch); } catch (Exception exception) { @@ -714,4 +888,34 @@ public void Execute(IImportExecuteContext context) } } } + + + internal class ProductImporterContext + { + public ProductImporterContext(IImportExecuteContext context, DataExchangeSettings dataExchangeSettings) + { + UtcNow = DateTime.UtcNow; + ImageDownloadFolder = Path.Combine(context.ImportFolder, "Content\\DownloadedImages"); + + if (dataExchangeSettings.ImageImportFolder.HasValue()) + ImageFolder = Path.Combine(context.ImportFolder, dataExchangeSettings.ImageImportFolder); + else + ImageFolder = context.ImportFolder; + + if (!System.IO.Directory.Exists(ImageDownloadFolder)) + System.IO.Directory.CreateDirectory(ImageDownloadFolder); + + //DownloaderContext = new FileDownloadManagerContext + //{ + // Timeout = TimeSpan.FromMinutes(dataExchangeSettings.ImageDownloadTimeout), + // Logger = context.Log, + // CancellationToken = context.CancellationToken + //}; + } + + public DateTime UtcNow { get; private set; } + public string ImageDownloadFolder { get; private set; } + public string ImageFolder { get; private set; } + //public FileDownloadManagerContext DownloaderContext { get; private set; } + } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs index a2114c26eb..80d426ee70 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs @@ -70,12 +70,14 @@ public partial class DataImporter : IDataImporter private readonly Lazy _stateProvinceService; private readonly Lazy _emailAccountService; private readonly Lazy _emailSender; + private readonly Lazy _fileDownloadManager; private readonly Lazy _seoSettings; private readonly Lazy _customerSettings; private readonly Lazy _dateTimeSettings; private readonly Lazy _forumSettings; private readonly Lazy _contactDataSettings; + private readonly Lazy _dataExchangeSettings; public DataImporter( ICommonServices services, @@ -105,11 +107,13 @@ public DataImporter( Lazy stateProvinceService, Lazy emailAccountService, Lazy emailSender, + Lazy fileDownloadManager, Lazy seoSettings, Lazy customerSettings, Lazy dateTimeSettings, Lazy forumSettings, - Lazy contactDataSettings) + Lazy contactDataSettings, + Lazy dataExchangeSettings) { _services = services; _customerService = customerService; @@ -140,12 +144,14 @@ public DataImporter( _stateProvinceService = stateProvinceService; _emailAccountService = emailAccountService; _emailSender = emailSender; + _fileDownloadManager = fileDownloadManager; _seoSettings = seoSettings; _customerSettings = customerSettings; _dateTimeSettings = dateTimeSettings; _forumSettings = forumSettings; _contactDataSettings = contactDataSettings; + _dataExchangeSettings = dataExchangeSettings; T = NullLocalizer.Instance; } @@ -356,8 +362,10 @@ private void ImportCoreOuter(DataImporterContext ctx) { ctx.Log = logger; + ctx.ExecuteContext.Log = logger; ctx.ExecuteContext.UpdateOnly = ctx.Request.Profile.UpdateOnly; ctx.ExecuteContext.KeyFieldNames = ctx.Request.Profile.KeyFieldNames.SplitSafe(","); + ctx.ExecuteContext.ImportFolder = ctx.Request.Profile.GetImportFolder(); { var mapConverter = new ColumnMapConverter(); @@ -389,7 +397,9 @@ private void ImportCoreOuter(DataImporterContext ctx) _productService.Value, _urlRecordService.Value, _storeMappingService.Value, - _seoSettings.Value); + _fileDownloadManager.Value, + _seoSettings.Value, + _dataExchangeSettings.Value); } else if (ctx.Request.Profile.EntityType == ImportEntityType.Customer) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index 1b731a94e3..d49a3a72e5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -2,6 +2,7 @@ using System.Threading; using SmartStore.Core; using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Logging; namespace SmartStore.Services.DataExchange.Import { @@ -17,11 +18,26 @@ public interface IImportExecuteContext /// string[] KeyFieldNames { get; } + /// + /// The import folder + /// + string ImportFolder { get; } + /// /// Use this dictionary for any custom data required along the export /// Dictionary CustomProperties { get; set; } + /// + /// To log information into the import log file + /// + ILogger Log { get; } + + /// + /// Cancellation token + /// + CancellationToken CancellationToken { get; } + /// /// Result of the import /// @@ -51,7 +67,6 @@ public interface IImportExecuteContext public class ImportExecuteContext : IImportExecuteContext { private DataExchangeAbortion _abortion; - private CancellationToken _cancellation; private ProgressValueSetter _progressValueSetter; private string _progressInfo; @@ -60,10 +75,10 @@ public ImportExecuteContext( ProgressValueSetter progressValueSetter, string progressInfo) { - _cancellation = cancellation; _progressValueSetter = progressValueSetter; _progressInfo = progressInfo; + CancellationToken = cancellation; CustomProperties = new Dictionary(); Result = new ImportResult(); } @@ -76,6 +91,12 @@ public ImportExecuteContext( public string[] KeyFieldNames { get; internal set; } + public ILogger Log { get; internal set; } + + public CancellationToken CancellationToken { get; private set; } + + public string ImportFolder { get; internal set; } + /// /// Use this dictionary for any custom data required along the import /// @@ -87,7 +108,7 @@ public DataExchangeAbortion Abort { get { - if (_cancellation.IsCancellationRequested || IsMaxFailures) + if (CancellationToken.IsCancellationRequested || IsMaxFailures) return DataExchangeAbortion.Hard; return _abortion; diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs index fa96fae19b..990d47495a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs @@ -1554,7 +1554,8 @@ public ActionResult DataExchange() var model = new DataExchangeSettingsModel { MaxFileNameLength = settings.MaxFileNameLength, - ImageImportFolder = settings.ImageImportFolder + ImageImportFolder = settings.ImageImportFolder, + ImageDownloadTimeout = settings.ImageDownloadTimeout }; StoreDependingSettings.GetOverrideKeys(settings, model, storeScope, _services.Settings); @@ -1575,6 +1576,7 @@ public ActionResult DataExchange(DataExchangeSettingsModel model, FormCollection settings.MaxFileNameLength = model.MaxFileNameLength; settings.ImageImportFolder = model.ImageImportFolder; + settings.ImageDownloadTimeout = model.ImageDownloadTimeout; StoreDependingSettings.UpdateSettings(settings, form, storeScope, _services.Settings); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/DataExchangeSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/DataExchangeSettingsModel.cs index f69e72e6d4..539505d733 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/DataExchangeSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/DataExchangeSettingsModel.cs @@ -16,6 +16,9 @@ public partial class DataExchangeSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.DataExchange.ImageImportFolder")] public string ImageImportFolder { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.DataExchange.ImageDownloadTimeout")] + public int ImageDownloadTimeout { get; set; } + #endregion #region Export diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml index 100dfd26e6..88bcdab917 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/DataExchange.cshtml @@ -43,5 +43,14 @@ @Html.ValidationMessageFor(model => model.ImageImportFolder)
              + @Html.SmartLabelFor(model => model.ImageDownloadTimeout) + + @Html.SettingEditorFor(model => model.ImageDownloadTimeout) + @Html.ValidationMessageFor(model => model.ImageDownloadTimeout) +
              } From aaeefa8b9aaf9c978d8d82a40a3ed107768dcb63 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 18 Feb 2016 20:16:13 +0100 Subject: [PATCH 042/423] Product importer should download images asynchronously --- .../Catalog/Importer/ProductImporter.cs | 44 +++++-------------- 1 file changed, 10 insertions(+), 34 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 7c89b77931..03157e4d77 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.IO; using System.Linq; +using SmartStore.Core.Async; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.DataExchange; @@ -149,35 +150,10 @@ private void ProcessProductPictures(IImportExecuteContext context, ProductImport // download images if (imageFiles.Any(x => x.Url.HasValue())) { - foreach (var image in imageFiles.Where(x => x.Url.HasValue())) - { - try - { - var timeout = (_dataExchangeSettings.ImageDownloadTimeout * 60 * 1000); - var response = _fileDownloadManager.DownloadFile(image.Url, false, timeout > 0 ? timeout : (int?)null); - image.Success = (response != null); - - if (response != null) - { - File.WriteAllBytes(image.Path, response.Data); - } - } - catch (Exception exception) - { - context.Result.AddWarning(exception.ToAllMessages(), row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); - } - } - - // async HttpClient deadlocks ITask. wrong synchronization context? // async downloading in batch processing is inefficient cause only the image processing benefits from async, - // not the record processing itself. a per record processing would speed up the import. - - //Task imageDownload = _fileDownloadManager.DownloadAsync(importerContext.DownloaderContext, imageFiles.Where(x => x.Url.HasValue())); + // not the record processing itself. a per record processing may speed up the import. - //if (imageDownload != null) - //{ - // imageDownload.Wait(); - //} + AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(importerContext.DownloaderContext, imageFiles.Where(x => x.Url.HasValue()))); } // import images @@ -905,17 +881,17 @@ public ProductImporterContext(IImportExecuteContext context, DataExchangeSetting if (!System.IO.Directory.Exists(ImageDownloadFolder)) System.IO.Directory.CreateDirectory(ImageDownloadFolder); - //DownloaderContext = new FileDownloadManagerContext - //{ - // Timeout = TimeSpan.FromMinutes(dataExchangeSettings.ImageDownloadTimeout), - // Logger = context.Log, - // CancellationToken = context.CancellationToken - //}; + DownloaderContext = new FileDownloadManagerContext + { + Timeout = TimeSpan.FromMinutes(dataExchangeSettings.ImageDownloadTimeout), + Logger = context.Log, + CancellationToken = context.CancellationToken + }; } public DateTime UtcNow { get; private set; } public string ImageDownloadFolder { get; private set; } public string ImageFolder { get; private set; } - //public FileDownloadManagerContext DownloaderContext { get; private set; } + public FileDownloadManagerContext DownloaderContext { get; private set; } } } From ed49b9fcef120b80dd3ebf16927e378c4e27fa42 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 18 Feb 2016 21:01:07 +0100 Subject: [PATCH 043/423] Redesigned install screen --- .../Orders/OrderProcessingService.cs | 70 +++---- .../Installation/installation.de.xml | 5 +- .../Installation/installation.en.xml | 6 +- .../Content/install/install.less | 179 ++++++++++++++---- .../Content/install/variables.custom.less | 4 +- .../Content/install/variables.less | 12 +- .../Controllers/HomeController.cs | 2 +- .../SmartStore.Web/Views/Install/Index.cshtml | 142 ++++++++------ 8 files changed, 285 insertions(+), 135 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs index 9210d60a57..e298174d3c 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs @@ -467,7 +467,7 @@ public void CheckOrderStatus(Order order) /// Place order result public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentRequest, Dictionary extraData) { - //think about moving functionality of processing recurring orders (after the initial order was placed) to ProcessNextRecurringPayment() method + // think about moving functionality of processing recurring orders (after the initial order was placed) to ProcessNextRecurringPayment() method if (processPaymentRequest == null) throw new ArgumentNullException("processPaymentRequest"); @@ -481,7 +481,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR { #region Order details (customer, totals) - //Recurring orders. Load initial order + // Recurring orders. Load initial order var initialOrder = _orderService.GetOrderById(processPaymentRequest.InitialOrderId); if (processPaymentRequest.IsRecurringPayment) { @@ -491,12 +491,12 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR processPaymentRequest.PaymentMethodSystemName = initialOrder.PaymentMethodSystemName; } - //customer + // customer var customer = _customerService.GetCustomerById(processPaymentRequest.CustomerId); if (customer == null) throw new ArgumentException(T("Customer.DoesNotExist")); - //affilites + // affilites var affiliateId = 0; var affiliate = _affiliateService.GetAffiliateById(customer.AffiliateId); if (affiliate != null && affiliate.Active && !affiliate.Deleted) @@ -504,7 +504,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR affiliateId = affiliate.Id; } - //customer currency + // customer currency string customerCurrencyCode = ""; decimal customerCurrencyRate = decimal.Zero; if (!processPaymentRequest.IsRecurringPayment) @@ -523,7 +523,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR customerCurrencyRate = initialOrder.CurrencyRate; } - //customer language + // customer language Language customerLanguage = null; if (!processPaymentRequest.IsRecurringPayment) { @@ -539,28 +539,28 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR customerLanguage = _workContext.WorkingLanguage; } - //check whether customer is guest + // check whether customer is guest if (customer.IsGuest() && !_orderSettings.AnonymousCheckoutAllowed) throw new SmartException(T("Checkout.AnonymousNotAllowed")); var storeId = _storeContext.CurrentStore.Id; - //load and validate customer shopping cart + // load and validate customer shopping cart IList cart = null; if (!processPaymentRequest.IsRecurringPayment) { - //load shopping cart + // load shopping cart cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, processPaymentRequest.StoreId); if (cart.Count == 0) throw new SmartException(T("ShoppingCart.CartIsEmpty")); - //validate the entire shopping cart + // validate the entire shopping cart var warnings = _shoppingCartService.GetShoppingCartWarnings(cart, customer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes), true); if (warnings.Count > 0) throw new SmartException(string.Join(" ", warnings)); - //validate individual cart items + // validate individual cart items foreach (var sci in cart) { var sciWarnings = _shoppingCartService.GetShoppingCartItemWarnings(customer, sci.Item.ShoppingCartType, @@ -572,7 +572,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR } } - //min totals validation + // min totals validation if (!processPaymentRequest.IsRecurringPayment) { var minOrderSubtotalAmountOk = ValidateMinOrderSubtotalAmount(cart); @@ -590,7 +590,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR } } - //tax display type + // tax display type var customerTaxDisplayType = TaxDisplayType.IncludingTax; if (!processPaymentRequest.IsRecurringPayment) { @@ -601,7 +601,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR customerTaxDisplayType = initialOrder.CustomerTaxDisplayType; } - //checkout attributes + // checkout attributes string checkoutAttributeDescription, checkoutAttributesXml; if (!processPaymentRequest.IsRecurringPayment) { @@ -614,15 +614,15 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR checkoutAttributeDescription = initialOrder.CheckoutAttributeDescription; } - //applied discount (used to store discount usage history) + // applied discount (used to store discount usage history) var appliedDiscounts = new List(); - //sub total + // sub total decimal orderSubTotalInclTax, orderSubTotalExclTax; decimal orderSubTotalDiscountInclTax = 0, orderSubTotalDiscountExclTax = 0; if (!processPaymentRequest.IsRecurringPayment) { - //sub total (incl tax) + // sub total (incl tax) decimal orderSubTotalDiscountAmount1 = decimal.Zero; Discount orderSubTotalAppliedDiscount1 = null; decimal subTotalWithoutDiscountBase1 = decimal.Zero; @@ -634,11 +634,11 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR orderSubTotalInclTax = subTotalWithoutDiscountBase1; orderSubTotalDiscountInclTax = orderSubTotalDiscountAmount1; - //discount history + // discount history if (orderSubTotalAppliedDiscount1 != null && !appliedDiscounts.Any(x => x.Id == orderSubTotalAppliedDiscount1.Id)) appliedDiscounts.Add(orderSubTotalAppliedDiscount1); - //sub total (excl tax) + // sub total (excl tax) decimal orderSubTotalDiscountAmount2 = decimal.Zero; Discount orderSubTotalAppliedDiscount2 = null; decimal subTotalWithoutDiscountBase2 = decimal.Zero; @@ -657,7 +657,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR } - //shipping info + // shipping info bool shoppingCartRequiresShipping = false; if (!processPaymentRequest.IsRecurringPayment) { @@ -687,7 +687,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR } } - //shipping total + // shipping total decimal? orderShippingTotalInclTax, orderShippingTotalExclTax = null; decimal orderShippingTaxRate = decimal.Zero; if (!processPaymentRequest.IsRecurringPayment) @@ -848,14 +848,14 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR #region Payment workflow - //skip payment workflow if order total equals zero + // skip payment workflow if order total equals zero var skipPaymentWorkflow = false; if (orderTotal.Value == decimal.Zero) { skipPaymentWorkflow = true; } - //payment workflow + // payment workflow Provider paymentMethod = null; if (!skipPaymentWorkflow) { @@ -863,7 +863,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR if (paymentMethod == null) throw new SmartException(T("Payment.CouldNotLoadMethod")); - //ensure that payment method is active + // ensure that payment method is active if (!paymentMethod.IsPaymentMethodActive(_paymentSettings)) throw new SmartException(T("Payment.MethodNotAvailable")); } @@ -872,7 +872,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR processPaymentRequest.PaymentMethodSystemName = ""; } - //recurring or standard shopping cart? + // recurring or standard shopping cart? var isRecurringShoppingCart = false; if (!processPaymentRequest.IsRecurringPayment) { @@ -897,7 +897,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR isRecurringShoppingCart = true; } - //process payment + // process payment ProcessPaymentResult processPaymentResult = null; if (!skipPaymentWorkflow) { @@ -905,7 +905,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR { if (isRecurringShoppingCart) { - //recurring cart + // recurring cart var recurringPaymentType = _paymentService.GetRecurringPaymentType(processPaymentRequest.PaymentMethodSystemName); switch (recurringPaymentType) { @@ -921,7 +921,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR } else { - //standard cart + // standard cart processPaymentResult = _paymentService.ProcessPayment(processPaymentRequest); } } @@ -929,11 +929,11 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR { if (isRecurringShoppingCart) { - //Old credit card info + // Old credit card info processPaymentRequest.CreditCardType = initialOrder.AllowStoringCreditCardNumber ? _encryptionService.DecryptText(initialOrder.CardType) : ""; processPaymentRequest.CreditCardName = initialOrder.AllowStoringCreditCardNumber ? _encryptionService.DecryptText(initialOrder.CardName) : ""; processPaymentRequest.CreditCardNumber = initialOrder.AllowStoringCreditCardNumber ? _encryptionService.DecryptText(initialOrder.CardNumber) : ""; - //MaskedCreditCardNumber + // MaskedCreditCardNumber processPaymentRequest.CreditCardCvv2 = initialOrder.AllowStoringCreditCardNumber ? _encryptionService.DecryptText(initialOrder.CardCvv2) : ""; try @@ -967,7 +967,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR } else { - //payment is not required + // payment is not required if (processPaymentResult == null) { processPaymentResult = new ProcessPaymentResult(); @@ -1331,7 +1331,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR #region Notifications, notes and attributes - //notes, messages + // notes, messages _orderService.AddOrderNote(order, T("Admin.OrderNotice.OrderPlaced")); //send email notifications @@ -1347,10 +1347,10 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR _orderService.AddOrderNote(order, T("Admin.OrderNotice.CustomerEmailQueued", orderPlacedCustomerNotificationQueuedEmailId)); } - //check order status + // check order status CheckOrderStatus(order); - //reset checkout data + // reset checkout data if (!processPaymentRequest.IsRecurringPayment) { _customerService.ResetCheckoutData(customer, processPaymentRequest.StoreId, clearCouponCodes: true, clearCheckoutAttributes: true); @@ -1365,7 +1365,7 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR //uncomment this line to support transactions //scope.Complete(); - //raise event + // raise event _eventPublisher.PublishOrderPlaced(order); if (!processPaymentRequest.IsRecurringPayment) diff --git a/src/Presentation/SmartStore.Web/App_Data/Localization/Installation/installation.de.xml b/src/Presentation/SmartStore.Web/App_Data/Localization/Installation/installation.de.xml index acd910374c..3582e6f7d8 100644 --- a/src/Presentation/SmartStore.Web/App_Data/Localization/Installation/installation.de.xml +++ b/src/Presentation/SmartStore.Web/App_Data/Localization/Installation/installation.de.xml @@ -183,6 +183,9 @@ Schreibe Beispiel Daten - + + + Optionen + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/App_Data/Localization/Installation/installation.en.xml b/src/Presentation/SmartStore.Web/App_Data/Localization/Installation/installation.en.xml index a11658d45b..4603606855 100644 --- a/src/Presentation/SmartStore.Web/App_Data/Localization/Installation/installation.en.xml +++ b/src/Presentation/SmartStore.Web/App_Data/Localization/Installation/installation.en.xml @@ -183,5 +183,9 @@ Creating sample data - + + + Options + + \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/install/install.less b/src/Presentation/SmartStore.Web/Content/install/install.less index 03145227fa..bdb13586e8 100644 --- a/src/Presentation/SmartStore.Web/Content/install/install.less +++ b/src/Presentation/SmartStore.Web/Content/install/install.less @@ -11,17 +11,6 @@ /* Fonts (Chrome DirectWrite & FF fix) -------------------------------------------------------------- */ -@font-face { - font-family: "Segoe UI Light"; - font-weight: 100; - src: local("Segoe UI Light"), url("../fonts/segoeui-semilight.eot") format('embedded-opentype'), url("../fonts/segoeui-semilight.woff") format('woff'), url("../fonts/segoeui-semilight.ttf") format('truetype'); -} -@font-face { - font-family: "Segoe UI Semibold"; - font-weight: 100; - src: local("Segoe UI Semibold"), url("../fonts/segoeui-semibold.eot") format('embedded-opentype'), url("../fonts/segoeui-semibold.woff") format('woff'), url("../fonts/segoeui-semibold.ttf") format('truetype'); -} - // CSS Reset @import "~/Content/bootstrap/reset.less"; @@ -78,63 +67,132 @@ /* INSTALL STYLES --------------------------------------------------------*/ -@installBaseColor: #29b6d9; body { - background: @installBaseColor url('images/bg.png') 50% 0% no-repeat; - background-attachment: fixed; + background: #f5f5f5; +} + +input:not([type=checkbox]):not([type=radio]), +select { + display: block; + box-sizing: border-box; + width: 100%; + height: 34px; + box-shadow: none; + border-color: #d2d2d2; +} + +textarea { + height: initial; + min-height: 34px; + box-shadow: none; + border-color: #d2d2d2; +} + +.bg-top { + position: fixed; + z-index: 0; + background: #3F51B5; + width: 100%; + height: 140px; +} + +.dropdown-menu li > a { + padding: 8px 20px 8px 12px; + > .fa { margin-right: 4px; } +} + +.help-block { + color: #a1a1a1; + margin-top: 8px; + font-size: 12px; } .install-head { - padding: 8px 0; - line-height: 40px; + position: relative; + z-index: 1; + margin: 20px 0; vertical-align: middle; } .install-panel { - margin-top: 4px; + position: relative; + z-index: 1; + margin-top: 20px; margin-bottom: 20px; padding: 30px; + padding-bottom: 0; background-color: #fff; - background-color: rgba(255,255,255, .5); - .box-shadow(~'0 0 15px rgba(0,0,0, .15), 0 10px 6px -10px rgba(0,0,0, .4)'); - .border-radius(4px); + .box-shadow(~'0 1px 1px 0 rgba(0,0,0,.06),0 2px 5px 0 rgba(0,0,0,.2)'); + .border-radius(3px); +} + +.buttonbar { + text-align: left; + background: #fafafa; + padding: 20px 30px; + margin-left: -30px; + margin-right: -30px; + border-radius: 0 0 3px 3px; +} + +.btn-install { + font-size: 22px; + line-height: 32px; + font-weight: 600; + padding-left: 1.5em; + padding-right: 1.5em; } .install-title { margin: 0; - color: #fff; - color: lighten(@installBaseColor, 40%); - text-shadow: 0 1px 0 darken(@installBaseColor, 12%); font-family: @pageTitleFontFamily; font-weight: @pageTitleFontWeight; + font-size: 22px; + color: #fff; + padding-left: 8px; + vertical-align: bottom; +} + +.install-intro { + font-size: 16px; + line-height: 22px; + color: #818181; + margin-bottom: 30px; } -.install-content { - //padding: 0 30px; +.install-content fieldset { + margin-bottom: 30px; } .install-content legend { - font-family: @headingsFontFamily; - font-weight: @headingsFontWeight; + font-weight: 600; font-size: 22px; - border-bottom-color: lighten(@installBaseColor, 12%); border-bottom-color: rgba(0,0,0, 0.1); color: @headingsColor; margin-bottom: 0; padding-bottom: 8px; + + > .fa { + color: #717171; + margin-right: 4px; + } } .install-icon { - font-size: 32px; - line-height: 32px; - vertical-align: bottom; - margin-right: 4px; - display: none; + margin-right: 10px; +} + +.install-icon .fa-circle { + color: #f90; +} +.install-icon .fa-inverse { + font-size: 0.7em; } .form-horizontal .control-label { width: 240px; + text-align: left; } .form-horizontal .controls { @@ -146,8 +204,10 @@ label.control-label { } .field-validation-error { - color: red; + display: inline-block; + color: @red; text-shadow: none !important; + padding-top: 4px; } label.control-label { @@ -155,3 +215,54 @@ label.control-label { } +#navbar-tools { + + .active-tool() { + .box-shadow(~'inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)'); + } + + position: absolute; + z-index: 100; + right: 0; + top: 15px; + margin: 0; + + .btn, + .btn-group { + margin-top: 0; + } + + .open .navbar-tool { + .active-tool(); + } + + .navbar-tool { + color: rgba(255,255,255, .87); + border: none; + background: transparent; + .box-shadow(none); + padding: 8px 6px; + .transition(color 0.1s linear); + + &:hover { + color: #fff; + } + + &:active { + .active-tool(); + } + + > .fa { + font-size: 20px; + } + + span { + display: inline-block; + margin-left: 3px; + vertical-align: top; + } + + } +} + + diff --git a/src/Presentation/SmartStore.Web/Content/install/variables.custom.less b/src/Presentation/SmartStore.Web/Content/install/variables.custom.less index 76db5fcef8..2bcad0ceeb 100644 --- a/src/Presentation/SmartStore.Web/Content/install/variables.custom.less +++ b/src/Presentation/SmartStore.Web/Content/install/variables.custom.less @@ -5,6 +5,6 @@ // Nicer page titles // ------------------------- -@pageTitleFontFamily: 'Segoe UI Light', 'Segoe UI', Arial, Helvetica, sans-serif; -@pageTitleFontWeight: 100; +@pageTitleFontFamily: 'Segoe UI', Arial, Helvetica, sans-serif; +@pageTitleFontWeight: 400; @pageTitleColor: @infoText; // @linkColor; // #009fff; diff --git a/src/Presentation/SmartStore.Web/Content/install/variables.less b/src/Presentation/SmartStore.Web/Content/install/variables.less index 4d85eb8b75..d4daaab546 100644 --- a/src/Presentation/SmartStore.Web/Content/install/variables.less +++ b/src/Presentation/SmartStore.Web/Content/install/variables.less @@ -71,9 +71,9 @@ @paddingSmall: 2px 10px; // 26px @paddingMini: 1px 6px; // 24px -@baseBorderRadius: 0; +@baseBorderRadius: 3px; @borderRadiusLarge: 4px; -@borderRadiusSmall: 0; +@borderRadiusSmall: 2px; // Tables @@ -128,10 +128,10 @@ @dropdownLinkColor: @grayDark; -@dropdownLinkColorHover: @white; -@dropdownLinkColorActive: @dropdownLinkColor; -@dropdownLinkBackgroundActive: #666; //@linkColor; -@dropdownLinkBackgroundHover: @dropdownLinkBackgroundActive; +@dropdownLinkColorHover: inherit; +@dropdownLinkColorActive: inherit; +@dropdownLinkBackgroundActive: #f5f5f5; +@dropdownLinkBackgroundHover: #f5f5f5; diff --git a/src/Presentation/SmartStore.Web/Controllers/HomeController.cs b/src/Presentation/SmartStore.Web/Controllers/HomeController.cs index 6b65cfaf2d..e16d5e80b7 100644 --- a/src/Presentation/SmartStore.Web/Controllers/HomeController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/HomeController.cs @@ -86,7 +86,7 @@ public ActionResult ContentSlider() var settings = _services.Settings.LoadSetting(); settings.BackgroundPictureUrl = pictureService.GetPictureUrl(settings.BackgroundPictureId, 0, false); - + var slides = settings.Slides .Where(s => s.LanguageCulture == _services.WorkContext.WorkingLanguage.LanguageCulture && diff --git a/src/Presentation/SmartStore.Web/Views/Install/Index.cshtml b/src/Presentation/SmartStore.Web/Views/Install/Index.cshtml index 969837c160..c368339816 100644 --- a/src/Presentation/SmartStore.Web/Views/Install/Index.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Install/Index.cshtml @@ -30,6 +30,8 @@ + + @@ -113,20 +115,57 @@ -
              - -
              -
              +
              + @T("Admin.DataExchange.Import.KeyFieldNames.Note") +
              +
              @Html.SmartLabelFor(x => x.Skip) From c70776042324b9802d3b51f576d1c27c950856b9 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 21 Feb 2016 17:41:25 +0100 Subject: [PATCH 051/423] ListBoxFor is insufficient for selecting import key fields because the user cannot influence the stored order --- .../Controllers/ImportController.cs | 2 +- .../Models/DataExchange/ImportProfileModel.cs | 11 +--------- .../Views/Import/_CreateOrUpdate.cshtml | 21 +++++++++++++++---- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index 7154bdc645..1dacfc8ac1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -449,7 +449,7 @@ public ActionResult Edit(ImportProfileModel model, bool continueEditing, FormCol var oldDelimiter = (oldCsvConfig != null ? oldCsvConfig.Delimiter.ToString() : null); // auto reset mappings cause they are invalid. note: delimiter can be whitespaced, so no oldDelimter.HasValue() etc. - resetMappings = (oldDelimiter != model.CsvConfiguration.Delimiter); + resetMappings = (oldDelimiter != model.CsvConfiguration.Delimiter && profile.ColumnMapping.HasValue()); profile.FileTypeConfiguration = csvConverter.ConvertTo(model.CsvConfiguration.Clone()); } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs index e09700f19b..16bf2a9675 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Admin.Models.Tasks; @@ -42,13 +40,6 @@ public partial class ImportProfileModel : EntityModelBase [SmartResourceDisplayName("Admin.DataExchange.Import.KeyFieldNames")] public string[] KeyFieldNames { get; set; } public List AvailableKeyFieldNames { get; set; } - public bool ShowKeyFieldNote - { - get - { - return KeyFieldNames.Contains("Id", StringComparer.OrdinalIgnoreCase); - } - } [SmartResourceDisplayName("Common.Execution")] public int ScheduleTaskId { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_CreateOrUpdate.cshtml index 7c87251732..a3c2f6e159 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_CreateOrUpdate.cshtml @@ -134,13 +134,26 @@ else if (Model.ExistingFileNames.Count > 1) @Html.SmartLabelFor(model => model.KeyFieldNames) - @Html.ListBoxFor(model => model.KeyFieldNames, - new MultiSelectList(Model.AvailableKeyFieldNames, "Value", "Text"), - new { multiple = "multiple", @class = "control-large" }) + @*ListBoxFor is insufficient here because the user cannot influence the stored order*@ + @Html.ValidationMessageFor(model => model.KeyFieldNames)
              From 4b65eaab0eeb8e62747cb3534fbe3b86233f9037 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 22 Feb 2016 12:11:30 +0100 Subject: [PATCH 052/423] Import\export customer avatar --- .../Customers/Importer/CustomerImporter.cs | 96 ++++++++++++++++--- .../DataExchange/DataExporter.cs | 5 +- .../DataExchange/DynamicEntityHelper.cs | 11 +++ .../DataExchange/Import/DataImporter.cs | 8 +- 4 files changed, 104 insertions(+), 16 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 84b4b6fdf6..f459f26fe3 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -1,57 +1,74 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using SmartStore.Core.Async; using SmartStore.Core.Data; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Forums; +using SmartStore.Core.Domain.Media; using SmartStore.Core.Events; using SmartStore.Services.Affiliates; using SmartStore.Services.Common; using SmartStore.Services.DataExchange.Import; using SmartStore.Services.Directory; using SmartStore.Services.Helpers; +using SmartStore.Services.Media; using SmartStore.Services.Security; +using SmartStore.Utilities; namespace SmartStore.Services.Customers.Importer { - public class CustomerImporter : IEntityImporter + public class CustomerImporter : EntityImporterBase, IEntityImporter { private const string _attributeKeyGroup = "Customer"; - private readonly ICommonServices _services; private readonly IRepository _customerRepository; + private readonly IRepository _pictureRepository; + private readonly ICommonServices _services; private readonly IGenericAttributeService _genericAttributeService; private readonly ICustomerService _customerService; + private readonly IPictureService _pictureService; private readonly IAffiliateService _affiliateService; private readonly ICountryService _countryService; private readonly IStateProvinceService _stateProvinceService; + private readonly FileDownloadManager _fileDownloadManager; private readonly CustomerSettings _customerSettings; private readonly DateTimeSettings _dateTimeSettings; private readonly ForumSettings _forumSettings; + private readonly DataExchangeSettings _dataExchangeSettings; public CustomerImporter( - ICommonServices services, IRepository customerRepository, + IRepository pictureRepository, + ICommonServices services, IGenericAttributeService genericAttributeService, ICustomerService customerService, + IPictureService pictureService, IAffiliateService affiliateService, ICountryService countryService, IStateProvinceService stateProvinceService, + FileDownloadManager fileDownloadManager, CustomerSettings customerSettings, DateTimeSettings dateTimeSettings, - ForumSettings forumSettings) + ForumSettings forumSettings, + DataExchangeSettings dataExchangeSettings) { - _services = services; _customerRepository = customerRepository; + _pictureRepository = pictureRepository; + _services = services; _genericAttributeService = genericAttributeService; _customerService = customerService; + _pictureService = pictureService; _affiliateService = affiliateService; _countryService = countryService; _stateProvinceService = stateProvinceService; + _fileDownloadManager = fileDownloadManager; _customerSettings = customerSettings; _dateTimeSettings = dateTimeSettings; _forumSettings = forumSettings; + _dataExchangeSettings = dataExchangeSettings; } private void SaveAttribute(ImportRow row, string key) @@ -72,6 +89,57 @@ private void UpsertRole(ImportRow row, CustomerRole role, string roleS } } + private void ImportAvatar(IImportExecuteContext context, ImportRow row) + { + var urlOrPath = row.GetDataValue("AvatarPictureUrl"); + if (urlOrPath.IsEmpty()) + return; + + Picture picture = null; + var equalPictureId = 0; + var currentPictures = new List(); + var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); + var image = CreateDownloadImage(urlOrPath, seoName, 1); + + if (image == null) + return; + + AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, new FileDownloadManagerItem[] { image })); + + if ((image.Success ?? false) && File.Exists(image.Path)) + { + Succeeded(image); + var pictureBinary = File.ReadAllBytes(image.Path); + + if (pictureBinary != null && pictureBinary.Length > 0) + { + var currentPictureId = row.Entity.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId); + if (currentPictureId != 0 && (picture = _pictureRepository.GetById(currentPictureId)) != null) + currentPictures.Add(picture); + + pictureBinary = _pictureService.ValidatePicture(pictureBinary); + pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); + + if (pictureBinary != null && pictureBinary.Length > 0) + { + if ((picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false)) != null) + { + _genericAttributeService.SaveAttribute(row.Entity.Id, SystemCustomerAttributeNames.AvatarPictureId, _attributeKeyGroup, picture.Id.ToString()); + } + } + else + { + context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "AvatarPictureUrl"); + } + + } + } + else + { + context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "AvatarPictureUrl"); + } + } + private int ProcessGenericAttributes(IImportExecuteContext context, ImportRow[] batch, List allCountryIds, @@ -119,9 +187,6 @@ private int ProcessGenericAttributes(IImportExecuteContext context, if (_customerSettings.FaxEnabled) SaveAttribute(row, SystemCustomerAttributeNames.Fax); - if (_customerSettings.AllowCustomersToUploadAvatars) - SaveAttribute(row, SystemCustomerAttributeNames.AvatarPictureId); - if (_forumSettings.ForumsEnabled) SaveAttribute(row, SystemCustomerAttributeNames.ForumPostCount); @@ -155,6 +220,11 @@ private int ProcessGenericAttributes(IImportExecuteContext context, if (!customerNumber.IsEmpty()) allCustomerNumbers.Add(customerNumber); } + + if (_customerSettings.AllowCustomersToUploadAvatars) + { + ImportAvatar(context, row); + } } var num = _customerRepository.Context.SaveChanges(); @@ -164,7 +234,6 @@ private int ProcessGenericAttributes(IImportExecuteContext context, private int ProcessCustomers(IImportExecuteContext context, ImportRow[] batch, - DateTime utcNow, List allAffiliateIds, IList allCustomerRoles) { @@ -251,8 +320,8 @@ private int ProcessCustomers(IImportExecuteContext context, row.SetProperty(context.Result, customer, (x) => x.LastLoginDateUtc); row.SetProperty(context.Result, customer, (x) => x.LastActivityDateUtc); - row.SetProperty(context.Result, customer, (x) => x.CreatedOnUtc, utcNow); - row.SetProperty(context.Result, customer, (x) => x.LastActivityDateUtc, utcNow); + row.SetProperty(context.Result, customer, (x) => x.CreatedOnUtc, UtcNow); + row.SetProperty(context.Result, customer, (x) => x.LastActivityDateUtc, UtcNow); if (affiliateId > 0 && allAffiliateIds.Contains(affiliateId)) { @@ -304,7 +373,6 @@ public static string[] DefaultKeyFields public void Execute(IImportExecuteContext context) { - var utcNow = DateTime.UtcNow; var customer = _services.WorkContext.CurrentCustomer; var allowManagingCustomerRoles = _services.Permissions.Authorize(StandardPermissionProvider.ManageCustomerRoles, customer); @@ -330,6 +398,8 @@ public void Execute(IImportExecuteContext context) { var segmenter = context.GetSegmenter(); + Init(context, _dataExchangeSettings); + context.Result.TotalRecords = segmenter.TotalRows; while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) @@ -342,7 +412,7 @@ public void Execute(IImportExecuteContext context) try { - ProcessCustomers(context, batch, utcNow, allAffiliateIds, allCustomerRoles); + ProcessCustomers(context, batch, allAffiliateIds, allCustomerRoles); } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs index 53cfc707b3..048e6149f5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs @@ -81,6 +81,7 @@ public partial class DataExporter : IDataExporter private readonly Lazy _mediaSettings; private readonly Lazy _contactDataSettings; + private readonly Lazy _customerSettings; public DataExporter( ICommonServices services, @@ -116,7 +117,8 @@ public DataExporter( Lazy> subscriptionRepository, Lazy> orderRepository, Lazy mediaSettings, - Lazy contactDataSettings) + Lazy contactDataSettings, + Lazy customerSettings) { _services = services; _dbContext = dbContext; @@ -154,6 +156,7 @@ public DataExporter( _mediaSettings = mediaSettings; _contactDataSettings = contactDataSettings; + _customerSettings = customerSettings; T = NullLocalizer.Instance; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs index a3bd172a02..2a58b55db1 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs @@ -310,6 +310,7 @@ private dynamic ToDynamic(DataExporterContext ctx, Customer customer) result._HasNewsletterSubscription = false; result._FullName = null; + result._AvatarPictureUrl = null; return result; } @@ -1205,6 +1206,16 @@ private List Convert(DataExporterContext ctx, Customer customer) dynObject._FullName = firstName.Grow(lastName, " "); + if (_customerSettings.Value.AllowCustomersToUploadAvatars) + { + var pictureId = genericAttributes.FirstOrDefault(x => x.Key == SystemCustomerAttributeNames.AvatarPictureId); + if (pictureId != null) + { + // reduce traffic and do not export default avatar + dynObject._AvatarPictureUrl = _pictureService.Value.GetPictureUrl(pictureId.Value.ToInt(), _mediaSettings.Value.AvatarPictureSize, false, ctx.Store.Url); + } + } + result.Add(dynObject); _services.EventPublisher.Publish(new RowExportingEvent diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs index faebeecbb0..eb350d3b53 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs @@ -404,16 +404,20 @@ private void ImportCoreOuter(DataImporterContext ctx) else if (ctx.Request.Profile.EntityType == ImportEntityType.Customer) { ctx.Importer = new CustomerImporter( - _services, _customerRepository.Value, + _pictureRepository.Value, + _services, _genericAttributeService.Value, _customerService, + _pictureService.Value, _affiliateService.Value, _countryService.Value, _stateProvinceService.Value, + _fileDownloadManager.Value, _customerSettings.Value, _dateTimeSettings.Value, - _forumSettings.Value); + _forumSettings.Value, + _dataExchangeSettings.Value); } else if (ctx.Request.Profile.EntityType == ImportEntityType.NewsLetterSubscription) { From 2c6f0b90daf650dd254132a0c42b4b6017876c2a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 22 Feb 2016 13:29:16 +0100 Subject: [PATCH 053/423] Import of parent grouped product id is insufficient --- .../Catalog/Importer/CategoryImporter.cs | 5 ++ .../Catalog/Importer/ProductImporter.cs | 88 +++++++++++++++++-- .../Customers/Importer/CustomerImporter.cs | 5 +- 3 files changed, 91 insertions(+), 7 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 94161c8bfd..789b9743ea 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -361,9 +361,14 @@ private int ProcessCategories(IImportExecuteContext context, // Perf: notify only about LAST insertion and update if (lastInserted != null) + { _services.EventPublisher.EntityInserted(lastInserted); + } + if (lastUpdated != null) + { _services.EventPublisher.EntityUpdated(lastUpdated); + } return num; } diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index c70108bfa5..1b1ffeeafe 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -9,7 +9,6 @@ using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Seo; using SmartStore.Core.Events; -using SmartStore.Core.IO; using SmartStore.Services.DataExchange.Import; using SmartStore.Services.Localization; using SmartStore.Services.Media; @@ -89,6 +88,36 @@ public ProductImporter( return (int?)null; } + private int ProcessParentMappings(IImportExecuteContext context, + ImportRow[] batch, + Dictionary srcToDestId) + { + foreach (var row in batch) + { + var id = row.GetDataValue("Id"); + var parentGroupedProductId = row.GetDataValue("ParentGroupedProductId"); + + if (id != 0 && parentGroupedProductId != 0 && srcToDestId.ContainsKey(id) && srcToDestId.ContainsKey(parentGroupedProductId)) + { + // only touch relationship if child and parent were inserted + if (srcToDestId[id].Inserted && srcToDestId[parentGroupedProductId].Inserted && srcToDestId[id].DestinationId != 0) + { + var product = _productRepository.GetById(srcToDestId[id].DestinationId); + if (product != null) + { + product.ParentGroupedProductId = srcToDestId[parentGroupedProductId].DestinationId; + + _productRepository.Update(product); + } + } + } + } + + var num = _productRepository.Context.SaveChanges(); + + return num; + } + private void ProcessProductPictures(IImportExecuteContext context, ImportRow[] batch) { // true, cause pictures must be saved and assigned an id prior adding a mapping. @@ -498,7 +527,7 @@ private void ProcessStoreMappings(IImportExecuteContext context, ImportRow[] batch) + private int ProcessProducts(IImportExecuteContext context, ImportRow[] batch, Dictionary srcToDestId) { _productRepository.AutoCommitEnabled = true; @@ -508,13 +537,14 @@ private int ProcessProducts(IImportExecuteContext context, ImportRow[] foreach (var row in batch) { Product product = null; - + var id = row.GetDataValue("Id"); + foreach (var keyName in context.KeyFieldNames) { switch (keyName) { case "Id": - product = _productService.GetProductById(row.GetDataValue("Id")); + product = _productService.GetProductById(id); break; case "Sku": product = _productService.GetProductBySku(row.GetDataValue("Sku")); @@ -570,7 +600,6 @@ private int ProcessProducts(IImportExecuteContext context, ImportRow[] row.SetProperty(context.Result, product, (x) => x.Gtin); row.SetProperty(context.Result, product, (x) => x.ManufacturerPartNumber); row.SetProperty(context.Result, product, (x) => x.ProductTypeId, (int)ProductType.SimpleProduct); - row.SetProperty(context.Result, product, (x) => x.ParentGroupedProductId); row.SetProperty(context.Result, product, (x) => x.VisibleIndividually, true); row.SetProperty(context.Result, product, (x) => x.Name); row.SetProperty(context.Result, product, (x) => x.ShortDescription); @@ -653,6 +682,11 @@ private int ProcessProducts(IImportExecuteContext context, ImportRow[] row.SetProperty(context.Result, product, (x) => x.CreatedOnUtc, UtcNow); product.UpdatedOnUtc = UtcNow; + if (id != 0 && !srcToDestId.ContainsKey(id)) + { + srcToDestId.Add(id, new ImportProductMapping { Inserted = row.IsTransient }); + } + if (row.IsTransient) { _productRepository.Insert(product); @@ -668,6 +702,15 @@ private int ProcessProducts(IImportExecuteContext context, ImportRow[] // commit whole batch at once var num = _productRepository.Context.SaveChanges(); + // get new product ids + foreach (var row in batch) + { + var id = row.GetDataValue("Id"); + + if (id != 0 && srcToDestId.ContainsKey(id)) + srcToDestId[id].DestinationId = row.Entity.Id; + } + // Perf: notify only about LAST insertion and update if (lastInserted != null) { @@ -700,6 +743,8 @@ public static string[] DefaultKeyFields public void Execute(IImportExecuteContext context) { + var srcToDestId = new Dictionary(); + using (var scope = new DbContextScope(ctx: _productRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { var segmenter = context.GetSegmenter(); @@ -722,7 +767,7 @@ public void Execute(IImportExecuteContext context) // =========================================================================== try { - ProcessProducts(context, batch); + ProcessProducts(context, batch, srcToDestId); } catch (Exception exception) { @@ -833,7 +878,38 @@ public void Execute(IImportExecuteContext context) } } } + + // =========================================================================== + // 7.) Map parent id of inserted products + // =========================================================================== + if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentGroupedProductId")) + { + segmenter.Reset(); + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + { + var batch = segmenter.CurrentBatch; + + _productRepository.Context.DetachAll(false); + + try + { + ProcessParentMappings(context, batch, srcToDestId); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessParentMappings"); + } + } + } } } } + + + internal class ImportProductMapping + { + public int DestinationId { get; set; } + public bool Inserted { get; set; } + } } diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index f459f26fe3..8f4ea97911 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -104,7 +104,10 @@ private void ImportAvatar(IImportExecuteContext context, ImportRow row if (image == null) return; - AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, new FileDownloadManagerItem[] { image })); + if (image.Url.HasValue() && !image.Success.HasValue) + { + AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, new FileDownloadManagerItem[] { image })); + } if ((image.Success ?? false) && File.Exists(image.Path)) { From cb9e2f617650ddc03effde6d2501a80eecaac3b9 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 22 Feb 2016 17:01:07 +0100 Subject: [PATCH 054/423] Testing customer import --- .../Catalog/Importer/CategoryImporter.cs | 24 +++----- .../Catalog/Importer/ProductImporter.cs | 55 +++++++++++++------ .../Customers/Importer/CustomerImporter.cs | 17 ++++-- .../DataExchange/Import/DataImporter.cs | 4 ++ .../_CreateOrUpdate.AssociatedProducts.cshtml | 2 +- 5 files changed, 64 insertions(+), 38 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 789b9743ea..5aca0d425e 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -239,14 +239,14 @@ private void ProcessStoreMappings(IImportExecuteContext context, ImportRow[] batch, - List allCategoryTemplateIds, + Dictionary templateViewPaths, Dictionary srcToDestId) { _categoryRepository.AutoCommitEnabled = true; Category lastInserted = null; Category lastUpdated = null; - var defaultTemplateId = allCategoryTemplateIds.First(); + var defaultTemplateId = templateViewPaths["CategoryTemplate.ProductsInGridOrLines"]; foreach (var row in batch) { @@ -288,10 +288,7 @@ private int ProcessCategories(IImportExecuteContext context, continue; } - category = new Category - { - CategoryTemplateId = defaultTemplateId - }; + category = new Category(); } row.Initialize(category, name); @@ -321,11 +318,8 @@ private int ProcessCategories(IImportExecuteContext context, row.SetProperty(context.Result, category, (x) => x.Alias); row.SetProperty(context.Result, category, (x) => x.DefaultViewMode); - var templateId = row.GetDataValue("CategoryTemplateId"); - if (templateId > 0 && allCategoryTemplateIds.Contains(templateId)) - { - category.CategoryTemplateId = templateId; - } + var tvp = row.GetDataValue("CategoryTemplateViewPath"); + category.CategoryTemplateId = (tvp.HasValue() && templateViewPaths.ContainsKey(tvp) ? templateViewPaths[tvp] : defaultTemplateId); row.SetProperty(context.Result, category, (x) => x.CreatedOnUtc, UtcNow); category.UpdatedOnUtc = UtcNow; @@ -393,9 +387,9 @@ public void Execute(IImportExecuteContext context) { var srcToDestId = new Dictionary(); - var allCategoryTemplateIds = _categoryTemplateService.GetAllCategoryTemplates() - .Select(x => x.Id) - .ToList(); + var templateViewPaths = _categoryTemplateService.GetAllCategoryTemplates() + .GroupBy(x => x.ViewPath, StringComparer.OrdinalIgnoreCase) + .ToDictionary(x => x.Key, x => x.First().Id); using (var scope = new DbContextScope(ctx: _categoryRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { @@ -416,7 +410,7 @@ public void Execute(IImportExecuteContext context) try { - ProcessCategories(context, batch, allCategoryTemplateIds, srcToDestId); + ProcessCategories(context, batch, templateViewPaths, srcToDestId); } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 1b1ffeeafe..be80cb3cee 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -33,6 +33,7 @@ public class ProductImporter : EntityImporterBase, IEntityImporter private readonly ICategoryService _categoryService; private readonly IProductService _productService; private readonly IUrlRecordService _urlRecordService; + private readonly IProductTemplateService _productTemplateService; private readonly IStoreMappingService _storeMappingService; private readonly FileDownloadManager _fileDownloadManager; private readonly SeoSettings _seoSettings; @@ -52,6 +53,7 @@ public ProductImporter( ICategoryService categoryService, IProductService productService, IUrlRecordService urlRecordService, + IProductTemplateService productTemplateService, IStoreMappingService storeMappingService, FileDownloadManager fileDownloadManager, SeoSettings seoSettings, @@ -70,6 +72,7 @@ public ProductImporter( _categoryService = categoryService; _productService = productService; _urlRecordService = urlRecordService; + _productTemplateService = productTemplateService; _storeMappingService = storeMappingService; _fileDownloadManager = fileDownloadManager; @@ -88,7 +91,7 @@ public ProductImporter( return (int?)null; } - private int ProcessParentMappings(IImportExecuteContext context, + private int ProcessProductMappings(IImportExecuteContext context, ImportRow[] batch, Dictionary srcToDestId) { @@ -527,12 +530,17 @@ private void ProcessStoreMappings(IImportExecuteContext context, ImportRow[] batch, Dictionary srcToDestId) + private int ProcessProducts( + IImportExecuteContext context, + ImportRow[] batch, + Dictionary templateViewPaths, + Dictionary srcToDestId) { _productRepository.AutoCommitEnabled = true; Product lastInserted = null; Product lastUpdated = null; + var defaultTemplateId = templateViewPaths["ProductTemplate.Simple"]; foreach (var row in batch) { @@ -579,7 +587,7 @@ private int ProcessProducts(IImportExecuteContext context, ImportRow[] context.Result.AddError("The 'Name' field is required for new products. Skipping row.", row.GetRowInfo(), "Name"); continue; } - + product = new Product(); } @@ -596,34 +604,39 @@ private int ProcessProducts(IImportExecuteContext context, ImportRow[] } } - row.SetProperty(context.Result, product, (x) => x.Sku); - row.SetProperty(context.Result, product, (x) => x.Gtin); - row.SetProperty(context.Result, product, (x) => x.ManufacturerPartNumber); row.SetProperty(context.Result, product, (x) => x.ProductTypeId, (int)ProductType.SimpleProduct); row.SetProperty(context.Result, product, (x) => x.VisibleIndividually, true); row.SetProperty(context.Result, product, (x) => x.Name); row.SetProperty(context.Result, product, (x) => x.ShortDescription); row.SetProperty(context.Result, product, (x) => x.FullDescription); - row.SetProperty(context.Result, product, (x) => x.ProductTemplateId); + row.SetProperty(context.Result, product, (x) => x.AdminComment); row.SetProperty(context.Result, product, (x) => x.ShowOnHomePage); row.SetProperty(context.Result, product, (x) => x.HomePageDisplayOrder); row.SetProperty(context.Result, product, (x) => x.MetaKeywords); row.SetProperty(context.Result, product, (x) => x.MetaDescription); row.SetProperty(context.Result, product, (x) => x.MetaTitle); row.SetProperty(context.Result, product, (x) => x.AllowCustomerReviews, true); + row.SetProperty(context.Result, product, (x) => x.ApprovedRatingSum); + row.SetProperty(context.Result, product, (x) => x.NotApprovedRatingSum); + row.SetProperty(context.Result, product, (x) => x.ApprovedTotalReviews); + row.SetProperty(context.Result, product, (x) => x.NotApprovedTotalReviews); row.SetProperty(context.Result, product, (x) => x.Published, true); + row.SetProperty(context.Result, product, (x) => x.Sku); + row.SetProperty(context.Result, product, (x) => x.ManufacturerPartNumber); + row.SetProperty(context.Result, product, (x) => x.Gtin); row.SetProperty(context.Result, product, (x) => x.IsGiftCard); row.SetProperty(context.Result, product, (x) => x.GiftCardTypeId); row.SetProperty(context.Result, product, (x) => x.RequireOtherProducts); - row.SetProperty(context.Result, product, (x) => x.RequiredProductIds); + row.SetProperty(context.Result, product, (x) => x.RequiredProductIds); // TODO: global scope row.SetProperty(context.Result, product, (x) => x.AutomaticallyAddRequiredProducts); row.SetProperty(context.Result, product, (x) => x.IsDownload); row.SetProperty(context.Result, product, (x) => x.DownloadId); row.SetProperty(context.Result, product, (x) => x.UnlimitedDownloads, true); row.SetProperty(context.Result, product, (x) => x.MaxNumberOfDownloads, 10); + row.SetProperty(context.Result, product, (x) => x.DownloadExpirationDays); row.SetProperty(context.Result, product, (x) => x.DownloadActivationTypeId, 1); row.SetProperty(context.Result, product, (x) => x.HasSampleDownload); - row.SetProperty(context.Result, product, (x) => x.SampleDownloadId, (int?)null, ZeroToNull); + row.SetProperty(context.Result, product, (x) => x.SampleDownloadId, (int?)null, ZeroToNull); // TODO: global scope row.SetProperty(context.Result, product, (x) => x.HasUserAgreement); row.SetProperty(context.Result, product, (x) => x.UserAgreementText); row.SetProperty(context.Result, product, (x) => x.IsRecurring); @@ -635,7 +648,7 @@ private int ProcessProducts(IImportExecuteContext context, ImportRow[] row.SetProperty(context.Result, product, (x) => x.AdditionalShippingCharge); row.SetProperty(context.Result, product, (x) => x.IsEsd); row.SetProperty(context.Result, product, (x) => x.IsTaxExempt); - row.SetProperty(context.Result, product, (x) => x.TaxCategoryId, 1); + row.SetProperty(context.Result, product, (x) => x.TaxCategoryId, 1); // TODO: global scope row.SetProperty(context.Result, product, (x) => x.ManageInventoryMethodId); row.SetProperty(context.Result, product, (x) => x.StockQuantity, 10000); row.SetProperty(context.Result, product, (x) => x.DisplayStockAvailability); @@ -661,24 +674,30 @@ private int ProcessProducts(IImportExecuteContext context, ImportRow[] row.SetProperty(context.Result, product, (x) => x.CustomerEntersPrice); row.SetProperty(context.Result, product, (x) => x.MinimumCustomerEnteredPrice); row.SetProperty(context.Result, product, (x) => x.MaximumCustomerEnteredPrice, 1000); + // HasTierPrices... ignore as long as no tier prices are imported + // LowestAttributeCombinationPrice... ignore as long as no combinations are imported row.SetProperty(context.Result, product, (x) => x.Weight); row.SetProperty(context.Result, product, (x) => x.Length); row.SetProperty(context.Result, product, (x) => x.Width); row.SetProperty(context.Result, product, (x) => x.Height); - row.SetProperty(context.Result, product, (x) => x.DeliveryTimeId); - row.SetProperty(context.Result, product, (x) => x.QuantityUnitId); + row.SetProperty(context.Result, product, (x) => x.DisplayOrder); + row.SetProperty(context.Result, product, (x) => x.DeliveryTimeId); // TODO: global scope + row.SetProperty(context.Result, product, (x) => x.QuantityUnitId); // TODO: global scope row.SetProperty(context.Result, product, (x) => x.BasePriceEnabled); row.SetProperty(context.Result, product, (x) => x.BasePriceMeasureUnit); row.SetProperty(context.Result, product, (x) => x.BasePriceAmount); row.SetProperty(context.Result, product, (x) => x.BasePriceBaseAmount); - row.SetProperty(context.Result, product, (x) => x.BundlePerItemPricing); + row.SetProperty(context.Result, product, (x) => x.BundleTitleText); row.SetProperty(context.Result, product, (x) => x.BundlePerItemShipping); + row.SetProperty(context.Result, product, (x) => x.BundlePerItemPricing); row.SetProperty(context.Result, product, (x) => x.BundlePerItemShoppingCart); - row.SetProperty(context.Result, product, (x) => x.BundleTitleText); row.SetProperty(context.Result, product, (x) => x.AvailableStartDateTimeUtc); row.SetProperty(context.Result, product, (x) => x.AvailableEndDateTimeUtc); row.SetProperty(context.Result, product, (x) => x.LimitedToStores); + var tvp = row.GetDataValue("ProductTemplateViewPath"); + product.ProductTemplateId = (tvp.HasValue() && templateViewPaths.ContainsKey(tvp) ? templateViewPaths[tvp] : defaultTemplateId); + row.SetProperty(context.Result, product, (x) => x.CreatedOnUtc, UtcNow); product.UpdatedOnUtc = UtcNow; @@ -745,6 +764,10 @@ public void Execute(IImportExecuteContext context) { var srcToDestId = new Dictionary(); + var templateViewPaths = _productTemplateService.GetAllProductTemplates() + .GroupBy(x => x.ViewPath, StringComparer.OrdinalIgnoreCase) + .ToDictionary(x => x.Key, x => x.First().Id); + using (var scope = new DbContextScope(ctx: _productRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { var segmenter = context.GetSegmenter(); @@ -767,7 +790,7 @@ public void Execute(IImportExecuteContext context) // =========================================================================== try { - ProcessProducts(context, batch, srcToDestId); + ProcessProducts(context, batch, templateViewPaths, srcToDestId); } catch (Exception exception) { @@ -894,7 +917,7 @@ public void Execute(IImportExecuteContext context) try { - ProcessParentMappings(context, batch, srcToDestId); + ProcessProductMappings(context, batch, srcToDestId); } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 8f4ea97911..5496914771 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -127,6 +127,8 @@ private void ImportAvatar(IImportExecuteContext context, ImportRow row { if ((picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false)) != null) { + _pictureRepository.Context.SaveChanges(); + _genericAttributeService.SaveAttribute(row.Entity.Id, SystemCustomerAttributeNames.AvatarPictureId, _attributeKeyGroup, picture.Id.ToString()); } } @@ -134,7 +136,6 @@ private void ImportAvatar(IImportExecuteContext context, ImportRow row { context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "AvatarPictureUrl"); } - } } else @@ -143,7 +144,7 @@ private void ImportAvatar(IImportExecuteContext context, ImportRow row } } - private int ProcessGenericAttributes(IImportExecuteContext context, + private void ProcessGenericAttributes(IImportExecuteContext context, ImportRow[] batch, List allCountryIds, List allStateProvinceIds, @@ -228,11 +229,9 @@ private int ProcessGenericAttributes(IImportExecuteContext context, { ImportAvatar(context, row); } - } - - var num = _customerRepository.Context.SaveChanges(); - return num; + _services.DbContext.SaveChanges(); + } } private int ProcessCustomers(IImportExecuteContext context, @@ -432,12 +431,18 @@ public void Execute(IImportExecuteContext context) try { + _services.DbContext.AutoDetectChangesEnabled = true; + ProcessGenericAttributes(context, batch, allCountryIds, allStateProvinceIds, allCustomerNumbers); } catch (Exception exception) { context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessGenericAttributes"); } + finally + { + _services.DbContext.AutoDetectChangesEnabled = false; + } } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs index eb350d3b53..2a0f1d9cfa 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs @@ -61,6 +61,7 @@ public partial class DataImporter : IDataImporter private readonly Lazy _manufacturerService; private readonly Lazy _categoryService; private readonly Lazy _categoryTemplateService; + private readonly Lazy _productTemplateService; private readonly Lazy _productService; private readonly Lazy _urlRecordService; private readonly Lazy _storeMappingService; @@ -98,6 +99,7 @@ public DataImporter( Lazy manufacturerService, Lazy categoryService, Lazy categoryTemplateService, + Lazy productTemplateService, Lazy productService, Lazy urlRecordService, Lazy storeMappingService, @@ -135,6 +137,7 @@ public DataImporter( _manufacturerService = manufacturerService; _categoryService = categoryService; _categoryTemplateService = categoryTemplateService; + _productTemplateService = productTemplateService; _productService = productService; _urlRecordService = urlRecordService; _storeMappingService = storeMappingService; @@ -396,6 +399,7 @@ private void ImportCoreOuter(DataImporterContext ctx) _categoryService.Value, _productService.Value, _urlRecordService.Value, + _productTemplateService.Value, _storeMappingService.Value, _fileDownloadManager.Value, _seoSettings.Value, diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.AssociatedProducts.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.AssociatedProducts.cshtml index cb54649e42..0827ea946f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.AssociatedProducts.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.AssociatedProducts.cshtml @@ -4,7 +4,7 @@ @if (Model.Id > 0) { -
              +
              @T("Admin.Catalog.Products.AssociatedProducts.Note1") @T("Admin.Catalog.Products.AssociatedProducts.Note2") From 01d87e3d9d606ea969580908ec752466a1d72e7b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 22 Feb 2016 17:18:22 +0100 Subject: [PATCH 055/423] Fixed: Customer could not delete his avatar --- changelog.md | 1 + .../Controllers/CustomerController.cs | 51 ++++++++++--------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/changelog.md b/changelog.md index 2419149497..e26f5d40f0 100644 --- a/changelog.md +++ b/changelog.md @@ -112,6 +112,7 @@ * Grouped products: Display order was not correct * Deletion of a customer could delete all newsletter subscriptions * PayPal: Fixed "The request was aborted: Could not create SSL/TLS secure channel." +* Customer could not delete his avatar ## SmartStore.NET 2.2.2 diff --git a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs index e6b81ab62f..104d5a4393 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs @@ -39,7 +39,7 @@ namespace SmartStore.Web.Controllers { - public partial class CustomerController : PublicControllerBase + public partial class CustomerController : PublicControllerBase { #region Fields @@ -1596,30 +1596,34 @@ public ActionResult UploadAvatar(CustomerAvatarModel model, HttpPostedFileBase u try { var customerAvatar = _pictureService.GetPictureById(customer.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId)); - if ((uploadedFile != null) && (!String.IsNullOrEmpty(uploadedFile.FileName))) - { - int avatarMaxSize = _customerSettings.AvatarMaximumSizeBytes; - if (uploadedFile.ContentLength > avatarMaxSize) - throw new SmartException(string.Format(_localizationService.GetResource("Account.Avatar.MaximumUploadedFileSize"), Prettifier.BytesToString(avatarMaxSize))); - byte[] customerPictureBinary = uploadedFile.InputStream.ToByteArray(); - if (customerAvatar != null) - customerAvatar = _pictureService.UpdatePicture(customerAvatar.Id, customerPictureBinary, uploadedFile.ContentType, null, true); - else - customerAvatar = _pictureService.InsertPicture(customerPictureBinary, uploadedFile.ContentType, null, true, false); - } + if ((uploadedFile != null) && (!String.IsNullOrEmpty(uploadedFile.FileName))) + { + var avatarMaxSize = _customerSettings.AvatarMaximumSizeBytes; + + if (uploadedFile.ContentLength > avatarMaxSize) + throw new SmartException(T("Account.Avatar.MaximumUploadedFileSize", Prettifier.BytesToString(avatarMaxSize))); + + byte[] customerPictureBinary = uploadedFile.InputStream.ToByteArray(); + + if (customerAvatar != null) + customerAvatar = _pictureService.UpdatePicture(customerAvatar.Id, customerPictureBinary, uploadedFile.ContentType, null, true); + else + customerAvatar = _pictureService.InsertPicture(customerPictureBinary, uploadedFile.ContentType, null, true, false); + } + else if (customerAvatar != null) + { + _pictureService.DeletePicture(customerAvatar); + customerAvatar = null; + } - int customerAvatarId = 0; - if (customerAvatar != null) - customerAvatarId = customerAvatar.Id; + var customerAvatarId = (customerAvatar != null ? customerAvatar.Id : 0); _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.AvatarPictureId, customerAvatarId); - model.AvatarUrl = _pictureService.GetPictureUrl( - customerAvatarId, - _mediaSettings.AvatarPictureSize, - false); - return View(model); + model.AvatarUrl = _pictureService.GetPictureUrl(customerAvatarId, _mediaSettings.AvatarPictureSize, false); + + return View(model); } catch (Exception exc) { @@ -1627,12 +1631,8 @@ public ActionResult UploadAvatar(CustomerAvatarModel model, HttpPostedFileBase u } } - //If we got this far, something failed, redisplay form - model.AvatarUrl = _pictureService.GetPictureUrl( - customer.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId), - _mediaSettings.AvatarPictureSize, - false); + model.AvatarUrl = _pictureService.GetPictureUrl(customer.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId), _mediaSettings.AvatarPictureSize, false); return View(model); } @@ -1655,6 +1655,7 @@ public ActionResult RemoveAvatar(CustomerAvatarModel model, HttpPostedFileBase u var customerAvatar = _pictureService.GetPictureById(customer.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId)); if (customerAvatar != null) _pictureService.DeletePicture(customerAvatar); + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.AvatarPictureId, 0); return RedirectToAction("Avatar"); From 9c20ec7f71bd6c20258998a4b70804161298d126 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 22 Feb 2016 20:28:36 +0100 Subject: [PATCH 056/423] More localized properties to import --- .../Catalog/Importer/CategoryImporter.cs | 43 ++++++++++++++ .../Catalog/Importer/ProductImporter.cs | 59 +++++-------------- .../DataExchange/Import/DataImporter.cs | 3 +- .../Import/ImportExecuteContext.cs | 8 +++ 4 files changed, 69 insertions(+), 44 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 5aca0d425e..1c70a482d7 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -10,6 +10,7 @@ using SmartStore.Core.Domain.Seo; using SmartStore.Core.Events; using SmartStore.Services.DataExchange.Import; +using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Seo; using SmartStore.Services.Stores; @@ -27,6 +28,7 @@ public class CategoryImporter : EntityImporterBase, IEntityImporter private readonly ICategoryTemplateService _categoryTemplateService; private readonly IStoreMappingService _storeMappingService; private readonly IPictureService _pictureService; + private readonly ILocalizedEntityService _localizedEntityService; private readonly FileDownloadManager _fileDownloadManager; private readonly SeoSettings _seoSettings; private readonly DataExchangeSettings _dataExchangeSettings; @@ -40,6 +42,7 @@ public CategoryImporter( ICategoryTemplateService categoryTemplateService, IStoreMappingService storeMappingService, IPictureService pictureService, + ILocalizedEntityService localizedEntityService, FileDownloadManager fileDownloadManager, SeoSettings seoSettings, DataExchangeSettings dataExchangeSettings) @@ -52,6 +55,7 @@ public CategoryImporter( _categoryTemplateService = categoryTemplateService; _storeMappingService = storeMappingService; _pictureService = pictureService; + _localizedEntityService = localizedEntityService; _fileDownloadManager = fileDownloadManager; _seoSettings = seoSettings; _dataExchangeSettings = dataExchangeSettings; @@ -117,6 +121,35 @@ private int ProcessSlugs(IImportExecuteContext context, ImportRow[] ba return _urlRecordRepository.Context.SaveChanges(); } + private int ProcessLocalizations(IImportExecuteContext context, ImportRow[] batch) + { + foreach (var row in batch) + { + foreach (var lang in context.Languages) + { + var name = row.GetDataValue("Name", lang.UniqueSeoCode); + var fullName = row.GetDataValue("FullName", lang.UniqueSeoCode); + var description = row.GetDataValue("Description", lang.UniqueSeoCode); + var bottomDescription = row.GetDataValue("BottomDescription", lang.UniqueSeoCode); + var metaKeywords = row.GetDataValue("MetaKeywords", lang.UniqueSeoCode); + var metaDescription = row.GetDataValue("MetaDescription", lang.UniqueSeoCode); + var metaTitle = row.GetDataValue("MetaTitle", lang.UniqueSeoCode); + + _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.Name, name, lang.Id); + _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.FullName, fullName, lang.Id); + _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.Description, description, lang.Id); + _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.BottomDescription, bottomDescription, lang.Id); + _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.MetaKeywords, metaKeywords, lang.Id); + _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.MetaDescription, metaDescription, lang.Id); + _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.MetaTitle, metaTitle, lang.Id); + } + } + + var num = _categoryRepository.Context.SaveChanges(); + + return num; + } + private int ProcessParentMappings(IImportExecuteContext context, ImportRow[] batch, Dictionary srcToDestId) @@ -461,6 +494,16 @@ public void Execute(IImportExecuteContext context) } } + // localizations + try + { + ProcessLocalizations(context, batch); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessLocalizedProperties"); + } + // process pictures if (srcToDestId.Any() && segmenter.HasColumn("ImageUrl")) { diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index be80cb3cee..84b0f11371 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -26,7 +26,6 @@ public class ProductImporter : EntityImporterBase, IEntityImporter private readonly IRepository _urlRecordRepository; private readonly IRepository _productRepository; private readonly ICommonServices _services; - private readonly ILanguageService _languageService; private readonly ILocalizedEntityService _localizedEntityService; private readonly IPictureService _pictureService; private readonly IManufacturerService _manufacturerService; @@ -46,7 +45,6 @@ public ProductImporter( IRepository urlRecordRepository, IRepository productRepository, ICommonServices services, - ILanguageService languageService, ILocalizedEntityService localizedEntityService, IPictureService pictureService, IManufacturerService manufacturerService, @@ -65,7 +63,6 @@ public ProductImporter( _urlRecordRepository = urlRecordRepository; _productRepository = productRepository; _services = services; - _languageService = languageService; _localizedEntityService = localizedEntityService; _pictureService = pictureService; _manufacturerService = manufacturerService; @@ -403,55 +400,31 @@ private int ProcessProductCategories(IImportExecuteContext context, ImportRow[] batch) { - //_rsProductManufacturer.AutoCommitEnabled = false; - - //string lastInserted = null; - - var languages = _languageService.GetAllLanguages(true); - foreach (var row in batch) { - - Product product = null; - - //get product - try - { - product = _productService.GetProductById(row.Entity.Id); - } - catch (Exception exception) - { - context.Result.AddWarning(exception.Message, row.GetRowInfo(), "ProcessLocalizations Product"); - } - - foreach (var lang in languages) + foreach (var lang in context.Languages) { - string localizedName = row.GetDataValue("Name", lang.UniqueSeoCode); - string localizedShortDescription = row.GetDataValue("ShortDescription", lang.UniqueSeoCode); - string localizedFullDescription = row.GetDataValue("FullDescription", lang.UniqueSeoCode); - - if (localizedName.HasValue()) - { - _localizedEntityService.SaveLocalizedValue(product, x => x.Name, localizedName, lang.Id); - } - if (localizedShortDescription.HasValue()) - { - _localizedEntityService.SaveLocalizedValue(product, x => x.ShortDescription, localizedShortDescription, lang.Id); - } - if (localizedFullDescription.HasValue()) - { - _localizedEntityService.SaveLocalizedValue(product, x => x.FullDescription, localizedFullDescription, lang.Id); - } + var name = row.GetDataValue("Name", lang.UniqueSeoCode); + var shortDescription = row.GetDataValue("ShortDescription", lang.UniqueSeoCode); + var fullDescription = row.GetDataValue("FullDescription", lang.UniqueSeoCode); + var metaKeywords = row.GetDataValue("MetaKeywords", lang.UniqueSeoCode); + var metaDescription = row.GetDataValue("MetaDescription", lang.UniqueSeoCode); + var metaTitle = row.GetDataValue("MetaTitle", lang.UniqueSeoCode); + var bundleTitleText = row.GetDataValue("BundleTitleText", lang.UniqueSeoCode); + + _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.Name, name, lang.Id); + _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.ShortDescription, shortDescription, lang.Id); + _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.FullDescription, fullDescription, lang.Id); + _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.MetaKeywords, metaKeywords, lang.Id); + _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.MetaDescription, metaDescription, lang.Id); + _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.MetaTitle, metaTitle, lang.Id); + _localizedEntityService.SaveLocalizedValue(row.Entity, x => x.BundleTitleText, bundleTitleText, lang.Id); } } // commit whole batch at once var num = _productManufacturerRepository.Context.SaveChanges(); - // Perf: notify only about LAST insertion and update - //if (lastInserted != null) - // _eventPublisher.EntityInserted(lastInserted); - return num; } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs index 2a0f1d9cfa..9413254f3d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs @@ -366,6 +366,7 @@ private void ImportCoreOuter(DataImporterContext ctx) ctx.Log = logger; ctx.ExecuteContext.Log = logger; + ctx.ExecuteContext.Languages = _languageService.Value.GetAllLanguages(true); ctx.ExecuteContext.UpdateOnly = ctx.Request.Profile.UpdateOnly; ctx.ExecuteContext.KeyFieldNames = ctx.Request.Profile.KeyFieldNames.SplitSafe(","); ctx.ExecuteContext.ImportFolder = ctx.Request.Profile.GetImportFolder(); @@ -392,7 +393,6 @@ private void ImportCoreOuter(DataImporterContext ctx) _urlRecordRepository.Value, _productRepository.Value, _services, - _languageService.Value, _localizedEntityService.Value, _pictureService.Value, _manufacturerService.Value, @@ -440,6 +440,7 @@ private void ImportCoreOuter(DataImporterContext ctx) _categoryTemplateService.Value, _storeMappingService.Value, _pictureService.Value, + _localizedEntityService.Value, _fileDownloadManager.Value, _seoSettings.Value, _dataExchangeSettings.Value); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index d49a3a72e5..a37b9b0013 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -2,6 +2,7 @@ using System.Threading; using SmartStore.Core; using SmartStore.Core.Domain.DataExchange; +using SmartStore.Core.Domain.Localization; using SmartStore.Core.Logging; namespace SmartStore.Services.DataExchange.Import @@ -28,6 +29,11 @@ public interface IImportExecuteContext /// Dictionary CustomProperties { get; set; } + /// + /// All languages + /// + IList Languages { get; } + /// /// To log information into the import log file /// @@ -91,6 +97,8 @@ public ImportExecuteContext( public string[] KeyFieldNames { get; internal set; } + public IList Languages { get; internal set; } + public ILogger Log { get; internal set; } public CancellationToken CancellationToken { get; private set; } From ec4f163ded9819ae8aab5f2c2621a9d7a973dc09 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 23 Feb 2016 01:29:08 +0100 Subject: [PATCH 057/423] Redesigned EntityPicker --- .../Domain/Common/CommonSettings.cs | 2 +- .../Content/bootstrap/custom/custom.less | 7 +- .../Content/smartstore.entitypicker.css | 144 ++++++++++++------ .../Controllers/CommonController.cs | 2 +- .../Scripts/smartstore.entityPicker.js | 13 +- .../Views/Common/EntityPicker.cshtml | 2 +- .../Views/Common/EntityPickerList.cshtml | 50 +++--- 7 files changed, 140 insertions(+), 80 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs b/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs index 9860646c94..637b1825f0 100644 --- a/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs @@ -13,7 +13,7 @@ public CommonSettings() SitemapIncludeTopics = true; FullTextMode = FulltextSearchMode.ExactMatch; AutoUpdateEnabled = true; - EntityPickerPageSize = 12; + EntityPickerPageSize = 24; } public bool UseSystemEmailForContactUsForm { get; set; } diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/custom.less b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/custom.less index 83ee4fbea1..2ccd3d84f3 100644 --- a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/custom.less +++ b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/custom.less @@ -490,7 +490,7 @@ th label { .modal-backdrop, .modal-backdrop.fade.in { - .opacity(60); + opacity: 0.6; } .modal-header .close { @@ -501,4 +501,9 @@ th label { .modal-large { width: 800px !important; margin: -250px 0 0 -400px !important; +} + +.modal-xlarge { + width: 960px !important; + margin: -250px 0 0 -480px !important; } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/smartstore.entitypicker.css b/src/Presentation/SmartStore.Web/Content/smartstore.entitypicker.css index 00183c84dc..59edbf1e41 100644 --- a/src/Presentation/SmartStore.Web/Content/smartstore.entitypicker.css +++ b/src/Presentation/SmartStore.Web/Content/smartstore.entitypicker.css @@ -2,77 +2,121 @@ SmartStore Component: smartstore.entitypicker.css -------------------------------------------------------------- */ -.entity-picker-filter .item { - min-width: 320px; +.entity-picker-list { + position: relative; + margin-left: -5px; + margin-right: -5px; +} + +.entity-picker-list .item-wrap { + position: relative; + box-sizing: border-box; + display: block; + float: left; + width: 33.3332%; + padding: 0 5px; } .entity-picker-list .item { - display: inline-block; - width: 360px; - min-height: 54px; - max-height: 54px; - padding: 3px 4px; - margin: 2px 0 1px 0; + position: relative; + box-sizing: border-box; + display: block; + height: 70px; + padding: 8px; + margin: 5px 0; border-radius: 3px; - border: #d4d4d4 solid 1px; - background-image: linear-gradient(to bottom, #fcfcfc, #e3e3e3); - background-color: #FCF8F0; -} -.entity-picker-list .selected { - box-shadow: 0 0 0 1px #FFCB7C; - border: #FFCB7C solid 1px; - background-image: linear-gradient(to bottom, #fcfcfc, #FFCB7C); - background-color: #F2EEE6; } .entity-picker-list .item:hover { cursor: pointer; } .entity-picker-list .item:not(.selected):hover { - background-image: linear-gradient(to bottom, #fcfcfc, #cacaca); - background-color: #F2EEE6; + background-color: #f5f5f5; } + .entity-picker-list .disable { - opacity: .4; - filter: Alpha(Opacity=40); -} -.entity-picker-list .thumb { - float: left; - width: 65px; -} -.entity-picker-list .img-polaroid { - padding: 2px; + opacity: 0.4; } -.entity-picker-list .thumb img { - max-width: 50px; - max-height: 50px; -} -.entity-picker-list .label { - display: inline-block; - float: left; - margin-right: 5px; + +.entity-picker-list .title { + font-weight: 400; + max-height: 36px; + overflow: hidden; + text-overflow: ellipsis; } + .entity-picker-list .highlight { - color: #f00; - background-color: transparent; + font-weight: 700; } + .entity-picker-list .summary { - font-size: 0.9em; - height: 2.65em; - min-height: 2.65em; - line-height: 1.22em; - vertical-align: top; - margin-top: 2px; + color: #aaa; + font-size: 12px; + height: 18px; + line-height: 18px; + vertical-align: middle; overflow: hidden; } .entity-picker-list .published { - float: right; - margin-top: -11px; + margin-left: 1px; + margin-right: 6px; + font-size: 15px; + line-height: 18px; + vertical-align: sub; +} +.entity-picker-list .published.fa-globe { + color: inherit; +} +.entity-picker-list .published.fa-eye-slash { + color: #aaa; } +.entity-picker-list .item.selected .published { + color: #fff; +} + .entity-picker-list .list-footer { clear: both; - float: left; - margin-top: 8px; + padding: 8px 12px 0 12px; + text-align: center; +} + +.entity-picker-list .thumb, +.entity-picker-list .data { + position: relative; + box-sizing: border-box; + display: block; +} +.entity-picker-list .thumb { + display: table-cell; + width: 54px; + height: 54px; + padding: 2px; + text-align: center; + vertical-align: middle; +} +.entity-picker-list .item:hover .thumb, +.entity-picker-list .item.selected .thumb { + background: #fff; + box-shadow: 1px 1px 2px rgba(0,0,0, 0.1); } -.entity-picker-list .list-footer a { - padding-bottom: 2px; +.entity-picker-list .thumb img { + max-width: 100%; + max-height: 100%; +} +.entity-picker-list .thumb + .data { + position: absolute; + left: 72px; + right: 8px; + top: 8px; + bottom: 8px; +} + +.entity-picker-list .item.selected { + background-color: #4060c0; } +.entity-picker-list .item.selected .title { + color: #fff; +} +.entity-picker-list .item.selected .summary { + color: rgba(255,255,255, 0.6); +} + diff --git a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs index 2a62c28163..2c8a1e083e 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs @@ -1051,7 +1051,7 @@ public ActionResult EntityPicker(EntityPickerModel model) [HttpPost] public ActionResult EntityPicker(EntityPickerModel model, FormCollection form) { - model.PageSize = _commonSettings.EntityPickerPageSize; + model.PageSize = _commonSettings.EntityPickerPageSize; model.PublishedString = T("Common.Published"); model.UnpublishedString = T("Common.Unpublished"); diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js index 019dd0a552..55a8aeba27 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js @@ -177,6 +177,13 @@ } }); + // lazy loading + dialog.find('.modal-body').on('scroll', function (e) { + if ($('.load-more:not(.loading)').visible(true, false, 'vertical')) { + fillList(this, { append: true }); + } + }); + // show more items dialog.on('click', 'a.entity-picker-showmore', function (e) { e.preventDefault(); @@ -274,7 +281,7 @@ url: dialog.find('form:first').attr('action'), beforeSend: function () { if (_.isTrue(opt.append)) { - dialog.find('.list-footer').remove(); + dialog.find('.load-more').addClass('loading'); } else { dialog.find('.entity-picker-list').empty(); @@ -282,7 +289,7 @@ } dialog.find('button[name=SearchEntities]').button('loading').prop('disabled', true); - dialog.find('.entity-picker-list').append(createCircularSpinner(20, true)); + dialog.find('.load-more').append(createCircularSpinner(20, true)); }, success: function (response) { var list = dialog.find('.entity-picker-list'), @@ -302,7 +309,7 @@ }, complete: function () { dialog.find('button[name=SearchEntities]').prop('disabled', false).button('reset'); - dialog.find('.entity-picker-list').find('.spinner').remove(); + dialog.find('.load-more.loading').parent().remove(); }, error: ajaxErrorHandler }); diff --git a/src/Presentation/SmartStore.Web/Views/Common/EntityPicker.cshtml b/src/Presentation/SmartStore.Web/Views/Common/EntityPicker.cshtml index 539f568faa..df16230b2b 100644 --- a/src/Presentation/SmartStore.Web/Views/Common/EntityPicker.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Common/EntityPicker.cshtml @@ -1,7 +1,7 @@ @model EntityPickerModel @using SmartStore.Web.Models.Common; -
              + @Html.SmartLabelFor(model => model.RoundDownRewardPoints) + + @Html.SettingEditorFor(model => model.RoundDownRewardPoints) + @Html.ValidationMessageFor(model => model.RoundDownRewardPoints) +
              From ec75faa92c73889b49d46511a0fd5d7688249ced Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 23 Feb 2016 15:29:20 +0100 Subject: [PATCH 059/423] Facebook authentication: Email missing in verification --- changelog.md | 1 + .../Core/FacebookProviderAuthorizer.cs | 33 +++++++++++++++++-- .../SmartStore.FacebookAuth/Description.txt | 2 +- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index 6e92c74afd..9683e94137 100644 --- a/changelog.md +++ b/changelog.md @@ -114,6 +114,7 @@ * Deletion of a customer could delete all newsletter subscriptions * PayPal: Fixed "The request was aborted: Could not create SSL/TLS secure channel." * Customer could not delete his avatar +* Facebook authentication: Email missing in verification ## SmartStore.NET 2.2.2 diff --git a/src/Plugins/SmartStore.FacebookAuth/Core/FacebookProviderAuthorizer.cs b/src/Plugins/SmartStore.FacebookAuth/Core/FacebookProviderAuthorizer.cs index 7bb6234362..5f89fe62cc 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Core/FacebookProviderAuthorizer.cs +++ b/src/Plugins/SmartStore.FacebookAuth/Core/FacebookProviderAuthorizer.cs @@ -2,19 +2,22 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Net; using System.Text; using System.Web; using System.Web.Mvc; using DotNetOpenAuth.AspNet; using DotNetOpenAuth.AspNet.Clients; +using Newtonsoft.Json.Linq; using SmartStore.Core.Domain.Customers; using SmartStore.Services; using SmartStore.Services.Authentication.External; namespace SmartStore.FacebookAuth.Core { - public class FacebookProviderAuthorizer : IOAuthProviderFacebookAuthorizer + public class FacebookProviderAuthorizer : IOAuthProviderFacebookAuthorizer { #region Fields @@ -95,13 +98,39 @@ private AuthorizeState VerifyAuthentication(string returnUrl) return state; } + private string GetEmailFromFacebook(string accessToken) + { + var result = ""; + var webRequest = WebRequest.Create("https://graph.facebook.com/me?fields=email&access_token=" + EscapeUriDataStringRfc3986(accessToken)); + + using (var webResponse = webRequest.GetResponse()) + using (var stream = webResponse.GetResponseStream()) + using (var reader = new StreamReader(stream)) + { + var strResponse = reader.ReadToEnd(); + var info = JObject.Parse(strResponse); + + if (info["email"] != null) + { + result = info["email"].ToString(); + } + } + return result; + } + private void ParseClaims(AuthenticationResult authenticationResult, OAuthAuthenticationParameters parameters) { var claims = new UserClaims(); claims.Contact = new ContactClaims(); - + if (authenticationResult.ExtraData.ContainsKey("username")) + { claims.Contact.Email = authenticationResult.ExtraData["username"]; + } + else + { + claims.Contact.Email = GetEmailFromFacebook(authenticationResult.ExtraData["accesstoken"]); + } claims.Name = new NameClaims(); diff --git a/src/Plugins/SmartStore.FacebookAuth/Description.txt b/src/Plugins/SmartStore.FacebookAuth/Description.txt index f7cc05d671..ef5b27035b 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Description.txt +++ b/src/Plugins/SmartStore.FacebookAuth/Description.txt @@ -1,7 +1,7 @@ FriendlyName: Facebook SystemName: SmartStore.FacebookAuth Group: Security -Version: 2.2.0.1 +Version: 2.2.0.2 MinAppVersion: 2.2.0 DisplayOrder: 5 FileName: SmartStore.FacebookAuth.dll From ee6246ed5f4d81bd469c551f506f666598a6c0c9 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 23 Feb 2016 18:43:14 +0100 Subject: [PATCH 060/423] entity picker now auto loading --- .../Scripts/smartstore.entityPicker.js | 45 +++++++++++++++++-- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js index 55a8aeba27..d250a9e0c2 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js @@ -142,6 +142,7 @@ success: function (response) { $('body').append(response); showAndFocusDialog(); + fillList('#entity-picker-' + opt.entity + '-dialog', { append: false }); }, complete: function () { if (_.isFunction(opt.onLoadDialogComplete)) { @@ -154,7 +155,9 @@ } function initDialog(context) { - var dialog = $(context); + var dialog = $(context), + keyUpTimer = null, + currentValue = ''; // search entities dialog.find('button[name=SearchEntities]').click(function (e) { @@ -168,13 +171,43 @@ dialog.find('.entity-picker-filter').slideToggle(); }); - // hit enter starts searching + // hit enter or key up starts searching dialog.find('input.entity-picker-searchterm').keydown(function (e) { if (e.keyCode == 13) { e.preventDefault(); dialog.find('button[name=SearchEntities]').click(); return false; } + }).keyup(function (e) { + try { + var val = $(this).val(); + + if (val.length < 1) { + dialog.find('.entity-picker-list').stop().empty(); + return; + } + + if (val !== currentValue) { + if (keyUpTimer) { + keyUpTimer = clearTimeout(keyUpTimer); + } + + keyUpTimer = setTimeout(function () { + fillList(dialog, { + append: false, + onSuccess: function () { + currentValue = val; + } + }); + }, 500); + } + } + catch (err) { } + }); + + // filter change starts searching + dialog.find('.entity-picker-filter .item').change(function () { + fillList(this, { append: false }); }); // lazy loading @@ -295,9 +328,9 @@ var list = dialog.find('.entity-picker-list'), data = dialog.data('entitypicker'); - list.append(response); + list.stop().append(response); - if (!_.isTrue(opt.append)) { + if (_.isFalse(opt.append)) { dialog.find('.entity-picker-filter').slideUp(); showStatus(dialog); } @@ -306,6 +339,10 @@ list.find('.thumb img:not(.zoomable-thumb)').addClass('zoomable-thumb'); list.thumbZoomer(); } + + if (_.isFunction(opt.onSuccess)) { + opt.onSuccess(); + } }, complete: function () { dialog.find('button[name=SearchEntities]').prop('disabled', false).button('reset'); From 0da2780e45f41502dc781bad005bce423ecf771b Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 23 Feb 2016 20:18:08 +0100 Subject: [PATCH 061/423] UI: implemented flex modal dialog (body fills available window area) --- .../Domain/Common/CommonSettings.cs | 2 +- .../Content/bootstrap/custom/custom.less | 60 +++++++++++++++++++ .../Content/smartstore.entitypicker.css | 4 +- .../Controllers/CommonController.cs | 4 +- .../Scripts/smartstore.entityPicker.js | 7 --- .../Views/Common/EntityPicker.cshtml | 6 +- 6 files changed, 69 insertions(+), 14 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs b/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs index 637b1825f0..84ceca5428 100644 --- a/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Common/CommonSettings.cs @@ -13,7 +13,7 @@ public CommonSettings() SitemapIncludeTopics = true; FullTextMode = FulltextSearchMode.ExactMatch; AutoUpdateEnabled = true; - EntityPickerPageSize = 24; + EntityPickerPageSize = 48; } public bool UseSystemEmailForContactUsForm { get; set; } diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/custom.less b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/custom.less index 2ccd3d84f3..f7c1a81348 100644 --- a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/custom.less +++ b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/custom.less @@ -503,7 +503,67 @@ th label { margin: -250px 0 0 -400px !important; } + +// XLARGE MODAL +// ------------------------------ + .modal-xlarge { width: 960px !important; margin: -250px 0 0 -480px !important; +} + + +// FLEX MODAL +// ------------------------------ + +.modal-flex { + top: 3% !important; + bottom: 3% !important; + margin-top: 0 !important; + margin-bottom: 0 !important; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + + &.fade.in { + display: -webkit-box !important; + display: -webkit-flex !important; + display: -ms-flexbox !important; + display: flex !important; + } + + .modal-body { + max-height: initial; + overflow-y: auto; + overflow-x: hidden; + .display-flex(); + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + -webkit-box-pack: start; + -webkit-justify-content: flex-start; + -ms-flex-pack: start; + justify-content: flex-start; + -webkit-box-align: stretch; + -webkit-align-items: stretch; + -ms-flex-align: stretch; + align-items: stretch; + -webkit-align-content: stretch; + -ms-flex-line-pack: stretch; + align-content: stretch; + } + + .modal-flex-fill-area { + //overflow: hidden; + -webkit-box-flex: 1; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/smartstore.entitypicker.css b/src/Presentation/SmartStore.Web/Content/smartstore.entitypicker.css index 59edbf1e41..04b6a2be35 100644 --- a/src/Presentation/SmartStore.Web/Content/smartstore.entitypicker.css +++ b/src/Presentation/SmartStore.Web/Content/smartstore.entitypicker.css @@ -75,7 +75,7 @@ .entity-picker-list .list-footer { clear: both; - padding: 8px 12px 0 12px; + padding: 12px; text-align: center; } @@ -89,6 +89,8 @@ display: table-cell; width: 54px; height: 54px; + max-width: 54px; + max-height: 54px; padding: 2px; text-align: center; vertical-align: middle; diff --git a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs index 2c8a1e083e..4826c5abd8 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs @@ -1022,7 +1022,7 @@ protected PdfReceiptHeaderFooterModel PreparePdfReceiptHeaderFooterModel(int sto public ActionResult EntityPicker(EntityPickerModel model) { - model.PageSize = _commonSettings.EntityPickerPageSize; + model.PageSize = 48; // _commonSettings.EntityPickerPageSize; model.AllString = T("Admin.Common.All"); if (model.Entity.IsCaseInsensitiveEqual("product")) @@ -1051,7 +1051,7 @@ public ActionResult EntityPicker(EntityPickerModel model) [HttpPost] public ActionResult EntityPicker(EntityPickerModel model, FormCollection form) { - model.PageSize = _commonSettings.EntityPickerPageSize; + model.PageSize = 48; // _commonSettings.EntityPickerPageSize; model.PublishedString = T("Common.Published"); model.UnpublishedString = T("Common.Unpublished"); diff --git a/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js b/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js index d250a9e0c2..989660953e 100644 --- a/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js +++ b/src/Presentation/SmartStore.Web/Scripts/smartstore.entityPicker.js @@ -217,13 +217,6 @@ } }); - // show more items - dialog.on('click', 'a.entity-picker-showmore', function (e) { - e.preventDefault(); - fillList(this, { append: true }); - return false; - }); - // item select and item hover dialog.find('.entity-picker-list').on('click', '.item', function (e) { var item = $(this); diff --git a/src/Presentation/SmartStore.Web/Views/Common/EntityPicker.cshtml b/src/Presentation/SmartStore.Web/Views/Common/EntityPicker.cshtml index df16230b2b..65f865ccf0 100644 --- a/src/Presentation/SmartStore.Web/Views/Common/EntityPicker.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Common/EntityPicker.cshtml @@ -1,13 +1,13 @@ @model EntityPickerModel @using SmartStore.Web.Models.Common; -
              +
              + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + - + - -
              +
              +
              @T("Plugins.Shipping.ByWeight.AddNewRecordTitle")
              +
              +
              @Html.SmartLabelFor(model => model.AddStoreId) @@ -96,143 +98,146 @@ @Html.ValidationMessageFor(model => model.AddStoreId)
              - @Html.SmartLabelFor(model => model.AddCountryId) - - @Html.DropDownListFor(model => model.AddCountryId, Model.AvailableCountries) - @Html.ValidationMessageFor(model => model.AddCountryId) -
              - @Html.SmartLabelFor(model => model.AddZip) - - @Html.EditorFor(model => model.AddZip) - @Html.ValidationMessageFor(model => model.AddZip) -
              - @Html.SmartLabelFor(model => model.AddShippingMethodId) - - @Html.DropDownListFor(model => model.AddShippingMethodId, Model.AvailableShippingMethods) - @Html.ValidationMessageFor(model => model.AddShippingMethodId) -
              - @Html.SmartLabelFor(model => model.AddFrom) - - @Html.EditorFor(model => model.AddFrom) [@Model.BaseWeightIn] - @Html.ValidationMessageFor(model => model.AddFrom) -
              - @Html.SmartLabelFor(model => model.AddTo) - - @Html.EditorFor(model => model.AddTo) [@Model.BaseWeightIn] - @Html.ValidationMessageFor(model => model.AddTo) -
              - @Html.SmartLabelFor(model => model.AddUsePercentage) - - @Html.EditorFor(model => model.AddUsePercentage) - @Html.ValidationMessageFor(model => model.AddUsePercentage) -
              - @Html.SmartLabelFor(model => model.AddShippingChargePercentage) - - @Html.EditorFor(model => model.AddShippingChargePercentage) - @Html.ValidationMessageFor(model => model.AddShippingChargePercentage) -
              - @Html.SmartLabelFor(model => model.AddShippingChargeAmount) - - @Html.EditorFor(model => model.AddShippingChargeAmount) [@Model.PrimaryStoreCurrencyCode] - @Html.ValidationMessageFor(model => model.AddShippingChargeAmount) -
              + @Html.SmartLabelFor(model => model.AddCountryId) + + @Html.DropDownListFor(model => model.AddCountryId, Model.AvailableCountries) + @Html.ValidationMessageFor(model => model.AddCountryId) +
              + @Html.SmartLabelFor(model => model.AddZip) + + @Html.EditorFor(model => model.AddZip) + @Html.ValidationMessageFor(model => model.AddZip) +
              + @Html.SmartLabelFor(model => model.AddShippingMethodId) + + @Html.DropDownListFor(model => model.AddShippingMethodId, Model.AvailableShippingMethods) + @Html.ValidationMessageFor(model => model.AddShippingMethodId) +
              + @Html.SmartLabelFor(model => model.AddFrom) + + @Html.EditorFor(model => model.AddFrom) [@Model.BaseWeightIn] + @Html.ValidationMessageFor(model => model.AddFrom) +
              + @Html.SmartLabelFor(model => model.AddTo) + + @Html.EditorFor(model => model.AddTo) [@Model.BaseWeightIn] + @Html.ValidationMessageFor(model => model.AddTo) +
              + @Html.SmartLabelFor(model => model.AddUsePercentage) + + @Html.EditorFor(model => model.AddUsePercentage) + @Html.ValidationMessageFor(model => model.AddUsePercentage) +
              + @Html.SmartLabelFor(model => model.AddShippingChargePercentage) + + @Html.EditorFor(model => model.AddShippingChargePercentage) + @Html.ValidationMessageFor(model => model.AddShippingChargePercentage) +
              + @Html.SmartLabelFor(model => model.AddShippingChargeAmount) + + @Html.EditorFor(model => model.AddShippingChargeAmount) [@Model.PrimaryStoreCurrencyCode] + @Html.ValidationMessageFor(model => model.AddShippingChargeAmount) +
              - @Html.SmartLabelFor(model => model.SmallQuantitySurcharge) - - @Html.EditorFor(model => model.SmallQuantitySurcharge) [@Model.PrimaryStoreCurrencyCode] - @Html.ValidationMessageFor(model => model.SmallQuantitySurcharge) -
              - @Html.SmartLabelFor(model => model.SmallQuantityThreshold) - - @Html.EditorFor(model => model.SmallQuantityThreshold) - @Html.ValidationMessageFor(model => model.SmallQuantityThreshold) -
              + @Html.SmartLabelFor(model => model.SmallQuantitySurcharge) + + @Html.EditorFor(model => model.SmallQuantitySurcharge) [@Model.PrimaryStoreCurrencyCode] + @Html.ValidationMessageFor(model => model.SmallQuantitySurcharge) +
              + @Html.SmartLabelFor(model => model.SmallQuantityThreshold) + + @Html.EditorFor(model => model.SmallQuantityThreshold) + @Html.ValidationMessageFor(model => model.SmallQuantityThreshold) +
                - -
              - - -
              - @T("Plugins.Shipping.ByWeight.SettingsTitle") - - - - - - - - - - + + + +
              - @Html.SmartLabelFor(model => model.CalculatePerWeightUnit) - - @Html.EditorFor(model => model.CalculatePerWeightUnit) - @Html.ValidationMessageFor(model => model.CalculatePerWeightUnit) -
              - @Html.SmartLabelFor(model => model.LimitMethodsToCreated) - - @Html.EditorFor(model => model.LimitMethodsToCreated) - @Html.ValidationMessageFor(model => model.LimitMethodsToCreated) -
              + + + + + + + + + + + + + + - -
              +
              +
              @T("Plugins.Shipping.ByWeight.SettingsTitle")
              +
              +
              + @Html.SmartLabelFor(model => model.CalculatePerWeightUnit) + + @Html.EditorFor(model => model.CalculatePerWeightUnit) + @Html.ValidationMessageFor(model => model.CalculatePerWeightUnit) +
              + @Html.SmartLabelFor(model => model.LimitMethodsToCreated) + + @Html.EditorFor(model => model.LimitMethodsToCreated) + @Html.ValidationMessageFor(model => model.LimitMethodsToCreated) +
                - -
              -
              + +
              } \ No newline at end of file From 3c4094c646b4f44ce2d8a56401ba3cc95aa2c92f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 24 Feb 2016 19:27:08 +0100 Subject: [PATCH 065/423] Removed some CSS (not used anymore) --- .../Views/TaxByRegion/Configure.cshtml | 140 +++++++++--------- .../Administration/Content/admin.less | 14 -- .../Views/Customer/_CreateOrUpdate.cshtml | 13 +- .../Administration/Views/Order/Edit.cshtml | 11 +- .../Product/_CreateOrUpdate.Pictures.cshtml | 27 ++-- ...ateOrUpdate.SpecificationAttributes.cshtml | 35 +++-- 6 files changed, 126 insertions(+), 114 deletions(-) diff --git a/src/Plugins/SmartStore.Tax/Views/TaxByRegion/Configure.cshtml b/src/Plugins/SmartStore.Tax/Views/TaxByRegion/Configure.cshtml index 5b8d09d71d..f3b670b3b2 100644 --- a/src/Plugins/SmartStore.Tax/Views/TaxByRegion/Configure.cshtml +++ b/src/Plugins/SmartStore.Tax/Views/TaxByRegion/Configure.cshtml @@ -1,10 +1,11 @@ -@{ - Layout = ""; -} -@model SmartStore.Tax.Models.ByRegionTaxRateListModel -@using SmartStore.Web.Framework; +@using SmartStore.Web.Framework; @using Telerik.Web.Mvc.UI; @using System.Linq; +@model SmartStore.Tax.Models.ByRegionTaxRateListModel +@{ + Layout = ""; +} +
              @@ -46,8 +47,8 @@
              -

              -

              + +

              @using (Html.BeginForm()) -{ -
              -

              @T("Plugins.Tax.CountryStateZip.AddRecord.Hint")

              -
              - +{ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + +
              - @Html.SmartLabelFor(model => model.AddCountryId) - - @Html.DropDownListFor(model => model.AddCountryId, Model.AvailableCountries) - @Html.ValidationMessageFor(model => model.AddCountryId) -
              - @Html.SmartLabelFor(model => model.AddStateProvinceId) - - @Html.DropDownListFor(model => model.AddStateProvinceId, Model.AvailableStates) - @Html.ValidationMessageFor(model => model.AddStateProvinceId) -
              - @Html.SmartLabelFor(model => model.AddZip) - - @Html.EditorFor(model => model.AddZip) - @Html.ValidationMessageFor(model => model.AddZip) -
              - @Html.SmartLabelFor(model => model.AddTaxCategoryId) - - @Html.DropDownListFor(model => model.AddTaxCategoryId, Model.AvailableTaxCategories) - @Html.ValidationMessageFor(model => model.AddTaxCategoryId) -
              - @Html.SmartLabelFor(model => model.AddPercentage) - - @Html.EditorFor(model => model.AddPercentage) - @Html.ValidationMessageFor(model => model.AddPercentage) -
              -   - - -
              +
              +
              @T("Plugins.Tax.CountryStateZip.AddRecord.Hint")
              +
              +
              + @Html.SmartLabelFor(model => model.AddCountryId) + + @Html.DropDownListFor(model => model.AddCountryId, Model.AvailableCountries) + @Html.ValidationMessageFor(model => model.AddCountryId) +
              + @Html.SmartLabelFor(model => model.AddStateProvinceId) + + @Html.DropDownListFor(model => model.AddStateProvinceId, Model.AvailableStates) + @Html.ValidationMessageFor(model => model.AddStateProvinceId) +
              + @Html.SmartLabelFor(model => model.AddZip) + + @Html.EditorFor(model => model.AddZip) + @Html.ValidationMessageFor(model => model.AddZip) +
              + @Html.SmartLabelFor(model => model.AddTaxCategoryId) + + @Html.DropDownListFor(model => model.AddTaxCategoryId, Model.AvailableTaxCategories) + @Html.ValidationMessageFor(model => model.AddTaxCategoryId) +
              + @Html.SmartLabelFor(model => model.AddPercentage) + + @Html.EditorFor(model => model.AddPercentage) + @Html.ValidationMessageFor(model => model.AddPercentage) +
              +   + + +
              } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index 12e3c546e8..0932e0a4bd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -1541,20 +1541,6 @@ table.payment-method-features td { text-decoration: none; } -#product-edit h3, -.caption-container h3 { - font-family: @headingsFontFamily; - font-weight: @headingsFontWeight; - font-size: 18px; - line-height: 36px; - color: #333; -} -#product-edit h3.bordered, -.caption-container h3.bordered { - border: 0; - border-bottom: 1px solid #e5e5e5; -} - /* Download editor -------------------------------------------------------------- */ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml index dee81edee0..a6ddb41d85 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Customer/_CreateOrUpdate.cshtml @@ -507,13 +507,16 @@ columns.Bound(x => x.Message); columns.Bound(x => x.CreatedOn).ReadOnly(); })) -
              -
              +
              -
              -

              @T("Admin.Customers.Customers.RewardPoints.AddTitle")

              -
              + + +
              +
              +
              @T("Admin.Customers.Customers.RewardPoints.AddTitle")
              +
              +
              @Html.SmartLabelFor(model => model.AddRewardPointsValue) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml index efaef854a2..272e8137c7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Order/Edit.cshtml @@ -1750,10 +1750,15 @@
              -
              -

              @T("Admin.Orders.OrderNotes.AddTitle")

              -
              + + + + ", giftCardText, giftCardAmount)); + var remaining = _currencyService.ConvertCurrency(gcuh.GiftCard.GetGiftCardRemainingAmount(), order.CurrencyRate); + var remainingFormatted = _priceFormatter.FormatPrice(remaining, true, false); + var remainingText = _localizationService.GetResource("ShoppingCart.Totals.GiftCardInfo.Remaining", language.Id).FormatInvariant(remainingFormatted); + + sb.AppendLine(string.Format("", + giftCardText, remainingText, giftCardAmount)); } //reward points @@ -914,14 +919,18 @@ public virtual void AddReturnRequestTokens(IList tokens, ReturnRequest re public virtual void AddGiftCardTokens(IList tokens, GiftCard giftCard) { - tokens.Add(new Token("GiftCard.SenderName", giftCard.SenderName)); + var order = giftCard.PurchasedWithOrderItem.Order; + var remainingAmount = _currencyService.ConvertCurrency(giftCard.GetGiftCardRemainingAmount(), order.CurrencyRate); + + tokens.Add(new Token("GiftCard.SenderName", giftCard.SenderName)); tokens.Add(new Token("GiftCard.SenderEmail", giftCard.SenderEmail)); tokens.Add(new Token("GiftCard.RecipientName", giftCard.RecipientName)); tokens.Add(new Token("GiftCard.RecipientEmail", giftCard.RecipientEmail)); tokens.Add(new Token("GiftCard.Amount", _priceFormatter.FormatPrice(giftCard.Amount, true, false))); - tokens.Add(new Token("GiftCard.CouponCode", giftCard.GiftCardCouponCode)); + tokens.Add(new Token("GiftCard.RemainingAmount", _priceFormatter.FormatPrice(remainingAmount, true, false))); + tokens.Add(new Token("GiftCard.CouponCode", giftCard.GiftCardCouponCode)); - var giftCardMesage = !String.IsNullOrWhiteSpace(giftCard.Message) ? + var giftCardMesage = !String.IsNullOrWhiteSpace(giftCard.Message) ? HtmlUtils.FormatText(giftCard.Message, false, true, false, false, false, false) : ""; tokens.Add(new Token("GiftCard.Message", giftCardMesage, true)); @@ -1177,8 +1186,9 @@ public virtual string[] GetListOfAllowedTokens() "%GiftCard.SenderEmail%", "%GiftCard.RecipientName%", "%GiftCard.RecipientEmail%", - "%GiftCard.Amount%", - "%GiftCard.CouponCode%", + "%GiftCard.Amount%", + "%GiftCard.RemainingAmount%", + "%GiftCard.CouponCode%", "%GiftCard.Message%", "%Customer.Email%", "%Customer.Username%", diff --git a/src/Presentation/SmartStore.Web/Controllers/OrderController.cs b/src/Presentation/SmartStore.Web/Controllers/OrderController.cs index b3acce902b..bf9e11078e 100644 --- a/src/Presentation/SmartStore.Web/Controllers/OrderController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/OrderController.cs @@ -291,12 +291,18 @@ protected OrderDetailsModel PrepareOrderDetailsModel(Order order) //gift cards foreach (var gcuh in order.GiftCardUsageHistory) { - model.GiftCards.Add(new OrderDetailsModel.GiftCard - { - CouponCode = gcuh.GiftCard.GiftCardCouponCode, + var remainingAmountBase = gcuh.GiftCard.GetGiftCardRemainingAmount(); + var remainingAmount = _currencyService.ConvertCurrency(remainingAmountBase, order.CurrencyRate); + + var gcModel = new OrderDetailsModel.GiftCard + { + CouponCode = gcuh.GiftCard.GiftCardCouponCode, Amount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(gcuh.UsedValue, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, language), - }); - } + Remaining = _priceFormatter.FormatPrice(remainingAmount, true, false) + }; + + model.GiftCards.Add(gcModel); + } //reward points if (order.RedeemedRewardPointsEntry != null) diff --git a/src/Presentation/SmartStore.Web/Models/Order/OrderDetailsModel.cs b/src/Presentation/SmartStore.Web/Models/Order/OrderDetailsModel.cs index a5738aa264..c4b4773f3a 100644 --- a/src/Presentation/SmartStore.Web/Models/Order/OrderDetailsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Order/OrderDetailsModel.cs @@ -126,7 +126,8 @@ public partial class GiftCard : ModelBase { public string CouponCode { get; set; } public string Amount { get; set; } - } + public string Remaining { get; set; } + } public partial class OrderNote : ModelBase { diff --git a/src/Presentation/SmartStore.Web/Views/Order/Details.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Order/Details.Mobile.cshtml index 80249d7776..289fb9fc47 100644 --- a/src/Presentation/SmartStore.Web/Views/Order/Details.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Order/Details.Mobile.cshtml @@ -439,8 +439,9 @@ { - } - } + } + } @if (Model.RedeemedRewardPoints > 0) { From 9eb131beff8fbcb8e32d8d0a57c943afa26e3de7 Mon Sep 17 00:00:00 2001 From: James Bright Date: Thu, 25 Feb 2016 16:46:20 -0500 Subject: [PATCH 068/423] USPS encoding issue fix for UI --- .../SmartStore.Web/Views/Checkout/ShippingMethod.Mobile.cshtml | 2 +- .../SmartStore.Web/Views/Checkout/ShippingMethod.cshtml | 2 +- .../SmartStore.Web/Views/ShoppingCart/EstimateShipping.cshtml | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/ShippingMethod.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/ShippingMethod.Mobile.cshtml index 6674bafc8e..07fc9cbf98 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/ShippingMethod.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/ShippingMethod.Mobile.cshtml @@ -27,7 +27,7 @@
              - +
              @if (!String.IsNullOrEmpty(shippingMethod.Description)) { diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/ShippingMethod.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/ShippingMethod.cshtml index 54c1a51dce..68d6091639 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/ShippingMethod.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/ShippingMethod.cshtml @@ -42,7 +42,7 @@ - @shippingMethod.Name + @Html.Raw(HttpUtility.HtmlDecode(shippingMethod.Name))
              diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/EstimateShipping.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/EstimateShipping.cshtml index b7840d8c70..d59e914960 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/EstimateShipping.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/EstimateShipping.cshtml @@ -88,7 +88,8 @@ {
              - @shippingOption.Name (@shippingOption.Price) + @Html.Raw(HttpUtility.HtmlDecode(shippingOption.Name)) + (@shippingOption.Price)
              @Html.Raw(shippingOption.Description) From 2c9cd0cece81cbffe0bb487e55c39b588fd2def4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 26 Feb 2016 14:51:21 +0100 Subject: [PATCH 069/423] Resolves #695 Implement checkbox in checkout to let customers subscribe to newsletters --- changelog.md | 1 + .../Orders/CheckoutNewsLetterSubscription.cs | 23 ++++ .../Domain/Orders/ShoppingCartSettings.cs | 5 + .../SmartStore.Core/SmartStore.Core.csproj | 1 + .../201601262000441_ImportFramework1.cs | 28 +++++ .../INewsLetterSubscriptionService.cs | 21 +++- .../Messages/NewsLetterSubscriptionService.cs | 53 +++++++-- .../Orders/OrderProcessingService.cs | 108 +++++++++++------- .../Controllers/SettingController.cs | 5 +- .../Infrastructure/AutoMapperStartupTask.cs | 3 +- .../Settings/ShoppingCartSettingsModel.cs | 18 ++- .../Views/Setting/ShoppingCart.cshtml | 12 +- .../Controllers/CheckoutController.cs | 3 +- .../Controllers/CustomerController.cs | 40 ++----- .../Controllers/ShoppingCartController.cs | 1 + .../Models/ShoppingCart/ShoppingCartModel.cs | 4 + .../Views/Checkout/Confirm.cshtml | 6 +- .../Views/ShoppingCart/OrderSummary.cshtml | 21 +++- .../Orders/OrderProcessingServiceTests.cs | 7 +- 19 files changed, 254 insertions(+), 106 deletions(-) create mode 100644 src/Libraries/SmartStore.Core/Domain/Orders/CheckoutNewsLetterSubscription.cs diff --git a/changelog.md b/changelog.md index 9cfc13f194..4085ebbfc9 100644 --- a/changelog.md +++ b/changelog.md @@ -49,6 +49,7 @@ * #738 Implement download of pictures via URLs in product import * Web-API: Bridge to import framework: uploading import files to import profile directory * Setting to round down calculated reward points +* #695 Implement checkbox in checkout to let customers subscribe to newsletters ### 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/CheckoutNewsLetterSubscription.cs b/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutNewsLetterSubscription.cs new file mode 100644 index 0000000000..8f96749c9c --- /dev/null +++ b/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutNewsLetterSubscription.cs @@ -0,0 +1,23 @@ +namespace SmartStore.Core.Domain.Orders +{ + /// + /// Setting for newsletter subscription in checkout + /// + public enum CheckoutNewsLetterSubscription + { + /// + /// No newsletter subscription checkbox + /// + None = 0, + + /// + /// Deactivated newsletter subscription checkbox + /// + Deactivated, + + /// + /// Activated newsletter subscription checkbox + /// + Activated + } +} diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs b/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs index e64000ea1b..16a8c367fd 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/ShoppingCartSettings.cs @@ -104,6 +104,11 @@ public ShoppingCartSettings() /// public bool ShowEsdRevocationWaiverBox { get; set; } + /// + /// Gets or sets a value indicating whether to show a checkbox to subscribe to newsletters + /// + public CheckoutNewsLetterSubscription NewsLetterSubscription { get; set; } + /// /// Gets or sets a number of "Cross-sells" on shopping cart page /// diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index 200def62df..4fba731067 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -181,6 +181,7 @@ + diff --git a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs index 0ebc7a469c..e633a7e487 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201601262000441_ImportFramework1.cs @@ -421,6 +421,34 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Punkte abrunden", "Specifies whether to round down calculated points. Otherwise the bonus points will be rounded up.", "Legt fest, ob bei der Punkteberechnung abgerundet werden soll. Ansonsten werden Bonuspunkte aufgerundet."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Order.GiftCards_Deactivated", + "Gift card deactivation order status", + "Geschenkgutschein wird deaktiviert, wenn Auftragsstatus..."); + + builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.NewsLetterSubscription", + "Subscribe to newsletters", + "Abonnieren von Newslettern", + "Specifies id customers can subscribe to newsletters when ordering and if the checkbox is enabled by default.", + "Legt fest, ob Kunden bei einer Bestellung Newsletter abonnieren knnen und ob die Checkbox standardmig aktiviert ist."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Orders.CheckoutNewsLetterSubscription.None", "Do not show", "Nicht anzeigen"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Orders.CheckoutNewsLetterSubscription.Deactivated", "Show deactivated", "Deaktiviert anzeigen"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Orders.CheckoutNewsLetterSubscription.Activated", "Show activated", "Aktiviert anzeigen"); + + builder.AddOrUpdate("Common.Options", "Options", "Optionen"); + + builder.AddOrUpdate("Checkout.SubscribeToNewsLetter", + "Subscribed to newsletter", + "Newsletter abonnieren"); + + builder.AddOrUpdate("Admin.OrderNotice.NewsLetterSubscriptionAdded", + "Subscribed to newsletter", + "Newsletter wurde abonniert"); + + builder.AddOrUpdate("Admin.OrderNotice.NewsLetterSubscriptionRemoved", + "Newsletter subscriber has been removed", + "Newsletter-Abonnent wurde entfernt"); } } } diff --git a/src/Libraries/SmartStore.Services/Messages/INewsLetterSubscriptionService.cs b/src/Libraries/SmartStore.Services/Messages/INewsLetterSubscriptionService.cs index 49a6b4d8a8..a579ff6859 100644 --- a/src/Libraries/SmartStore.Services/Messages/INewsLetterSubscriptionService.cs +++ b/src/Libraries/SmartStore.Services/Messages/INewsLetterSubscriptionService.cs @@ -27,12 +27,21 @@ public partial interface INewsLetterSubscriptionService /// if set to true [publish subscription events]. void DeleteNewsLetterSubscription(NewsLetterSubscription newsLetterSubscription, bool publishSubscriptionEvents = true); - /// - /// Gets a newsletter subscription by newsletter subscription identifier - /// - /// The newsletter subscription identifier - /// NewsLetter subscription - NewsLetterSubscription GetNewsLetterSubscriptionById(int newsLetterSubscriptionId); + /// + /// Adds or deletes a newsletter subscription + /// + /// true add subscription, false delete + /// Email address + /// Store identifier + /// true added subscription, false removed subscription, null did nothing + bool? AddNewsLetterSubscriptionFor(bool add, string email, int storeId); + + /// + /// Gets a newsletter subscription by newsletter subscription identifier + /// + /// The newsletter subscription identifier + /// NewsLetter subscription + NewsLetterSubscription GetNewsLetterSubscriptionById(int newsLetterSubscriptionId); /// /// Gets a newsletter subscription by newsletter subscription GUID diff --git a/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs b/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs index fe238a9530..dde1d076c9 100644 --- a/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs +++ b/src/Libraries/SmartStore.Services/Messages/NewsLetterSubscriptionService.cs @@ -7,7 +7,6 @@ namespace SmartStore.Services.Messages { - public class NewsLetterSubscriptionService : INewsLetterSubscriptionService { private readonly IEventPublisher _eventPublisher; @@ -126,12 +125,52 @@ public virtual void DeleteNewsLetterSubscription(NewsLetterSubscription newsLett _eventPublisher.EntityDeleted(newsLetterSubscription); } - /// - /// Gets a newsletter subscription by newsletter subscription identifier - /// - /// The newsletter subscription identifier - /// NewsLetter subscription - public virtual NewsLetterSubscription GetNewsLetterSubscriptionById(int newsLetterSubscriptionId) + public virtual bool? AddNewsLetterSubscriptionFor(bool add, string email, int storeId) + { + bool? result = null; + + if (email.IsEmail()) + { + var newsletter = GetNewsLetterSubscriptionByEmail(email, storeId); + if (newsletter != null) + { + if (add) + { + newsletter.Active = true; + UpdateNewsLetterSubscription(newsletter); + result = true; + } + else + { + DeleteNewsLetterSubscription(newsletter); + result = false; + } + } + else + { + if (add) + { + InsertNewsLetterSubscription(new NewsLetterSubscription + { + NewsLetterSubscriptionGuid = Guid.NewGuid(), + Email = email, + Active = true, + CreatedOnUtc = DateTime.UtcNow, + StoreId = storeId + }); + result = true; + } + } + } + return result; + } + + /// + /// Gets a newsletter subscription by newsletter subscription identifier + /// + /// The newsletter subscription identifier + /// NewsLetter subscription + public virtual NewsLetterSubscription GetNewsLetterSubscriptionById(int newsLetterSubscriptionId) { if (newsLetterSubscriptionId == 0) return null; diff --git a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs index e298174d3c..c5cdb7b58a 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs @@ -69,8 +69,9 @@ public partial class OrderProcessingService : IOrderProcessingService private readonly IAffiliateService _affiliateService; private readonly IEventPublisher _eventPublisher; private readonly IGenericAttributeService _genericAttributeService; + private readonly INewsLetterSubscriptionService _newsLetterSubscriptionService; - private readonly PaymentSettings _paymentSettings; + private readonly PaymentSettings _paymentSettings; private readonly RewardPointsSettings _rewardPointsSettings; private readonly OrderSettings _orderSettings; private readonly TaxSettings _taxSettings; @@ -78,48 +79,48 @@ public partial class OrderProcessingService : IOrderProcessingService private readonly CurrencySettings _currencySettings; private readonly ShoppingCartSettings _shoppingCartSettings; - #endregion + #endregion - #region Ctor + #region Ctor - /// - /// Ctor - /// - /// Order service - /// Web helper - /// Localization service - /// Language service - /// Product service - /// Payment service - /// Logger - /// Order total calculationservice - /// Price calculation service - /// Price formatter - /// Product attribute parser - /// Product attribute formatter - /// Gift card service - /// Shopping cart service - /// Checkout attribute service - /// Shipping service - /// Shipment service - /// Tax service - /// Customer service - /// Discount service - /// Encryption service - /// Work context + /// + /// Ctor + /// + /// Order service + /// Web helper + /// Localization service + /// Language service + /// Product service + /// Payment service + /// Logger + /// Order total calculationservice + /// Price calculation service + /// Price formatter + /// Product attribute parser + /// Product attribute formatter + /// Gift card service + /// Shopping cart service + /// Checkout attribute service + /// Shipping service + /// Shipment service + /// Tax service + /// Customer service + /// Discount service + /// Encryption service + /// Work context /// Store context - /// Workflow message service - /// Customer activity service - /// Currency service + /// Workflow message service + /// Customer activity service + /// Currency service /// Affiliate service - /// Event published - /// Payment settings - /// Reward points settings - /// Order settings - /// Tax settings - /// Localization settings - /// Currency settings - public OrderProcessingService(IOrderService orderService, + /// Event published + /// Payment settings + /// Reward points settings + /// Order settings + /// Tax settings + /// Localization settings + /// Currency settings + public OrderProcessingService(IOrderService orderService, IWebHelper webHelper, ILocalizationService localizationService, ILanguageService languageService, @@ -148,7 +149,8 @@ public OrderProcessingService(IOrderService orderService, IAffiliateService affiliateService, IEventPublisher eventPublisher, IGenericAttributeService genericAttributeService, - PaymentSettings paymentSettings, + INewsLetterSubscriptionService newsLetterSubscriptionService, + PaymentSettings paymentSettings, RewardPointsSettings rewardPointsSettings, OrderSettings orderSettings, TaxSettings taxSettings, @@ -185,6 +187,7 @@ public OrderProcessingService(IOrderService orderService, this._affiliateService = affiliateService; this._eventPublisher = eventPublisher; this._genericAttributeService = genericAttributeService; + this._newsLetterSubscriptionService = newsLetterSubscriptionService; this._paymentSettings = paymentSettings; this._rewardPointsSettings = rewardPointsSettings; this._orderSettings = orderSettings; @@ -1379,9 +1382,28 @@ public virtual PlaceOrderResult PlaceOrder(ProcessPaymentRequest processPaymentR _eventPublisher.PublishOrderPaid(order); } - #endregion - } - } + #endregion + + #region Newsletter subscription + + if (extraData.ContainsKey("SubscribeToNewsLetter") && _shoppingCartSettings.NewsLetterSubscription != CheckoutNewsLetterSubscription.None) + { + var addSubscription = extraData["SubscribeToNewsLetter"].ToBool(); + + bool? nsResult = _newsLetterSubscriptionService.AddNewsLetterSubscriptionFor(addSubscription, customer.Email, order.StoreId); + + if (nsResult.HasValue) + { + if (nsResult.Value) + _orderService.AddOrderNote(order, T("Admin.OrderNotice.NewsLetterSubscriptionAdded")); + else + _orderService.AddOrderNote(order, T("Admin.OrderNotice.NewsLetterSubscriptionRemoved")); + } + } + + #endregion + } + } else { result.AddError(T("Payment.PayingFailed")); diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs index 990d47495a..44545f4f50 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs @@ -796,13 +796,14 @@ public ActionResult ShoppingCart() if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageSettings)) return AccessDeniedView(); - //load settings for a chosen store scope var storeScope = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext); var shoppingCartSettings = _services.Settings.LoadSetting(storeScope); + var model = shoppingCartSettings.ToModel(); - StoreDependingSettings.GetOverrideKeys(shoppingCartSettings, model, storeScope, _services.Settings); + model.AvailableNewsLetterSubscription = shoppingCartSettings.NewsLetterSubscription.ToSelectList(); + StoreDependingSettings.GetOverrideKeys(shoppingCartSettings, model, storeScope, _services.Settings); return View(model); } diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs index 1d1a7130e8..fee7b88343 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs @@ -651,7 +651,8 @@ public void Execute() Mapper.CreateMap() .ForMember(dest => dest.MinimumOrderPlacementInterval, mo => mo.Ignore()) .ForMember(dest => dest.Id, mo => mo.Ignore()); - Mapper.CreateMap(); + Mapper.CreateMap() + .ForMember(dest => dest.AvailableNewsLetterSubscription, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.MoveItemsFromWishlistToCart, mo => mo.Ignore()) .ForMember(dest => dest.ShowItemsFromWishlistToCartButton, mo => mo.Ignore()); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ShoppingCartSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ShoppingCartSettingsModel.cs index df20029b62..70b9353888 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/ShoppingCartSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/ShoppingCartSettingsModel.cs @@ -1,4 +1,7 @@ -using SmartStore.Web.Framework; +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Core.Domain.Orders; +using SmartStore.Web.Framework; namespace SmartStore.Admin.Models.Settings { @@ -40,7 +43,10 @@ public class ShoppingCartSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.CrossSellsNumber")] public int CrossSellsNumber { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.EmailWishlistEnabled")] + [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.RoundPricesDuringCalculation")] + public bool RoundPricesDuringCalculation { get; set; } + + [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.EmailWishlistEnabled")] public bool EmailWishlistEnabled { get; set; } [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.AllowAnonymousUsersToEmailWishlist")] @@ -79,8 +85,8 @@ public class ShoppingCartSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.ShowEsdRevocationWaiverBox")] public bool ShowEsdRevocationWaiverBox { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.RoundPricesDuringCalculation")] - public bool RoundPricesDuringCalculation { get; set; } - - } + [SmartResourceDisplayName("Admin.Configuration.Settings.ShoppingCart.NewsLetterSubscription")] + public CheckoutNewsLetterSubscription NewsLetterSubscription { get; set; } + public SelectList AvailableNewsLetterSubscription { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml index 6ee39a782a..e58201b20e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/ShoppingCart.cshtml @@ -51,8 +51,8 @@ @(Html.SmartStore().TabStrip().Name("catalogsettings-edit").Items(x => { x.Add().Text(T("Admin.Configuration.Settings.ShoppingCart.CartSettings").Text).Content(@TabCartSettings()).Selected(true); - x.Add().Text(T("Admin.Configuration.Settings.ShoppingCart.WishlistSettings").Text).Content(@TabWishlistSettings()); x.Add().Text(T("Admin.Configuration.Settings.ShoppingCart.Checkout").Text).Content(@TabCheckoutSettings()); + x.Add().Text(T("Admin.Configuration.Settings.ShoppingCart.WishlistSettings").Text).Content(@TabWishlistSettings()); })) } @@ -308,6 +308,16 @@ @Html.ValidationMessageFor(model => model.ShowCommentBox)
              + + + +
              +
              +
              @T("Admin.Orders.OrderNotes.AddTitle")
              +
              +
              @Html.SmartLabelFor(model => model.AddOrderNoteMessage) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Pictures.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Pictures.cshtml index 7c08977c2b..a8e66f1e74 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Pictures.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Pictures.cshtml @@ -38,9 +38,7 @@ }) .EnableCustomBinding(true)) -

              -

              @T("Admin.Catalog.Products.Pictures.AddNew")

              -

              + + + + + - + +
              +
              +
              @T("Admin.Catalog.Products.Pictures.AddNew")
              +
              +
              @Html.SmartLabelFor(model => model.AddPictureModel.PictureId) @@ -92,12 +98,15 @@
              - - +   + + +
              } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SpecificationAttributes.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SpecificationAttributes.cshtml index fd1512ae37..87982efc85 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SpecificationAttributes.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.SpecificationAttributes.cshtml @@ -1,7 +1,5 @@ -@model ProductModel - -@using Telerik.Web.Mvc.UI; - +@using Telerik.Web.Mvc.UI; +@model ProductModel @{ Layout = ""; } @@ -11,9 +9,6 @@ /*hide "add spec" table if no attributes are defined*/ if (Model.AddSpecificationAttributeModel.AvailableAttributes.Count > 0) { -
              -

              @T("Admin.Catalog.Products.SpecificationAttributes.AddNew")

              -
              - + +
              + + + - - + +
              +
              +
              @T("Admin.Catalog.Products.SpecificationAttributes.AddNew")
              +
              +
              @Html.SmartLabelFor(model => model.AddSpecificationAttributeModel.SpecificationAttributeId) @@ -112,13 +115,15 @@
              - - +   + + +
              From 02ac0a796d5c469fd1a778d6b14da635ff2b788d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 25 Feb 2016 19:54:20 +0100 Subject: [PATCH 066/423] Minor refactoring, #482 related --- .../Orders/OrderTotalCalculationService.cs | 16 +++++++++++--- .../Payments/PaymentExtentions.cs | 21 +------------------ .../Payments/PaymentService.cs | 8 ++++++- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs b/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs index 2fa180e938..e154dd519d 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderTotalCalculationService.cs @@ -810,7 +810,12 @@ public virtual decimal GetTaxTotal(IList cart, out So decimal taxRate = decimal.Zero; var provider = _providerManager.GetProvider(paymentMethodSystemName); - decimal paymentMethodAdditionalFee = provider.GetAdditionalHandlingFee(cart, _shoppingCartSettings.RoundPricesDuringCalculation); + var paymentMethodAdditionalFee = (provider != null ? provider.Value.GetAdditionalHandlingFee(cart) : decimal.Zero); + + if (_shoppingCartSettings.RoundPricesDuringCalculation) + { + paymentMethodAdditionalFee = Math.Round(paymentMethodAdditionalFee, 2); + } decimal paymentMethodAdditionalFeeExclTax = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, false, customer, out taxRate); decimal paymentMethodAdditionalFeeInclTax = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, true, customer, out taxRate); @@ -917,9 +922,14 @@ public virtual decimal GetTaxTotal(IList cart, out So if (usePaymentMethodAdditionalFee && !String.IsNullOrEmpty(paymentMethodSystemName)) { var provider = _providerManager.GetProvider(paymentMethodSystemName); - decimal paymentMethodAdditionalFee = provider.GetAdditionalHandlingFee(cart, _shoppingCartSettings.RoundPricesDuringCalculation); + var paymentMethodAdditionalFee = (provider != null ? provider.Value.GetAdditionalHandlingFee(cart) : decimal.Zero); + + if (_shoppingCartSettings.RoundPricesDuringCalculation) + { + paymentMethodAdditionalFee = Math.Round(paymentMethodAdditionalFee, 2); + } - paymentMethodAdditionalFeeWithoutTax = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, false, customer); + paymentMethodAdditionalFeeWithoutTax = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, false, customer); } //tax diff --git a/src/Libraries/SmartStore.Services/Payments/PaymentExtentions.cs b/src/Libraries/SmartStore.Services/Payments/PaymentExtentions.cs index 3ba3d4c3d3..d9cd258259 100644 --- a/src/Libraries/SmartStore.Services/Payments/PaymentExtentions.cs +++ b/src/Libraries/SmartStore.Services/Payments/PaymentExtentions.cs @@ -9,7 +9,7 @@ namespace SmartStore.Services.Payments { - public static class PaymentExtentions + public static class PaymentExtentions { /// /// Is payment method active? @@ -34,25 +34,6 @@ public static bool IsPaymentMethodActive(this Provider paymentMe return paymentSettings.ActivePaymentMethodSystemNames.Contains(paymentMethod.Metadata.SystemName, StringComparer.OrdinalIgnoreCase); } - /// - /// Gets an additional handling fee of a payment method - /// - /// Shoping cart - /// Whether to round the fee - /// Additional handling fee - public static decimal GetAdditionalHandlingFee(this Provider paymentMethod, IList cart, bool round) - { - var result = decimal.Zero; - if (paymentMethod != null) - { - result = paymentMethod.Value.GetAdditionalHandlingFee(cart); - - if (round) - result = Math.Round(result, 2); - } - return result; - } - /// /// Calculate payment method fee /// diff --git a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs index 5d82085595..0bce57bb5e 100644 --- a/src/Libraries/SmartStore.Services/Payments/PaymentService.cs +++ b/src/Libraries/SmartStore.Services/Payments/PaymentService.cs @@ -357,8 +357,14 @@ public virtual bool CanRePostProcessPayment(Order order) public virtual decimal GetAdditionalHandlingFee(IList cart, string paymentMethodSystemName) { var paymentMethod = LoadPaymentMethodBySystemName(paymentMethodSystemName); + var paymentMethodAdditionalFee = (paymentMethod != null ? paymentMethod.Value.GetAdditionalHandlingFee(cart) : decimal.Zero); - return paymentMethod.GetAdditionalHandlingFee(cart, _shoppingCartSettings.RoundPricesDuringCalculation); + if (_shoppingCartSettings.RoundPricesDuringCalculation) + { + paymentMethodAdditionalFee = Math.Round(paymentMethodAdditionalFee, 2); + } + + return paymentMethodAdditionalFee; } From 6ec37426eac94c5bd3d03e28f3ff8635df7ee2af Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 25 Feb 2016 21:48:05 +0100 Subject: [PATCH 067/423] Resolves #713 Display gift card remaining amount in frontend order details and order messages --- changelog.md | 1 + .../Messages/MessageTokenProvider.cs | 22 ++++++++++++++----- .../Controllers/OrderController.cs | 16 +++++++++----- .../Models/Order/OrderDetailsModel.cs | 3 ++- .../Views/Order/Details.Mobile.cshtml | 5 +++-- .../SmartStore.Web/Views/Order/Details.cshtml | 9 ++++---- 6 files changed, 38 insertions(+), 18 deletions(-) diff --git a/changelog.md b/changelog.md index 9683e94137..9cfc13f194 100644 --- a/changelog.md +++ b/changelog.md @@ -91,6 +91,7 @@ * Product filter: Specification attributes are sorted by display order rather than alphabetically by name * #856 Don't route topics which are excluded from sitemap * #851 Replace reCAPTCHA with "I'm not a robot" CAPTCHA +* #713 Display gift card remaining amount in frontend order details and order messages ### Bugfixes * #523 Redirecting to payment provider performed by core instead of plugin diff --git a/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs b/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs index 7b0e2c6f8d..6cb58a46e2 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs @@ -507,7 +507,12 @@ protected virtual string ProductListToHtmlTable(Order order, Language language) string giftCardText = String.Format(_localizationService.GetResource("Messages.Order.GiftCardInfo", language.Id), HttpUtility.HtmlEncode(gcuh.GiftCard.GiftCardCouponCode)); string giftCardAmount = _priceFormatter.FormatPrice(-(_currencyService.ConvertCurrency(gcuh.UsedValue, order.CurrencyRate)), true, order.CustomerCurrencyCode, false, language); - sb.AppendLine(string.Format("
               {0} {1}
               {0}
              {1}
              {2}
              - - @string.Format(T("Order.GiftCardInfo").Text, gc.CouponCode): + @string.Format(T("Order.GiftCardInfo").Text, gc.CouponCode): +
              + @string.Format(T("ShoppingCart.Totals.GiftCardInfo.Remaining").Text, gc.Remaining)
              diff --git a/src/Presentation/SmartStore.Web/Views/Order/Details.cshtml b/src/Presentation/SmartStore.Web/Views/Order/Details.cshtml index 8e56cfc5a6..5a4fbfb5c6 100644 --- a/src/Presentation/SmartStore.Web/Views/Order/Details.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Order/Details.cshtml @@ -532,8 +532,9 @@ {
              - - @string.Format(T("Order.GiftCardInfo").Text, gc.CouponCode): + @string.Format(T("Order.GiftCardInfo").Text, gc.CouponCode): +
              + @string.Format(T("ShoppingCart.Totals.GiftCardInfo.Remaining").Text, gc.Remaining)
              @@ -541,8 +542,8 @@
              + @Html.SmartLabelFor(model => model.NewsLetterSubscription) + + @Html.SettingOverrideCheckbox(model => Model.NewsLetterSubscription) + @Html.DropDownListFor(model => model.NewsLetterSubscription, Model.AvailableNewsLetterSubscription) + @Html.ValidationMessageFor(model => model.NewsLetterSubscription) +
              @Html.SmartLabelFor(model => model.ShowConfirmOrderLegalHint) diff --git a/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs b/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs index cef4f0de15..1bca0033b8 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CheckoutController.cs @@ -831,8 +831,9 @@ public ActionResult ConfirmOrder(FormCollection form) var placeOrderExtraData = new Dictionary(); placeOrderExtraData["CustomerComment"] = form["customercommenthidden"]; + placeOrderExtraData["SubscribeToNewsLetter"] = form["SubscribeToNewsLetterHidden"]; - var placeOrderResult = _orderProcessingService.PlaceOrder(processPaymentRequest, placeOrderExtraData); + var placeOrderResult = _orderProcessingService.PlaceOrder(processPaymentRequest, placeOrderExtraData); if (placeOrderResult.Success) { diff --git a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs index 104d5a4393..cd0f271796 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs @@ -979,8 +979,7 @@ public ActionResult Info(CustomerInfoModel model) if (ModelState.IsValid) { //username - if (_customerSettings.UsernamesEnabled && - this._customerSettings.AllowUsersToChangeUsernames) + if (_customerSettings.UsernamesEnabled && _customerSettings.AllowUsersToChangeUsernames) { if (!customer.Username.Equals(model.Username.Trim(), StringComparison.InvariantCultureIgnoreCase)) { @@ -1040,7 +1039,7 @@ public ActionResult Info(CustomerInfoModel model) if (model.CustomerNumber != currentCustomerNumber && customerNumbers.Where(x => x.Value == model.CustomerNumber).Any()) { - this.NotifyError("Common.CustomerNumberAlreadyExists"); + NotifyError("Common.CustomerNumberAlreadyExists"); } else { @@ -1083,38 +1082,13 @@ public ActionResult Info(CustomerInfoModel model) //newsletter if (_customerSettings.NewsletterEnabled) { - //save newsletter value - var newsletter = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(customer.Email, _storeContext.CurrentStore.Id); - if (newsletter != null) - { - if (model.Newsletter) - { - newsletter.Active = true; - _newsLetterSubscriptionService.UpdateNewsLetterSubscription(newsletter); - } - else - { - _newsLetterSubscriptionService.DeleteNewsLetterSubscription(newsletter); - } - } - else - { - if (model.Newsletter) - { - _newsLetterSubscriptionService.InsertNewsLetterSubscription(new NewsLetterSubscription() - { - NewsLetterSubscriptionGuid = Guid.NewGuid(), - Email = customer.Email, - Active = true, - CreatedOnUtc = DateTime.UtcNow, - StoreId = _storeContext.CurrentStore.Id - }); - } - } + _newsLetterSubscriptionService.AddNewsLetterSubscriptionFor(model.Newsletter, customer.Email, _storeContext.CurrentStore.Id); } - if (_forumSettings.ForumsEnabled && _forumSettings.SignaturesEnabled) - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Signature, model.Signature); + if (_forumSettings.ForumsEnabled && _forumSettings.SignaturesEnabled) + { + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Signature, model.Signature); + } return RedirectToAction("Info"); } diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index 17bd2f0a1c..d593c6c742 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -665,6 +665,7 @@ protected void PrepareShoppingCartModel(ShoppingCartModel model, model.GiftCardBox.Display = _shoppingCartSettings.ShowGiftCardBox; model.DisplayCommentBox = _shoppingCartSettings.ShowCommentBox; + model.NewsLetterSubscription = _shoppingCartSettings.NewsLetterSubscription; model.DisplayEsdRevocationWaiverBox = _shoppingCartSettings.ShowEsdRevocationWaiverBox; //cart warnings diff --git a/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs b/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs index 6a38af28ac..48d054e2e0 100644 --- a/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs +++ b/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using SmartStore.Core.Domain.Catalog; +using SmartStore.Core.Domain.Orders; using SmartStore.Web.Framework.Modelling; using SmartStore.Web.Models.Common; using SmartStore.Web.Models.Media; @@ -51,6 +52,9 @@ public ShoppingCartModel() public bool DisplayCommentBox { get; set; } public string CustomerComment { get; set; } + public CheckoutNewsLetterSubscription NewsLetterSubscription { get; set; } + public bool? SubscribeToNewsLetter { get; set; } + public bool DisplayEsdRevocationWaiverBox { get; set; } #region Nested Classes diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml index 82c4444f62..0f369476d6 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml @@ -28,9 +28,10 @@ + - if (Model.TermsOfServiceEnabled) - { + if (Model.TermsOfServiceEnabled) + {
              + + @Html.Widget("order_edit_top") +
              diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/BillingAddress.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/BillingAddress.cshtml index cf32007b54..2e011c985f 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/BillingAddress.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/BillingAddress.cshtml @@ -30,7 +30,7 @@
              -
              From fe72ee42b569d427c01a2e29acf8c96e0c8f00a1 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 29 Feb 2016 19:06:21 +0100 Subject: [PATCH 078/423] Eliminated merge conflict generated text --- .../Orders/OrderProcessingService.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs index 5b068d152f..f86c2ce499 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs @@ -554,16 +554,11 @@ public virtual PlaceOrderResult PlaceOrder( IList cart = null; if (!processPaymentRequest.IsRecurringPayment) { -<<<<<<< HEAD - // load shopping cart - cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, processPaymentRequest.StoreId); -======= //load shopping cart if (processPaymentRequest.ShoppingCartItems.Count > 0) cart = processPaymentRequest.ShoppingCartItems; else cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, processPaymentRequest.StoreId); ->>>>>>> Minor enhancement if (cart.Count == 0) throw new SmartException(T("ShoppingCart.CartIsEmpty")); @@ -1365,13 +1360,8 @@ public virtual PlaceOrderResult PlaceOrder( // check order status CheckOrderStatus(order); -<<<<<<< HEAD - // reset checkout data - if (!processPaymentRequest.IsRecurringPayment) -======= //reset checkout data if (!processPaymentRequest.IsRecurringPayment && !processPaymentRequest.IsMultiOrder) ->>>>>>> Minor enhancement { _customerService.ResetCheckoutData(customer, processPaymentRequest.StoreId, clearCouponCodes: true, clearCheckoutAttributes: true); } From 76f7dc49667aae48e291eb6a370ba639f56a3391 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 29 Feb 2016 21:04:34 +0100 Subject: [PATCH 079/423] Fixed: Attribute with a product linkage throws exception if added to cart ("There is already an open DataReader associated with this Connection which must be closed first") --- changelog.md | 1 + .../SmartStore.Services/Catalog/PriceCalculationService.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 1ac8c1f4d0..bb3b86b4db 100644 --- a/changelog.md +++ b/changelog.md @@ -132,6 +132,7 @@ * PayPal Express: Void and refund out of function ("The transaction id is not valid") * Customer could not delete his avatar * Facebook authentication: Email missing in verification +* Attribute with a product linkage throws exception if added to cart ## SmartStore.NET 2.2.2 diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs index 1ce3b347ce..7a06368334 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs @@ -756,9 +756,9 @@ public virtual decimal GetUnitPrice(OrganizedShoppingCartItem shoppingCartItem, { product.MergeWithCombination(shoppingCartItem.Item.AttributesXml, _productAttributeParser); - decimal attributesTotalPrice = decimal.Zero; + var attributesTotalPrice = decimal.Zero; - var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml); + var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml).ToList(); if (pvaValues != null) { From fc07a92be7d2f6a39a0356d953c153cae9a71284 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Mon, 29 Feb 2016 22:32:54 +0100 Subject: [PATCH 080/423] Increased Version --- SmartStoreNET.Tasks.Targets | 2 +- src/AssemblySharedInfo.cs | 2 +- src/AssemblyVersionInfo.cs | 6 +++--- src/Libraries/SmartStore.Core/SmartStoreVersion.cs | 3 ++- src/Plugins/SmartStore.AmazonPay/Description.txt | 4 ++-- src/Plugins/SmartStore.Clickatell/Description.txt | 4 ++-- src/Plugins/SmartStore.DevTools/Description.txt | 4 ++-- src/Plugins/SmartStore.DiscountRules/Description.txt | 4 ++-- src/Plugins/SmartStore.FacebookAuth/Description.txt | 4 ++-- src/Plugins/SmartStore.GoogleAnalytics/Description.txt | 4 ++-- src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt | 4 ++-- src/Plugins/SmartStore.OfflinePayment/Description.txt | 4 ++-- src/Plugins/SmartStore.PayPal/Description.txt | 4 ++-- src/Plugins/SmartStore.Shipping/Description.txt | 4 ++-- src/Plugins/SmartStore.ShippingByWeight/Description.txt | 4 ++-- src/Plugins/SmartStore.Tax/Description.txt | 4 ++-- src/Plugins/SmartStore.WebApi/Description.txt | 4 ++-- 17 files changed, 33 insertions(+), 32 deletions(-) diff --git a/SmartStoreNET.Tasks.Targets b/SmartStoreNET.Tasks.Targets index f9b7371083..4fb755fcf5 100644 --- a/SmartStoreNET.Tasks.Targets +++ b/SmartStoreNET.Tasks.Targets @@ -44,7 +44,7 @@ x86 $(BUILD_NUMBER) - 2.2.2 + 2.5.0 $(StageFolder) .$(Version) diff --git a/src/AssemblySharedInfo.cs b/src/AssemblySharedInfo.cs index 0d0c359b93..79225de4c7 100644 --- a/src/AssemblySharedInfo.cs +++ b/src/AssemblySharedInfo.cs @@ -7,6 +7,6 @@ [assembly: AssemblyDescription("SmartStore.NET")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("SmartStore AG")] -[assembly: AssemblyCopyright("Copyright © SmartStore AG 2015")] +[assembly: AssemblyCopyright("Copyright © SmartStore AG 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/src/AssemblyVersionInfo.cs b/src/AssemblyVersionInfo.cs index 0c8dfa2cfb..67335b18c1 100644 --- a/src/AssemblyVersionInfo.cs +++ b/src/AssemblyVersionInfo.cs @@ -9,7 +9,7 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("2.2.0.0")] +[assembly: AssemblyVersion("2.5.0.0")] -[assembly: AssemblyFileVersion("2.2.0.0")] -[assembly: AssemblyInformationalVersion("2.2.2.0")] +[assembly: AssemblyFileVersion("2.5.0.0")] +[assembly: AssemblyInformationalVersion("2.5.0.0")] diff --git a/src/Libraries/SmartStore.Core/SmartStoreVersion.cs b/src/Libraries/SmartStore.Core/SmartStoreVersion.cs index 658a37fe4e..df3c302220 100644 --- a/src/Libraries/SmartStore.Core/SmartStoreVersion.cs +++ b/src/Libraries/SmartStore.Core/SmartStoreVersion.cs @@ -19,7 +19,8 @@ public static class SmartStoreVersion new Version("1.2.1"), // MC: had to be :-( new Version("2.0"), new Version("2.1"), - new Version("2.2") + new Version("2.2"), + new Version("2.5") }; private const string HELP_BASEURL = "http://docs.smartstore.com/display/SMNET25/"; diff --git a/src/Plugins/SmartStore.AmazonPay/Description.txt b/src/Plugins/SmartStore.AmazonPay/Description.txt index 383de5f866..3e203915e6 100644 --- a/src/Plugins/SmartStore.AmazonPay/Description.txt +++ b/src/Plugins/SmartStore.AmazonPay/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Pay with Amazon SystemName: SmartStore.AmazonPay -Version: 2.2.0.3 +Version: 2.5.0 Group: Payment -MinAppVersion: 2.2.0 +MinAppVersion: 2.5.0 Author: SmartStore AG DisplayOrder: 1 FileName: SmartStore.AmazonPay.dll diff --git a/src/Plugins/SmartStore.Clickatell/Description.txt b/src/Plugins/SmartStore.Clickatell/Description.txt index 9b5152e1bc..d5e561a2fc 100644 --- a/src/Plugins/SmartStore.Clickatell/Description.txt +++ b/src/Plugins/SmartStore.Clickatell/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Clickatell SMS Provider SystemName: SmartStore.Clickatell Group: Mobile -Version: 2.2.0 -MinAppVersion: 2.2.0 +Version: 2.5.0 +MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.Clickatell.dll ResourceRootKey: Plugins.Sms.Clickatell \ No newline at end of file diff --git a/src/Plugins/SmartStore.DevTools/Description.txt b/src/Plugins/SmartStore.DevTools/Description.txt index c2fd9f3fd6..fff9df57de 100644 --- a/src/Plugins/SmartStore.DevTools/Description.txt +++ b/src/Plugins/SmartStore.DevTools/Description.txt @@ -1,8 +1,8 @@ FriendlyName: SmartStore.NET Developer Tools (MiniProfiler and other goodies) SystemName: SmartStore.DevTools Group: Developer -Version: 2.2.0 -MinAppVersion: 2.2.0 +Version: 2.5.0 +MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.DevTools.dll ResourceRootKey: Plugins.Developer.DevTools \ No newline at end of file diff --git a/src/Plugins/SmartStore.DiscountRules/Description.txt b/src/Plugins/SmartStore.DiscountRules/Description.txt index a8f07ab2e8..2a17b4fcd9 100644 --- a/src/Plugins/SmartStore.DiscountRules/Description.txt +++ b/src/Plugins/SmartStore.DiscountRules/Description.txt @@ -2,8 +2,8 @@ Description: Contains common discount requirement rule providers like "Billing country is", "Customer role is", "Had spent amount" etc. Group: Marketing SystemName: SmartStore.DiscountRules -Version: 2.2.0 -MinAppVersion: 2.2.0 +Version: 2.5.0 +MinAppVersion: 2.5.0 DisplayOrder: 0 FileName: SmartStore.DiscountRules.dll ResourceRootKey: Plugins.SmartStore.DiscountRules diff --git a/src/Plugins/SmartStore.FacebookAuth/Description.txt b/src/Plugins/SmartStore.FacebookAuth/Description.txt index ef5b27035b..6e028c02d4 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Description.txt +++ b/src/Plugins/SmartStore.FacebookAuth/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Facebook SystemName: SmartStore.FacebookAuth Group: Security -Version: 2.2.0.2 -MinAppVersion: 2.2.0 +Version: 2.5.0 +MinAppVersion: 2.5.0 DisplayOrder: 5 FileName: SmartStore.FacebookAuth.dll ResourceRootKey: Plugins.ExternalAuth.Facebook \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Description.txt b/src/Plugins/SmartStore.GoogleAnalytics/Description.txt index 2e32945c39..908e329118 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Description.txt +++ b/src/Plugins/SmartStore.GoogleAnalytics/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Google Analytics SystemName: SmartStore.GoogleAnalytics Group: Analytics -Version: 2.2.2.2 -MinAppVersion: 2.2.0 +Version: 2.5.0 +MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.GoogleAnalytics.dll ResourceRootKey: Plugins.Widgets.GoogleAnalytics diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt index 55dfd4845a..d30ed161f9 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Google Merchant Center (GMC) feed SystemName: SmartStore.GoogleMerchantCenter Group: Marketing -Version: 2.2.0.5 -MinAppVersion: 2.2.0 +Version: 2.5.0 +MinAppVersion: 2.5.0 Author: SmartStore AG DisplayOrder: 1 FileName: SmartStore.GoogleMerchantCenter.dll diff --git a/src/Plugins/SmartStore.OfflinePayment/Description.txt b/src/Plugins/SmartStore.OfflinePayment/Description.txt index 33f0f72b71..0b09221978 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Description.txt +++ b/src/Plugins/SmartStore.OfflinePayment/Description.txt @@ -2,8 +2,8 @@ Description: Contains common offline payment methods like Direct Debit, Invoice, Prepayment etc. Group: Payment SystemName: SmartStore.OfflinePayment -Version: 2.2.2.1 -MinAppVersion: 2.2.0 +Version: 2.5.0 +MinAppVersion: 2.5.0 DisplayOrder: 0 FileName: SmartStore.OfflinePayment.dll ResourceRootKey: Plugins.SmartStore.OfflinePayment diff --git a/src/Plugins/SmartStore.PayPal/Description.txt b/src/Plugins/SmartStore.PayPal/Description.txt index 5aca36a6c0..976b7116dc 100644 --- a/src/Plugins/SmartStore.PayPal/Description.txt +++ b/src/Plugins/SmartStore.PayPal/Description.txt @@ -2,8 +2,8 @@ Description: Provides the PayPal payment methods PayPal Express, PayPal Standard and PayPal Direct. SystemName: SmartStore.PayPal Group: Payment -Version: 2.2.0.4 -MinAppVersion: 2.2.0 +Version: 2.5.0 +MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.PayPal.dll ResourceRootKey: Plugins.SmartStore.PayPal diff --git a/src/Plugins/SmartStore.Shipping/Description.txt b/src/Plugins/SmartStore.Shipping/Description.txt index 533761599a..5d85eddbb8 100644 --- a/src/Plugins/SmartStore.Shipping/Description.txt +++ b/src/Plugins/SmartStore.Shipping/Description.txt @@ -2,8 +2,8 @@ Description: Provides shipping methods for fixed rate shipping and computation based on weight. SystemName: SmartStore.Shipping Group: Shipping -Version: 2.2.0.1 -MinAppVersion: 2.2.0 +Version: 2.5.0 +MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.Shipping.dll ResourceRootKey: Plugins.SmartStore.Shipping \ No newline at end of file diff --git a/src/Plugins/SmartStore.ShippingByWeight/Description.txt b/src/Plugins/SmartStore.ShippingByWeight/Description.txt index 65f72e227d..03312a5943 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Description.txt +++ b/src/Plugins/SmartStore.ShippingByWeight/Description.txt @@ -1,8 +1,8 @@ FriendlyName: Shipping by weight SystemName: SmartStore.ShippingByWeight Group: Shipping -Version: 2.2.0.1 -MinAppVersion: 2.2.0 +Version: 2.5.0 +MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.ShippingByWeight.dll ResourceRootKey: Plugins.Shipping.ByWeight \ No newline at end of file diff --git a/src/Plugins/SmartStore.Tax/Description.txt b/src/Plugins/SmartStore.Tax/Description.txt index 245ae96884..79d79ec7a8 100644 --- a/src/Plugins/SmartStore.Tax/Description.txt +++ b/src/Plugins/SmartStore.Tax/Description.txt @@ -2,8 +2,8 @@ Description: Contains default tax providers like FixedRate, ByRegion etc. Group: Tax SystemName: SmartStore.Tax -Version: 2.2.0 -MinAppVersion: 2.2.0 +Version: 2.5.0 +MinAppVersion: 2.5.0 DisplayOrder: 0 FileName: SmartStore.Tax.dll ResourceRootKey: Plugins.SmartStore.Tax diff --git a/src/Plugins/SmartStore.WebApi/Description.txt b/src/Plugins/SmartStore.WebApi/Description.txt index 5cd3474d8f..aa73f1d263 100644 --- a/src/Plugins/SmartStore.WebApi/Description.txt +++ b/src/Plugins/SmartStore.WebApi/Description.txt @@ -1,8 +1,8 @@ FriendlyName: SmartStore.NET Web Api SystemName: SmartStore.WebApi -Version: 2.2.0.5 +Version: 2.5.0 Group: Api -MinAppVersion: 2.2.0 +MinAppVersion: 2.5.0 Author: SmartStore AG DisplayOrder: 1 FileName: SmartStore.WebApi.dll From 4f951a9d8286fe7a61b3858902f3fc17a1031cdc Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 1 Mar 2016 10:28:01 +0100 Subject: [PATCH 081/423] Moving export files into subfolder 'Export' --- .../{ => Export}/DataExportResult.cs | 0 .../{ => Export}/DataExportTask.cs | 0 .../DataExchange/{ => Export}/DataExporter.cs | 0 .../{ => Export}/DynamicEntityHelper.cs | 0 .../{ => Export}/ExportConfigurationInfo.cs | 0 .../{ => Export}/ExportDataSegmenter.cs | 0 .../{ => Export}/ExportExecuteContext.cs | 0 .../{ => Export}/ExportExtensions.cs | 0 .../{ => Export}/ExportFeaturesAttribute.cs | 0 .../{ => Export}/ExportProfileService.cs | 0 .../{ => Export}/ExportProviderBase.cs | 0 .../{ => Export}/ExportXmlHelper.cs | 0 .../{ => Export}/IDataExporter.cs | 0 .../{ => Export}/IExportProfileService.cs | 0 .../{ => Export}/IExportProvider.cs | 0 .../SmartStore.Services.csproj | 30 +++++++++---------- 16 files changed, 15 insertions(+), 15 deletions(-) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/DataExportResult.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/DataExportTask.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/DataExporter.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/DynamicEntityHelper.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/ExportConfigurationInfo.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/ExportDataSegmenter.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/ExportExecuteContext.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/ExportExtensions.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/ExportFeaturesAttribute.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/ExportProfileService.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/ExportProviderBase.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/ExportXmlHelper.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/IDataExporter.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/IExportProfileService.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/IExportProvider.cs (100%) diff --git a/src/Libraries/SmartStore.Services/DataExchange/DataExportResult.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/DataExportResult.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/DataExportResult.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/DataExportTask.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExportTask.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/DataExportTask.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/DataExportTask.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/DataExporter.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/DynamicEntityHelper.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportConfigurationInfo.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportConfigurationInfo.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/ExportConfigurationInfo.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/ExportConfigurationInfo.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportDataSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportDataSegmenter.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/ExportDataSegmenter.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/ExportDataSegmenter.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/ExportExecuteContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/ExportExtensions.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportFeaturesAttribute.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportFeaturesAttribute.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/ExportFeaturesAttribute.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/ExportFeaturesAttribute.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/ExportProfileService.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportProviderBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/ExportProviderBase.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/ExportXmlHelper.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/ExportXmlHelper.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/IDataExporter.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/IDataExporter.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/IDataExporter.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProfileService.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/IExportProfileService.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/IExportProfileService.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProvider.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/IExportProvider.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/IExportProvider.cs diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index f48d08d079..3cd734dd9b 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -187,7 +187,7 @@ - + @@ -202,31 +202,31 @@ - - + + - + - - - + + + - - - - - + + + + + - - - + + + From 895bc3550a87d8d5dce4cd7fab9e8d48fe0b4a06 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 1 Mar 2016 11:25:56 +0100 Subject: [PATCH 082/423] EntityImporterBase implements IEntityImporter --- .../Catalog/Importer/CategoryImporter.cs | 4 ++-- .../Catalog/Importer/ProductImporter.cs | 4 ++-- .../Customers/Importer/CustomerImporter.cs | 4 ++-- .../DataExchange/Import/EntityImporterBase.cs | 9 ++++++++- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 1c70a482d7..703c86f735 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -18,7 +18,7 @@ namespace SmartStore.Services.Catalog.Importer { - public class CategoryImporter : EntityImporterBase, IEntityImporter + public class CategoryImporter : EntityImporterBase { private readonly IRepository _categoryRepository; private readonly IRepository _urlRecordRepository; @@ -416,7 +416,7 @@ public static string[] DefaultKeyFields } } - public void Execute(IImportExecuteContext context) + protected override void Import(IImportExecuteContext context) { var srcToDestId = new Dictionary(); diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 4dbcf7f271..496e5b54e4 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -18,7 +18,7 @@ namespace SmartStore.Services.Catalog.Importer { - public class ProductImporter : EntityImporterBase, IEntityImporter + public class ProductImporter : EntityImporterBase { private readonly IRepository _productPictureRepository; private readonly IRepository _productManufacturerRepository; @@ -733,7 +733,7 @@ public static string[] DefaultKeyFields } } - public void Execute(IImportExecuteContext context) + protected override void Import(IImportExecuteContext context) { var srcToDestId = new Dictionary(); diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 5496914771..a056820739 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -20,7 +20,7 @@ namespace SmartStore.Services.Customers.Importer { - public class CustomerImporter : EntityImporterBase, IEntityImporter + public class CustomerImporter : EntityImporterBase { private const string _attributeKeyGroup = "Customer"; @@ -373,7 +373,7 @@ public static string[] DefaultKeyFields } } - public void Execute(IImportExecuteContext context) + protected override void Import(IImportExecuteContext context) { var customer = _services.WorkContext.CurrentCustomer; var allowManagingCustomerRoles = _services.Permissions.Authorize(StandardPermissionProvider.ManageCustomerRoles, customer); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index d196ed3359..9ef5baf6d5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -8,7 +8,7 @@ namespace SmartStore.Services.DataExchange.Import { - public abstract class EntityImporterBase + public abstract class EntityImporterBase : IEntityImporter { private const string _imageDownloadFolder = @"Content\DownloadedImages"; @@ -22,6 +22,13 @@ public abstract class EntityImporterBase public FileDownloadManagerContext DownloaderContext { get; private set; } + protected abstract void Import(IImportExecuteContext context); + + public void Execute(IImportExecuteContext context) + { + Import(context); + } + public void Init(IImportExecuteContext context, DataExchangeSettings dataExchangeSettings) { UtcNow = DateTime.UtcNow; From 5ddd776ad3e52f86db449ba12f23b9ba03849719 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 2 Mar 2016 15:05:32 +0100 Subject: [PATCH 083/423] Added modal-flex class to dialogs on checkout confirm page --- .../Themes/Alpha/Content/checkout.less | 14 -------------- .../SmartStore.Web/Views/Checkout/Confirm.cshtml | 8 ++++---- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/checkout.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/checkout.less index ec8e364519..60e01fb235 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/checkout.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/checkout.less @@ -240,22 +240,8 @@ /* Terms of service ================================================ */ - -#terms-of-service-modal { - width: 650px; -} -#terms-of-service-modal .modal-body { - min-height: 300px; - overflow: hidden; -} -#iframe-terms-of-service { - min-height: 400px; - width: 100%; -} - .terms-of-service.alert { padding-bottom: 4px !important; - a.read { font-weight: bold; &:hover { diff --git a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml index 0f369476d6..8f7522eb04 100644 --- a/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Checkout/Confirm.cshtml @@ -37,11 +37,11 @@ $(document).ready(function () { $("#terms-of-service-trigger").click(function (event) { event.preventDefault(); - $("#terms-of-service-modal .modal-body").html('