From 363777da1bbb37da384b8b4f0d26e15556303ed5 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 23 Mar 2016 11:19:39 +0100 Subject: [PATCH 001/285] Fixed: Adding new shipping method throws an exception --- changelog.md | 1 + .../201603121451066_ThirdPartyEmailHandOver.cs | 4 ++++ .../Controllers/ShippingController.cs | 15 +++++++++++---- .../Views/Shipping/_CreateOrUpdateMethod.cshtml | 8 +++++++- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/changelog.md b/changelog.md index 8aba6eb6f6..491abb7e66 100644 --- a/changelog.md +++ b/changelog.md @@ -16,6 +16,7 @@ * Bundles without selected attributes could throw an exception on product detail page * GMC feed did not export the product type (category path) * The error message of a payment provider when capturing a payment was not displayed +* Adding new shipping method throws an exception ## SmartStore.NET 2.5 diff --git a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs index 3eda0b6cee..067c624451 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs @@ -150,6 +150,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Wählen Sie bitte das zu importierende Objekt und laden Sie eine Importdatei hoch."); builder.Delete("Admin.DataExchange.Import.ProfileEntitySelectNote"); + + builder.AddOrUpdate("Admin.Configuration.Restriction.SaveBeforeEdit", + "You need to save the data before you can specify restrictions.", + "Sie müssen zunächst speichern, bevor Sie Einschränkungen festlegen können."); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ShippingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ShippingController.cs index 887b5dabbc..ed497b0175 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ShippingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ShippingController.cs @@ -73,10 +73,17 @@ private void PrepareShippingMethodModel(ShippingMethodModel model, ShippingMetho { var allFilters = _shippingService.GetAllShippingMethodFilters(); - model.FilterConfigurationUrls = allFilters - .Select(x => "'" + x.GetConfigurationUrl(shippingMethod.Id) + "'") - .OrderBy(x => x) - .ToList(); + if (shippingMethod != null) + { + model.FilterConfigurationUrls = allFilters + .Select(x => "'" + x.GetConfigurationUrl(shippingMethod.Id) + "'") + .OrderBy(x => x) + .ToList(); + } + else + { + model.FilterConfigurationUrls = new List(); + } } #endregion diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shipping/_CreateOrUpdateMethod.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shipping/_CreateOrUpdateMethod.cshtml index e1d89401a9..8d63b6e67b 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shipping/_CreateOrUpdateMethod.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shipping/_CreateOrUpdateMethod.cshtml @@ -87,7 +87,13 @@ @helper TabRestrictions() { - if (Model.FilterConfigurationUrls.Count == 0) + if (Model.Id == 0) + { +
+ @T("Admin.Configuration.Restriction.SaveBeforeEdit") +
+ } + else if (Model.FilterConfigurationUrls.Count == 0) {
@T("Admin.Configuration.Shipping.Methods.RestrictionNote") From db7e29411de351cefd5b1a0423dbd94f1a642684 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 23 Mar 2016 21:25:00 +0100 Subject: [PATCH 002/285] Missing string resources for new settings CustomerNumberMethod and CustomerNumberVisibility --- ...201603121451066_ThirdPartyEmailHandOver.cs | 21 ++++ .../Controllers/SettingController.cs | 4 +- .../Settings/CustomerUserSettingsModel.cs | 4 +- .../Views/Setting/CustomerUser.cshtml | 106 +++++++++--------- 4 files changed, 80 insertions(+), 55 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs index 067c624451..c930dd23f6 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs @@ -154,6 +154,27 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Configuration.Restriction.SaveBeforeEdit", "You need to save the data before you can specify restrictions.", "Sie müssen zunächst speichern, bevor Sie Einschränkungen festlegen können."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.CustomerNumberMethod", + "Customer numbers", + "Kundennummern", + "Specifies whether to assign customer numbers and whether these should be created automatically.", + "Legt fest, ob Kundennummern vergeben werden und ob diese automatisch vergeben werden sollen."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.CustomerNumberVisibility", + "Customer number presentation", + "Darstellung der Kundennummer", + "Specifies the presentation and handling of the customer number to the customer.", + "Legt die Darstellung und Handhabung der Kundennummer gegenüber dem Kunden fest."); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberMethod.Disabled", "Disabled", "Deaktiviert"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberMethod.Enabled", "Enabled", "Aktiviert"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberMethod.AutomaticallySet", "Automatically assigned", "Automatisch vergeben"); + + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberVisibility.None", "Do not display", "Nicht anzeigen"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberVisibility.Display", "Display", "Anzeigen"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberVisibility.EditableIfEmpty", "Editable if empty", "Editierbar falls leer"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberVisibility.Editable", "Always editable", "Stets editierbar"); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs index ab728985e2..4d23358a6c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs @@ -953,8 +953,8 @@ public ActionResult CustomerUser() StoreDependingSettings.GetOverrideKeys(externalAuthenticationSettings, model.ExternalAuthenticationSettings, storeScope, _services.Settings, false); - model.CustomerSettings.AvailableCustomerNumberMethods = customerSettings.CustomerNumberMethod.ToSelectList(); - model.CustomerSettings.AvailableCustomerNumberVisibilities = customerSettings.CustomerNumberVisibility.ToSelectList(); + model.CustomerSettings.AvailableCustomerNumberMethods = customerSettings.CustomerNumberMethod.ToSelectList(false); + model.CustomerSettings.AvailableCustomerNumberVisibilities = customerSettings.CustomerNumberVisibility.ToSelectList(false); model.CustomerSettings.AvailableRegisterCustomerRoles = allCustomerRoles .Where(x => x.SystemName != SystemCustomerRoleNames.Registered && x.SystemName != SystemCustomerRoleNames.Guests) diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs index 2e076d6471..bfbb495345 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs @@ -26,12 +26,12 @@ public partial class CustomerSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.UsernamesEnabled")] public bool UsernamesEnabled { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.DefaultSortOrderMode")] + [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.CustomerNumberMethod")] public CustomerNumberMethod CustomerNumberMethod { get; set; } public SelectList AvailableCustomerNumberMethods { get; set; } public IList AvailableRegisterCustomerRoles { get; set; } - [SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.DefaultSortOrderMode")] + [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.CustomerNumberVisibility")] public CustomerNumberVisibility CustomerNumberVisibility { get; set; } public SelectList AvailableCustomerNumberVisibilities { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml index d3c493aad1..1d868a640d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml @@ -43,6 +43,10 @@ toggleAvatar(); toggleUsername(); + + $('#@Html.FieldIdFor(model => model.CustomerSettings.CustomerNumberMethod)').change(function () { + $('#pnlCustomerNumberVisibility').toggle($(this).val() !== '@((int)CustomerNumberMethod.Disabled)'); + }).trigger('change'); }); function toggleAvatar() { @@ -76,29 +80,6 @@ @Html.ValidationMessageFor(model => model.CustomerSettings.UsernamesEnabled) - - - - @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNumberMethod) - - - @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.CustomerNumberMethod) - @Html.DropDownListFor(model => model.CustomerSettings.CustomerNumberMethod, Model.CustomerSettings.AvailableCustomerNumberMethods) - @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNumberMethod) - - - - - - @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNumberVisibility) - - - @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.CustomerNumberVisibility) - @Html.DropDownListFor(model => model.CustomerSettings.CustomerNumberVisibility, Model.CustomerSettings.AvailableCustomerNumberVisibilities) - @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNumberVisibility) - - - @Html.SmartLabelFor(model => model.CustomerSettings.AllowUsersToChangeUsernames) @@ -117,6 +98,47 @@ @Html.ValidationMessageFor(model => model.CustomerSettings.CheckUsernameAvailabilityEnabled) + + + @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNameFormat) + + + @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.UserRegistrationType) + @Html.DropDownListFor(model => model.CustomerSettings.CustomerNameFormat, ((CustomerNameFormat)Model.CustomerSettings.CustomerNameFormat).ToSelectList()) + @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNameFormat) + + + + + @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNameFormatMaxLength) + + + @Html.SettingEditorFor(model => model.CustomerSettings.CustomerNameFormatMaxLength) + @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNameFormatMaxLength) + + + + + + @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNumberMethod) + + + @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.CustomerNumberMethod) + @Html.DropDownListFor(model => model.CustomerSettings.CustomerNumberMethod, Model.CustomerSettings.AvailableCustomerNumberMethods) + @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNumberMethod) + + + + + @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNumberVisibility) + + + @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.CustomerNumberVisibility) + @Html.DropDownListFor(model => model.CustomerSettings.CustomerNumberVisibility, Model.CustomerSettings.AvailableCustomerNumberVisibilities) + @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNumberVisibility) + + + @Html.SmartLabelFor(model => model.CustomerSettings.UserRegistrationType) @@ -127,6 +149,15 @@ @Html.ValidationMessageFor(model => model.CustomerSettings.UserRegistrationType) + + + @Html.SmartLabelFor(model => model.CustomerSettings.DefaultPasswordFormat) + + + @Html.DropDownListFor(model => model.CustomerSettings.DefaultPasswordFormat, ((PasswordFormat)Model.CustomerSettings.DefaultPasswordFormat).ToSelectList()) + @Html.ValidationMessageFor(model => model.CustomerSettings.DefaultPasswordFormat) + + @Html.SmartLabelFor(model => model.CustomerSettings.RegisterCustomerRoleId) @@ -137,6 +168,7 @@ @Html.ValidationMessageFor(model => model.CustomerSettings.RegisterCustomerRoleId) + @Html.SmartLabelFor(model => model.CustomerSettings.AllowCustomersToUploadAvatars) @@ -209,25 +241,6 @@ @Html.ValidationMessageFor(model => model.CustomerSettings.HideBackInStockSubscriptionsTab) - - - @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNameFormat) - - - @Html.SettingOverrideCheckbox(model => Model.CustomerSettings.UserRegistrationType) - @Html.DropDownListFor(model => model.CustomerSettings.CustomerNameFormat, ((CustomerNameFormat)Model.CustomerSettings.CustomerNameFormat).ToSelectList()) - @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNameFormat) - - - - - @Html.SmartLabelFor(model => model.CustomerSettings.CustomerNameFormatMaxLength) - - - @Html.SettingEditorFor(model => model.CustomerSettings.CustomerNameFormatMaxLength) - @Html.ValidationMessageFor(model => model.CustomerSettings.CustomerNameFormatMaxLength) - - @Html.SmartLabelFor(model => model.CustomerSettings.HideNewsletterBlock) @@ -246,15 +259,6 @@ @Html.ValidationMessageFor(model => model.CustomerSettings.StoreLastVisitedPage) - - - @Html.SmartLabelFor(model => model.CustomerSettings.DefaultPasswordFormat) - - - @Html.DropDownListFor(model => model.CustomerSettings.DefaultPasswordFormat, ((PasswordFormat)Model.CustomerSettings.DefaultPasswordFormat).ToSelectList()) - @Html.ValidationMessageFor(model => model.CustomerSettings.DefaultPasswordFormat) - - } @helper TabCustomerFormFields() From 3e378f0e0821fb90174dbbf37dc57e0a5fefd734 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 25 Mar 2016 00:00:03 +0100 Subject: [PATCH 003/285] Editing content slider slide does not handle picture transiency correctly --- .../Controllers/ContentSliderController.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ContentSliderController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ContentSliderController.cs index e9824617e3..73192b52db 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ContentSliderController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ContentSliderController.cs @@ -234,16 +234,11 @@ public ActionResult EditSlide(ContentSliderSlideModel model, bool continueEditin model.LanguageName = lang.Name; } - model.PictureUrl = _pictureService.GetPictureUrl(model.PictureId); + model.PictureUrl = _pictureService.GetPictureUrl(model.PictureId); - //delete an old picture (if deleted or updated) - int prevPictureId = _contentSliderSettings.Slides[index].PictureId; - if (prevPictureId > 0 && prevPictureId != model.PictureId) - { - var prevPicture = _pictureService.GetPictureById(prevPictureId); - if (prevPicture != null) - _pictureService.DeletePicture(prevPicture); - } + // delete an old picture (if deleted or updated) + int prevPictureId = _contentSliderSettings.Slides[index].PictureId; + MediaHelper.UpdatePictureTransientState(prevPictureId, model.PictureId, true); _contentSliderSettings.Slides[index] = model.ToEntity(); _settingService.SaveSetting(_contentSliderSettings); From 20d85faa6a54dd3237975c16589c9a60cb0b8886 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 29 Mar 2016 20:58:26 +0200 Subject: [PATCH 004/285] Resolves #900 Attribute Values: Assigning IsPreselected to more than one value causes an error --- changelog.md | 1 + .../NameValueCollectionExtensions.cs | 4 +-- .../Controllers/ShoppingCartController.cs | 28 +++++++++++-------- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/changelog.md b/changelog.md index 491abb7e66..303e27e949 100644 --- a/changelog.md +++ b/changelog.md @@ -17,6 +17,7 @@ * GMC feed did not export the product type (category path) * The error message of a payment provider when capturing a payment was not displayed * Adding new shipping method throws an exception +* Attribute Values: Assigning IsPreselected to more than one value causes an error ## SmartStore.NET 2.5 diff --git a/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs b/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs index a2e53aa8da..a14c25e424 100644 --- a/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs +++ b/src/Libraries/SmartStore.Services/Extensions/NameValueCollectionExtensions.cs @@ -88,7 +88,7 @@ public static string CreateSelectedAttributesXml(this NameValueCollection collec var ctrlAttributes = collection[controlId]; if (ctrlAttributes.HasValue()) { - int selectedAttributeId = int.Parse(ctrlAttributes); + var selectedAttributeId = ctrlAttributes.SplitSafe(",").SafeGet(0).ToInt(); if (selectedAttributeId > 0) selectedAttributes = productAttributeParser.AddProductAttribute(selectedAttributes, attribute, selectedAttributeId.ToString()); } @@ -102,7 +102,7 @@ public static string CreateSelectedAttributesXml(this NameValueCollection collec { foreach (var item in cblAttributes.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { - int selectedAttributeId = int.Parse(item); + var selectedAttributeId = item.SplitSafe(",").SafeGet(0).ToInt(); if (selectedAttributeId > 0) selectedAttributes = productAttributeParser.AddProductAttribute(selectedAttributes, attribute, selectedAttributeId.ToString()); } diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index 44563a507a..eb34bfd641 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -1065,14 +1065,17 @@ protected void ParseAndSaveCheckoutAttributes(List ca { string selectedAttributes = ""; var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(); + if (!cart.RequiresShipping()) { //remove attributes which require shippable products checkoutAttributes = checkoutAttributes.RemoveShippableAttributes(); } + foreach (var attribute in checkoutAttributes) { string controlId = string.Format("checkout_attribute_{0}", attribute.Id); + switch (attribute.AttributeControlType) { case AttributeControlType.DropdownList: @@ -1082,13 +1085,13 @@ protected void ParseAndSaveCheckoutAttributes(List ca var rblAttributes = form[controlId]; if (!String.IsNullOrEmpty(rblAttributes)) { - int selectedAttributeId = int.Parse(rblAttributes); + var selectedAttributeId = rblAttributes.SplitSafe(",").SafeGet(0).ToInt(); if (selectedAttributeId > 0) - selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, - attribute, selectedAttributeId.ToString()); + selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, attribute, selectedAttributeId.ToString()); } } break; + case AttributeControlType.Checkboxes: { var cblAttributes = form[controlId]; @@ -1096,14 +1099,14 @@ protected void ParseAndSaveCheckoutAttributes(List ca { foreach (var item in cblAttributes.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { - int selectedAttributeId = int.Parse(item); - if (selectedAttributeId > 0) - selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, - attribute, selectedAttributeId.ToString()); + var selectedAttributeId = item.SplitSafe(",").SafeGet(0).ToInt(); + if (selectedAttributeId > 0) + selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, attribute, selectedAttributeId.ToString()); } } } break; + case AttributeControlType.TextBox: case AttributeControlType.MultilineTextbox: { @@ -1111,29 +1114,31 @@ protected void ParseAndSaveCheckoutAttributes(List ca if (!String.IsNullOrEmpty(txtAttribute)) { string enteredText = txtAttribute.Trim(); - selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, - attribute, enteredText); + selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, attribute, enteredText); } } break; + case AttributeControlType.Datepicker: { var date = form[controlId + "_day"]; var month = form[controlId + "_month"]; var year = form[controlId + "_year"]; DateTime? selectedDate = null; + try { selectedDate = new DateTime(Int32.Parse(year), Int32.Parse(month), Int32.Parse(date)); } catch { } + if (selectedDate.HasValue) { - selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, - attribute, selectedDate.Value.ToString("D")); + selectedAttributes = _checkoutAttributeParser.AddCheckoutAttribute(selectedAttributes, attribute, selectedDate.Value.ToString("D")); } } break; + case AttributeControlType.FileUpload: { var postedFile = this.Request.Files[controlId].ToPostedFileResult(); @@ -1166,6 +1171,7 @@ protected void ParseAndSaveCheckoutAttributes(List ca } } break; + default: break; } From 48886096142f4527b61e9517abb4d44340f9b35a Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 30 Mar 2016 10:48:25 +0200 Subject: [PATCH 005/285] Resolves #905 Paypal Express: Unlocalized strings --- .../Localization/resources.de-de.xml | 12 +++++++++--- .../Localization/resources.en-us.xml | 12 +++++++++--- .../Providers/PayPalExpressProvider.cs | 6 +++--- .../Providers/PayPalStandardProvider.cs | 2 +- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index 6087bfe6cd..17ca631122 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -78,6 +78,15 @@ Teilerstattung von {0} + + Zahlartgebühren + + + Rabatt + + + Angewendeter Geschenkgutschein + @@ -304,9 +313,6 @@ Versandgebühren - - Zahlungsgebühren - Umsatzsteuer diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index 45d036a94a..9dbfca9abc 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -78,6 +78,15 @@ Partial refund of {0} + + Payment method fee + + + Discount + + + Giftcard Applied + @@ -304,9 +313,6 @@ Shipping fee - - Payment method fee - Sales tax diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs index d8bab5bc43..d64372f59c 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs @@ -208,7 +208,7 @@ public SetExpressCheckoutResponseType SetExpressCheckout(PayPalProcessPaymentReq var additionalHandlingFee = GetAdditionalHandlingFee(cart); cartItems.Add(new PaymentDetailsItemType() { - Name = "Zahlartgebühren", + Name = T("Plugins.Payments.PayPal.PaymentMethodFee").Text, Quantity = "1", Amount = new BasicAmountType() { @@ -260,7 +260,7 @@ public SetExpressCheckoutResponseType SetExpressCheckout(PayPalProcessPaymentReq { cartItems.Add(new PaymentDetailsItemType() { - Name = "Threadrock Discount", + Name = T("Plugins.Payments.PayPal.ThreadrockDiscount").Text, Quantity = "1", Amount = new BasicAmountType() // this is the total discount { @@ -297,7 +297,7 @@ public SetExpressCheckoutResponseType SetExpressCheckout(PayPalProcessPaymentReq cartItems.Add(new PaymentDetailsItemType() { - Name = "Giftcard Applied", + Name = T("Plugins.Payments.PayPal.GiftcardApplied").Text, Quantity = "1", Amount = new BasicAmountType() { diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs index 3e287d0c74..b4ea50d405 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs @@ -450,7 +450,7 @@ public List GetLineItems(PostProcessPaymentRequest postProcessPa var item = new PayPalLineItem { Type = PayPalItemType.PaymentFee, - Name = T("Plugins.Payments.PayPalStandard.PaymentMethodFee").Text, + Name = T("Plugins.Payments.PayPal.PaymentMethodFee").Text, Quantity = 1, Amount = order.PaymentMethodAdditionalFeeExclTax }; From 46d2f6757cefe3ff07743fe2a5f6b6ac8f37b6f9 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 30 Mar 2016 17:26:43 +0200 Subject: [PATCH 006/285] Fixes #904: Don't generate microdata aggregate rating if product does not have any ratings --- .../SmartStore.Web/Views/Product/ReviewOverview.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Views/Product/ReviewOverview.cshtml b/src/Presentation/SmartStore.Web/Views/Product/ReviewOverview.cshtml index 8c3841cde9..3535ca8458 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/ReviewOverview.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/ReviewOverview.cshtml @@ -11,7 +11,7 @@ } @if (Model.AllowCustomerReviews) { -
+
0 ? " itemprop='aggregateRating' itemscope itemtype='http://schema.org/AggregateRating'" : "")> @* STARS *@
From 21e6071afc26521f3965c3b06808ae32ebe32600 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 30 Mar 2016 18:27:47 +0200 Subject: [PATCH 007/285] Added pathes /Login and /Register to robots.txt --- .../SmartStore.Web/Controllers/CommonController.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs index 9391825dcf..2734c4d8ee 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs @@ -859,7 +859,9 @@ public ActionResult RobotsTextFile() "/Product/EmailAFriend", "/Search", "/Config", - "/Settings" + "/Settings", + "/Login", + "/Register" }; From 7edd91102e0667f801dd3eec3e8e0addcc3e8bea Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 31 Mar 2016 21:58:09 +0200 Subject: [PATCH 008/285] Fix 'occurred' typo --- src/Presentation/SmartStore.Web/500.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/500.html b/src/Presentation/SmartStore.Web/500.html index f9eaa260f1..741ebbcab5 100644 --- a/src/Presentation/SmartStore.Web/500.html +++ b/src/Presentation/SmartStore.Web/500.html @@ -41,7 +41,7 @@

- We apologize, an error ocurred while handling your request, this is not a problem with your computer or internet connection. + We apologize, an error occurred while handling your request, this is not a problem with your computer or internet connection. The details have been sent to our support team and we will investigate the issue very soon.

From 095ddb5e245b5cf7d032d236d22ccdfedba9d05d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 4 Apr 2016 11:51:42 +0200 Subject: [PATCH 009/285] #895 Web API: Missing Location Header in post response --- .../WebApi/WebApiEntityController.cs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs index 2d7a98ecda..000f1549ea 100644 --- a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs +++ b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiEntityController.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Data.Entity; using System.Data.Entity.Core.Metadata.Edm; using System.Data.Entity.Infrastructure; using System.Linq; @@ -8,15 +10,13 @@ using System.Web.Http; using System.Web.Http.OData; using System.Web.Http.OData.Routing; -using System.Data.Entity; +using Autofac; +using SmartStore.ComponentModel; using SmartStore.Core; using SmartStore.Core.Data; +using SmartStore.Services; using SmartStore.Services.Directory; using SmartStore.Services.Localization; -using System.Collections.Generic; -using SmartStore.Services; -using Autofac; -using SmartStore.ComponentModel; namespace SmartStore.Web.Framework.WebApi { @@ -338,7 +338,20 @@ protected internal virtual SingleResult GetRelatedEntity( public override HttpResponseMessage Post(TEntity entity) { - return Request.CreateResponse(HttpStatusCode.OK, CreateEntity(entity)); + var response = Request.CreateResponse(HttpStatusCode.OK, CreateEntity(entity)); + + try + { + var entityUrl = Url.ODataLink( + new EntitySetPathSegment(entity.GetType().Name.EnsureEndsWith("s")), + new KeyValuePathSegment(entity.Id.ToString()) + ); + + response.Headers.Location = new Uri(entityUrl); + } + catch { } + + return response; } protected override TEntity CreateEntity(TEntity entity) From 17da9a431c118ff00853594dee2e36ab31791897 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 4 Apr 2016 13:24:51 +0200 Subject: [PATCH 010/285] Resolves #895. Web API: Option to allow authentification without MD5 content hash --- src/Plugins/SmartStore.WebApi/Description.txt | 2 +- .../Localization/resources.de-de.xml | 10 ++++++++-- .../Localization/resources.en-us.xml | 6 ++++++ .../Models/WebApiConfigModel.cs | 7 ++++++- .../Views/WebApi/Configure.cshtml | 9 +++++++++ src/Plugins/SmartStore.WebApi/changelog.md | 4 ++++ .../Caching/WebApiCachingControllingData.cs | 4 +++- .../Security/WebApiAuthenticateAttribute.cs | 16 +++++++++++++++- .../WebApi/WebApiSettings.cs | 1 + 9 files changed, 53 insertions(+), 6 deletions(-) diff --git a/src/Plugins/SmartStore.WebApi/Description.txt b/src/Plugins/SmartStore.WebApi/Description.txt index aa73f1d263..eacdc805f8 100644 --- a/src/Plugins/SmartStore.WebApi/Description.txt +++ b/src/Plugins/SmartStore.WebApi/Description.txt @@ -1,6 +1,6 @@ FriendlyName: SmartStore.NET Web Api SystemName: SmartStore.WebApi -Version: 2.5.0 +Version: 2.5.0.1 Group: Api MinAppVersion: 2.5.0 Author: SmartStore AG diff --git a/src/Plugins/SmartStore.WebApi/Localization/resources.de-de.xml b/src/Plugins/SmartStore.WebApi/Localization/resources.de-de.xml index f34a2ceb2e..650e50d1ab 100644 --- a/src/Plugins/SmartStore.WebApi/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.WebApi/Localization/resources.de-de.xml @@ -30,10 +30,10 @@ Aus Sicherheitsgründen darf die Zeit, zu der die Anfrage an die API gesendet wurde, nicht zu weit von der aktuellen Server-Zeit abweichen. Mit dieser Einstellung legen Sie dieses Zeitfenster in Minuten fest. - Unauthorisierte Zugriffe speichern + Unautorisierte Zugriffe speichern - Jeder unauthorisierte Zugriff wird in der Ereignisliste gespeichert. + Jeder unautorisierte Zugriff wird in der Ereignisliste gespeichert. Keine Zeitstempelprüfung @@ -41,6 +41,12 @@ Legt fest, ob geprüft werden soll, ob der Zeitstempel der letzten Anfrage kleiner gleich der der aktuellen ist. Verhindert das Auftreten von HmacResult.TimestampOlderThanLastRequest. + + Authentifizierung ohne MD5-Hash erlauben + + + Legt fest, ob Authentifizierungen ohne MD5 Inhalts-Hash erlaubt sind. + Öffentlicher Schlüssel diff --git a/src/Plugins/SmartStore.WebApi/Localization/resources.en-us.xml b/src/Plugins/SmartStore.WebApi/Localization/resources.en-us.xml index c3a2bca64a..7aafa97635 100644 --- a/src/Plugins/SmartStore.WebApi/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.WebApi/Localization/resources.en-us.xml @@ -40,6 +40,12 @@ Specifies whether to validate that the timestamp of the previous request is less or equal than the one of the current. Prevents the occurrence of HmacResult.TimestampOlderThanLastRequest. + + + Allow authentification without MD5 hash + + + Specifies whether to allow authentifications without MD5 content hash. Public key diff --git a/src/Plugins/SmartStore.WebApi/Models/WebApiConfigModel.cs b/src/Plugins/SmartStore.WebApi/Models/WebApiConfigModel.cs index e5c3e39e0f..23882c149e 100644 --- a/src/Plugins/SmartStore.WebApi/Models/WebApiConfigModel.cs +++ b/src/Plugins/SmartStore.WebApi/Models/WebApiConfigModel.cs @@ -18,7 +18,10 @@ public class WebApiConfigModel : ModelBase [SmartResourceDisplayName("Plugins.Api.WebApi.NoRequestTimestampValidation")] public bool NoRequestTimestampValidation { get; set; } - [SmartResourceDisplayName("Plugins.Api.WebApi.LogUnauthorized")] + [SmartResourceDisplayName("Plugins.Api.WebApi.AllowEmptyMd5Hash")] + public bool AllowEmptyMd5Hash { get; set; } + + [SmartResourceDisplayName("Plugins.Api.WebApi.LogUnauthorized")] public bool LogUnauthorized { get; set; } public int GridPageSize { get; set; } @@ -29,12 +32,14 @@ public void Copy(WebApiSettings settings, bool fromSettings) { ValidMinutePeriod = settings.ValidMinutePeriod; NoRequestTimestampValidation = settings.NoRequestTimestampValidation; + AllowEmptyMd5Hash = settings.AllowEmptyMd5Hash; LogUnauthorized = settings.LogUnauthorized; } else { settings.ValidMinutePeriod = ValidMinutePeriod; settings.NoRequestTimestampValidation = NoRequestTimestampValidation; + settings.AllowEmptyMd5Hash = AllowEmptyMd5Hash; settings.LogUnauthorized = LogUnauthorized; } } diff --git a/src/Plugins/SmartStore.WebApi/Views/WebApi/Configure.cshtml b/src/Plugins/SmartStore.WebApi/Views/WebApi/Configure.cshtml index 9e21e21993..9c6dbd98ef 100644 --- a/src/Plugins/SmartStore.WebApi/Views/WebApi/Configure.cshtml +++ b/src/Plugins/SmartStore.WebApi/Views/WebApi/Configure.cshtml @@ -107,6 +107,15 @@ @Html.ValidationMessageFor(model => model.NoRequestTimestampValidation) + + + @Html.SmartLabelFor(model => model.AllowEmptyMd5Hash) + + + @Html.EditorFor(model => model.AllowEmptyMd5Hash) + @Html.ValidationMessageFor(model => model.AllowEmptyMd5Hash) + + @Html.SmartLabelFor(model => model.LogUnauthorized) diff --git a/src/Plugins/SmartStore.WebApi/changelog.md b/src/Plugins/SmartStore.WebApi/changelog.md index 57922440d4..a2f7a528c8 100644 --- a/src/Plugins/SmartStore.WebApi/changelog.md +++ b/src/Plugins/SmartStore.WebApi/changelog.md @@ -1,5 +1,9 @@ #Release Notes +##Web Api 2.5.0.1 +###New Features +* Option to allow authentification without MD5 content hash + ##Web Api 2.2.0.5 ###New Features * Bridge to import framework: uploading import files to import profile directory diff --git a/src/Presentation/SmartStore.Web.Framework/WebApi/Caching/WebApiCachingControllingData.cs b/src/Presentation/SmartStore.Web.Framework/WebApi/Caching/WebApiCachingControllingData.cs index d68ad50cef..f0b5de4270 100644 --- a/src/Presentation/SmartStore.Web.Framework/WebApi/Caching/WebApiCachingControllingData.cs +++ b/src/Presentation/SmartStore.Web.Framework/WebApi/Caching/WebApiCachingControllingData.cs @@ -39,6 +39,7 @@ public static WebApiControllingCacheData Data() { ValidMinutePeriod = settings.ValidMinutePeriod, NoRequestTimestampValidation = settings.NoRequestTimestampValidation, + AllowEmptyMd5Hash = settings.AllowEmptyMd5Hash, LogUnauthorized = settings.LogUnauthorized, ApiUnavailable = (plugin == null || !plugin.Installed), PluginVersion = (plugin == null ? "1.0" : plugin.Version.ToString()) @@ -57,6 +58,7 @@ public partial class WebApiControllingCacheData public bool ApiUnavailable { get; set; } public int ValidMinutePeriod { get; set; } public bool NoRequestTimestampValidation { get; set; } + public bool AllowEmptyMd5Hash { get; set; } public bool LogUnauthorized { get; set; } public string PluginVersion { get; set; } @@ -64,7 +66,7 @@ public string Version { get { - return "{0} {1}".FormatWith(WebApiGlobal.MaxApiVersion, PluginVersion); + return "{0} {1}".FormatInvariant(WebApiGlobal.MaxApiVersion, PluginVersion); } } } diff --git a/src/Presentation/SmartStore.Web.Framework/WebApi/Security/WebApiAuthenticateAttribute.cs b/src/Presentation/SmartStore.Web.Framework/WebApi/Security/WebApiAuthenticateAttribute.cs index 3a0e10857e..db9be1cdb6 100644 --- a/src/Presentation/SmartStore.Web.Framework/WebApi/Security/WebApiAuthenticateAttribute.cs +++ b/src/Presentation/SmartStore.Web.Framework/WebApi/Security/WebApiAuthenticateAttribute.cs @@ -167,7 +167,21 @@ protected virtual HmacResult IsAuthenticated(HttpActionContext actionContext, Da string signatureProvider = _hmac.CreateSignature(apiUser.SecretKey, messageRepresentation); if (signatureProvider != signatureConsumer) - return HmacResult.InvalidSignature; + { + if (cacheControllingData.AllowEmptyMd5Hash) + { + messageRepresentation = _hmac.CreateMessageRepresentation(context, null, headTimestamp); + + signatureProvider = _hmac.CreateSignature(apiUser.SecretKey, messageRepresentation); + + if (signatureProvider != signatureConsumer) + return HmacResult.InvalidSignature; + } + else + { + return HmacResult.InvalidSignature; + } + } customer = GetCustomer(apiUser.CustomerId); if (customer == null) diff --git a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiSettings.cs b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiSettings.cs index 1f2207d94f..392e14d13f 100644 --- a/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiSettings.cs +++ b/src/Presentation/SmartStore.Web.Framework/WebApi/WebApiSettings.cs @@ -7,5 +7,6 @@ public class WebApiSettings : ISettings public int ValidMinutePeriod { get; set; } public bool LogUnauthorized { get; set; } public bool NoRequestTimestampValidation { get; set; } + public bool AllowEmptyMd5Hash { get; set; } } } From 0558dc44564d2b57f485ed7d0ade64f745986792 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 4 Apr 2016 17:23:44 +0200 Subject: [PATCH 011/285] Resolves #911 jQuery Mobile doesn't load the minified version --- .../SmartStore.Web/Views/Shared/_Root.Head.Mobile.cshtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Views/Shared/_Root.Head.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Shared/_Root.Head.Mobile.cshtml index 0b77f51229..44325f9fed 100644 --- a/src/Presentation/SmartStore.Web/Views/Shared/_Root.Head.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Shared/_Root.Head.Mobile.cshtml @@ -41,7 +41,7 @@ }); - + @RenderBody() From 03d9b4cf3a34abc116dba17484e79a73dbb60954 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 5 Apr 2016 14:51:57 +0200 Subject: [PATCH 012/285] Plugin list: auto focus filter input box --- .../SmartStore.Web/Administration/Views/Plugin/List.cshtml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/List.cshtml index 6458d88ec1..e8c60696f4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Plugin/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Plugin/List.cshtml @@ -289,6 +289,10 @@ btnExecuteTasks = $('#btn-execute-tasks'); // Execute filtering + setTimeout(function() { + $('#filter-query').focus(); + }, 800); + var mainContainer = $('#local-plugins'); $('#filter-query').on('keyup', function (e) { var el = $(this); From 614e9c0ba222c3cf5b6af17bc14e3e63ae42208d Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 6 Apr 2016 19:45:50 +0200 Subject: [PATCH 013/285] Removed some IRepository shorthand extension methods (like Where(), GetSingle() etc.), as their usage led to some weird and inperformant sql --- .../SmartStore.Core/Data/RepositoryExtensions.cs | 16 ---------------- .../Catalog/ProductService.cs | 4 ++-- .../Media/TransientMediaClearTask.cs | 2 +- .../SmartStore.Services/Security/AclService.cs | 3 +-- .../SmartStore.Services/Seo/UrlRecordService.cs | 2 +- 5 files changed, 5 insertions(+), 22 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Data/RepositoryExtensions.cs b/src/Libraries/SmartStore.Core/Data/RepositoryExtensions.cs index e1c4a7569a..ed034c1730 100644 --- a/src/Libraries/SmartStore.Core/Data/RepositoryExtensions.cs +++ b/src/Libraries/SmartStore.Core/Data/RepositoryExtensions.cs @@ -10,22 +10,6 @@ namespace SmartStore.Core.Data public static class RepositoryExtensions { - - public static IEnumerable LoadAll(this IRepository rs) where T : BaseEntity - { - return rs.Table.AsEnumerable(); - } - - public static IEnumerable Where(this IRepository rs, Func predicate) where T : BaseEntity - { - return rs.Table.Where(predicate); - } - - public static T GetSingle(this IRepository rs, Func predicate) where T : BaseEntity - { - return rs.Table.SingleOrDefault(predicate); - } - public static T GetFirst(this IRepository rs, Func predicate) where T : BaseEntity { return rs.Table.FirstOrDefault(predicate); diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs index 3f4f76cc20..6dc93b582d 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductService.cs @@ -1156,7 +1156,7 @@ public virtual Product GetProductByManufacturerPartNumber(string manufacturerPar manufacturerPartNumber = manufacturerPartNumber.Trim(); - var product = _productRepository + var product = _productRepository.Table .Where(x => !x.Deleted && x.ManufacturerPartNumber == manufacturerPartNumber) .OrderBy(x => x.Id) .FirstOrDefault(); @@ -1171,7 +1171,7 @@ public virtual Product GetProductByName(string name) name = name.Trim(); - var product = _productRepository + var product = _productRepository.Table .Where(x => !x.Deleted && x.Name == name) .OrderBy(x => x.Id) .FirstOrDefault(); diff --git a/src/Libraries/SmartStore.Services/Media/TransientMediaClearTask.cs b/src/Libraries/SmartStore.Services/Media/TransientMediaClearTask.cs index 4392ca231a..b67c04fbe5 100644 --- a/src/Libraries/SmartStore.Services/Media/TransientMediaClearTask.cs +++ b/src/Libraries/SmartStore.Services/Media/TransientMediaClearTask.cs @@ -43,7 +43,7 @@ public void Execute(TaskExecutionContext ctx) { using (var scope = new DbContextScope(autoDetectChanges: false, validateOnSave: false, hooksEnabled: false)) { - var pictures = _pictureRepository.Where(x => x.IsTransient && x.UpdatedOnUtc < olderThan).ToList(); + var pictures = _pictureRepository.Table.Where(x => x.IsTransient && x.UpdatedOnUtc < olderThan).ToList(); foreach (var picture in pictures) { _pictureService.DeletePicture(picture); diff --git a/src/Libraries/SmartStore.Services/Security/AclService.cs b/src/Libraries/SmartStore.Services/Security/AclService.cs index 8f0a52eed7..f1ea323ff2 100644 --- a/src/Libraries/SmartStore.Services/Security/AclService.cs +++ b/src/Libraries/SmartStore.Services/Security/AclService.cs @@ -60,8 +60,7 @@ public bool HasActiveAcl { if (!_hasActiveAcl.HasValue) { - var query = _aclRecordRepository.Where(x => !x.IsIdle); - _hasActiveAcl = query.Any(); + _hasActiveAcl = _aclRecordRepository.TableUntracked.Any(x => !x.IsIdle); } return _hasActiveAcl.Value; } diff --git a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs index fa1dde780b..38fe4a7bda 100644 --- a/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs +++ b/src/Libraries/SmartStore.Services/Seo/UrlRecordService.cs @@ -68,7 +68,7 @@ public virtual IList GetUrlRecordsByIds(int[] urlRecordIds) if (urlRecordIds == null || urlRecordIds.Length == 0) return new List(); - var urlRecords = _urlRecordRepository + var urlRecords = _urlRecordRepository.Table .Where(x => urlRecordIds.Contains(x.Id)) .ToList(); From a1504dccb2841c2981dbbe00929c7ba90d1f54e6 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 7 Apr 2016 02:01:53 +0200 Subject: [PATCH 014/285] Publishing SmartStore.Web from within Visual Studio now deploys the project correctly. No need to execute ClickToBuild.cmd anymore. Just publish directly to any target, including Azure. --- changelog.md | 1 + .../Administration/SmartStore.Admin.csproj | 8 +- .../0000019_5_virtual_gift_card_75.jpeg | Bin 4360 -> 0 bytes .../Thumbs/0000020_25_virtual_gift_card.jpeg | Bin 131072 -> 0 bytes .../0000020_25_virtual_gift_card_125.jpeg | Bin 8260 -> 0 bytes .../0000020_25_virtual_gift_card_75.jpeg | Bin 4360 -> 0 bytes .../0000021_50_physical_gift_card_75.jpeg | Bin 4360 -> 0 bytes .../0000022_100_physical_gift_card_75.jpeg | Bin 4360 -> 0 bytes ...re_one_89_mini_notebook_case_black_75.jpeg | Bin 2941 -> 0 bytes ...womens_supernova_csh_7_running_shoe_75.jpg | Bin 5143 -> 0 bytes ...0000028_adobe_photoshop_elements_7_75.jpeg | Bin 6940 -> 0 bytes ...va_ups_battery_lead_acid_br800blk__75.jpeg | Bin 4313 -> 0 bytes .../0000034_black_white_diamond_heart_75.jpg | Bin 3176 -> 0 bytes ...ackberry_bold_9000_phone_black_att_75.jpeg | Bin 5434 -> 0 bytes .../0000036_build_your_own_computer.jpeg | Bin 13754 -> 0 bytes .../0000036_build_your_own_computer_125.jpeg | Bin 11019 -> 0 bytes .../0000052_etnies_mens_digit_sneaker.jpg | Bin 21286 -> 0 bytes .../0000052_etnies_mens_digit_sneaker_125.jpg | Bin 15006 -> 0 bytes .../Content/Images/Thumbs/placeholder.txt | 1 - .../Content/Images/uploaded/placeholder.txt | 1 - .../Media/Thumbs/placeholder.txt | 2 +- .../Media/Uploaded/placeholder.txt | 2 +- .../SmartStore.Web/SmartStore.Web.csproj | 95 +++++++++++++++--- 23 files changed, 84 insertions(+), 26 deletions(-) delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000019_5_virtual_gift_card_75.jpeg delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card.jpeg delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card_125.jpeg delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card_75.jpeg delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000021_50_physical_gift_card_75.jpeg delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000022_100_physical_gift_card_75.jpeg delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000024_acer_aspire_one_89_mini_notebook_case_black_75.jpeg delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000026_adidas_womens_supernova_csh_7_running_shoe_75.jpg delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000028_adobe_photoshop_elements_7_75.jpeg delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000029_apc_back_ups_rs_800va_ups_800_va_ups_battery_lead_acid_br800blk__75.jpeg delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000034_black_white_diamond_heart_75.jpg delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000035_blackberry_bold_9000_phone_black_att_75.jpeg delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000036_build_your_own_computer.jpeg delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000036_build_your_own_computer_125.jpeg delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000052_etnies_mens_digit_sneaker.jpg delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000052_etnies_mens_digit_sneaker_125.jpg delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/Thumbs/placeholder.txt delete mode 100644 src/Presentation/SmartStore.Web/Content/Images/uploaded/placeholder.txt diff --git a/changelog.md b/changelog.md index 303e27e949..28df4dfbf1 100644 --- a/changelog.md +++ b/changelog.md @@ -8,6 +8,7 @@ * #886 Add setting to hide manufacturer images on product detail page and to hide default image for manufacturers ### Improvements +* (Dev) Publishing SmartStore.Web from within Visual Studio now deploys the project correctly. No need to execute ClickToBuild.cmd anymore. Just publish directly to any target, including Azure. * Exports the product detail link including the attribute query string when exporting attribute combinations ### Bugfixes diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index 112d13d833..ed9af3db99 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -465,12 +465,8 @@ - - Always - - - Always - + + Designer diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000019_5_virtual_gift_card_75.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000019_5_virtual_gift_card_75.jpeg deleted file mode 100644 index b46a43618f632d669647c7b33dca2853700c79a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4360 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 diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card.jpeg deleted file mode 100644 index 231fcaca34a89f0788b3554375ce2f04d08fac34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 131072 zcmeFZ1yGw&*De~QMM`msOA8b)#hujf0>vFlvEp7FS|}c*&`>lKNpW{~x8iOI5S#=J z5a9BC-+#{i&p+qPIdlFyckZ1zZ)PQVCHu`Pj2OAgX!2?{}`>O-)&jWDD9*{p3c>Rz*^aC zJG;7jdi(kZ1}7$`re|j7<`>ps@Quwa#P-fE>iFdJ?EC_KdG!yyumCv!)nxxt>_6y5 zcCQyUE-nr(-aquh!uGls95UPoPX!*5zt+GrccpkH_zj=(&5x{#4gyvoO(fMPw{gNJ zY{Kj8sDG&TPtE?<6bt^}((Jz~_J8ZO03gD_y6-$3G5`>8d-IisfjFcb+J4F$pXE#P z`&PRekjxZvO!?85q+fIV6~VV{;8S_L-<2 z?;U`f^Z(fO|Ji=|KX(0}*prIMxB-U-_X{h74MgfD5VB+z*~;q8+EK3L3d?-5O~>Gh za5J-ef?6UXC+5>x*=)mC8hy1vXDgPidQg{nRyI~q%1jc`GZV{95Z?XCex!y@+?UE; z2VA$ArzV)*b}&~}e+Q6l8%$Lbn1swrV8jQ`qywptUU6+}S(!M}&wdHm3hkP5HH8}E zIMUgs`ncp-J3IG(3aw~JT4k{Tz}kSfoOgf@!?RFo-4&{;ztSQP*b{dKDMcYw}=2B}@Hrz^=hkVJHq;f*wFG483@sg63Ky zkr6p!+K%%Ae7Xuu1fKWo@MQi}u-KB1b&W|F{LVyD62#j^(YE%RnNr$qlJ6qY^FWm` z7hB?tHmA1hA%nqUwdlGJ&G0)=I$47H0Nq9J7`=VAT;hj>6%IBZmrbe=BF1({`lesk zBcod~*9DtZNmaJ|9FwY!vm4iKtQV#XS8T9CKgnj*ZpYV9u5~vMkk!bqst1kgnc@jZma$~^HvFrQ_^CsbYiSE(R| z?+)<5oYIwv$mg) zGMsgq*-M?BtgP7*CMqN@hL~g?s&QAYE*Fet@M{KMs4l-^00rn&twKHS0M;5cX&Gl1 zKa;LJswUmflme)w#UYRX#xe`$8G-ZRPg6gcat-vba}Y-9%dq>{zwY~`rh9&iZYDqp zwYO19OE|(-!y!C>{ms`GYZr-&vg}A#7Ja^2W!00_W!QMq!t7Q0e>Z&Jw2UBiT@HyS zdH>aQCv)s_7MY0TIZs9RTnnOmXZy?u{FQvndA-P5Th{$jEbEbd(Zy6hhw|(ts#|z#XnhfDgdbAqs)xxju(i+$# zH9KDRT_VNs8y@8rZu0XUcu#SqHBvgHO7I@z-WH`1b^~o0j$zy9+5}rv1d>)nWa;xFg>Lq=av~A`afBh{@h#3ZJxme8U6yx! zIF&rfCjH~fQmzKAq-RvzMLdi1n(hUGD~GLlstim#q!T%BpehHSt6Lop8l?y$H_1T# z{W(+d00xAFIokIQz|S%*%cem8QYCr)1Mw4KL0O;eH48`xo=nGx&tVn{dl&BYYpa0` zK58vaL(|W}=yT}jQ8rMunu~|OIr?F!0Oqb-kr(fu$_ZwcocG6yI^Z|8HQtr%Wp(aRY zOU+uQICYX)fMBihU-X2Ts3D{fpRc}Fg#3lLhkQF_{=-9TQ~({_@_AXjZ?&ie zYlFv}gkRQMHI}PRY0lYy9Z~6GGp;=Yq@4MeG#%$a>zY7P<+{@yg z?xw`bV0m^lHq%xq%~ujREDc>b&-}t zM7GZp3!8P1-y2FBni)sZlrFb1z9jl>!E^_RyQCUH$95Vmi#k{J|6N{a_WI(bs4PwW zq_nb!%^_E?S+h5Rr|6-cj#Y;-XtzTLlqI8`J=t1fI%9Ve7Y8q;>p(dZSvN2V61w!R z?P-K)sCghbufev!Bk1c$bvlf(T}R)NekdZwH~(>Zwl)Puu3{+a z&VPiTXWjg4rNO*zfQX`;z|g+6IOc^&@KznlD^ycP^a|!NV0AlJt0H43)1fAU{kwK} zb_slv82uee;QVCr%%nN)m|l3u)j-xNZm^>K+`GAn+6*ru+^(8i(#`(5vQC(JAq12J z;>?ap{CfWe_#cVBMl{NGsr3M8Wc2H+h-e`7~+{%^{A5WMZ)otFJcj)(RF zzMh(BT-d!S#U6ieBS&k!Ai1$YG8{EpKjX3IvUnI@2?D>ebZU*l$Mm0%xWQ0p23$tt zFG7z({vBwLY|)PlUQ40cP(ysV9=o|Gb!W$;&tk$r2q(%l2VZ=DYPsp_@teXsFLc2U z?>9}e?UqX9B!$zwT2jGDshO%FYrZGd6*`)RCeO1E{zT0tHLgw?d0cU5qA*X^2WdXi zu2u0)Fj$m}YX*?yGZqC;wXzB~XhUEh-Ci-*hnM=ypO8xWk_t+$p(jUp8zj7NGgB2T znrhWZOa%rGRPi^=(}^<#75-W>!0T{+V}J~!7!$a|nRclEEZG~dN}lrfoHwa@s)jT{ zHuQ@sLn#+OGt#a=%mka>_7a*pbhl2WLndZoklNW1`#Dqem_{;8Fg{rEQgMwTwmb2w zH%@H&iScr8k*7zHAMZrPhJSk*Q$Dd$pIQsnL5~HU*VESDprFi?L);H;l-N?UhUz`BZ=%~-x=*zZgvB4j3irgd{5*CoGu)gsmxRG80k3`RoNQ>`RD^P zg)XP==cDx9oDVmJB0S8=85HfUWYw1DU&(hD?7Gw-UaV^%1kN*$4_kK4)AP z=LzK$&##HM@?@u~^hA2_h#nRG%#h!JRDdU^3^}t~i?7PFqc{QokbG-XL4n0f=AB!n z8WDjGo^JzJ@QTCF6neTq-L|#JAm)32OfPVJMffJOrf3~6v{k}(6&a7VvI2+Jw8i=J zU~et3ppQ#MTF7n3XLxfb=h}04bC_r3UvIz0$OS<>%OY#;0O5gLNShvJg5mR4F0?Cb zgt}Lfx4+Nc^fNVbmy2kaP&ny=2dlQ}>y}i63Cu+&qU=azjlZYl5xlMADC`bE-7F@p zX=^Hrg#3U#2z&ncoPKdge6-+?YIQ~(optUnQLSDdgQrf4UChicp#xeA@=J;1cYvrJ zwx;b?v&_q(Q^;D~50o`?q|21{O9Kq60_j>NuR8-Dr>V}9NOiLCn%vSq+iHfb_I~%s zgsBgvn^Hp#@IMmPf6kU!jm%lM+8+Qdcz=Vq4du3FLoPM{V#!f{_+J+>*F(Tn9h%S~ zQGs-sHJks7mktd^UA7JUJ`Z03;s&y46Hbj%X>uAcygJ`~;yBOKI;6K_S zjYBK2fm)j~)^2i*)WC$%6uPLD7s6>8Nmt4mwfhHIzbst&xb=M#)DJ`~n-_f-bt?U? zAffe^o)WbE3|NQ00e-P(ruK_QF7GG%m?XLc5zMcYZg@I9`6Ib6H9T1l`yb(3e!#Of z@8A28ImYPicSOIU>`qv01JQ3HC-JW!BIvU?-L*gFz1X4t@$rZ#hra{_DbVg#dl{An zv{4#Vwa4r(iu}FerN*-s56iGF=u??DC@Cw3lm$qbZMViYqjb}V*XgB)8#uB#m_Xg0 zS2p!bzPm-UoP@N$%744<_ADug~-XsOr4*{@Px5qcd#fA6~j4)L+pKS*U$QmD89x{u;^J#S%?Eb3_l>9gh^Z$ z->Z+}PWAsZvZV8{S8J+=%ee%|fwm+aZB+vk%@&Mw5{+)MWQBL#a3Ai$p0`pyz}u*6x@Hmlm@!fQmDK&Z z^35IKu{YEsP`K4muNNXb%@|S(g_BV1(tl$k^C~q{d#In_!a#&zuq67_FnAviBRyC{ zJ3b=+)|8{)i!u!uu)hhF6sSD-{37p44E*7$R4-{gXq%qDV|^CNAi}MJjL?vy`s?-Y zLs-gE$E44YWn};EB^#o!oCO;nA?@P6vv-4ikLlpIeg5`56eV`9hGM^ZbO*p)9Yay9 zcZ(4hxqGgwx~=nJ6!I3sdQZn|oU2TdbMS`ZG;P?p+A+8;iv8mEVZoJ6o%qA1JHR{w zu3K;|cspT+e=BJ_p+XfGF0JI4>)na3q}_@HMnFT}qg1O$rsL#IV)wigQs zx75(e8KrP=o;gBu_0odU=8Z8yy(^F6=v+i?gQ3bILPR3saJMaZVb2uB?6<&FpWmE=`mprj;MFl=&G z^&jiN{}7@6PoMx##rxX)DdGkbzg$o3%ubh`?0+@nZk~PFiI*a*=+keF6K8BAn`yet zL%!F&5YV<9KkTy(Sd146?mj*EaRi!8Oh#P3(881M6Y5x}meXw!kq8R=X~kW6`Fj7F zZZgZTNI8RR^xE*nDw?7aqZS&;D6m>q2W4>$*8dip_nTQ$-S>p=EezM?knsHPR}z#@ zSqDu&8z)CP5!r5lH=_cKHa`BBkV<|0t4mU&7Zni6JPy{13lQw@8or4&$0UJ=#S-Huq@)$ zIanBMKo@JC%n?GWtTZ?y5HIEKbjTVrSl##(H>A(*go1To5ERs^#9F5H0~5y>BV_p< zGc0;RsJ^~9%eaRc-s_HiF=oXcJA%@La&heE7-r`|%dT^$`5f!`n$xE4U1XxoWVt8# z1p`J(?Dn=r3rB&iF`hg8(mMG|Pju^#r@zs&(tLCUh2i{$$DWN1_z4#qKn!R=PrNK3 z?)?Xl8;SE=vfl^k7t)Z1y<1gr0PuT&-)ygLw_e%v-I*d8XInL0=j(A*xxJE9TMvZ> zb&@x6$bwG78QrKwgr8C4b^q&>wBLlUb|Anj%y~Z?Ss>~$M6s71Bmg4m0%ibFx5j~) zW5R(Fn6}?kqX|ZAF+u80hnD~lP6FW4i=w|XvCA@~3GkA|y*{6T!VK$n-mLCn%(dh@ z|Ho?)yv^#Uu&d$aM@Hc%9%c^=jpHj;?2Bk0%jP_*9I%DPtx!qCV~qqjqk>=};hoH( zw-V@-{L5z~57A$)wdgm<5h~~F`KE=DUll>!Xzr+M--@JgwC-v*DkiKLM)zRkc33_+K+|1d5I<$!$^A1Za z>q?>W7584tl#w(jwb%T~(-~^_qZvYiC+}Jmou%6A3Dpe++R}Miao+Z17gR7^+juygrR3#7n0~H60>lrac z7@>c`4}yBMY7wG|t0T@iw&T(%@2V#bq|>B6Pq)e`QMeNwZbry@F^KNsuqn#E$1V*0 zBC!Pg9+!NQgexjoay)xFfBnp#60MbWJKXj-JFbI?c7_c#``rQa7IyR2aJ<-4rNsAI zkG#6v#KffYM8T2p8xfZi%JGMO7?B(BWt)@PoM$VMk|)Z3{Yi$qJMz!*b|#s(6D#}l zSl9Jp!UVxuP^x|0qtSi22KLePqe@K*@NN9s5xqALa}UiZAS+2FG%KXiqHA2AouQqX$~Rg*O%ER=I1@!wY@R4_|Mu()JgWPY@$vd zQ}jsk@Ypi3O*n{*h%w5QLhY$0a?;}sg`UO{D(8O?mnefrgeD1{utn?&l7U0GIac!4GQ@A2VsB_pV^KxrrBNLBqOhvY4`9{-O#E zoVrkDOFFQ>Kzpb%mKCbXt42*LT|^JGp4v69&;T1t74dM1g?6vAs}cXH&@bruoa0=# zogII;ov$KF^-y8h=v(amdO5De5l%YN9vxZ@-k301-fuBfl?(D!mGktnyAO5Syhd_c z&nHYvpeL12qSzl(Q1V0q*m^e#up;#@UMyd$xVGRtUk{ddBgyLDSL~48_%fDfNBOhN z#_ux*vUgY^^S@w7E=LS-e+Gygta-20C(hic5bPvf>9X%-`**%4Ib&j>Rb zPsfQ6PW$tT=0{8(E&}Qy0%xucgx$&woZPJy)H-VVS=2#y@(kFL01=4N}qNHvOOka_I^a*pBS#NIH z*@UWuQc0Sl$Hdj=pqz7!3kzhQ+;+8*&;7Ek`0s%$z%l+=v7=!xpK|Z+=m+QvvP9yh zX(g@dA2qRm+n=pNFDr+%{ z;m!i%!e^N(UffhhYVVfRan2v5Ly`~nj#6*A?f`44%+)V%Qtki_V-MAfc5&3vKB!Xh zKpON&GPoi{bBbf^yQl5!up&&f5fwa*c-m>SD^x+Rz`Rf>x_@N}KVdmpjaPUhK5F05 zGjXny@vBK{P|~qxc4fIN=pK9Hk#<{~?N|y((acfpdNIn*y9k)_F=uubI~ydOH;m=U z1S_mVbz>%461TIXHi3+UoY`OhdqVcFDEHse;{el;Z6KKM**|mnpZ|#-{1+7ApP#AA zQn9{qcL4oDA-g9@7*$Cw+J62M2)bP+ppC?RETYG}Jl@k)XM24|&Sa`gH-qL2x=@X$ zaCO`SOrz}dH#+fvauM^GkSC!DW%Zr*xI2N#MgIWrZ%Fz_-;-IY{#`TDBqm(ctC!v& zT4u(|?jQ9R3?JA0NPwztTEx}+31=-Kp?E8(A=Wt>igNXK||N73Yq#o!ItiZs4iuGom z`cl?c)s`FfgyHn~eWjpp{&df1?ncIG;Akqy^HxX9X8IpUUb=}cFR5%Ku+ zEtSMUc_B-7)Ivce^+~TDx&~7w&ayDLua+}`wLB66{t6sTrJwBv05bw5An4iNhB0Z@ zWOV|0b!Bh8eU`+U>4ILtU*KI4Xlj+s~9wqJ{cJ&z6#wBga6S+!gO#9~B?h zqfdbtkU<2Z3EM?bQjgCI_V~?4?atuS+XpPZsIPbyXFK}X92^MiE1X~wW{tRZtt2+w zbf>Y4@*rrSP4uXcv_~GVu4s*(=;rf{$wa4vfxIuPdQE!`ryhd)Pv}Nt@c)3z_%tuz?dQ_p9Gwv*58&jAY1{JWaw}pPhy`8X&kw z8t6|pwCE1-%gNV1d;T7^|HhoLiqvLwLnz!NIqmZ;J@d}%Yo#1NJXSp6Gfy>|jd<`h z;!BJY+rd&%;#avKX(B;dx4ACUF>Yt)21s}ZcqNCp~McO09uV z3&%=+H+F#KcdVQPASF&6fDG@ZQ|}(~7H%U$xAkyz|L_i^pF6T1cjy5< zi6qWqBSQ8BalJa0qp#$?1CV{RKMcG)%9uZDic6DiCiX?jaJHWodx!L@3Bh6?XLa~2 zI}l6ZDc3%MzMm*%${7{VMhb$E9u@kbCg!`!>4}~Kj|dp#EOQR*)cnubY1<;D)gj1b zCYllMbys~uPnR=}*bKTjq3*Z%OXa61s+PLxU1f5wf{0kU4Bw@q1!YaQw0nl-{V*Ck zDdR$)lNdJa|u9Rm%>#VG7glBe_ zcYyQqsb>>y*6Oi!35|tu;i=UCS;8ulYSAGkl8b0JVmD%e;MZtbhU^Myg|0bUEgkcS zu}V7%C}*!m0pWZST^cBd4bItWKgcJ-na7kjXju*R-+TUhoi3`@IZ4YUT{}m7jPbRy zASh82l;g=+J(c4zsn&-G$52q5gHZ}mBF<#@4r?@Q#G?CR*3cZK_m%(by0^9Mv0aD1 zT$VzRgcwD4|3H8}(Zpc8Ga1zfxxl{I5b?3{1t8X(FYz+ZB`#VI^~eCN3U9;lSNyyA z*^~LNc<@htPAud?`G$ z!l8uX4x;W~H*%uBo2vhCOH6)#?s#E&En?)fdyuqreX1rX9s0A*2lqkQ?;a zc_X|%F)OjMF6VXtNK^U@ zB5u_(>EXyO-MiR!aJ8JS$_(t>_4Rx~>4n%+#@zffLxMeYuH9O>))sO5)cdXSD}-Hg ziRqqZiV{QRVXzpdn;3V)gPb?OcgRHiik0U`QN}+Lu=jPO6SWl`diwK6c^%>4vYD~r zE49a6#b(sVTE?Qn=C?n&6q;G%vNY|p?dLnyl(u2HjhUlgv0gp;iv>dZ66aK$F~;R7 z8_M_|D{TPO)HU${sXRCD4Re=kOoYwY$C_ojjw7FrewZ>ee7%AQn`*|ydk%n||kcLzNxTt`k=sS-KInTZU z%hLJF)z>REYvtKSu9Y!yIt);Qnz<kP@zZ%V$NO<}7aY zwA?j49iVuSfW=X3f0lTQpK<*rGxQEntpEzLG4axbFcbLOm*1`+O{z~EYAVs8zdP7_ zPCu|_gks)iJ$$Dh+%)nhN5BH)3eUe*Hb&|FH;0-_ zMb%LCxX;9LB2bBgOE^&&s_a=PpH8*OVRdU&w5&YQA9$s+0+2N%YlbMo^Vx_A+VTEc zH#~mZJHB|NP1^lC;4;k}kv|x$;2w8dC#wo`rV*4*I*+wXUXzY>>k_9OU^czK|ISQv z=kD#{&O8FVR41jv*wP3uQ{P-@i$_oP?T?mn*FFl{D6jVxQxn;_mQXr%_bhx}=eIJo zu&B}`bjpwyLZz=&1ag#DQl4-W%f6O0ko~oCv3qbpm)R}Ho_L@+6mnbo%mVk=jrMgv z^YWqZIaQCT_sZd$6=mL!i8ULp^=E788}Ksm0ok*5=afnz$eL?xS3#GTwC1H;gL~r6 z4jPe);TMI!cs9zd?7crpWm3Dm?0al?Aoap7Ajgeu>n zdAo`6w%Nb)O|xZA?pwyKKJ78>nnLXyZR?mA%gbYlCGGxspj3sE6tOrQBIT<1>~;Fr zz=k^joi~;|puH?5LmAAttDT{+;^ww`cCtErmZh8_V(0Rm`a%biq+&XwTW6a4zEV*Q zMu}LZImNr5ny+>`$-X7D0O#);Nr(#W);aFQ!eeFd4N~8|Cvp5Y@dUBd>CqHw_1geO zqHH)5&E<7K0%s0oB0>grsbkx@eU?1x?w)oQxpAA$5Ru}4w8KZqZ)3%4Nzc=wcKFJ0 z_gN*qE1M5iB=etD1O`EcM%}Y$J|h`#R(>JlU@_3*O4|k2J*LMqc?LAllqpjgwNAf? zJ|09R4J&V!3ONBTmRTX1J!AlN%ZdC$Aq5RD`(6;yryRqeBd(LB_cAL9hLZ5$)=K@! zK^>i?vXpMjt$R+nkeL4I_M(eO)F>L>%AA$j8Dq>_LA9wI?KRVFr_#Ba<%yl1K%+WlT(`Prx@zctZYqFVK0lhLG1&Y+)1DyehQs`5D|SA=&^Jg0j*!b!YVx^o?rJ&xTtK zBbG5CP&Ao{W~*unys{BYuB4rWL!tNR;8}vQKz;#NFBdx%TS6u9tB^xut$M1pfohZ{!kItc^A>1t zp%>%Zu89{=ir#h#hE7iCgUh96EVIG0=HSmyV&+1>70i`6gZFYvyF#O=?h zcsn~H${V624)_la9h`oOO%qgXWV?$M`cq*W7h?VD&z;ExR=cDg0?w?IcCxArB= z!u{0?f~=Id1!+&EE>o`+xQ*Du=dF+5HFO9-UQ6ErOadsq!^k^b)d_Xd&Zmmjh4vYE zi??TTEBD=_3|FW=f0V!!zUH&Sy#r*+#x-<$c!#hQ$r^N)(u;MyQ8jeQ5DD%%HnN5) z;4k_qS3kSuT#ZC#M%9>MqszJiCH`h)&G>Va?5wa%Zhob5uHKor++x5cppdVUgSc4| z`p%xUAs72n^?HEew~x_3vZ*AULBc#Tmd^wm7fp>{Mdlj_W=XMCx5j=R-6s`i<-ohV z{~t19ff=_MS9%8u81hWGV$-C*6%x;A3TG@wxK#v6sU>g+h_~8+F$Sr8P4EgxMOaO} z3^~5fO8?%NoxXYQEr3X84~L~QGC2ehPHXo1iMQDrvG(5Qs*e++yspA{cF`%yW+#uN zKaER>9HBS6fD6XJw-GL5$I%(rrDrq^S(=o7%u`4TVoG zE(BV~>A?UtGEhkWT9iqyC(Q=2H!UlRB`QIZIIp~HP2ta#b8DIuuwnaJhGZkUL}b-j zw$Z%c!;kX{tbE*UPdK&IcwYgEsC8 z3@_isu2IEgyd|qAydhXH0$?k#aB?e$c{(=GuvL*#zL3p<05k3YXfB&xBeO^#nR`s;qPf%JRuh`-J;yhNm(iWQsb5s(RZoEm$Q)LPSMR0HI!EZ>^QvOYZ0Ml^ zj$N{%7;{ZTUw|p8*}xdzrSs){jk?B+Ofqe6rzEyaqeX(G-jNN4d=@T%a_+jvYiXuS zAMFJZ9j@vi2M0U0T9MXOFCI5O_wN*T)N*;MM<(Y2Eyt%q^L6Zu?VPpCJoNSZ)WnSK z{}wgY_1Gzh93zdAEm&tJU9zn+ly0mAzYO!5EEA26)@ZSOli;XCwJfHpp?m(Hi73S8Dp*e<&<>1*a- z5?CKrQ>UHC0>v21^1SHPMYwiZaO#G)h^b1(W*7@sP}+&hHK|?C-PTKM!AbDYJ(qwmG6n!S77bS+8gj5;bwid!i!ET6u9sw-v<PHyZl-6X4IIZc&>F z7zznwUZ}SXTi?u>_(ZyU|HmTT_VuS{(js)pg&)p;<~Q>e-qZ04y?@zxELpsMlcOVX zK<&IVrXHwJf5y}zM_H_$`IEFk%1wzoLq~1vlRo)t35j-iJsxg=To4aWb=r8%mp1Tu z0KLgfoF_D{gQ9WTNG{JBmS*;R_0O30;IM%~`S90xHLQ)qZ|Q-60Pj7sq#&fOjBy9j zVm&F=5_i{(>;=DQ5|hPapyCg={J>B7)_cK%qv9Q9Q#*kEfp%W0vqv>e?sEv_`52rZ zLp7$Y3j}xo4z{}N;UwZJU3Hv++71!4Lo}>AK9P~?nl&>eR;bzyL z2wky$6q||P%7igRHWxh)N8xog>P0$|%5K#roRgd`(vmQ@OdjPlR3P_#Do7@Ya=`&sSJKHgnFigwQ{b0)@RphBQy4N@}d*tc&Cn(<7 zvr7aGj20?oE+KeBBW=9$cmTNaktgsPPea0;lXP)}Kc9tzmEoG`< zP$dL&&UQ`bnZ_7uy4<6uv|JMfYWDi06v`S2c4K}eB%2rK-)>@B1p>#eMo!(m-Sts* zUuSkE-`&Rp;lkH3@-xSO8;rDP_8p%XweV<1k!>#JmM61)UeJ!G2!S(`1d{k`q036< zy}jC*@KDdI{PK+z9Go&0T52YWsaEYx?DT&9HR^cKOmDC}bZCsJ*F=viKQb)izaHh_%L^-Aw&CDTh#|6KkV*hORgM9=1|EG! zT}$Wu3NWHbutGNv3g4s--&RFrDd&Pks7z}g3LBo6nx3vJkg5Gly|7Rf{S_NH7KDEg zh>M{^igosLPU4xk`F*7bY%xqJ{af~7$f0)&HY+apsPR=tAnWhxbQWexCo{zc}xGEozlCN660mW zobm}Jj2CT8}aUKo#sfDYTN%zJt6 z;yv9W&Ob{5P=i5Rhce6k?~FbiPC|Vq%Pnn8B%5QfIt-P~T7Sp~q*>qclks`cR_Rmkx7$@05Z4 zRgolNZ8kw<{t`1s#YY%N_+ku;w^^6V08t}2d(KS3U9>3(|G_@r!81K0A9lhMshF={ z-LDjaeISlW-kF_XL69s5wH9^E?P#BQIkJF5kGmJD&L|nh1J>&3_tSu;TEp zA3Bg0?AxLZV%VS<`aIpO-3@2`+M|)1yM&~vEwJEtVIl)zVm?rFtA2)D7?5E;sq5c% z2~ZH1&Dl9CQ$b6hR1DEvD1{C)iqi)|#%Mj=VEZBAPSKpKCCb{F)5*kI`Q~z;cXq4C zCQ5%)S$L*@N(;e>N0n z3o23J)b+dUve^89jDM+)Yk_K8uj`Cs!kD05BJ;X{1WV|=?o2DcdB3xH*_f#f!VW_;vUb)jn>#V2ada?n6tPX3QY zgi``YwkkSH5_Xz9*BC1h@gYo9U6Wu(6(|>$FTfG)FI!)U=37QHlD8DPE80 zfs#Y#GAqBd#Q{&QB&oOO(UA;=C0#$Q70V?_c5v1*_f@Wen1@r;CdjhzGd{nb%=y`c z_pO|$fa_f`L4CXUVo7MT@26s42UoLq(hcyJjZ}nywb^OiCrO5?`IQsA!Z*)GTsIeN zbwwLJi(fI$tN4j$$8leJJ_e7mS+~9kk7XchufJve6=qCauzu2*5}>a)Y!_s`zCH9H zIEhL7^X8mFR3S21}d^$juPe|{ByHY|l^+50+f{V3j^t+H*b z{S|~`v|@WvyRT{Fmu&n{K^KQH!+@4R_hJ5zcm-BgCOB|PZ`TY%#lO~vVPz)w2cn7; zD+RVQ$sJ=OVbKwvxEKNj$4kcf4mlKOhaMO1YdKiF7 zHa}VEf{eTJB64Wmi)97Vp(@njZoao$)ofWF={w39N}MOTLGr6e)v~B7vDh{nN1-m= z+J%7dC|SB?V5qb*uj6`6{ggvSZqvAk->)$7EuR;^efTyYx_taeLsQs+7`}8jp;$Fy zn6D$R5gS%!GqE~=_;HYMTRC2D2r}e6bX@pG{#Z&K52F#wjO(2^_Os9aVUD{X^JBaB z`UkqTQ&9^?PVe;acmWP~0G0PpgV~UZc>-J}ZMbZ`G}$iHD68z`zE}l9;?DtNvd{$q z*QFw0C;N>~+-l@KWAj_$I=ad_^j+3W%bmnI8(p6`W%XB-f&HeuF0)rHz2a9t;rs;% zO^9i%VkP8$Hv=f;OTyb%e4-f<}_+{P$ zA`kD05XGsfOa9hq`>dPa{Z|TI@(lt^KSXY;*kW3+SezAq7H~fzRiMEQJc)ZFNMP(o zmI#rMsoECw(>uA3pdW}%r{3p?N~ia472;ka)k8d{7QD7pqWz+WV%@8|2EV?+)`zqs zu7tMRzUyG*yJ1#e%B6);XB<5|D-5j3*k(p1yVV00TboOOGZ^yhBG*GYrx#{AgYL%a z(Dk>E{Pxq5o(q_|Ic}$!Wk%Dy|48a0`8C(l=J%TC9Y7*);y$%y!-O82{EEl;640k? zcn5fGSY=;Tag%0m`zO^l`|?G^z0?2dKRW$Xlq~|gk5%OV&f&Guo{nJM+TM&Je&3E| z($|p8C!WWmRZy$yen0ZOZMp^Yy+=n^l&D11@$5egLHB07H2O|d$PJfj_RV-4nyTyk zu^Y;cc-~EW+_)=FdbT{;8zOh2gL2J>ywc-V{&y z9;F#E?GSlHpT?nWl_dJOdCjejhvAfSZtnHKP!TqM+3w;ke&4kUus3erkU3oPX1xK0 z*uN==1MH4SryZR!6RB>kqTlMJ&?8uOwj8-q#W7$>2X1=zbf1(^{x2fnYX5}8?ET4O zy~cpk)1YoMvxROF(J`HXlRg<+`$1sjzo+y3m+!d$dQgMd+f(3R6yRSEl~DXwCB}a(b#q!uQ0kXa za7w-d07LzIP}FLvgXf1&h%}YnAtkoQ6^A7~2HP0J@ZO`$)Mr!umB(YVazyqGgyx(+ zVQ`)-b0$*T7U3{vDuJb5p5yndVww zJdfX2nMa3<Hf_mlc$&lky}6QkUWXk zE4zzDPKiLrXw9euFIBT!=DekOK1z)bFaH}R&cD_6{Og!j>^#Rd-kA(17U!CqB93(8 zU*dh=bp`hjP%N?SE4KU(#tFXx61<97uV}5=AlUa@SR6B)z}cWWj_*M>*N;~5yt(~I zs4*7J!TJZn_3TQgV|Z0Kbi1-oA)?+rOGNMEZ3hv4&o1kG{L;m`3|6VFrvzo0+o$`i zX)?5*Qty|aQYe@5QyHPX9YiRPB&Ut_#CW+1!B&LsXKu!c!Z6<6B)!CS?oTLijRwf9 zGDlSyYDS^Amh&d#NHWp>-4x{(oWOmx?hbHZ*Y-e~?G8ZXk5%v!-2~?`sWhE`-)i&C zU;5e*u3Mid^>La%Th=rM2kS*|f+m*E*uATOJ9d+ed5dE0YUM$zqVT%wZdp+_A<)}Z zOYeg$xJenoSgGK`>egz zXYV9JWQhQ&gMk8YOfdA4{8Yb(2`(GOo2#)bl6wz)jGc(abN9;Hiy*p^!*{5_x77X6 zKU0dngvl>Zm`FPq-(ajc|C%E;xI3ZOg(UsaEz*a>x_Qwm$^I^ReRyD>%O)bx7aN1{ z0%-mtTMcUcUMME0;)_pdE@Z8Hx%AbspF(5A$S9Yyy~ATsYfGyo zsT=@pD1X?hU*v9fel2FOU{7+~(VSmaRc2bGuXJ|FlyI^+ybveYAwb2#&-lhmPlv@* zc!Ct?JqFml1;#qI-ZwQc+-PIr)hp$xE`n^|wU23lwcFPe;_Z0F1v=gUK2gLDF81y5 zdq5OQoGdx@Kw)4#gYV5YmAb`bs*lQ!)3*oaq3#q6Iy-E+;`@|RBV<{eq)$C>s|Kvx z>me2LyPNWngF5LPs(+_5FXk&=9!$Y%_x6U&FyXDiyx^~09y{Bl0MUyZ({n~RZ@gcr z^!)I;iE(Zikvfa5fpYTrWUv1#xfbM>COrpwPz^ifoh;TJLJP)07euy&7}zqKz7b zZOSpr3-eL+D<~1ohR)Wg8=+QVi9@(D@Ak90Y)#0x8^y-qnh-mQ0P0I3dCgii-8zFc z;tEQA9f3FD%3ARb5+A$S2)_L_4blYojCdnXxdx$}mW~!)+?dS2_uSK*mBD1n)z;d0 z(IqGB%Nk%R<=nUh_bN4&T%7=cM+Ts+TW}#m7a4TDDw`3u1x9ah*pab&UtCflTTD4z z-#|%0GzO;k6h-U56xqeKoF6jq3pt|a4f2^Ywj6?%re2QHb487gr?o{5NeceMz)KP* zL)r;m4qUUq`jW;!u#hqr)4E_iRid(cEzr5yLRg2a{Ut%X+PNJ(2+|tahhqPLvT0DX zr#~mrO7=8&*^KzDk{1sUI!K345d@b(Op?4Ro_KD**RAI9mm*79+-74Jxi`Qw!D zV8!o4yPpDx6HU0x{YyxXupFmyBe!(yoc9>_GW&c~q@@kcgxcGJc;urXO5L=TbB(GZ ze2q$DGeKr0Pw4!o_VtTJdscNHH=id*F2o?pL5krv7a?sA-`2!Uaq%Vzqe58}4@H$8g?d#E055?NmKY#Y^ ze-IGJE?=x(Ep(F!8n3nX`Wx;LRGRJ6U^t()hS-(fuhfq^`S+Kf7e7_eREIe zuSo|_d1*|M+M8jGm>?SPN8!`9cX>UvMvi6owlLRY%@hNZA&-~!CdOY1xgZ`cZ>L;1 z`47yf=4|v|^1FJOFUg*j!qGjg-e<_yd)X`sD?b|&Au@d72HA{o(Qur`jvS3Z%cr83 z8k_GfipHoYMUd4N*ZnhJ2->fyF3mx`FctKfz_HUCle0ZQXLr6gi()s7?S5^S>e38~ zYl@N@wbr#8n5ir!xI%T~cU&UR3VoiOaPDn_ zH0`vMVqEbXo3#u~CBx<1pklxjM0P!oO$tJY#T`Vjm|>};z^h$=tGPev$kKhvN%Sz$ zd;{s`dpEN!$)TL2=J%XnS?hV4>xriytwr39|6^%TK1W$iZ%uEiY&>tl^rN(hUYbs7 z&otcec*B$TPJWCgi;t2mgmzjsc>DkIdS-XGLv`nN_ErkQJRls zxut7;%wGb2_j7L<-0No7FCIQbw3uud=j{m%#z-9G$d53yJc~<>vlc<7p=LY!S5VSG zn`J6U?o8Acn}(d#bHfv2gJ1ERA?pO$FH^jxv2HTw-s8*^!ZT#nEE~SGVg;;-zUK#< zXF8i{PNbDKD!)-i&bMBmFf2C^x$g(pzwjzj`<=nF&ZN$o&Ldq~u6%eB3fq1PrUW;0sBQ_ z+&b&$oe+ta_Oj}qmfPKk4K~ai&z7}BdWvl3&^LOT^-7E#&1vc`9<)C$5>y=L=x9B` z({kk%ue9<%%Qn*hFTjXG<`EN#2xt%av>>;RM8zJ=AV$-XONL_%@Mv>p6fDLYa%%5GG}IJ zi`olhp8Er5Z7se_bgfcMk>a<>xybDb+Vddoe8@_Hqs#|r>HK=P0=h5Ur?=0vYKR-5 zOSiT-hF+bS0?FU;D2sw4%a5nW&F;9tGnjW_#aydp;I9zy4)^=V=skD zM^ZU10i1%{#uy?fE+K=~1O?;lDR72`&}U6CODUB+`i=wrXj;&7Y#Pp06<$NX@^u7j zt2*WQ3+>fXJ7_sdrf=45m2Iicwoz%n%yP&r!0u<3n3gqT7o`c3m#$y+#GYYeY2A^S z3it#U`iSveuM@9mUx@J!Y8;z+&C=XWy6&oYj+Dr*p5QH# zP?D^REj9kH*I$X1r(>K~sQ0cbTQCXoHse>+4%ula;tmz&fgL@f+*mptv*Ba}hr36ZUz4(1*@AL8aI6Z59txi)zaEC)>8f@37xT$a34=PVU!u;qLAOWvh^+1VuZa?lNH5OPXs!6Xz!@^%$-zOW%5lpO@s5@G~t5` zTqYnxh6!pCh!^7d>Z5oY)XTri8y3$YrTYc{@FS%px#LH`nF4jwf%tenZjyUiui@@C zcH_p%^TxPCvWQ(^@oMEMQC10Dop8MDbOujzA%Z$LR!wk7C+vV`T(}?;6)UCssD+v~ zBghALeS(Af$o)7?jpn!YO_godKG<1XHCFC~6d49{2Wlvq&TS*JfkNi( z7~gJ9e6xr%*!`sL<&+enE|r9-D2@(IpKl|HTW?cZ=jl;+ml0w)DQnfBPpY0!u6qzZ zUG+TFShtC4*E+q-Ht8U^uQsxW1;~vEg~bjM?xIO&_)Ag@)ty4?kcz&P-%cLoTz`&p z!fel)DfLr&{u{8u;Cpkg^h6RVg$oOcF@YH}ME{;V6a*1CE=<5BcqCdummIb??o0LJ zG0LY~py+&FW0kcUTe4dTzHl=-`}qtKj=k~wGEsp)dG3I?4&sbnIw{sK@2ih})Ce>j zIV6*{KBF3iWupoSfx5U;yM^=!Hd957_3Ld?hrhdW64sO*e@TKvaEU)pOOOpB6`qPv z*hu!^3n@Q1*T9-^i1aZ?z8q5FriluW-L-m3%3#C0?XDl4-v= z{d_g*UWo@gF6p~vN5GaxN`&}+evOTO&Mv&eKSJnBU%mbaOp8Sa4a z(wQ}pDcX^Kuc{4I0dq0^TH@iYrIqt=qNTLUA+>a4y3ceN3Yxv^%kT#2Ue>w3`=ubI4mhOm6!`uC!U9; zhVtLn5(HVq{6DHYkNjEszrwf>0$PE{(;T2{NAx+W3 z6Twr13@4HsKD6BzgfT=B!t-aA+wDe{o~YM+d2fj*uBC_4kI&ZmB}d2m*mOdlQHR?% z&TvVUjBHSMM@MI8!Ih(Z=i((x$~Qh+!j0K&d4{`UHA zxLc?c57d83kV9LobLE=ny>`K{L{?`&j&WPg=g|CdtB-n&*k(AB6uksJbWV~|53esF z+b>NVT7AzQJxW@`y=@vYu<(uF(#QjT*U{L5v ziUZ)7_F?DN*oG7@*2|9outGbJ5B z;_?Ag%FOC>f~@ap1Y2R+!DkK3^AlrtjK9Ho30$Xz>*lvX4NnC!;#-Iyh+ZZ+I;JgU zSvmsUenFL`oKg4r&${t!*Oft6$Ge|+A4^hh8yQ_m^~WfgsU(e&uVRltMIw@ zgf<<8L#~sA!<_!vgT+;hxtIXABdPD2ao?x*@51qmewDDcM~e6X4ey2oj zfsx%`>BR(JRDAYcrdL22Qw}uAsFLRvo)x={3S5P#sePbiyrYz$tGX{oFAzU~7Qe!DIB19oRN_3V|k+HS4t0U`OZyDCtUsH}1TobhKgN+^2Lwi3QS!&xrC$n^xI{R^4M5NyU<#vfic zWOqSGlHNTl;5)>lT zDrU%>@OftZs0Qo(4+}z`?LXY@Qh5ZwD~OGAzgc!RqLx1bPKkrigm24eo z&hm!urKTCLM=}?EN$NF$-0^xsmTY3L2ukfwyPX*8heXWHSuY**CDf$U0Et^0Y{~PP)Xpe}CIw zxuzCS#p;!Y75(@MJ=XSL$qZubqzq z8JD`)Y8-kvXI+gJjA2_<*5G4(&Ou?n4xdFoO$>jWI6EwsH`dAN!CxvKux{HC{QoeX z%Sa&>21Hw?C?am0Gtla!?bm8;2P5RA*R4Z6b!AJoM9S}9yB}YDc>aUyPj&Km9{q2s z!&dqyoHL1Y^HX~L>;t4q%E5gN&L^_;Lqi{0T{7WP+}|cXFAqL;QLAKs^n^eiWh0cm z50)5?c+C^~?Z4>}3PL=s+^Gr(GTx=+bUA4ZX{at#P%I6U94$pEo-;lauILZ&MN6s= z{lka|#6@KO8Y9gJX|*gQkAwBw8k&!;&7;(w?*Bv(N;-zypGP$ z16Cs02J%#MZNMrg*8;@g%xUwhzr2H6HN;Bik8NU+IYtU#++-#8k~DR?5jgl9@4Gx)n|FTux?eaDtyUo+?VWn^w2`5^S9~ za(gdkaZ&Aoj3;c0h@ic5Llb7l9&EVoN$RXHwa5Y+8;$44tKgRek|gFMYvX@i3)H)X zQ@#W?Ncf`LYO&VqWKYu9sso+}K$nD8cq45lZs}4bG|@c`Zr@w-2a~@k~V&FXvh&^k?o8$rchp?@&$?- zC=Bl%W}pT5?T$d=iT4K0_~6oWE%V;LZDO`qV`yi}ohVkckr~U>0l%g^JluDFRQe_- zye4+(ly`lkgnEbr>FIm;sLV1b@WM;AiJ}KrDsXk!HA-=GtBjhBQsOuk{znup z*Y^%1PYTz}>-0A9lVBv;rh0i>TO9a0aLl~PZ{+xVW?(yPi*@NbHd0=OeFqoQs_6W!91u`j zR5)Npm;qEGu8qjBZ|hAO{rRp4%oM&R$aCJuR8^1Of7$_!P|q@5){WoY&7U0pm9e~z>$f*CO?Q`#{QX6gjVkvKso;@^ED8BP1ak2v>Zh%+0M4N2sGTLjC`UoP6IB}IxO@-jv*gsYspKMmv@ z3@GGAHK`g&T(^S)I<;`zT>9SH`A!6FNg?X#qLJNu;AwQ z{^+vk67EVk(nCL>F10(&HHsKHNlO1}{#t*RmfTRQRGiOI`%x4CH2rEOS)%1cX=u#> zbs7?pTddhrGfzcHp|7Ue@E+DqnEhZP79z55i?iX?pIM}e#jU<71%}D$XkUNDn!wlC zd`bT-QH=gk2`QcNUctkCrckao+w8B5J+Sccyi%Sq#{E!~-C!8mUR~HpJP(B}(~%6X z*=0j%UaPAZql z5W$Z;K<%X-D#X8+D3!T!W+To*e!P_2v z$-?f6GrRS{ozv17$h+>d|42_D=QD`6Zs*v693-54H^<2omXHvRQh6ofFnzR>$HSL1bMY9PW z_+pxr76l2Lj#lbTn~ReMKG7c4I_-&P;Ml~6vQ!h^fncA|tmhs{;9GcPC_^m3NR(op_yTd!c z*uEY2MOggrQO*&DkBUZ*;@7gD(965FoMFmqJ>kc}_05J_T*x~r!oVRvi{Jt^HD_25 zJwVl7N`UrHxcCfJ@k;ENErJy&9Y-<4e-WLYctP_kE8= zyV6Mfl6}`1=AR?hsUzP}Bo$N&CC~+vhSrUY2QHc8!a9(5q0eke414we&FG+%mcU~4 zhzr^F#^15|hd}@kxqpQ|wdtN}MEG7SiJie=BL?J>Q=55S$S*5rbQyc<3zt9jn_*LN zN1sE!&1Q>W6n4ci@XDJD6)qE!15(!e3&`N=akaQKZny}4efZ0ExIEW6f4x{EIwkZ;vkJsvKlfW>&rNBHrV7RXo_?5LUkS=QI1e?G3BHU%Ay zPbuay<=VkCU`p=^sIR%IHq!IPc;DH0HUFGy8uJ`+U3<)o!dZvqDkJgV%8)m8J zfN6IuktZY6*jjoE_)!pu*VzQvVnqa%88L7`O1uoa>b`(*F?@Y^A?0jXIZv_2>RHxx z8-yu4N6YzAqV|I^1r|GLdT%L#1k6;5gt|lQkFwy8lu`2yX7gdJWEX{{fcv0XgA3SFRU4p}k$4-{`RpYnjFk3ztYg#(h0Ev36t>Qg;1#n@kZ2@8YN z684sE>@Uw&u800p{fHTmNkDD;!Uy;efIxaBj`Xf{2kC7$751qUpymp3Z+bCKi4^G@ zN%g5~Lz??4U7Hw4Aua;-8O(_SU^hc~@R@&zm5w_Ii(!1Lz37ylV)@1m50Nr#DrOG5 z0QnRfa|xbyT#8FM%EgK=&tAA@M&Yrx$6erE-w@|@2G%ak`Y5m; z!5OMd`E@*Ac*COV9|jhg8ZHqi3PeGvKvp5|gm&zqxDn3KO_Gs7%;g$k1~R~RIG^z8 zqpv5J`b&zEb7Uy`3!VV;s)w6sB7g@-Hm(4K;)BpI2lz!_QUCRySs z;o|-+j*fcROky>py*2f7ceV?-S+=paBkPUtr8`@XhenGa@6LI`=lDE4X-P;}VJM|i-kx3K?8pU2i>pB~hHEmpv-;&M#MjTcqa99e~$L%*OY zKTqUq@%A%7 z?%M8O0+PW+*D{Ji;qk@%`??!@HOe#T_vi|lNQ*KHqR$pHsKjd^3c#=UqcG9F;OVNU z@N8a}sW?Z}JljBAOK8;(e~;`*3jus>1(s`A_yEA6vzx z!u@jcGH%rt`=<-4;!oCiYAV|)y&H&jhVaXW)D(t}+5Cc+ilC_lh+}F+P|QLJL=O z*Now33ht-8yo}q6vtMAAmdAMR-uEWJ6Vsn00U$0L9+azuZpa+lUvw(T7)%*nqZ?@K z_sW8+#fbl{CZ!9GAe(Dvoq5MQef4@|W#E!5CU4+Npf9{PczN~LEN`uPG>}Gg$lyXs z04$a1oBV-!{7@}N>1>~Byt3Q^6mx&{Y}C3n#8an0j%wub!!<3*=yA>yI;0Tt3rJ2c zgitRATF-NbZM{Y4db>0)CrC{N?3^1y6N32Jc%Cx9wb;1+=b@~I!IRi`RzZ_?F}+cL z0ub+`duBqCMEGfTNwB2* z_7hIq*i_6C*ha2A0pe0lm0 z{S{4+4*xL1w4p&!GWbp_&h)WxnGL5N%81=8MZ)+dfSqrX!b^ovgL;BCE4qUZl{n(H zTaYt;$sd8R85RlDJ1oXlcg$~;wR<3l%)4bNGN?HrVghnU=nO^}>({Rcx9{;*xB61x z2tFU1$$ZNN5!`oGsuKz|U)~=0hY=-dKU5eggO5lpe)AP*S!8$;kIV02TP|2_1db&d zZ?5^-$i?-n-cE}#!b9*_ckI8JwNv*Fsx%_-_cNcJwf#YuspwoaI9eKW7->f4Etvdt)v zIHiG&pH(qtZ(+05T=9#mx=}RA)joOA=8eMl5bSgFRm}Qd$7XNmZVCyU_23&vYpC>q zUru!$IN?JX6)vo$b7(gq?~#_dAc0`Rf7O!DgHj6Pdhu_}LuJW;elUnWfh)QZfY<3P zk&hd@Nnq!AEiEsUq;=5`g;a1FDvPd?mZJ_Hc;UH|p0o=g@buileKOf5&vG57OZE{c zsq-9YS+n2z#LT$)sYrRdGt~Y@_Q!@Y+|W!m94Bl6#U> z89f$|1*PC?f7Xi~$m&#xg<=PAL0ywsqP=!aJ?F1Z*z<}y=WTpK4VqHvtlAd#^G(&U zLewYBmkYvc9L?BfuY*XA{GJ~w!0B@xYaF$?n5#3W<2y{`{xV58d=e&1@i?%^3&Dbq zV|)#HMq?b|Qoj1~%^}tSv~YYq%#*df|I%3r5Vr@)JgS&vbc#!%F0f{ zJJqIwUJKOXTG3pPU1Gvo%ID`P0BXco?Sjl0{B!+oo|&FdN`3rq1ub~%UuCA zr_??})ekBgRMp@n7a^UdE@R`8A6dlAZ{!54q}C~+Hv5^KYPxjiw(P@Ofq(Ulc_fe9 zci{w%*#{E9dI?2>y|!H16NPl{))%o8b%J-r68=tzt{IYuA{9bj@8>vYvG#~iIvIWuAu zO^BXok$8xToy+)M93%-`tdElkjn4E|QF)S8C;H^}E?F$I zp#Q^Br=jybS~(B-h2&W2p$=R%ClWDrHFP_Z;T*DtzZh6cgO9IQ^D?Q5Y0f3jY)Fcn z#|*>&>%;yNVa|eMm`Fx_BOr1hKc*QIzZRqB=~z``zfzXjpi~owvfQ~OLl;jq;*$t2 znt1ST84b^X9hs9w13&{j*fyz#NAU?KX5ujC55Po~mh7uAn^Ci3NZ%~@v3*UKS#?~t zb-NKABvnVS1`SVP`pbsWVCBo2P18o&I3}a?5u< z$L?1J%u4y+Mx%d`3L;1)Vh2?5_JyOmRWJ4+wc22o!|fOp4{0wU*AX zXpx6!C^mKLShTJMTu^V_j{7*c@|3voyXmq$NtPQYQ?)gavXHLIi#r&|8yx3Mv^la1v& zBY)E?P|x8I0+a+u-5D0b8EU7X^&ljM^@x$zWu5U6TXP=10hv{^vhVz@Cg7QP5#Mip z{IQhM%5nEdr&LqYQKS4wukfn-kp<}scRTBTGz8cW%ZPTt(X+9j);oN0zSK822gBuS zfmH#R`ISMgvF)WP->&fna`IG}N0YH9>l>o=WmgJ^dn06M5l=Adk)YQi>W$_{=5K&( zUe<2z>$cH09WBOF6x!iY!>7jk4c#F+5>H!lj4OZl(27A70;?Dx1daR=D2n(#AT6^E zU8(`?_ENJplxUa&Y9TFirj9ukI**wvIbWt;LD;vwSbt3pmFS5)6r#o2mZzcv#Q7sH z!cA?3Z|E{1kfh0+u

-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 diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card_125.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card_125.jpeg deleted file mode 100644 index 23b629526da20cbada054d2505e26a1b87b10d56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8260 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 DBoQ_m diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card_75.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000020_25_virtual_gift_card_75.jpeg deleted file mode 100644 index b46a43618f632d669647c7b33dca2853700c79a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4360 zcmbuBdoL|%M7J&88febzjyuKwcfwp_5Pl{_gZ_e=h@GCKKohwS&w*9 zJPZ8d;o|NBNJvNkr?v(legioFJpD)V)5(A2;OAxWb3joRZ~;6eC3JvoiV~8F65?(E z1^^P$|MCs|w@7T0l#<>qBP%Dbuw_911=uDbDY;EbQd(L{YRfu#OAkmXN-Jp}blk4& zds;>(X1B%FKg(owof;er`|@@D8~FXlPc8{S z^50MPFR=f`rMShlO-f2qO6Dh*#I{RYOHxrvTKnL3B}ZSG(=p0A7FT6=JN;SK*eR!b z=mcNo%*7FT)qR#6J;6`3f0F&*z*7ESWd8>HAFgR&hor<-@+1`j1R&b{Lv7ElRJy6m zePQ)pnPHM43it=a@w@NUj|fAQfrPn(%$&=Xj)os3>IpGm44>Skr6ZW=hV7pG7^=aq zFE*{IV@Ew5H#IrW&9l8^T2kqpSRL%XZ_QcCr!#6_o~NNfsQVr;D-f2oZQW2G=Xks&(t6^cYfN;E)D2_hP7C9kgF6}v-E|7BkvT`tDzXmTWoJSl_+ex&s!$uDZtkOeZ&;BMeE&;WHgt0!+^yZ?ffTHRqz)lHF8mOzD6MM%fsy zu+vz#jNoPY$-gGFwtz4+70W5oxtJXU4}(;4*sz>LV^_GzXulAK;f$90k- z)V;aL6THM}LwDQJvrFFE6+RLJQ?D_2ve#-s9=mJ#TapBPN>jG%cF67bX+)BQpt(0U z6|r-<;k@vYK$k&Oe>yt-${`rzo@}qNZ#rvdY~>q#^WEg^rNXpC+8bK&@kZ9Naf6j? zW?#5e-@dSRgg`%oC`TZ1(<>f;mpZi$%B+7Krkg0Bz*OFCd%yca?Q#2~?A_zWN$NY( zOK6km1q%#V>It4@EyukfJfJ^p9W^r!>G?3l6WTzy-kJ8u5dWUqnf?7Cdbt(O@ur*xXe>l)Tq%U2EsWFJ1qMAtI+DV z!qxf}D;wiVg6%icPdm#Qs=e&biczMeGREHb@v?eGN;CqeS%xPnoOZ*S{dL!2*GLht z45BW7Nz^RpAM)g@P-kuk_3nd4{`}sI`uLe`gcTQ%j4$t-Ey22sYxH_N%k+Hb8I*fu zyqsmZv70_VYAs*5W(1KH11V{dCgsEzVn9`q;cQ)}pZaB6J#`8b5)C8T9kv3vm}D{@cf)KEKZxeS z(pvSob<)ex_4RQiO@bXliA{LLT#oWuSUZuI__YSHREd9cXMUA#t9~y9sD;Y)cVq+~ zywf_J*sd?fg*%d&T{m7f1{x;1=qp^~YH1EuV-IBo=pHPw-;hjsc`&KDo1<+POLbL| z_hlHnE*`eoKKGpjJ!>%#s_54xs6X!14mIkUpceTJ)cAJIf8QVGQn|mP+xFb#KdcWa zUO;`(Ht^alP)xaBBRc%3rbf3q?38~&`qq@cN%%FzQ6|Oc$LB2~AL4Mf;MscMu<%eF zrg&&i5~PL9pw(?gxN!OVRpS^E`M$C3|Iz}l8`nRC40AD?r%=xY4@^Wld1T<9pabxp% z(~Js6^FUQ8+f7%r%=Gg&XlvoEI6rIdF1k|Hd<9mWtV8gV=^?^+2mX2*5#FL%n4C94 zLlxM2nc5!H2r5;Ec%AUaCu3zoR!>mX?Iu5leX0FmhZ6%x6@|^MVy5kP((GF%% z{hs1s2e~iuU$f~+25_Ojl2Iwz>kfE`1@C=`$0WqorfPataiG-3Ennlu**KV^u%@3|BX~-0E?7cuXEU-etl}T3v$|QP*Myu8X@P|#=nz(eCkXr#kl0r?#%KwX}5x| ztn9sZZHC1a0}D0~L(nRryed>hug)qumK=*%Y#mL*c}!kzSODwU%o_JWF)%@^W{sg8 zsV$)LY?~J88bT3)8%hxaO)lixNSL%3z+p7-aC5$Q_t4^TWH)@zgcU;7(TX~hYE_)A zHRRhe+KSnkb@`G|H=+5VD`LHYOyUMC=6_mMog}_QD1L1K^_MC9XUuaOULcJRex@k3 zL)~fp+7tGAX6f;R-l2T>on-qQ(xc`2B>03XG@#N^D`&c{E^A5iQ@U+i@%rqT@uFyV zpwPXT3+v8J`|*uIQo`K?uXMLdvMAooBXpS7!e!FccoTHbll}TZU!pWUSZ42^wP)fz zH{pX{74Sy4@=yHERup)yBfu-Oxw}4US%OG`0vDFuZnXf{=cj;?D?J^GX9e%7nCP8I z%$SMLvkV`qnLjvcg^*bYRi~U}S=C(U-Az_(Kc91EA$Pt6QTTdCWE3Z2F{6r>6+P@YxI>pU^5c~i56)P$F-#SZI5s(;rIux za$~K&$@Ta=99)ThcOv9^@JFXLbS8e&ZZBN()ChH;XP(rIdA{MDR;s`f>4Z3PGuqXK z;o!4)u7Vf3Jg~Bip=b=4&ZEZ06`|l^iwgqY~I01D^j8z<0DmpEO&s zm5ox-Zy=hF>1F_KT{Gb zujFwWZ8Jt0rz1VJYmDKT0xs^90k(SffL|wz`MPXSF1#z)6=T5_mX@N)TdBLCnqydc z02?dEjMjc0z&!WG{At$USeYxt&!Cb=?r;3Fi&NvGmIRm4%qrYguOo8gL!9~S%u@pB zAEJr!h#%y1kt!jd3%zE!qKXr#3lLpqxgC(EfoQEHgg_Z=K+;?C85CJW#OGuP6SgaU z1EcI}oxzD#1+yYTjZ#cZy%i#r2uLO-8=-JMZYef^Pw75jAH?<6Owtg*ItMQ*z~tV6 zP7-B3JZ}idn?+^_eJ<#QKp0}+7ouF^&e^^%m>uuHyxt`kq*a?n2oFt$1{Au%AA|8? zK#I0mkBHbj1Jfjqg<6lnfB9syj*7bXw|nzwt0%87F2wpB(rvVYKSb7h%nwN2PEq}} zDGD1nVP&Kgv|@RuKIz>|BjWKlp>qDZ?`8?RH-P_vF=-#p!<*Doeu3+?!p#N7NXgS{ zUv5X6l=HFPKAMZji9;_*_K3{DE1Y-eZd5`A-lSkGGB0Q!(vC7Y zpQ&CKMQ7gFxX5G&=DD4xYZBgGIN{xjU?a$@DppQnfFOdsK;}(HHPseJnJgBMUwJ&^{M$uQDa-lxQ#^N z`#1eLwU1ilHp&^@BDJJn`QGVL>l7)GGDvcBXS{jK23?PgHH8?vKD95Lfa~AUZno}h zPZ(QVF_S$d9|)B<`f_2z`{DTPhA9;rUX4~oT7P>z{O);3EL~W0Eze;fpZ?_i*;&?Y zUXI$R0oJWN!(TVk@??&sZ`#pg622z^m6HJQT&>~5ThbpvL~QrrS9VG6cTYWywPlhb zxVF(IxvVn#5Z5ImL;8o%dX2c`$22>+xmQ$;_~dN6nW4FuvmHnPSo>yHeObEzqInI6A$RY1Kf!3GtQuPa?B$V6wmfhk{f9?J$uz$R@;=m zQTGNIoXO^SxlIJ?xGL_%a05*v2wi_00t%#(XPF+270#J$iQDpgi-(<3af}kwA#J zN&5@B(aRS({jp~cAmec0QZ8u?961JP_I7+1MzFW~yCL4O-9WUK82BKlAbb-8i0s`y h60KLn0EL;&n-PjEeUEGxtcXY&**Wf8dzg6YzW{*3JwE^d diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000021_50_physical_gift_card_75.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000021_50_physical_gift_card_75.jpeg deleted file mode 100644 index b46a43618f632d669647c7b33dca2853700c79a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4360 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 diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000022_100_physical_gift_card_75.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000022_100_physical_gift_card_75.jpeg deleted file mode 100644 index b46a43618f632d669647c7b33dca2853700c79a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4360 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 diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000024_acer_aspire_one_89_mini_notebook_case_black_75.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000024_acer_aspire_one_89_mini_notebook_case_black_75.jpeg deleted file mode 100644 index 4b9ef163cf25fde7605c33fb29c0c13717794ae5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2941 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 diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000026_adidas_womens_supernova_csh_7_running_shoe_75.jpg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000026_adidas_womens_supernova_csh_7_running_shoe_75.jpg deleted file mode 100644 index 72b7a3b370a49850a0884ba405180aaacb0501b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5143 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 kgwX>PCOrz;f&H;E#42=x| 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 diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000034_black_white_diamond_heart_75.jpg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000034_black_white_diamond_heart_75.jpg deleted file mode 100644 index feac89b5fb4ec9064ffa0f9b5b5451c7d7df12a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3176 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 pfSJ>%{1|}Z6MXV(ri`{u8AW~%a&R@wtUPq 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 diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000036_build_your_own_computer.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000036_build_your_own_computer.jpeg deleted file mode 100644 index af6447c685ada3db550fa960c02407425b1ae255..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13754 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 Wj4>+#KkTghf72NFPqQF;;lBU}MC=v- diff --git a/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000036_build_your_own_computer_125.jpeg b/src/Presentation/SmartStore.Web/Content/Images/Thumbs/0000036_build_your_own_computer_125.jpeg deleted file mode 100644 index 41ddd6848a98979a7f4a51adb4ae9e076e34e2e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11019 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 false AnyCPU + AllFilesInProjectFolder pdbonly @@ -57,7 +58,7 @@ prompt 4 false - AllFilesInTheProject + AllFilesInProjectFolder false false AnyCPU @@ -1058,7 +1059,6 @@ - @@ -1076,6 +1076,7 @@ + @@ -1541,7 +1542,6 @@ - @@ -1550,7 +1550,6 @@ - @@ -2137,15 +2136,8 @@ - if not exist "$(TargetDir)x86" md "$(TargetDir)x86" - xcopy /s /y "$(SolutionDir)..\lib\sqlce\x86\*.*" "$(TargetDir)x86" - xcopy /s /y "$(SolutionDir)..\lib\ClearScript.V8\Native\x86\*.*" "$(TargetDir)x86" -if not exist "$(TargetDir)amd64" md "$(TargetDir)amd64" - xcopy /s /y "$(SolutionDir)..\lib\sqlce\amd64\*.*" "$(TargetDir)amd64" - xcopy /s /y "$(SolutionDir)..\lib\ClearScript.V8\Native\amd64\*.*" "$(TargetDir)amd64" - -if not exist "$(TargetDir)ClearScript.V8" md "$(TargetDir)ClearScript.V8" - xcopy /s /y "$(SolutionDir)..\lib\ClearScript.V8\lib\*.*" "$(TargetDir)ClearScript.V8" + + @@ -2156,9 +2148,80 @@ if not exist "$(TargetDir)ClearScript.V8" md "$(TargetDir)ClearScript.V8" - + + + + false + + + + + **\*.cs; + **\*.orig; + **\*.bak; + **\*.log; + **\*.csproj; + **\*.csproj.user; + **\*.DotSettings.user; + **\packages.config; + **\*.Debug.config; + **\*.Release.config; + **\obj\**; + **\bin\*.xml; + **\bin\HostRestart\**; + Administration\bin\**; + App_Data\_Backup\**; + App_Data\_temp\**; + App_Data\ExportProfiles\**; + App_Data\ImportProfiles\**; + App_Data\Migrations\**; + App_Data\InstalledPlugins.txt; + App_Data\Licenses.lic; + App_Data\Settings.txt; + App_Data\SmartStore.Db.sdf; + Exchange\**; + Content\Files\ExportImport\**; + Content\Images\Thumbs\**; + Media\**\*.png; + Media\**\*.jpg; + Media\**\*.jpeg; + Media\**\*.gif; + Media\**\*.pdf; + Media\**\*.docx; + Properties\**; + Plugins\*\bin\**; + + + + + + + + + + + + + + + + + + + + + + - - + $(ProjectDir)Media\Thumbs\placeholder.txt + + + + + + + + + \ No newline at end of file From 547c8745e0525b5d2109a41f6d9c5c1a72126adc Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 7 Apr 2016 11:07:56 +0200 Subject: [PATCH 015/285] Manufacturerers weren't displayed according to limited to stores configuration --- .../SmartStore.Web/Controllers/CatalogController.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs index 8224308811..3a213b1098 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs @@ -561,7 +561,7 @@ public ActionResult Manufacturer(int manufacturerId, CatalogPagingFilteringModel public ActionResult ManufacturerAll() { var model = new List(); - var manufacturers = _manufacturerService.GetAllManufacturers(); + var manufacturers = _manufacturerService.GetAllManufacturers(null, _services.StoreContext.CurrentStore.Id); foreach (var manufacturer in manufacturers) { var modelMan = manufacturer.ToModel(); @@ -590,7 +590,8 @@ public ActionResult ManufacturerNavigation(int currentManufacturerId) { var currentManufacturer = _manufacturerService.GetManufacturerById(currentManufacturerId); - var manufacturers = _manufacturerService.GetAllManufacturers(); + var manufacturers = _manufacturerService.GetAllManufacturers(null, _services.StoreContext.CurrentStore.Id); + var model = new ManufacturerNavigationModel { TotalManufacturers = manufacturers.Count, From 7d7bf95658af35af99a2c786d2e0b6effd40cf07 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 7 Apr 2016 11:50:07 +0200 Subject: [PATCH 016/285] PayPal Plus: Getting started with new payment provider --- .../SmartStore.PayPal/Content/favicon.png | Bin 0 -> 744 bytes .../Content/images/logo200x53.png | Bin 5173 -> 0 bytes .../Content/smartstore.paypal.css | 7 - .../Controllers/PayPalPlusController.cs | 97 +++++++++++ src/Plugins/SmartStore.PayPal/Description.txt | 4 +- .../Localization/resources.de-de.xml | 56 ++++++- .../Localization/resources.en-us.xml | 51 +++++- .../Models/ApiConfigurationModels.cs | 37 +++++ src/Plugins/SmartStore.PayPal/Plugin.cs | 2 + .../Providers/PayPalPlusProvider.cs | 62 +++++++ .../Settings/PayPalSettings.cs | 31 ++-- .../SmartStore.PayPal.csproj | 11 +- .../Views/PayPalDirect/Configure.cshtml | 2 +- .../Views/PayPalExpress/Configure.cshtml | 2 +- .../Views/PayPalPlus/Configure.cshtml | 157 ++++++++++++++++++ .../Views/PayPalStandard/Configure.cshtml | 2 +- .../Views/PayPalStandard/PaymentInfo.cshtml | 2 +- src/Plugins/SmartStore.PayPal/changelog.md | 4 + 18 files changed, 495 insertions(+), 32 deletions(-) create mode 100644 src/Plugins/SmartStore.PayPal/Content/favicon.png delete mode 100644 src/Plugins/SmartStore.PayPal/Content/images/logo200x53.png create mode 100644 src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs create mode 100644 src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs create mode 100644 src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml diff --git a/src/Plugins/SmartStore.PayPal/Content/favicon.png b/src/Plugins/SmartStore.PayPal/Content/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..aa29944a07e5f4733bbe0173969deafc9abdcbbd GIT binary patch literal 744 zcmVP)BLkxv&=N0B)<3R5d(VOd0Ro5#L;yJ;AP2;P2=$D(upk@D zpIBDLA7E7g0R(md6AQ@sjH*B;J6!w!KR~DbW55Rfv#<&M7Zw1s0Ro5xW*g9KHDpE1 z-#;@j{{02D70F${elmQqvT^xp>tGM$9s~#=uosyAgO#ZwtoX;k`0p=J! zR#KE?U}I?q2>}EU*nofkegMt*rwVrs$m#zvozM95C&Mol6^383z~K7*OCBT!5I_(w zFtPCfwW+|uj`8mwFowI}?_aQ?KNS=hKD)Sr;~Qw}8;}%006`4+$0*0Z%qRqRCMz?; zZ!s~r0spwT7=FnsFnl*KVEE6;$-wXjX!w7|;~+VJ0D>66%m9x8Mxf+38;k!x^mG^* zfB%M<$qZD+#tP90!VC=WfTA-%41fTFL?aWU8p3u~hCd+NftsM{10=z~@aq?hbC2Oa zQx=fE0O9}y5DQdZ73^Aw0ssH<@&Nt)2O5x!XMu9_f&OM<_{H#uf%*Rdpx8%XLIG<9 z2p|?{xT!%B&wqx$JlqWb+1VJ8tX~1dQ^BtI#|ZK+3IGToCTOyg2GhU){Q1Yn^NWF% zApumXV$s@OTiaS!t1Y&J+S}G5P_(Xgwf1#yTV1jC_Hwn=wNMBV0%y;(vBMIg)kNqW+0BwGMW#&6)pEEh%{?0l3?7crB z2W5#BA5w8Q*8dCi>98e zn?J27Z?cDhimHa$px?5Tb;7q_`Q;yG0}~V`2y9fZ%f!isz`OGOkIda#l30F*!)=e| z5~j#(R1<^#1>H!eQ~+^8JhAd6q-IgGj#)|VoQ}M;^N)LpYaaP>eBijutp<8 z*jB!nWh~d!aMP46=`ZwE56D$8^nA$sLiNMTZ zYD!e(mWrl^Uz}gPVe~My8JcNJ>WamFpRPl)F6kRe6E#->n*g#ZNC%@Xr1NPqT$Bj< zCmavE5MfIrsX zRQ|3b&~8>Ls_GvFnQbWQ+g+^e#f5L*XO*2}lYT^I5CFt;?U8ExXy%|Ee*s5EIRGv$ zuc|-&2(_IBxyV*b>cc1pWcf!QpI9*o7-yCwdZ&0c^!wgPDPG>CqMrm}A^K+e(POWz zIXRiq3MXzd_)`!!7VnS4xk6RlFA#w45p_EQ;l-k&=(^Yc`!jD^J2CdEnlZ`Z_MA6l zK~j@@YHvPZmCF^23rpfr)5dpSbLt@f`^9zuyniQI=l!=(*zJ^C`hM@7M#al%IIcyR zVeWIM(2Iecxr$9+vhCC$(ov4ONx&%zV?{U)D8#d0oCJ6pJ$|P+-O(%-o>09^_@ z!Bp65*8N91G7fx1+$_|7ey1sxHa|N5r3{ysmwdeDKIeD}C^3qBd%tfIYa%DOH_r)T{$tT~Ji8DwnbZg*@nzg;(Y3 zYyiIkE;If%;>E|UCCNt&Mcd-Vktxlv3qXYnjrxJ0zu)xvirS0?D)p|Z<2T=>BK?#o zqfC*0<8u0{9Yv!Lb<9-sVni+hJwG6I68hExrN{AC*Dxsv2m&e;YBH#(+Wc1)eZ1&U zQPsvTpV4Cbjc(Rkf^*>bQ|>~2t7K|exgdm`-7T4w1Au*x%-euqI_d0UOwSLcpQ!ml zoYuYG`}HE+qc9sdHH5rwV!$OJ_ja@;ca_aq@l_IQGFm`X*lH1~aFVqHC5e^q1Cw%q za(8p>!d*SSu`hdbSZt^iziDjw*MS$zBGF#6UU}8#2S>HFH>>E=2#Y{JLWuuG43vJ>xqc@$yxsiDzXg+BQ(s%cl1)7I1# zVEkW#tfx|Z*dC-+ko^brJRs*_cN4+3wky5%fwx&@s&t-Y_TV&Y-GQLW+_Gzy_qzcD z7d0)h0Lp@W2(+;dGjW@WEF`Vg}^Y}`5bj5bqA=WLr?Kyio1W%O|e z&Eq);u9#ZgkbV!?7@dJ}_7e4>_aBgHi52dE@L=Pf-KHm%OX>TqLKXeh;SZmyjsYi! z=+l7EpugAhzLNG`K*~}-knA_wp`zHAG47k3$XBSH6ZHBr#DSQCdA%*Fmm7;7NOs7& z?hnrU;ZP7%nVar=FJT@SF7*BbGFY)ZTx7MB02}yCW$qg&u_mIle%UB92$Hd%b$=*n zjGc{y5`U~f>B552m?_ZcCQS-7zmUDVyrfcJ`TYh3PrC&F8 zV5+fmgkiXyn0`AI;rqmD0R={tLyke>gi9P3{Cn<*uRW&qS0LEe|S1(hg)< zV&zmt=jWCH2vv=b%-!7o>k#&S*i(1&v~Af6RNS}eZA5~fknX%GJ-cLVu%=J18dd$m z0*O8T+)b0-uy(HQ#20{ZR zy#*_=CQ_!Ymua2ct|44j1U59)&i{GO@0!-mdjfdEDx)U{9{bE2@kGs5;2cBomos$A zWXTehw3~EA@5JUF(vnI>Z%=#0=`8!OWL{oPU;g}wK{B=g;Tu4)St2Z|n=`GKiMRfy zDffh^--|5^r|hk$s^1BgRfX*A`%SbUHH-Adtu*)n?LQ#RpsDzo_J;9M+!JMqUd9#_ z9SyCgs!$w4*#H6s>pmqd;}T1XJN&5e01;B70w+a4+)`KRXF2QqUUaJ%{wJ#1?|`i2 z6DuwQK5S`UM84B=Y1yX}VIEvFE2=j4b^8%uT#({atE)69J4z20 zCd&XKk{Yclg&;-~v46gQm+UjD$iOz}knX7#&Xqc%s42|Rf~r=wteu~GPm@?vP~uz9 zi8wh=h4WB9Rgeh^r=(!CbDjS~>_y~=gunYNx&EEq4Xi8QyCq zokxIqRdS%NY_i!?pf9}@gTFs@ut}2h|Yo5t8kPk+Vv2T zaPw3K)cdqclc?)>F=aBh_ntwz4h2n?Cxx$PipejC`U{RwR*RIPa~nQi(U7UYnbq4% z3WAQ0i6cP=?^n^n&cW#Fap>794Si>TH5!q|h6^oK71n1m7Fg(|ASL5YIhPQYlDwfj z++3Erp8Myd`5`LtIjgRv+24VvbwfE24m7)xx^x%z)ZTm`o~U`1oIXZx%^)XVSv~rY z%6E-z|F)%e-eZ|Bd#gD!pR|i`P5q4(KTdaX+LF3rFOe_dlV3%oBri{>^rS}1{$9() zipKg1aK`;p4u7;9^jd*9al;+|krebh%`)cG+`ll{_hLz6`59%2mFrRcAL#Eznok7Q z_dA&DjbWp%ajsZg2!{^^E!IHq@n6X*Ra}@8^g$!=qNsde(~ae6m7`+mmJfRu*@AEf zcJJ(g^6Xz1UU0B?2c12Uhzxv#4tlSkM;1Jn?qX?T)eONurEtP< zbVfDD$+NH>C1*5Uytweh*mcX>lPUd%rCos^?yFt!*Ng=!)q!m}kq6pEb;+p6o45A6 zDZ7}uZ1at(eksE>rAeA&1kXj*)>9qPJSvFYGjgCDLis7ez0I}rGH%AlUR`}sfd2)< zq#OV$X@b3aUWaX#F>$Wo#AM3Pr;odS2Hq5+-_BNG>ex*cN@S>?(l4Q`h{Tf1Us!PV z>lyp9Z1YD|89wCz#HU#zIWfj6$+m4@JZsNj+CQuy#fg>UK+TpCfejwA2DON=7f&Nj zUq?^(-4BVo=zIzXS!Uc|KljjFmwoILL%`n!e&%+dG`~z+T)=$`#y#nrHL?ctlk0>qxovz5TbKqN?EmP>X`>?duUxsO=Ymvsv%c z%|0PHJdmP^#QooRdEX--GBr1RUNHU%+R0af|t zD?eSBcAeh`yBC5E{)w3uRbg3!%~b{tIwzOcEu4~Z2`%V5jrUrAv%5O;eoO~(g5J&H z5%<_FC6dLJVbCZaw3X9`T59J#kn#GZl~EAWA*LYfbCqd*c;Q}Pzo~CI+haq^^p_vZ zRW4-4K*SB)=O}AGmTo;4YhnbrJ)3gOxMxSH!eon}bV=Ss&8ct?PI%+BL8p!CRFKL_ zC#YRBb?{SHS~?^0Oy*n>9?Et-KK8T^VLj8a|DNr}a#``BU!k%xcXXSJgvxB0O7h3S z^eYoSqUM~*K1Ei=?3p7M+^xB4Gw1h-ur9 zC`i=Vga1X`x5i#o)6bMzmRLEpqb>Cm=roHQg=cp@FxM8E>do=#nP?B3GrRr!5D|nh zMEegwR1Bd4odizrOqY#h%F@l}q$KuBz!rs=Cdr$M{XX@Q9|eg*=m^41p%SEpK$EnF zNR@qFP$)0N=d654l%1$f^uk4oz7OLR4j@X-{m0?5PPd0h71j@hCHq!?*1Sv;upbcS zO7SxYMA#o9L)u4TvEbQ?s?BRub+?F2q%$z;Js@`9(KJck)T!ChI@klkZlVB!-IZFg z-%w#`ij=$>r?nN^Ase8lq5ON7zv947ublnH^2Wo$4D2Q8tO*;!WefzI4$TG~0F|vF zUjjA-xK#$@m60aNo2nR-<_-RU@NhRe->jpygBQRJ)dn5t@`W<3&+|o~AxwcObB?Su z=*1I%CX-jlN0VPG7a7NZ?#s<@_x-o!~Ac#FE5E zhGv7_jVO!rAm)&`;>Qd)_{2hIc&)A;V0AAd-v!O;SJi`X z%f(L9>;PsF{hBHBOK-w>@DAPbs*AwMZFJ=a9!PaTZwE3vW%eRXlDCL`M$N|oVK**h z1mQC|T*C9hsJ7q+;PH^vo>kHJHkBs7V?7|$0mTZ0TV^TpWOJVw`&^v9YDW0E06fpkk23hFWfx2{>< z;BDjLT)%y&pyLI}d7pZCCLafcZ?$_2aC~CLB&T|glgL;A6reB?PG=NpdzP`$2s?fft=3mV}lWn>015ctTwSd|0* z)Vy}yK+kbTILa7NkQ|iGS@AJ2262Ik^S2PvacP93mk|ZY!s(w~RU|5Rn=1&bn^rFv z@ + { + public PayPalPlusController( + IPaymentService paymentService, + IOrderService orderService, + IOrderProcessingService orderProcessingService) : base( + PayPalPlusProvider.SystemName, + paymentService, + orderService, + orderProcessingService) + { + } + + [NonAction] + public override IList ValidatePaymentForm(FormCollection form) + { + var warnings = new List(); + return warnings; + } + + [NonAction] + public override ProcessPaymentRequest GetPaymentInfo(FormCollection form) + { + var paymentInfo = new ProcessPaymentRequest(); + return paymentInfo; + } + + [AdminAuthorize, ChildActionOnly] + public ActionResult Configure() + { + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + + var model = new PayPalPlusConfigurationModel + { + ConfigGroups = T("Plugins.SmartStore.PayPal.ConfigGroups").Text.SplitSafe(";") + }; + + model.TransactModeValues = new SelectList(new List + { + new { ID = (int)TransactMode.Authorize, Name = T("Plugins.SmartStore.PayPal.ModeAuth") }, + new { ID = (int)TransactMode.AuthorizeAndCapture, Name = T("Plugins.SmartStore.PayPal.ModeAuthAndCapture") } + }, + "ID", "Name", (int)settings.TransactMode); + + model.AvailableSecurityProtocols = GetSecurityProtocols() + .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) + .ToList(); + + model.Copy(settings, true); + + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + storeDependingSettingHelper.GetOverrideKeys(settings, model, storeScope, Services.Settings); + + return View(model); + } + + [HttpPost, AdminAuthorize, ChildActionOnly] + public ActionResult Configure(PayPalPlusConfigurationModel model, FormCollection form) + { + if (!ModelState.IsValid) + return Configure(); + + ModelState.Clear(); + + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + + model.Copy(settings, false); + + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); + + // multistore context not possible, see IPN handling + Services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); + + Services.Settings.ClearCache(); + NotifySuccess(T("Admin.Common.DataSuccessfullySaved")); + + return Configure(); + } + + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Description.txt b/src/Plugins/SmartStore.PayPal/Description.txt index ef3a69a027..67e7b90636 100644 --- a/src/Plugins/SmartStore.PayPal/Description.txt +++ b/src/Plugins/SmartStore.PayPal/Description.txt @@ -1,8 +1,8 @@ FriendlyName: PayPal -Description: Provides the PayPal payment methods PayPal Express, PayPal Standard and PayPal Direct. +Description: Provides the PayPal payment methods PayPal Standard, PayPal Direct, PayPal Express and PayPal Plus. SystemName: SmartStore.PayPal Group: Payment -Version: 2.5.0.1 +Version: 2.5.0.2 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.PayPal.dll diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index 17ca631122..294fb52d68 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -4,7 +4,23 @@ PayPal Zahlungsarten - Stellt die PayPal-Zahlungsarten PayPal Express, PayPal Standard und PayPal Direct zur Verfügung. + Stellt die PayPal-Zahlungsarten PayPal Standard, PayPal Direct, PayPal Express und PayPal Plus zur Verfügung. + + + + API Zugang;Datenaustausch;Sonstiges + + + Autorisierung sofort, Abbuchung später + + + Sofort abbuchen + + + Button im Miniwarenkorb anzeigen + + + Legt fest, ob der Checkout-Button im Miniwarenkorb angezeigt werden soll. @@ -89,8 +105,8 @@ - - + + PayPal Express @@ -333,5 +349,37 @@ Zahlungsgebühr: {10}]]> - + + + + PayPal Plus + + + + + + Stellen Sie bitte sicher, dass PayPal die Primärwährung Ihres Shops unterstützt.

+
    +
  1. Loggen Sie sich in Ihr PayPal-Konto ein.
  2. +
  3. Klicken Sie auf den Register Mein Profil.
  4. +
  5. Klicken Sie unter Verkäufer/Händler auf API-Zugriff.
  6. +
  7. Klicken Sie auf API-Berechtigung anfordern.
  8. +
  9. Wählen Sie die Option Fordern Sie eine API-Signature an.
  10. +
  11. Klicken Sie auf Zustimmen und senden.
  12. +
+

Aktivieren Sie optional den PayPal-Service zum Empfang von IPN-Nachrichten (Instant Payment Notification):

+
    +
  1. In Ihr Premier- oder Business-Konto einloggen.
  2. +
  3. Auf den Register Mein Profil klicken.
  4. +
  5. Unter Sprach-Kodierung > Weitere Einstellungen wählen Sie bitte UTF-8.
  6. +
  7. Sofortige Zahlungsbestätigung klicken.
  8. +
  9. Einstellungen für sofortige Zahlungsbestätigungen wählen klicken.
  10. +
  11. Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalPlus/IPNHandler) eingeben.
  12. +
  13. Speichern klicken. Danach sollten Sie eine Nachricht über die erfolgreiche Aktivierung von IPN erhalten.
  14. +
]]> +
+
+
+
+ \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index 9dbfca9abc..24db661f71 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -4,7 +4,23 @@ PayPal Payment Methods - Provides the PayPal payment methods PayPal Express, PayPal Standard and PayPal Direct. + Provides the PayPal payment methods PayPal Standard, PayPal Direct, PayPal Express and PayPal Plus. + + + + API access;Data exchange;Miscellaneous + + + Authorize immediately, debit later + + + Immediately debit + + + Show button in mini shopping cart + + + Soecifies to show the checkout button in the mini shopping cart. @@ -334,4 +350,37 @@ Payment fee: {10}]]> + + + PayPal Plus + + + + + + If you're using PayPal ensure that your primary store currency is supported by Paypal.

+
    +
  1. Log into your PayPal account.
  2. +
  3. Click on Profile from the Home Menu.
  4. +
  5. Under Account Information choose API Access.
  6. +
  7. Choose Request API credentials.
  8. +
  9. Choose API Signature as the credential type.
  10. +
  11. Enable the checkbox and agree to the license Terms-I agree to the API Certificate Terms of Use and terms incorporated therein and press the Submit button.
  12. +
+

Before being able to receive IPN (Instant Payment Notification) messages (optional), you'll need 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 Language Encoding and More options and select UTF-8.
  6. +
  7. Click Instant Payment Notification in the Selling Preferences column.
  8. +
  9. Click the Edit IPN Settings button to update your settings.
  10. +
  11. Select Receive IPN messages (enabled) and enter the URL of your IPN handler (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalPlus/IPNHandler).
  12. +
  13. Click Save, and you should get a message that you have successfully activated IPN.
  14. +
]]> +
+
+
+
+ \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs index ed2526fbc4..9d697dd85d 100644 --- a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs +++ b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs @@ -130,4 +130,41 @@ public void Copy(PayPalExpressPaymentSettings settings, bool fromSettings) } } } + + + public class PayPalPlusConfigurationModel : ApiConfigurationModel + { + [SmartResourceDisplayName("Plugins.SmartStore.PayPal.ShowButtonInMiniShoppingCart")] + public bool ShowButtonInMiniShoppingCart { get; set; } + + public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) + { + if (fromSettings) + { + SecurityProtocol = settings.SecurityProtocol; + UseSandbox = settings.UseSandbox; + IpnChangesPaymentStatus = settings.IpnChangesPaymentStatus; + TransactMode = Convert.ToInt32(settings.TransactMode); + ApiAccountName = settings.ApiAccountName; + ApiAccountPassword = settings.ApiAccountPassword; + Signature = settings.Signature; + AdditionalFee = settings.AdditionalFee; + AdditionalFeePercentage = settings.AdditionalFeePercentage; + ShowButtonInMiniShoppingCart = settings.ShowButtonInMiniShoppingCart; + } + else + { + settings.SecurityProtocol = SecurityProtocol; + settings.UseSandbox = UseSandbox; + settings.IpnChangesPaymentStatus = IpnChangesPaymentStatus; + settings.TransactMode = (TransactMode)TransactMode; + settings.ApiAccountName = ApiAccountName; + settings.ApiAccountPassword = ApiAccountPassword; + settings.Signature = Signature; + settings.AdditionalFee = AdditionalFee; + settings.AdditionalFeePercentage = AdditionalFeePercentage; + settings.ShowButtonInMiniShoppingCart = ShowButtonInMiniShoppingCart; + } + } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Plugin.cs b/src/Plugins/SmartStore.PayPal/Plugin.cs index 0883d41784..336c38f253 100644 --- a/src/Plugins/SmartStore.PayPal/Plugin.cs +++ b/src/Plugins/SmartStore.PayPal/Plugin.cs @@ -28,6 +28,7 @@ public override void Install() _settingService.SaveSetting(new PayPalExpressPaymentSettings()); _settingService.SaveSetting(new PayPalDirectPaymentSettings()); _settingService.SaveSetting(new PayPalStandardPaymentSettings()); + _settingService.SaveSetting(new PayPalPlusPaymentSettings()); _localizationService.ImportPluginResourcesFromXml(this.PluginDescriptor); @@ -39,6 +40,7 @@ public override void Uninstall() _settingService.DeleteSetting(); _settingService.DeleteSetting(); _settingService.DeleteSetting(); + _settingService.DeleteSetting(); _localizationService.DeleteLocaleStringResources(PluginDescriptor.ResourceRootKey); diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs new file mode 100644 index 0000000000..d18f0759dc --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs @@ -0,0 +1,62 @@ +using System; +using SmartStore.Core.Domain.Payments; +using SmartStore.Core.Plugins; +using SmartStore.PayPal.Controllers; +using SmartStore.PayPal.Settings; +using SmartStore.Services.Payments; + +namespace SmartStore.PayPal +{ + [SystemName("Payments.PayPalPlus")] + [FriendlyName("PayPal Plus")] + [DisplayOrder(1)] + public partial class PayPalPlusProvider : PayPalProviderBase + { + public PayPalPlusProvider() + { + } + + public static string SystemName + { + get { return "Payments.PayPalPlus"; } + } + + public override PaymentMethodType PaymentMethodType + { + get + { + return PaymentMethodType.StandardAndRedirection; + } + } + + protected override string GetResourceRootKey() + { + return "Plugins.Payments.PayPalPlus"; + } + + protected override string GetControllerName() + { + return "PayPalPlus"; + } + + /// + /// Process a payment + /// + /// Payment info required for an order processing + /// Process payment result + public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest) + { + var result = new ProcessPaymentResult + { + NewPaymentStatus = PaymentStatus.Pending + }; + + return result; + } + + public override Type GetControllerType() + { + return typeof(PayPalPlusController); + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs index 9e728d3e40..2b48ebc678 100644 --- a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs +++ b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs @@ -36,6 +36,7 @@ public abstract class PayPalApiSettingsBase : PayPalSettingsBase public string Signature { get; set; } } + public class PayPalDirectPaymentSettings : PayPalApiSettingsBase, ISettings { public PayPalDirectPaymentSettings() @@ -84,7 +85,20 @@ public PayPalExpressPaymentSettings() public decimal DefaultShippingPrice { get; set; } } - public class PayPalStandardPaymentSettings : PayPalSettingsBase, ISettings + public class PayPalPlusPaymentSettings : PayPalApiSettingsBase, ISettings + { + public PayPalPlusPaymentSettings() + { + UseSandbox = true; + } + + /// + /// Specifies whether to display the checkout button in mini shopping cart + /// + public bool ShowButtonInMiniShoppingCart { get; set; } + } + + public class PayPalStandardPaymentSettings : PayPalSettingsBase, ISettings { public PayPalStandardPaymentSettings() { @@ -101,18 +115,13 @@ public PayPalStandardPaymentSettings() public string IpnUrl { get; set; } } - /// - /// Represents payment processor transaction mode - /// - public enum TransactMode : int + + /// + /// Represents payment processor transaction mode + /// + public enum TransactMode { - /// - /// Authorize - /// Authorize = 1, - /// - /// Authorize and capture - /// AuthorizeAndCapture = 2 } } diff --git a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj index 57974e8e71..4e90e87d49 100644 --- a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj +++ b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj @@ -115,6 +115,7 @@ Properties\AssemblyVersionInfo.cs + @@ -127,6 +128,7 @@ + @@ -172,6 +174,9 @@ + + PreserveNewest + PreserveNewest @@ -183,9 +188,6 @@ PreserveNewest - - Always - Always @@ -271,6 +273,9 @@ PreserveNewest + + PreserveNewest + Designer diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/Configure.cshtml index 2a07a8326d..128ae981b6 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalDirect/Configure.cshtml @@ -15,7 +15,7 @@ diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/Configure.cshtml index 37ec7f1e5f..5738f2df4a 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalExpress/Configure.cshtml @@ -16,7 +16,7 @@ diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml new file mode 100644 index 0000000000..928d73ae02 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml @@ -0,0 +1,157 @@ +@using SmartStore.PayPal.Models; +@using SmartStore.Web.Framework; +@using SmartStore.Web.Framework.UI; +@model PayPalPlusConfigurationModel +@{ + Layout = ""; + Html.AddCssFileParts(true, "~/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css"); +} + +
+
+
+ + @Html.Raw(T("Plugins.Payments.PayPalPlus.AdminInstruction")) +
+
+
+ + PayPal + +
+
+ +@Html.Action("StoreScopeConfiguration", "Setting", new { area = "Admin" }) + +@using (Html.BeginForm()) +{ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ @Model.ConfigGroups.SafeGet(0) +
+
+
+ @Html.SmartLabelFor(model => model.UseSandbox) + + @*multistore context not possible, see IPN handling*@ + @Html.EditorFor(model => model.UseSandbox) + @Html.ValidationMessageFor(model => model.UseSandbox) +
+ @Html.SmartLabelFor(model => model.ApiAccountName) + + @Html.SettingEditorFor(model => model.ApiAccountName) + @Html.ValidationMessageFor(model => model.ApiAccountName) +
+ @Html.SmartLabelFor(model => model.ApiAccountPassword) + + @Html.SettingEditorFor(model => model.ApiAccountPassword) + @Html.ValidationMessageFor(model => model.ApiAccountPassword) +
+ @Html.SmartLabelFor(model => model.Signature) + + @Html.SettingEditorFor(model => model.Signature) + @Html.ValidationMessageFor(model => model.Signature) +
+
+
+ @Model.ConfigGroups.SafeGet(1) +
+
+
+ @Html.SmartLabelFor(model => model.SecurityProtocol) + + @Html.DropDownListFor(model => model.SecurityProtocol, Model.AvailableSecurityProtocols, T("Common.Unspecified")) +
+ @Html.SmartLabelFor(model => model.TransactMode) + + @Html.SettingOverrideCheckbox(model => model.TransactMode) + @Html.DropDownListFor(model => model.TransactMode, Model.TransactModeValues) +
+ @Html.SmartLabelFor(model => model.IpnChangesPaymentStatus) + + @Html.SettingEditorFor(model => model.IpnChangesPaymentStatus) + @Html.ValidationMessageFor(model => model.IpnChangesPaymentStatus) +
+
+
+ @Model.ConfigGroups.SafeGet(2) +
+
+
+ @Html.SmartLabelFor(model => model.ShowButtonInMiniShoppingCart) + + @Html.SettingEditorFor(model => model.ShowButtonInMiniShoppingCart) + @Html.ValidationMessageFor(model => model.ShowButtonInMiniShoppingCart) +
+ @Html.SmartLabelFor(model => model.AdditionalFee) + + @Html.SettingEditorFor(model => model.AdditionalFee) + @Html.ValidationMessageFor(model => model.AdditionalFee) +
+ @Html.SmartLabelFor(model => model.AdditionalFeePercentage) + + @Html.SettingEditorFor(model => model.AdditionalFeePercentage) + @Html.ValidationMessageFor(model => model.AdditionalFeePercentage) +
+   + + +
+} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml index fdd6911358..5846c1ed43 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/Configure.cshtml @@ -14,7 +14,7 @@ diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/PaymentInfo.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/PaymentInfo.cshtml index c5b73f2d11..ee77251801 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/PaymentInfo.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalStandard/PaymentInfo.cshtml @@ -5,7 +5,7 @@
- PayPal + PayPal
@T("Plugins.Payments.PayPalStandard.Fields.RedirectionTip") diff --git a/src/Plugins/SmartStore.PayPal/changelog.md b/src/Plugins/SmartStore.PayPal/changelog.md index 7f022d9901..f27fa62f5d 100644 --- a/src/Plugins/SmartStore.PayPal/changelog.md +++ b/src/Plugins/SmartStore.PayPal/changelog.md @@ -1,5 +1,9 @@ #Release Notes +##Paypal 2.5.0.2 +###New Features +* PayPal Plus payment provider + ##Paypal 2.5.0.1 ###Bugfixes * PayPal Standard: The order amount transmitted to PayPal was wrong if gift cards or reward points were applied From 879581a396b1fc87fbf65b1ad78008dc5b9da173 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 8 Apr 2016 14:16:18 +0200 Subject: [PATCH 017/285] Data exchange: Warning if downloadable file is in use and thus cannot be opened --- ...201603121451066_ThirdPartyEmailHandOver.cs | 4 +++ .../Controllers/ExportController.cs | 28 +++++++++++++----- .../Controllers/ImportController.cs | 29 ++++++++++++++----- 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs index c930dd23f6..a4a2190ed1 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs @@ -175,6 +175,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberVisibility.Display", "Display", "Anzeigen"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberVisibility.EditableIfEmpty", "Editable if empty", "Editierbar falls leer"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberVisibility.Editable", "Always editable", "Stets editierbar"); + + builder.AddOrUpdate("Admin.Common.FileInUse", + "The file is in use and cannot be opened.", + "Die Datei ist in Benutzung und kann nicht geöffnet werden."); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 6d368aead9..0278aac4d0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -1099,10 +1099,17 @@ public ActionResult DownloadLogFile(int id) var path = profile.GetExportLogPath(); if (System.IO.File.Exists(path)) { - var stream = new FileStream(path, FileMode.Open); - var result = new FileStreamResult(stream, MediaTypeNames.Text.Plain); + try + { + var stream = new FileStream(path, FileMode.Open); + var result = new FileStreamResult(stream, MediaTypeNames.Text.Plain); - return result; + return result; + } + catch (IOException) + { + NotifyWarning(T("Admin.Common.FileInUse")); + } } } @@ -1125,12 +1132,19 @@ public ActionResult DownloadExportFile(int id, string name) if (System.IO.File.Exists(path)) { - var stream = new FileStream(path, FileMode.Open); + try + { + var stream = new FileStream(path, FileMode.Open); - var result = new FileStreamResult(stream, MimeTypes.MapNameToMimeType(path)); - result.FileDownloadName = Path.GetFileName(path); + var result = new FileStreamResult(stream, MimeTypes.MapNameToMimeType(path)); + result.FileDownloadName = Path.GetFileName(path); - return result; + return result; + } + catch (IOException) + { + NotifyWarning(T("Admin.Common.FileInUse")); + } } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index 9603804170..aa1b4c9c6a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -609,10 +609,17 @@ public ActionResult DownloadLogFile(int id) var path = profile.GetImportLogPath(); if (System.IO.File.Exists(path)) { - var stream = new FileStream(path, FileMode.Open); - var result = new FileStreamResult(stream, MediaTypeNames.Text.Plain); + try + { + var stream = new FileStream(path, FileMode.Open); + var result = new FileStreamResult(stream, MediaTypeNames.Text.Plain); - return result; + return result; + } + catch (IOException) + { + NotifyWarning(T("Admin.Common.FileInUse")); + } } } @@ -635,11 +642,19 @@ public ActionResult DownloadImportFile(int id, string name) if (System.IO.File.Exists(path)) { - var stream = new FileStream(path, FileMode.Open); - var result = new FileStreamResult(stream, MimeTypes.MapNameToMimeType(path)); - result.FileDownloadName = Path.GetFileName(path); + try + { + var stream = new FileStream(path, FileMode.Open); - return result; + var result = new FileStreamResult(stream, MimeTypes.MapNameToMimeType(path)); + result.FileDownloadName = Path.GetFileName(path); + + return result; + } + catch (IOException) + { + NotifyWarning(T("Admin.Common.FileInUse")); + } } } } From c05507827915492b0e623fbf23d2161ed10c29b3 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 8 Apr 2016 17:18:19 +0200 Subject: [PATCH 018/285] Reloading plugin list now restarts app aggressively --- .../Administration/Controllers/PluginController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs index c3a1a7c9f9..aebeec2e49 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs @@ -255,8 +255,8 @@ public ActionResult ReloadList() if (!_permissionService.Authorize(StandardPermissionProvider.ManagePlugins)) return AccessDeniedView(); - //restart application - _services.WebHelper.RestartAppDomain(); + // restart application + _services.WebHelper.RestartAppDomain(aggressive: true); return RedirectToAction("List"); } From 339e8d9835796f6adfa9aa066eee0d3f1f5b5743 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 9 Apr 2016 00:34:41 +0200 Subject: [PATCH 019/285] DevTools plugin should be installed by default --- src/Presentation/SmartStore.Web/Web.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Web.config b/src/Presentation/SmartStore.Web/Web.config index f68a783d15..40c1ea7c79 100644 --- a/src/Presentation/SmartStore.Web/Web.config +++ b/src/Presentation/SmartStore.Web/Web.config @@ -81,7 +81,7 @@ - + From afdb3a3ded117a205b014332059fc14f41481b0a Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 9 Apr 2016 00:36:13 +0200 Subject: [PATCH 020/285] Use V8JsEngine in release mode by default. Azure doesn't like MSIEJsEngine. --- src/Presentation/SmartStore.Web/Web.Release.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Web.Release.config b/src/Presentation/SmartStore.Web/Web.Release.config index 49f9b30567..f9afce5a4c 100644 --- a/src/Presentation/SmartStore.Web/Web.Release.config +++ b/src/Presentation/SmartStore.Web/Web.Release.config @@ -5,7 +5,7 @@ - + From 3b777e2a1433e8434cb3d27da78ee796265d6bf5 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 9 Apr 2016 13:16:42 +0200 Subject: [PATCH 021/285] Import localized SEO names for product and categories --- changelog.md | 1 + .../Catalog/Importer/CategoryImporter.cs | 85 +++++++++++-------- .../Catalog/Importer/ProductImporter.cs | 85 +++++++++++-------- 3 files changed, 97 insertions(+), 74 deletions(-) diff --git a/changelog.md b/changelog.md index 28df4dfbf1..c11940bd9b 100644 --- a/changelog.md +++ b/changelog.md @@ -6,6 +6,7 @@ * #571 Option to display another checkbox on confirm page to let the customer accept that his email address can be handed over to a third party * #870 Implement address import for customers (both billing & shipping address) * #886 Add setting to hide manufacturer images on product detail page and to hide default image for manufacturers +* Import localized SEO names for product and categories ### Improvements * (Dev) Publishing SmartStore.Web from within Visual Studio now deploys the project correctly. No need to execute ClickToBuild.cmd anymore. Just publish directly to any target, including Azure. diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index b140603b7a..84a933a3cf 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -63,57 +63,68 @@ public CategoryImporter( private int ProcessSlugs(IImportExecuteContext context, ImportRow[] batch) { - var slugMap = new Dictionary(100); + var entityName = typeof(Category).Name; + var slugMap = new Dictionary(); + Func slugLookup = ((s) => { - if (slugMap.ContainsKey(s)) - { - return slugMap[s]; - } - return (UrlRecord)null; + return (slugMap.ContainsKey(s) ? slugMap[s] : null); }); - var entityName = typeof(Category).Name; - foreach (var row in batch) { - if (row.IsNew || row.NameChanged || row.Segmenter.HasColumn("SeName")) + if (!row.IsNew && !row.NameChanged) + continue; + + try { - try + UrlRecord urlRecord = null; + var seName = row.GetDataValue("SeName"); + seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); + + if (row.IsNew) + { + // dont't bother validating SeName for new entities. + urlRecord = new UrlRecord + { + EntityId = row.Entity.Id, + EntityName = entityName, + Slug = seName, + LanguageId = 0, + IsActive = true, + }; + _urlRecordRepository.Insert(urlRecord); + } + else { - string seName = row.GetDataValue("SeName"); - seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); + urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); + } - UrlRecord urlRecord = null; + if (urlRecord != null) + { + // a new record was inserted to the store: keep track of it for this batch. + slugMap[seName] = urlRecord; + } - if (row.IsNew) - { - // dont't bother validating SeName for new entities. - urlRecord = new UrlRecord - { - EntityId = row.Entity.Id, - EntityName = entityName, - Slug = seName, - LanguageId = 0, - IsActive = true, - }; - _urlRecordRepository.Insert(urlRecord); - } - else + foreach (var lang in context.Languages) + { + if (row.Segmenter.HasColumn("SeName", lang.UniqueSeoCode)) { - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); - } + seName = row.GetDataValue("SeName", lang.UniqueSeoCode); + seName = row.Entity.ValidateSeName(seName, null, false, _urlRecordService, _seoSettings, lang.Id, slugLookup); - if (urlRecord != null) - { - // a new record was inserted to the store: keep track of it for this batch. - slugMap[seName] = urlRecord; + urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, lang.Id); + + if (urlRecord != null) + { + slugMap[seName] = urlRecord; + } } } - catch (Exception exception) - { - context.Result.AddWarning(exception.Message, row.GetRowInfo(), "SeName"); - } + } + catch (Exception exception) + { + context.Result.AddWarning(exception.Message, row.GetRowInfo(), "SeName"); } } diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 57f8c41976..c63ea1fcf9 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -430,57 +430,68 @@ private int ProcessLocalizations(IImportExecuteContext context, ImportRow[] batch) { - var slugMap = new Dictionary(100); + var entityName = typeof(Product).Name; + var slugMap = new Dictionary(); + Func slugLookup = ((s) => { - if (slugMap.ContainsKey(s)) - { - return slugMap[s]; - } - return (UrlRecord)null; + return (slugMap.ContainsKey(s) ? slugMap[s] : null); }); - var entityName = typeof(Product).Name; - foreach (var row in batch) { - if (row.IsNew || row.NameChanged || row.Segmenter.HasColumn("SeName")) + if (!row.IsNew && !row.NameChanged) + continue; + + try { - try + UrlRecord urlRecord = null; + var seName = row.GetDataValue("SeName"); + seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); + + if (row.IsNew) + { + // dont't bother validating SeName for new entities. + urlRecord = new UrlRecord + { + EntityId = row.Entity.Id, + EntityName = entityName, + Slug = seName, + LanguageId = 0, + IsActive = true, + }; + _urlRecordRepository.Insert(urlRecord); + } + else { - string seName = row.GetDataValue("SeName"); - seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); + urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); + } - UrlRecord urlRecord = null; + if (urlRecord != null) + { + // a new record was inserted to the store: keep track of it for this batch. + slugMap[seName] = urlRecord; + } - if (row.IsNew) - { - // dont't bother validating SeName for new entities. - urlRecord = new UrlRecord - { - EntityId = row.Entity.Id, - EntityName = entityName, - Slug = seName, - LanguageId = 0, - IsActive = true, - }; - _urlRecordRepository.Insert(urlRecord); - } - else + foreach (var lang in context.Languages) + { + if (row.Segmenter.HasColumn("SeName", lang.UniqueSeoCode)) { - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); - } + seName = row.GetDataValue("SeName", lang.UniqueSeoCode); + seName = row.Entity.ValidateSeName(seName, null, false, _urlRecordService, _seoSettings, lang.Id, slugLookup); - if (urlRecord != null) - { - // a new record was inserted to the store: keep track of it for this batch. - slugMap[seName] = urlRecord; + urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, lang.Id); + + if (urlRecord != null) + { + slugMap[seName] = urlRecord; + } } } - catch (Exception exception) - { - context.Result.AddWarning(exception.Message, row.GetRowInfo(), "SeName"); - } + } + catch (Exception exception) + { + context.Result.AddWarning(exception.Message, row.GetRowInfo(), "SeName"); } } From 35233703fe1bb615f02590414049caeb274351e3 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 11 Apr 2016 12:57:52 +0200 Subject: [PATCH 022/285] PayPal Plus: Service class for generic REST API implementation. We should not use old SOAP services for new providers. --- .../Controllers/PayPalExpressController.cs | 3 - .../Controllers/PayPalPlusController.cs | 50 ++- .../SmartStore.PayPal/DependencyRegistrar.cs | 8 + .../Filters/PayPalPlusCheckoutFilter.cs | 41 +++ .../Localization/resources.de-de.xml | 32 +- .../Localization/resources.en-us.xml | 30 +- .../Models/ApiConfigurationModels.cs | 20 +- .../Models/PayPalPlusCheckoutModel.cs | 8 + .../Providers/PayPalExpressProvider.cs | 10 +- .../Providers/PayPalStandardProvider.cs | 46 ++- .../SmartStore.PayPal/RouteProvider.cs | 13 +- .../Services/IPayPalService.cs | 21 ++ .../Services/PayPalLineItem.cs | 46 --- .../Services/PayPalProcessPaymentRequest.cs | 12 - .../Services/PayPalService.cs | 292 ++++++++++++++++++ .../Settings/PayPalSettings.cs | 23 +- .../SmartStore.PayPal.csproj | 16 +- .../Views/PayPalPlus/Configure.cshtml | 61 ++-- .../PayPalPlus/PaymentWall.Mobile.cshtml | 15 + .../Views/PayPalPlus/PaymentWall.cshtml | 47 +++ src/Plugins/SmartStore.PayPal/packages.config | 1 + 21 files changed, 676 insertions(+), 119 deletions(-) create mode 100644 src/Plugins/SmartStore.PayPal/Filters/PayPalPlusCheckoutFilter.cs create mode 100644 src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs create mode 100644 src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs delete mode 100644 src/Plugins/SmartStore.PayPal/Services/PayPalLineItem.cs delete mode 100644 src/Plugins/SmartStore.PayPal/Services/PayPalProcessPaymentRequest.cs create mode 100644 src/Plugins/SmartStore.PayPal/Services/PayPalService.cs create mode 100644 src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml create mode 100644 src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs index dc0271f69d..7e647a9970 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs @@ -4,16 +4,13 @@ using System.Net; using System.Text; using System.Web.Mvc; -using System.Web.Routing; using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Discounts; -using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Logging; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Shipping; using SmartStore.PayPal.Models; using SmartStore.PayPal.PayPalSvc; -using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.PayPal.Validators; using SmartStore.Services.Common; diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index 540dcee1f9..465f716561 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Web.Mvc; using SmartStore.PayPal.Models; +using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Services.Orders; using SmartStore.Services.Payments; @@ -13,15 +14,19 @@ namespace SmartStore.PayPal.Controllers { public class PayPalPlusController : PayPalControllerBase { + private readonly IPayPalService _payPalService; + public PayPalPlusController( IPaymentService paymentService, IOrderService orderService, - IOrderProcessingService orderProcessingService) : base( + IOrderProcessingService orderProcessingService, + IPayPalService payPalService) : base( PayPalPlusProvider.SystemName, paymentService, orderService, orderProcessingService) { + _payPalService = payPalService; } [NonAction] @@ -93,5 +98,48 @@ public ActionResult Configure(PayPalPlusConfigurationModel model, FormCollection return Configure(); } + [AdminAuthorize] + public ActionResult CreateExperienceProfile() + { + string profileId = null; + string error = null; + + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + + var store = Services.StoreService.GetStoreById(storeScope == 0 ? Services.StoreContext.CurrentStore.Id : storeScope); + + var result = _payPalService.SetCheckoutExperience(settings, store); + + if (result.Success) + { + profileId = (string)result.Json.id; + } + else + { + error = result.ErrorName.EmptyNull().EnsureEndsWith(".").Grow(result.ErrorMessage, " "); + } + + return new JsonResult { Data = new { id = profileId, error = error}, JsonRequestBehavior = JsonRequestBehavior.AllowGet }; + } + + public ActionResult PaymentInfo() + { + return new EmptyResult(); + } + + public ActionResult PaymentWall() + { + var model = new PayPalPlusCheckoutModel(); + + return View(model); + } + + [HttpPost] + public ActionResult PaymentWall(FormCollection form) + { + + return RedirectToAction("Confirm", "Checkout", new { area = "" }); + } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/DependencyRegistrar.cs b/src/Plugins/SmartStore.PayPal/DependencyRegistrar.cs index d05fa4025a..346a7ab86b 100644 --- a/src/Plugins/SmartStore.PayPal/DependencyRegistrar.cs +++ b/src/Plugins/SmartStore.PayPal/DependencyRegistrar.cs @@ -3,6 +3,7 @@ using SmartStore.Core.Infrastructure; using SmartStore.Core.Infrastructure.DependencyManagement; using SmartStore.PayPal.Filters; +using SmartStore.PayPal.Services; using SmartStore.Web.Controllers; namespace SmartStore.PayPal @@ -11,10 +12,17 @@ public class DependencyRegistrar : IDependencyRegistrar { public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, bool isActiveModule) { + builder.RegisterType().As().InstancePerRequest(); + if (isActiveModule) { builder.RegisterType().AsActionFilterFor(x => x.PaymentMethod()).InstancePerRequest(); + builder.RegisterType().AsActionFilterFor(x => x.FlyoutShoppingCart()).InstancePerRequest(); + + builder.RegisterType() + .AsActionFilterFor(x => x.PaymentMethod()) + .InstancePerRequest(); } } diff --git a/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusCheckoutFilter.cs b/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusCheckoutFilter.cs new file mode 100644 index 0000000000..db1bb64542 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusCheckoutFilter.cs @@ -0,0 +1,41 @@ +using System.Web.Mvc; +using System.Web.Routing; +using SmartStore.Services; +using SmartStore.Services.Payments; + +namespace SmartStore.PayPal.Filters +{ + public class PayPalPlusCheckoutFilter : IActionFilter + { + private readonly ICommonServices _services; + private readonly IPaymentService _paymentService; + + public PayPalPlusCheckoutFilter( + ICommonServices services, + IPaymentService paymentService) + { + _services = services; + _paymentService = paymentService; + } + + public void OnActionExecuting(ActionExecutingContext filterContext) + { + if (filterContext == null || filterContext.ActionDescriptor == null || filterContext.HttpContext == null || filterContext.HttpContext.Request == null) + return; + + var store = _services.StoreContext.CurrentStore; + + if (!_paymentService.IsPaymentMethodActive(PayPalPlusProvider.SystemName, store.Id)) + return; + + var routeValues = new RouteValueDictionary(new { action = "PaymentWall", controller = "PayPalPlus" }); + + filterContext.Result = new RedirectToRouteResult("SmartStore.PayPalPlus", routeValues); + } + + public void OnActionExecuted(ActionExecutedContext filterContext) + { + + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index 294fb52d68..3c5ce4e3cb 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -4,25 +4,43 @@ PayPal Zahlungsarten - Stellt die PayPal-Zahlungsarten PayPal Standard, PayPal Direct, PayPal Express und PayPal Plus zur Verfügung. + Stellt die PayPal-Zahlungsarten PayPal Standard, PayPal Direct, PayPal Express und PayPal PLUS zur Verfügung. API Zugang;Datenaustausch;Sonstiges + + {0}]]> + Autorisierung sofort, Abbuchung später Sofort abbuchen - - Button im Miniwarenkorb anzeigen + + Jetzt erstellen - - Legt fest, ob der Checkout-Button im Miniwarenkorb angezeigt werden soll. + + Client-ID - + + Legt die API Client-ID fest. + + + Secret + + + Legt die API Secret fest. + + + Experience Profil-ID + + + Legt die Experience Profil-ID fest. Das Profil beinhaltet globale Daten wie z.B. Shop-Name und -Logo. Ein Profil braucht nur einmalig angelegt werden. + + @@ -352,7 +370,7 @@ Zahlungsgebühr: {10}]]> - PayPal Plus + PayPal PLUS diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index 24db661f71..b278abb100 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -4,23 +4,41 @@ PayPal Payment Methods - Provides the PayPal payment methods PayPal Standard, PayPal Direct, PayPal Express and PayPal Plus. + Provides the PayPal payment methods PayPal Standard, PayPal Direct, PayPal Express and PayPal PLUS. API access;Data exchange;Miscellaneous + + {0}]]> + Authorize immediately, debit later Immediately debit - - Show button in mini shopping cart + + Create now + + + Client ID + + + Specifies the API client ID. + + + Secret + + + Specifies the API secret. + + + Experience profile ID - - Soecifies to show the checkout button in the mini shopping cart. + + Specifies the experience profile ID. The profile conatins global data like shop name and logo. A profile needs to be created only once. @@ -352,7 +370,7 @@ Payment fee: {10}]]> - PayPal Plus + PayPal PLUS diff --git a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs index 9d697dd85d..05a235004c 100644 --- a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs +++ b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs @@ -134,8 +134,14 @@ public void Copy(PayPalExpressPaymentSettings settings, bool fromSettings) public class PayPalPlusConfigurationModel : ApiConfigurationModel { - [SmartResourceDisplayName("Plugins.SmartStore.PayPal.ShowButtonInMiniShoppingCart")] - public bool ShowButtonInMiniShoppingCart { get; set; } + [SmartResourceDisplayName("Plugins.SmartStore.PayPal.ClientId")] + public string ClientId { get; set; } + + [SmartResourceDisplayName("Plugins.SmartStore.PayPal.Secret")] + public string Secret { get; set; } + + [SmartResourceDisplayName("Plugins.SmartStore.PayPal.ExperienceProfileId")] + public string ExperienceProfileId { get; set; } public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) { @@ -150,7 +156,10 @@ public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) Signature = settings.Signature; AdditionalFee = settings.AdditionalFee; AdditionalFeePercentage = settings.AdditionalFeePercentage; - ShowButtonInMiniShoppingCart = settings.ShowButtonInMiniShoppingCart; + + ClientId = settings.ClientId; + Secret = settings.Secret; + ExperienceProfileId = settings.ExperienceProfileId; } else { @@ -163,7 +172,10 @@ public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) settings.Signature = Signature; settings.AdditionalFee = AdditionalFee; settings.AdditionalFeePercentage = AdditionalFeePercentage; - settings.ShowButtonInMiniShoppingCart = ShowButtonInMiniShoppingCart; + + settings.ClientId = ClientId; + settings.Secret = Secret; + settings.ExperienceProfileId = ExperienceProfileId; } } } diff --git a/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs b/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs new file mode 100644 index 0000000000..56c44dc329 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs @@ -0,0 +1,8 @@ +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.PayPal.Models +{ + public class PayPalPlusCheckoutModel : ModelBase + { + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs index d64372f59c..63e3efc0f3 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalExpressProvider.cs @@ -12,7 +12,6 @@ using SmartStore.Core.Plugins; using SmartStore.PayPal.Controllers; using SmartStore.PayPal.PayPalSvc; -using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Services.Catalog; using SmartStore.Services.Common; @@ -576,4 +575,13 @@ public DoExpressCheckoutPaymentResponseType DoExpressCheckoutPayment(ProcessPaym return result; } } + + + 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/Providers/PayPalStandardProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs index b4ea50d405..4632649153 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalStandardProvider.cs @@ -14,7 +14,6 @@ using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.PayPal.Controllers; -using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Services; using SmartStore.Services.Localization; @@ -23,9 +22,6 @@ namespace SmartStore.PayPal { - /// - /// PayPalStandard provider - /// [SystemName("Payments.PayPalStandard")] [FriendlyName("PayPal Standard")] [DisplayOrder(2)] @@ -573,4 +569,46 @@ public override void GetPaymentInfoRoute(out string actionName, out string contr routeValues = new RouteValueDictionary() { { "area", "SmartStore.PayPal" } }; } } + + + public class PayPalLineItem : ICloneable + { + public PayPalItemType Type { get; set; } + public string Name { get; set; } + public int Quantity { get; set; } + public decimal Amount { get; set; } + + public decimal AmountRounded + { + get + { + return Math.Round(Amount, 2); + } + } + + public PayPalLineItem Clone() + { + var item = new PayPalLineItem + { + Type = this.Type, + Name = this.Name, + Quantity = this.Quantity, + Amount = this.Amount + }; + return item; + } + + object ICloneable.Clone() + { + return this.Clone(); + } + } + + public enum PayPalItemType + { + CartItem = 0, + Shipping, + PaymentFee, + Tax + } } diff --git a/src/Plugins/SmartStore.PayPal/RouteProvider.cs b/src/Plugins/SmartStore.PayPal/RouteProvider.cs index bebfbdb8ec..74e44ce093 100644 --- a/src/Plugins/SmartStore.PayPal/RouteProvider.cs +++ b/src/Plugins/SmartStore.PayPal/RouteProvider.cs @@ -29,8 +29,17 @@ public void RegisterRoutes(RouteCollection routes) ) .DataTokens["area"] = "SmartStore.PayPal"; - //Legacay Routes - routes.MapRoute("SmartStore.PayPalExpress.IPN", + routes.MapRoute("SmartStore.PayPalPlus", + "Plugins/SmartStore.PayPal/{controller}/{action}", + new { controller = "PayPalPlus", action = "Index" }, + new[] { "SmartStore.PayPal.Controllers" } + ) + .DataTokens["area"] = Plugin.SystemName; + + + + //Legacay Routes + routes.MapRoute("SmartStore.PayPalExpress.IPN", "Plugins/PaymentPayPalExpress/IPNHandler", new { controller = "PayPalExpress", action = "IPNHandler" }, new[] { "SmartStore.PayPal.Controllers" } diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs new file mode 100644 index 0000000000..32079413c8 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Stores; +using SmartStore.PayPal.Settings; + +namespace SmartStore.PayPal.Services +{ + public interface IPayPalService + { + void AddOrderNote(PayPalSettingsBase settings, Order order, string anyString); + + void LogError(Exception exception, string shortMessage = null, string fullMessage = null, bool notify = false, IList errors = null, bool isWarning = false); + + PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data, IList errors = null); + + bool EnsureAccessToken(PayPalSessionData session, PayPalApiSettingsBase settings); + + PayPalResponse SetCheckoutExperience(PayPalApiSettingsBase settings, Store store); + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalLineItem.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalLineItem.cs deleted file mode 100644 index 9c68e538fe..0000000000 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalLineItem.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; - -namespace SmartStore.PayPal.Services -{ - public class PayPalLineItem : ICloneable - { - public PayPalItemType Type { get; set; } - public string Name { get; set; } - public int Quantity { get; set; } - public decimal Amount { get; set; } - - public decimal AmountRounded - { - get - { - return Math.Round(Amount, 2); - } - } - - public PayPalLineItem Clone() - { - var item = new PayPalLineItem - { - Type = this.Type, - Name = this.Name, - Quantity = this.Quantity, - Amount = this.Amount - }; - return item; - } - - object ICloneable.Clone() - { - return this.Clone(); - } - } - - - public enum PayPalItemType - { - CartItem = 0, - Shipping, - PaymentFee, - Tax - } -} diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalProcessPaymentRequest.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalProcessPaymentRequest.cs deleted file mode 100644 index ff64026abf..0000000000 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalProcessPaymentRequest.cs +++ /dev/null @@ -1,12 +0,0 @@ -using SmartStore.Services.Payments; - -namespace SmartStore.PayPal.Services -{ - 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/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs new file mode 100644 index 0000000000..815a907c94 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using SmartStore.Core.Domain.Logging; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Localization; +using SmartStore.Core.Logging; +using SmartStore.PayPal.Settings; +using SmartStore.Services; +using SmartStore.Services.Media; +using SmartStore.Services.Orders; + +namespace SmartStore.PayPal.Services +{ + public class PayPalService : IPayPalService + { + private readonly ICommonServices _services; + private readonly IOrderService _orderService; + private readonly Lazy _pictureService; + + public PayPalService( + ICommonServices services, + IOrderService orderService, + Lazy pictureService) + { + _services = services; + _orderService = orderService; + _pictureService = pictureService; + + T = NullLocalizer.Instance; + Logger = NullLogger.Instance; + } + + public Localizer T { get; set; } + public ILogger Logger { get; set; } + + public static string GetApiUrl(bool sandbox) + { + return sandbox ? "https://api.sandbox.paypal.com" : "https://api.paypal.com"; + } + + public void AddOrderNote(PayPalSettingsBase settings, Order order, string anyString) + { + try + { + if (order == null || anyString.IsEmpty() || (settings != null && !settings.AddOrderNotes)) + return; + + string[] orderNoteStrings = T("Plugins.SmartStore.PayPal.OrderNoteStrings").Text.SplitSafe(";"); + var faviconUrl = "{0}Plugins/{1}/Content/favicon.png".FormatInvariant(_services.WebHelper.GetStoreLocation(false), Plugin.SystemName); + + var sb = new StringBuilder(); + sb.AppendFormat("", faviconUrl); + + var note = orderNoteStrings.SafeGet(0).FormatInvariant(anyString); + + sb.AppendFormat("{0}", note); + + _orderService.AddOrderNote(order, sb.ToString()); + } + catch { } + } + + public void LogError(Exception exception, string shortMessage = null, string fullMessage = null, bool notify = false, IList errors = null, bool isWarning = false) + { + try + { + if (exception != null) + { + shortMessage = exception.ToAllMessages(); + fullMessage = exception.ToString(); + } + + if (shortMessage.HasValue()) + { + shortMessage = "PayPal. " + shortMessage; + Logger.InsertLog(isWarning ? LogLevel.Warning : LogLevel.Error, shortMessage, fullMessage.EmptyNull()); + + if (notify) + { + if (isWarning) + _services.Notifier.Warning(new LocalizedString(shortMessage)); + else + _services.Notifier.Error(new LocalizedString(shortMessage)); + } + } + } + catch (Exception) { } + + if (errors != null && shortMessage.HasValue()) + { + errors.Add(shortMessage); + } + } + + public PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data, IList errors = null) + { + var encoding = Encoding.UTF8; + var result = new PayPalResponse(); + HttpWebResponse webResponse = null; + + var url = GetApiUrl(settings.UseSandbox) + path.EnsureStartsWith("/"); + + if (method.IsCaseInsensitiveEqual("GET") && data.HasValue()) + { + url = url.EnsureEndsWith("?") + data; + } + + if (settings.SecurityProtocol.HasValue) + { + ServicePointManager.SecurityProtocol = settings.SecurityProtocol.Value; + } + + var request = (HttpWebRequest)WebRequest.Create(url); + request.Method = method; + request.Accept = "application/json"; + request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; + + if (path.EmptyNull().EndsWith("/token")) + { + request.Credentials = new NetworkCredential(settings.ClientId, settings.Secret); + } + else + { + request.Headers["Authorization"] = "Bearer " + accessToken.EmptyNull(); + } + + + if ((method.IsCaseInsensitiveEqual("POST") || method.IsCaseInsensitiveEqual("PATCH")) && data.HasValue()) + { + byte[] bytes = encoding.GetBytes(data); + + request.ContentLength = bytes.Length; + + using (var stream = request.GetRequestStream()) + { + stream.Write(bytes, 0, bytes.Length); + } + } + else + { + request.ContentLength = 0; + } + + try + { + webResponse = request.GetResponse() as HttpWebResponse; + result.Success = (webResponse.StatusCode == HttpStatusCode.OK); + } + catch (WebException wexc) + { + result.Success = false; + webResponse = wexc.Response as HttpWebResponse; + } + catch (Exception exception) + { + result.Success = false; + LogError(exception, errors: errors); + } + + try + { + if (webResponse != null) + { + using (var reader = new StreamReader(webResponse.GetResponseStream(), encoding)) + { + var rawResponse = reader.ReadToEnd(); + if (rawResponse.HasValue()) + { + if (rawResponse.StartsWith("[")) + result.Json = JArray.Parse(rawResponse); + else + result.Json = JObject.Parse(rawResponse); + + if (result.Json != null) + { + if (!result.Success) + { + result.ErrorName = (string)result.Json.name; + result.ErrorMessage = (string)result.Json.message; + + var details = (string)result.Json.details; + if (details.IsEmpty()) + details = webResponse.StatusDescription; + + if (details.HasValue()) + result.ErrorMessage = result.ErrorMessage.EnsureEndsWith(".").Grow(details, " "); + + LogError(null, result.ErrorName, result.ErrorMessage, false, errors); + } + } + } + } + } + } + catch (Exception exception) + { + LogError(exception, errors: errors); + } + finally + { + if (webResponse != null) + { + webResponse.Close(); + webResponse.Dispose(); + } + } + + return result; + } + + public bool EnsureAccessToken(PayPalSessionData session, PayPalApiSettingsBase settings) + { + if (session.AccessToken.IsEmpty() || DateTime.UtcNow >= session.TokenExpiration) + { + var data = new Dictionary(); + data.Add("grant_type", "client_credentials"); + + var result = CallApi("POST", "/v1/oauth2/token", null, settings, JsonConvert.SerializeObject(data)); + + if (result.Success) + { + session.AccessToken = (string)result.Json.access_token; + + var expireSeconds = ((string)result.Json.expires_in).ToInt(5 * 60); + + session.TokenExpiration = DateTime.UtcNow.AddSeconds(expireSeconds); + } + } + + return session.AccessToken.HasValue(); + } + + public PayPalResponse SetCheckoutExperience(PayPalApiSettingsBase settings, Store store) + { + var session = new PayPalSessionData(); + if (!EnsureAccessToken(session, settings)) + return null; + + var logo = _pictureService.Value.GetPictureById(store.LogoPictureId); + + var data = new Dictionary(); + var presentation = new Dictionary(); + var inpuFields = new Dictionary(); + + presentation.Add("brand_name", store.Name); + presentation.Add("locale_code", _services.WorkContext.WorkingLanguage.UniqueSeoCode.EmptyNull().ToUpper()); + + if (logo != null) + presentation.Add("logo_image", _pictureService.Value.GetPictureUrl(logo, showDefaultPicture: false, storeLocation: store.Url)); + + inpuFields.Add("allow_note", false); + inpuFields.Add("no_shipping", 0); + inpuFields.Add("address_override", 1); + + data.Add("name", store.Name); + data.Add("presentation", presentation); + data.Add("input_fields", inpuFields); + + var result = CallApi("POST", "/v1/payment-experience/webprofiles", session.AccessToken, settings, JsonConvert.SerializeObject(data)); + + if (result.Success) + { + + //return (string)result.Json.id; + } + + return result; + } + } + + + public class PayPalResponse + { + public bool Success { get; set; } + public dynamic Json { get; set; } + public string ErrorName { get; set; } + public string ErrorMessage { get; set; } + } + + public class PayPalSessionData + { + public string AccessToken { get; set; } + public DateTime TokenExpiration { get; set; } + } + +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs index 2b48ebc678..e5509c8e7d 100644 --- a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs +++ b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs @@ -9,16 +9,19 @@ public PayPalSettingsBase() { SecurityProtocol = SecurityProtocolType.Tls12; IpnChangesPaymentStatus = true; + AddOrderNotes = true; } 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; } + public bool AddOrderNotes { 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; } public decimal AdditionalFee { get; set; } @@ -34,6 +37,9 @@ public abstract class PayPalApiSettingsBase : PayPalSettingsBase public string ApiAccountName { get; set; } public string ApiAccountPassword { get; set; } public string Signature { get; set; } + + public string ClientId { get; set; } + public string Secret { get; set; } } @@ -41,8 +47,8 @@ public class PayPalDirectPaymentSettings : PayPalApiSettingsBase, ISettings { public PayPalDirectPaymentSettings() { + UseSandbox = true; TransactMode = TransactMode.Authorize; - UseSandbox = true; } } @@ -87,15 +93,12 @@ public PayPalExpressPaymentSettings() public class PayPalPlusPaymentSettings : PayPalApiSettingsBase, ISettings { + public string ExperienceProfileId { get; set; } + public PayPalPlusPaymentSettings() { UseSandbox = true; } - - /// - /// Specifies whether to display the checkout button in mini shopping cart - /// - public bool ShowButtonInMiniShoppingCart { get; set; } } public class PayPalStandardPaymentSettings : PayPalSettingsBase, ISettings diff --git a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj index 4e90e87d49..10e754fcef 100644 --- a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj +++ b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj @@ -68,6 +68,10 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + + ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + True + @@ -123,7 +127,9 @@ + + @@ -132,9 +138,9 @@ - + + - @@ -276,6 +282,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + Designer diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml index 928d73ae02..b787a41e76 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml @@ -1,4 +1,5 @@ -@using SmartStore.PayPal.Models; +@using SmartStore.PayPal; +@using SmartStore.PayPal.Models; @using SmartStore.Web.Framework; @using SmartStore.Web.Framework.UI; @model PayPalPlusConfigurationModel @@ -15,7 +16,7 @@
@@ -23,6 +24,8 @@ @Html.Action("StoreScopeConfiguration", "Setting", new { area = "Admin" }) +@Html.ValidationSummary(false) + @using (Html.BeginForm()) { @@ -47,29 +50,34 @@ @@ -116,15 +124,6 @@ - - - -
- @Html.SmartLabelFor(model => model.ApiAccountName) + @Html.SmartLabelFor(model => model.ClientId) - @Html.SettingEditorFor(model => model.ApiAccountName) - @Html.ValidationMessageFor(model => model.ApiAccountName) + @Html.SettingEditorFor(model => model.ClientId) + @Html.ValidationMessageFor(model => model.ClientId)
- @Html.SmartLabelFor(model => model.ApiAccountPassword) + @Html.SmartLabelFor(model => model.Secret) - @Html.SettingEditorFor(model => model.ApiAccountPassword) - @Html.ValidationMessageFor(model => model.ApiAccountPassword) + @Html.SettingEditorFor(model => model.Secret) + @Html.ValidationMessageFor(model => model.Secret)
- @Html.SmartLabelFor(model => model.Signature) + @Html.SmartLabelFor(model => model.ExperienceProfileId) - @Html.SettingEditorFor(model => model.Signature) - @Html.ValidationMessageFor(model => model.Signature) + @Html.SettingEditorFor(model => model.ExperienceProfileId) + + +  @(T("Plugins.SmartStore.PayPal.CreateNow"))! + + + @Html.ValidationMessageFor(model => model.ExperienceProfileId)
- @Html.SmartLabelFor(model => model.ShowButtonInMiniShoppingCart) - - @Html.SettingEditorFor(model => model.ShowButtonInMiniShoppingCart) - @Html.ValidationMessageFor(model => model.ShowButtonInMiniShoppingCart) -
@Html.SmartLabelFor(model => model.AdditionalFee) @@ -154,4 +153,24 @@
-} \ No newline at end of file +} + + \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml new file mode 100644 index 0000000000..29ea3ec39e --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml @@ -0,0 +1,15 @@ +@using SmartStore.Web.Models.Checkout; +@using SmartStore.PayPal.Models; +@model PayPalPlusCheckoutModel +@{ + Layout = "~/Views/Shared/_Root.cshtml"; + Html.AddTitleParts(T("PageTitle.Checkout").Text); + //Html.AddCssFileParts(true, Url.Content("~/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css")); +} +
+
+

@T("Checkout.SelectPaymentMethod")

+
+
+
+
\ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml new file mode 100644 index 0000000000..b5b630756d --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml @@ -0,0 +1,47 @@ +@using SmartStore.Web.Models.Checkout; +@using SmartStore.PayPal.Models; +@model PayPalPlusCheckoutModel +@{ + Layout = "~/Views/Shared/_Checkout.cshtml"; + Html.AddTitleParts(T("PageTitle.Checkout").Text); + //Html.AddCssFileParts(true, Url.Content("~/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css")); +} +@section orderProgress +{ + @Html.Action("CheckoutProgress", "Checkout", new { step = CheckoutProgressStep.Payment }) +} +
+
+

@T("Checkout.SelectPaymentMethod")

+
+
+ hello world! + @using (Html.BeginForm()) + { + @Html.Widget("checkout_payment_method_top") + + + + +
+ +  @T("Common.Back") + + + + + +
+ + @Html.Widget("checkout_payment_method_bottom") + } +
+
+ + diff --git a/src/Plugins/SmartStore.PayPal/packages.config b/src/Plugins/SmartStore.PayPal/packages.config index d1ef764942..8480e349e2 100644 --- a/src/Plugins/SmartStore.PayPal/packages.config +++ b/src/Plugins/SmartStore.PayPal/packages.config @@ -7,4 +7,5 @@ + \ No newline at end of file From 686b3e86673dd2a0eabf9571cf222b24260921dc Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 9 Apr 2016 02:00:50 +0200 Subject: [PATCH 023/285] Removed obsolete comment --- src/Presentation/SmartStore.Web/SmartStore.Web.csproj | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index 144cfa8336..186177349f 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -2126,12 +2126,6 @@ - From a1f747247e71ca95194021f7e0b9f1317ae141e0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 11 Apr 2016 22:34:55 +0200 Subject: [PATCH 024/285] PayPal Plus: Getting an access token --- .../Controllers/PayPalPlusController.cs | 19 +++--- .../Services/IPayPalService.cs | 2 +- .../Services/PayPalService.cs | 67 ++++++++++++------- .../Views/PayPalPlus/Configure.cshtml | 9 ++- 4 files changed, 58 insertions(+), 39 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index 465f716561..de97e1e616 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -102,7 +102,6 @@ public ActionResult Configure(PayPalPlusConfigurationModel model, FormCollection public ActionResult CreateExperienceProfile() { string profileId = null; - string error = null; var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); var settings = Services.Settings.LoadSetting(storeScope); @@ -110,17 +109,19 @@ public ActionResult CreateExperienceProfile() var store = Services.StoreService.GetStoreById(storeScope == 0 ? Services.StoreContext.CurrentStore.Id : storeScope); var result = _payPalService.SetCheckoutExperience(settings, store); - - if (result.Success) - { - profileId = (string)result.Json.id; - } - else + if (result != null) { - error = result.ErrorName.EmptyNull().EnsureEndsWith(".").Grow(result.ErrorMessage, " "); + if (result.Success) + { + profileId = (string)result.Json.id; + } + else if (result.ErrorMessage.HasValue()) + { + NotifyError(result.ErrorMessage); + } } - return new JsonResult { Data = new { id = profileId, error = error}, JsonRequestBehavior = JsonRequestBehavior.AllowGet }; + return new JsonResult { Data = new { id = profileId }, JsonRequestBehavior = JsonRequestBehavior.AllowGet }; } public ActionResult PaymentInfo() diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs index 32079413c8..ecb725c0a9 100644 --- a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -14,7 +14,7 @@ public interface IPayPalService PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data, IList errors = null); - bool EnsureAccessToken(PayPalSessionData session, PayPalApiSettingsBase settings); + PayPalResponse EnsureAccessToken(PayPalSessionData session, PayPalApiSettingsBase settings); PayPalResponse SetCheckoutExperience(PayPalApiSettingsBase settings, Store store); } diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 815a907c94..53f5d69e39 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -3,6 +3,7 @@ using System.IO; using System.Net; using System.Text; +using System.Web; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SmartStore.Core.Domain.Logging; @@ -100,7 +101,7 @@ public void LogError(Exception exception, string shortMessage = null, string ful public PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data, IList errors = null) { - var encoding = Encoding.UTF8; + var encoding = Encoding.ASCII; var result = new PayPalResponse(); HttpWebResponse webResponse = null; @@ -119,11 +120,23 @@ public PayPalResponse CallApi(string method, string path, string accessToken, Pa var request = (HttpWebRequest)WebRequest.Create(url); request.Method = method; request.Accept = "application/json"; - request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; + request.ContentType = "application/x-www-form-urlencoded"; + + if (HttpContext.Current != null && HttpContext.Current.Request != null) + { + request.UserAgent = HttpContext.Current.Request.UserAgent; + } + else + { + request.UserAgent = Plugin.SystemName; + } if (path.EmptyNull().EndsWith("/token")) { - request.Credentials = new NetworkCredential(settings.ClientId, settings.Secret); + // see https://github.com/paypal/sdk-core-dotnet/blob/master/Source/SDK/OAuthTokenCredential.cs + byte[] credentials = Encoding.UTF8.GetBytes("{0}:{1}".FormatInvariant(settings.ClientId, settings.Secret)); + + request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(credentials)); } else { @@ -167,7 +180,7 @@ public PayPalResponse CallApi(string method, string path, string accessToken, Pa { if (webResponse != null) { - using (var reader = new StreamReader(webResponse.GetResponseStream(), encoding)) + using (var reader = new StreamReader(webResponse.GetResponseStream(), Encoding.UTF8)) { var rawResponse = reader.ReadToEnd(); if (rawResponse.HasValue()) @@ -181,17 +194,21 @@ public PayPalResponse CallApi(string method, string path, string accessToken, Pa { if (!result.Success) { - result.ErrorName = (string)result.Json.name; - result.ErrorMessage = (string)result.Json.message; + var name = (string)result.Json.name; + var message = (string)result.Json.message; + + if (name.IsEmpty()) + name = (string)result.Json.error; + + if (message.IsEmpty()) + message = (string)result.Json.error_description; - var details = (string)result.Json.details; - if (details.IsEmpty()) - details = webResponse.StatusDescription; + if (message.IsEmpty()) + message = webResponse.StatusDescription; - if (details.HasValue()) - result.ErrorMessage = result.ErrorMessage.EnsureEndsWith(".").Grow(details, " "); + result.ErrorMessage = "{0} ({1}).".FormatInvariant(message.NaIfEmpty(), name.NaIfEmpty()); - LogError(null, result.ErrorName, result.ErrorMessage, false, errors); + LogError(null, result.ErrorMessage, result.Json.ToString(), false, errors); } } } @@ -214,7 +231,7 @@ public PayPalResponse CallApi(string method, string path, string accessToken, Pa return result; } - public bool EnsureAccessToken(PayPalSessionData session, PayPalApiSettingsBase settings) + public PayPalResponse EnsureAccessToken(PayPalSessionData session, PayPalApiSettingsBase settings) { if (session.AccessToken.IsEmpty() || DateTime.UtcNow >= session.TokenExpiration) { @@ -231,16 +248,25 @@ public bool EnsureAccessToken(PayPalSessionData session, PayPalApiSettingsBase s session.TokenExpiration = DateTime.UtcNow.AddSeconds(expireSeconds); } + else + { + return result; + } } - return session.AccessToken.HasValue(); + return new PayPalResponse + { + Success = session.AccessToken.HasValue() + }; } public PayPalResponse SetCheckoutExperience(PayPalApiSettingsBase settings, Store store) { var session = new PayPalSessionData(); - if (!EnsureAccessToken(session, settings)) - return null; + var result = EnsureAccessToken(session, settings); + + if (!result.Success) + return result; var logo = _pictureService.Value.GetPictureById(store.LogoPictureId); @@ -262,13 +288,7 @@ public PayPalResponse SetCheckoutExperience(PayPalApiSettingsBase settings, Stor data.Add("presentation", presentation); data.Add("input_fields", inpuFields); - var result = CallApi("POST", "/v1/payment-experience/webprofiles", session.AccessToken, settings, JsonConvert.SerializeObject(data)); - - if (result.Success) - { - - //return (string)result.Json.id; - } + result = CallApi("POST", "/v1/payment-experience/webprofiles", session.AccessToken, settings, JsonConvert.SerializeObject(data)); return result; } @@ -279,7 +299,6 @@ public class PayPalResponse { public bool Success { get; set; } public dynamic Json { get; set; } - public string ErrorName { get; set; } public string ErrorMessage { get; set; } } diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml index b787a41e76..e0ac907730 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml @@ -53,7 +53,8 @@ @Html.SmartLabelFor(model => model.ClientId) - @Html.SettingEditorFor(model => model.ClientId) + @Html.SettingOverrideCheckbox(model => model.ClientId) + @Html.TextBoxFor(model => model.ClientId, new { @class = "control-xlarge" }) @Html.ValidationMessageFor(model => model.ClientId) @@ -62,7 +63,8 @@ @Html.SmartLabelFor(model => model.Secret) - @Html.SettingEditorFor(model => model.Secret) + @Html.SettingOverrideCheckbox(model => model.Secret) + @Html.TextBoxFor(model => model.Secret, new { @class = "control-xlarge" }) @Html.ValidationMessageFor(model => model.Secret) @@ -165,9 +167,6 @@ if (!_.isEmpty(response.id)) { $('#@Html.FieldIdFor(x => x.ExperienceProfileId)').val(response); } - if (!_.isEmpty(response.error)) { - EventBroker.publish('message', { title: response.error, type: 'error' }); - } } }); }); From 9b0130fcaf82a83839312a50cde664608d578130 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 12 Apr 2016 01:03:20 +0200 Subject: [PATCH 025/285] Minor installation fixes --- .../SmartStore.Web/Controllers/InstallController.cs | 4 ++-- .../Installation/InstallationLocalizationService.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/InstallController.cs b/src/Presentation/SmartStore.Web/Controllers/InstallController.cs index 46b37c5dd1..7c5a919eb0 100644 --- a/src/Presentation/SmartStore.Web/Controllers/InstallController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/InstallController.cs @@ -177,8 +177,8 @@ public ActionResult Index() if (DataSettings.DatabaseIsInstalled()) return RedirectToRoute("HomePage"); - //set page timeout to 5 minutes - this.Server.ScriptTimeout = 300; + // set page timeout to 10 minutes + this.Server.ScriptTimeout = 600; var model = new InstallModel { diff --git a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallationLocalizationService.cs b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallationLocalizationService.cs index e9352a710e..04ba8b6e49 100644 --- a/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallationLocalizationService.cs +++ b/src/Presentation/SmartStore.Web/Infrastructure/Installation/InstallationLocalizationService.cs @@ -53,7 +53,7 @@ public virtual InstallationLanguage GetCurrentLanguage() if (cookie != null && !String.IsNullOrEmpty(cookie.Value)) cookieLanguageCode = cookie.Value; - // ensure it's available (it could be delete since the previous installation) + // ensure it's available (it could be deleted since the previous installation) var availableLanguages = GetAvailableLanguages(); var language = availableLanguages @@ -79,7 +79,7 @@ public virtual InstallationLanguage GetCurrentLanguage() private bool MatchLanguageByCurrentCulture(InstallationLanguage language) { - var curCulture = CultureInfo.GetCultureInfoByIetfLanguageTag("tr-TR"); // Thread.CurrentThread.CurrentCulture; + var curCulture = Thread.CurrentThread.CurrentUICulture; if (language.Code.IsCaseInsensitiveEqual(curCulture.IetfLanguageTag)) return true; From c921285b67dfb4a069c107acc97078f9b3f754a6 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 12 Apr 2016 19:41:16 +0200 Subject: [PATCH 026/285] Resolves #918 Compare products: Display base price information --- changelog.md | 1 + .../Controllers/CatalogController.cs | 4 ++-- .../Controllers/CatalogHelper.cs | 5 ++-- .../Themes/Alpha/Content/pages.less | 1 + .../Catalog/CompareProducts.Mobile.cshtml | 20 ++++++++++------ .../Views/Catalog/CompareProducts.cshtml | 24 ++++++++++++------- 6 files changed, 36 insertions(+), 19 deletions(-) diff --git a/changelog.md b/changelog.md index c11940bd9b..d5835ffed4 100644 --- a/changelog.md +++ b/changelog.md @@ -11,6 +11,7 @@ ### Improvements * (Dev) Publishing SmartStore.Web from within Visual Studio now deploys the project correctly. No need to execute ClickToBuild.cmd anymore. Just publish directly to any target, including Azure. * Exports the product detail link including the attribute query string when exporting attribute combinations +* #918 Compare products: Display base price information ### Bugfixes * Fixed ajax cache issue when saving payment or shipping restrictions. Internet Explorer showed the old data state (before storage). diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs index 3a213b1098..6c75df166f 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogController.cs @@ -1017,7 +1017,7 @@ public ActionResult CompareProducts() var products = _compareProductsService.GetComparedProducts(); - _helper.PrepareProductOverviewModels(products, prepareSpecificationAttributes: true, prepareFullDescription: true) + _helper.PrepareProductOverviewModels(products, prepareSpecificationAttributes: true, prepareFullDescription: true, isCompareList: true) .ToList() .ForEach(model.Products.Add); @@ -1067,7 +1067,7 @@ public ActionResult FlyoutCompare() var products = _compareProductsService.GetComparedProducts(); - _helper.PrepareProductOverviewModels(products, prepareSpecificationAttributes: true) + _helper.PrepareProductOverviewModels(products, prepareSpecificationAttributes: true, isCompareList: true) .ToList() .ForEach(model.Products.Add); diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index ebc76f7358..68e7442173 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -1059,7 +1059,8 @@ public IEnumerable PrepareProductOverviewModels( bool prepareColorAttributes = false, bool prepareManufacturers = false, bool isCompact = false, - bool prepareFullDescription = false) + bool prepareFullDescription = false, + bool isCompareList = false) { if (products == null) throw new ArgumentNullException("products"); @@ -1395,7 +1396,7 @@ from a in colorAttribute.ProductVariantAttributeValues.Take(50) model.Manufacturers = PrepareManufacturersOverviewModel(_manufacturerService.GetProductManufacturersByProductId(product.Id), cachedManufacturerModels, false); } - if (_catalogSettings.ShowBasePriceInProductLists) + if (_catalogSettings.ShowBasePriceInProductLists || isCompareList) { model.BasePriceInfo = contextProduct.GetBasePriceInfo(_localizationService, _priceFormatter, _currencyService, _taxService, _priceCalculationService, workingCurrency); } diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/pages.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/pages.less index 11a472e78c..4df8f7751d 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/pages.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/pages.less @@ -67,6 +67,7 @@ } .compare-products-table tr.product-name { background-color: #e5e5e5; + font-weight: bold; } .compare-products-table tr.product-name a { color: #888; diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/CompareProducts.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/CompareProducts.Mobile.cshtml index 77cb8c034f..69605ebe6e 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/CompareProducts.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/CompareProducts.Mobile.cshtml @@ -1,13 +1,12 @@ -@model CompareProductsModel +@using SmartStore.Core; +@using SmartStore.Core.Infrastructure; +@using SmartStore.Web.Models.Catalog; +@model CompareProductsModel @{ Layout = "~/Views/Shared/_Root.cshtml"; - //title Html.AddTitleParts(T("PageTitle.CompareProducts").Text); } -@using SmartStore.Core; -@using SmartStore.Core.Infrastructure; -@using SmartStore.Web.Models.Catalog; @{ string columnWidth = ""; if (Model.Products.Count > 0) @@ -15,7 +14,6 @@ columnWidth = Math.Round((decimal)(90M / Model.Products.Count), 0).ToString() + "%"; } - var specificationAttributes = new List(); foreach (var product in Model.Products) { @@ -51,7 +49,15 @@ @product.Name
- @T("Products.Compare.Price"): @product.ProductPrice.Price +
+ @T("Products.Compare.Price"): @product.ProductPrice.Price +
+ @if (product.BasePriceInfo.HasValue()) + { +
+ @product.BasePriceInfo +
+ }
@foreach (var specificationAttribute in specificationAttributes) diff --git a/src/Presentation/SmartStore.Web/Views/Catalog/CompareProducts.cshtml b/src/Presentation/SmartStore.Web/Views/Catalog/CompareProducts.cshtml index 95efb83034..8202ddd22c 100644 --- a/src/Presentation/SmartStore.Web/Views/Catalog/CompareProducts.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Catalog/CompareProducts.cshtml @@ -1,13 +1,12 @@ -@model CompareProductsModel +@using SmartStore.Core; +@using SmartStore.Core.Infrastructure; +@using SmartStore.Web.Models.Catalog; +@model CompareProductsModel @{ Layout = "~/Views/Shared/_ColumnsTwo.cshtml"; - //title Html.AddTitleParts(T("PageTitle.CompareProducts").Text); } -@using SmartStore.Core; -@using SmartStore.Core.Infrastructure; -@using SmartStore.Web.Models.Catalog; @{ string columnWidth = ""; if (Model.Products.Count > 0) @@ -15,7 +14,6 @@ columnWidth = Math.Round((decimal)(90M / Model.Products.Count), 0).ToString() + "%"; } - var specificationAttributes = new List(); foreach (var product in Model.Products) { @@ -55,7 +53,9 @@ @if (product.DefaultPictureModel.ImageUrl.HasValue()) {

- @product.DefaultPictureModel.AlternateText + + @product.DefaultPictureModel.AlternateText +

}

@@ -87,7 +87,15 @@ @foreach (var product in Model.Products) { - @product.ProductPrice.Price +

+ @product.ProductPrice.Price +
+ @if (product.BasePriceInfo.HasValue()) + { +
+ @product.BasePriceInfo +
+ } } From 1a7b4c57d37fde7aec4d46a1bc52eca0c6c84a12 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 13 Apr 2016 01:58:27 +0200 Subject: [PATCH 027/285] Slightly redesigned export profiles list and changed some resources. --- ...201603121451066_ThirdPartyEmailHandOver.cs | 12 +- .../Administration/Content/admin.less | 2 +- .../Administration/Content/variables.less | 2 +- .../Administration/Views/Export/List.cshtml | 25 +-- .../Views/Export/_ProfileList.cshtml | 167 ++++++++++-------- 5 files changed, 106 insertions(+), 102 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs index a4a2190ed1..75e785db86 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs @@ -138,8 +138,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Nach der Anzahl der getätigten Bestellungen filtern."); builder.AddOrUpdate("Admin.DataExchange.Export.SystemProfileNote", - "The following list contains system profiles intended to export data in the SmartStore.NET own format. There are additional export buttons available for these imports. You can find them over the object lists, such as the product or order list.", - "Die folgende Liste enthält Systemprofile, über die Daten im SmartStore.NET eigenen Format exportiert werden können. Für diese Importe stehen zusätzliche Export-Buttons zur Verfügung. Sie finden diese über den entsprechenden Listen, wie z.B. der Produkt- oder Auftragsliste."); + "The following list contains system profiles, which are provided by plugins such as the Data Export Plugin. You can customize system profiles as desired, but cannot create new ones. These profiles also add additional action buttons. You will find these above data lists, such as the product or order list.", + "Die folgende Liste enthält Systemprofile, die von Plugins wie bspw. dem Datenexporte Plugin bereitgestellt werden. Sie können Systemprofile nach Belieben anpassen, aber keine Neuen erstellen. Für diese Profile stehen außerdem zusätzliche Aktions-Buttons zur Verfügung. Sie finden diese über den entsprechenden Listen, wie z.B. der Produkt- oder Auftragsliste."); builder.AddOrUpdate("Admin.DataExchange.AddNewProfile", "New profile", @@ -179,6 +179,14 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Common.FileInUse", "The file is in use and cannot be opened.", "Die Datei ist in Benutzung und kann nicht geöffnet werden."); + + + builder.AddOrUpdate("Admin.DataExchange.Export.UserProfilesTitle", + "User profiles", + "Benutzerprofile"); + builder.AddOrUpdate("Admin.DataExchange.Export.SystemProfilesTitle", + "System profiles", + "Systemprofile"); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index 0932e0a4bd..d1dd3c54ad 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -405,7 +405,7 @@ td.adminData > input[type="checkbox"] { .title, .head { font-size: @fontSizeLarge; - padding: 8px 0; + padding: 12px 0; } .head { margin-bottom: 12px; diff --git a/src/Presentation/SmartStore.Web/Administration/Content/variables.less b/src/Presentation/SmartStore.Web/Administration/Content/variables.less index 1de0d20a5e..741c1de2bc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/variables.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/variables.less @@ -71,7 +71,7 @@ // ------------------------- // Based on 14px font-size and 20px line-height -@fontSizeLarge: @baseFontSize * 1.25; // ~18px +@fontSizeLarge: @baseFontSize * 1.4; // ~18px @fontSizeSmall: @baseFontSize * 0.85; // ~12px @fontSizeMini: @baseFontSize * 0.75; // ~11px diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 683a397cd9..224389951d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -13,30 +13,9 @@
-@if (!hasUserProfiles && !hasSystemProfiles) -{ -
- - @T("Admin.DataExchange.Export.NoProfiles") -
-} -
-
- @Html.Partial("_ProfileList", Model.Where(x => !x.IsSystemProfile), new ViewDataDictionary { { "IsSystemProfile", false } }) -
- - @if (hasSystemProfiles) - { -
- - @T("Admin.DataExchange.Export.SystemProfileNote") -
- -
- @Html.Partial("_ProfileList", Model.Where(x => x.IsSystemProfile), new ViewDataDictionary { { "IsSystemProfile", true } }) -
- } + @Html.Partial("_ProfileList", Model.Where(x => !x.IsSystemProfile), new ViewDataDictionary { { "IsSystemProfile", false } }) + @Html.Partial("_ProfileList", Model.Where(x => x.IsSystemProfile), new ViewDataDictionary { { "IsSystemProfile", true } })
diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml index 7bee030812..529ff0124f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml @@ -2,81 +2,98 @@ @using SmartStore.Admin.Models.DataExchange; @{ var isSystemProfiles = (bool)ViewData["IsSystemProfile"]; + var titleResKey = isSystemProfiles ? "Admin.DataExchange.Export.SystemProfilesTitle" : "Admin.DataExchange.Export.UserProfilesTitle"; } - - - - - - - - - - - - - - - @foreach (var profile in Model.OrderBy(x => x.Name)) - { - - - - - - - - - - - } - @if (!isSystemProfiles) - { - - - - - } - -
@T("Admin.DataExchange.Export.Name")@T("Admin.DataExchange.Export.EntityType")@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.ProviderSystemName
-
- @profile.Provider.EntityTypeName.NaIfEmpty() - - @Html.IconForFileExtension(profile.Provider.FileExtension, true) - - @Html.Partial("ProfileFileCount", profile.Details) - -
- @Html.Partial("~/Administration/Views/ScheduleTask/_LastRun.cshtml", profile.TaskModel) -
-
-
- @Html.Partial("~/Administration/Views/ScheduleTask/_NextRun.cshtml", profile.TaskModel) -
-
-
- - - @T("Common.Cancel") - +
-   - - -  @T("Admin.DataExchange.AddNewProfile") - -
\ No newline at end of file + @if (isSystemProfiles) + { +
+ + @Html.Raw(T("Admin.DataExchange.Export.SystemProfileNote")) +
+ } + + @if (Model.Any()) + { + + + + + + + + + + + + + + + @foreach (var profile in Model.OrderBy(x => x.Name)) + { + + + + + + + + + + + } + +
@T("Admin.DataExchange.Export.Name")@T("Admin.DataExchange.Export.EntityType")@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.ProviderSystemName
+
+ @profile.Provider.EntityTypeName.NaIfEmpty() + + @Html.IconForFileExtension(profile.Provider.FileExtension, true) + + @Html.Partial("ProfileFileCount", profile.Details) + +
+ @Html.Partial("~/Administration/Views/ScheduleTask/_LastRun.cshtml", profile.TaskModel) +
+
+
+ @Html.Partial("~/Administration/Views/ScheduleTask/_NextRun.cshtml", profile.TaskModel) +
+
+
+ + + + @T("Common.Cancel") + + + +   @T("Admin.Configuration.ActivityLog") + +
+ } + else { +
@T("Admin.DataExchange.Export.NoProfiles")
+ } + + + @if (!isSystemProfiles) + { + +   @T("Admin.DataExchange.AddNewProfile") + + } + \ No newline at end of file From 98951486adf065e3b5bfa35310546dc471ad94a4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 13 Apr 2016 11:00:50 +0200 Subject: [PATCH 028/285] Exception handler in task scheduler could throw exception on his part --- src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs index bd2734c136..b34fe37186 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs @@ -222,7 +222,10 @@ private void HandleException(AggregateException exception, string url) { using (var response = wex.Response as HttpWebResponse) { - msg += " HTTP {0}, {1}".FormatCurrent((int)response.StatusCode, response.StatusDescription); + if (response != null) + { + msg += " HTTP {0}, {1}".FormatCurrent((int)response.StatusCode, response.StatusDescription); + } logger.Error(msg); } } From 2e5c28d534b7d197844628994278756672c58d72 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Wed, 13 Apr 2016 18:06:50 +0200 Subject: [PATCH 029/285] Updated changelog --- changelog.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index d5835ffed4..9b75e12d36 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,4 @@ -# Release Notes +# Release Notes ## SmartStore.NET 2.5.1 @@ -21,6 +21,7 @@ * The error message of a payment provider when capturing a payment was not displayed * Adding new shipping method throws an exception * Attribute Values: Assigning IsPreselected to more than one value causes an error +* BizImporter: fixed redirection bug when default file extension in .biz wasn't .html ## SmartStore.NET 2.5 From d9452deb4a2811ade0321c247146c493a3b3d480 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 13 Apr 2016 18:36:29 +0200 Subject: [PATCH 030/285] PayPal Plus: Upsert checkout experience profile --- .../Controllers/PayPalPlusController.cs | 16 +-- .../Localization/resources.de-de.xml | 5 +- .../Localization/resources.en-us.xml | 5 +- .../Models/ApiConfigurationModels.cs | 2 +- .../Services/IPayPalService.cs | 2 +- .../Services/PayPalService.cs | 112 ++++++++++++------ .../Views/PayPalPlus/Configure.cshtml | 21 ++-- 7 files changed, 100 insertions(+), 63 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index de97e1e616..539451b9b7 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -99,29 +99,29 @@ public ActionResult Configure(PayPalPlusConfigurationModel model, FormCollection } [AdminAuthorize] - public ActionResult CreateExperienceProfile() + public ActionResult UpsertExperienceProfile(string profileId) { - string profileId = null; - var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); var settings = Services.Settings.LoadSetting(storeScope); var store = Services.StoreService.GetStoreById(storeScope == 0 ? Services.StoreContext.CurrentStore.Id : storeScope); - var result = _payPalService.SetCheckoutExperience(settings, store); + var result = _payPalService.UpsertCheckoutExperience(settings, store, profileId); if (result != null) { - if (result.Success) + if (result.Success && result.Id.HasValue()) { - profileId = (string)result.Json.id; + settings.ExperienceProfileId = result.Id; + + Services.Settings.SaveSetting(settings, storeScope); } - else if (result.ErrorMessage.HasValue()) + else { NotifyError(result.ErrorMessage); } } - return new JsonResult { Data = new { id = profileId }, JsonRequestBehavior = JsonRequestBehavior.AllowGet }; + return RedirectToAction("ConfigureProvider", "Plugin", new { area = "admin", systemName = PayPalPlusProvider.SystemName }); } public ActionResult PaymentInfo() diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index 3c5ce4e3cb..43419425fe 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -22,6 +22,9 @@ Jetzt erstellen + + Jetzt aktualisieren + Client-ID @@ -38,7 +41,7 @@ Experience Profil-ID - Legt die Experience Profil-ID fest. Das Profil beinhaltet globale Daten wie z.B. Shop-Name und -Logo. Ein Profil braucht nur einmalig angelegt werden. + Legt die Experience Profil-ID fest. Das Profil beinhaltet globale Daten wie z.B. Shop-Name und -Logo. Ein Profil braucht nur einmalig angelegt werden. Über den Button können Sie ein neues Profil erstellen oder ein vorhandenes aktualisieren. diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index b278abb100..392835a2b9 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -22,6 +22,9 @@ Create now + + Update now + Client ID @@ -38,7 +41,7 @@ Experience profile ID - Specifies the experience profile ID. The profile conatins global data like shop name and logo. A profile needs to be created only once. + Specifies the experience profile ID. The profile contains global data like shop name and logo. A profile needs to be created only once. Using the button, you can create a new profile or update an existing one. diff --git a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs index 05a235004c..cb37dd4500 100644 --- a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs +++ b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs @@ -13,7 +13,7 @@ public abstract class ApiConfigurationModel: ModelBase { public string[] ConfigGroups { get; set; } - [SmartResourceDisplayName("Plugins.Payments.PayPal.UseSandbox")] + [SmartResourceDisplayName("Plugins.Payments.PayPal.UseSandbox")] public bool UseSandbox { get; set; } [SmartResourceDisplayName("Plugins.Payments.PayPal.IpnChangesPaymentStatus")] diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs index ecb725c0a9..9678132a17 100644 --- a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -16,6 +16,6 @@ public interface IPayPalService PayPalResponse EnsureAccessToken(PayPalSessionData session, PayPalApiSettingsBase settings); - PayPalResponse SetCheckoutExperience(PayPalApiSettingsBase settings, Store store); + PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, Store store, string profileId); } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 53f5d69e39..03df71f0cc 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -101,35 +101,32 @@ public void LogError(Exception exception, string shortMessage = null, string ful public PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data, IList errors = null) { - var encoding = Encoding.ASCII; + var isJson = (data.HasValue() && data.StartsWith("{")); + var encoding = (isJson ? Encoding.UTF8 : Encoding.ASCII); var result = new PayPalResponse(); HttpWebResponse webResponse = null; var url = GetApiUrl(settings.UseSandbox) + path.EnsureStartsWith("/"); if (method.IsCaseInsensitiveEqual("GET") && data.HasValue()) - { url = url.EnsureEndsWith("?") + data; - } if (settings.SecurityProtocol.HasValue) - { ServicePointManager.SecurityProtocol = settings.SecurityProtocol.Value; - } var request = (HttpWebRequest)WebRequest.Create(url); request.Method = method; request.Accept = "application/json"; - request.ContentType = "application/x-www-form-urlencoded"; + request.ContentType = (isJson ? "application/json" : "application/x-www-form-urlencoded"); - if (HttpContext.Current != null && HttpContext.Current.Request != null) - { - request.UserAgent = HttpContext.Current.Request.UserAgent; - } - else + try { - request.UserAgent = Plugin.SystemName; + if (HttpContext.Current != null && HttpContext.Current.Request != null) + request.UserAgent = HttpContext.Current.Request.UserAgent; + else + request.UserAgent = Plugin.SystemName; } + catch { } if (path.EmptyNull().EndsWith("/token")) { @@ -144,7 +141,7 @@ public PayPalResponse CallApi(string method, string path, string accessToken, Pa } - if ((method.IsCaseInsensitiveEqual("POST") || method.IsCaseInsensitiveEqual("PATCH")) && data.HasValue()) + if (data.HasValue() && (method.IsCaseInsensitiveEqual("POST") || method.IsCaseInsensitiveEqual("PUT") || method.IsCaseInsensitiveEqual("PATCH"))) { byte[] bytes = encoding.GetBytes(data); @@ -163,7 +160,7 @@ public PayPalResponse CallApi(string method, string path, string accessToken, Pa try { webResponse = request.GetResponse() as HttpWebResponse; - result.Success = (webResponse.StatusCode == HttpStatusCode.OK); + result.Success = ((int)webResponse.StatusCode < 400); } catch (WebException wexc) { @@ -185,32 +182,41 @@ public PayPalResponse CallApi(string method, string path, string accessToken, Pa var rawResponse = reader.ReadToEnd(); if (rawResponse.HasValue()) { - if (rawResponse.StartsWith("[")) - result.Json = JArray.Parse(rawResponse); - else - result.Json = JObject.Parse(rawResponse); - - if (result.Json != null) + if (webResponse.ContentType.IsCaseInsensitiveEqual("application/json")) { - if (!result.Success) + if (rawResponse.StartsWith("[")) + result.Json = JArray.Parse(rawResponse); + else + result.Json = JObject.Parse(rawResponse); + + if (result.Json != null) { - var name = (string)result.Json.name; - var message = (string)result.Json.message; + if (!result.Success) + { + var name = (string)result.Json.name; + var message = (string)result.Json.message; - if (name.IsEmpty()) - name = (string)result.Json.error; + if (name.IsEmpty()) + name = (string)result.Json.error; - if (message.IsEmpty()) - message = (string)result.Json.error_description; + if (message.IsEmpty()) + message = (string)result.Json.error_description; - if (message.IsEmpty()) - message = webResponse.StatusDescription; + if (message.IsEmpty()) + message = webResponse.StatusDescription; - result.ErrorMessage = "{0} ({1}).".FormatInvariant(message.NaIfEmpty(), name.NaIfEmpty()); + result.ErrorMessage = "{0} ({1}).".FormatInvariant(message.NaIfEmpty(), name.NaIfEmpty()); - LogError(null, result.ErrorMessage, result.Json.ToString(), false, errors); + LogError(null, result.ErrorMessage, result.Json.ToString(), false, errors); + } } } + else if (!result.Success) + { + result.ErrorMessage = rawResponse; + + LogError(null, result.ErrorMessage, null, false, errors); + } } } } @@ -235,10 +241,7 @@ public PayPalResponse EnsureAccessToken(PayPalSessionData session, PayPalApiSett { if (session.AccessToken.IsEmpty() || DateTime.UtcNow >= session.TokenExpiration) { - var data = new Dictionary(); - data.Add("grant_type", "client_credentials"); - - var result = CallApi("POST", "/v1/oauth2/token", null, settings, JsonConvert.SerializeObject(data)); + var result = CallApi("POST", "/v1/oauth2/token", null, settings, "grant_type=client_credentials"); if (result.Success) { @@ -260,7 +263,7 @@ public PayPalResponse EnsureAccessToken(PayPalSessionData session, PayPalApiSett }; } - public PayPalResponse SetCheckoutExperience(PayPalApiSettingsBase settings, Store store) + public PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, Store store, string profileId) { var session = new PayPalSessionData(); var result = EnsureAccessToken(session, settings); @@ -268,13 +271,32 @@ public PayPalResponse SetCheckoutExperience(PayPalApiSettingsBase settings, Stor if (!result.Success) return result; + var name = store.Name; var logo = _pictureService.Value.GetPictureById(store.LogoPictureId); + var path = "/v1/payment-experience/web-profiles"; var data = new Dictionary(); var presentation = new Dictionary(); var inpuFields = new Dictionary(); - presentation.Add("brand_name", store.Name); + if (profileId.IsEmpty()) + { + result = CallApi("GET", path, session.AccessToken, settings, null); + if (result.Success && result.Json != null) + { + foreach (var profile in result.Json) + { + var profileName = (string)profile.name; + if (profileName.IsCaseInsensitiveEqual(name)) + { + profileId = (string)profile.id; + break; + } + } + } + } + + presentation.Add("brand_name", name); presentation.Add("locale_code", _services.WorkContext.WorkingLanguage.UniqueSeoCode.EmptyNull().ToUpper()); if (logo != null) @@ -284,11 +306,22 @@ public PayPalResponse SetCheckoutExperience(PayPalApiSettingsBase settings, Stor inpuFields.Add("no_shipping", 0); inpuFields.Add("address_override", 1); - data.Add("name", store.Name); + data.Add("name", name); data.Add("presentation", presentation); data.Add("input_fields", inpuFields); - result = CallApi("POST", "/v1/payment-experience/webprofiles", session.AccessToken, settings, JsonConvert.SerializeObject(data)); + if (profileId.HasValue()) + path = string.Concat(path, "/", HttpUtility.UrlPathEncode(profileId)); + + result = CallApi(profileId.HasValue() ? "PUT" : "POST", path, session.AccessToken, settings, JsonConvert.SerializeObject(data)); + + if (result.Success) + { + if (result.Json != null) + result.Id = (string)result.Json.id; + else + result.Id = profileId; + } return result; } @@ -300,6 +333,7 @@ public class PayPalResponse public bool Success { get; set; } public dynamic Json { get; set; } public string ErrorMessage { get; set; } + public string Id { get; set; } } public class PayPalSessionData diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml index e0ac907730..45c7a157f3 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml @@ -75,8 +75,10 @@ @Html.SettingEditorFor(model => model.ExperienceProfileId) - -  @(T("Plugins.SmartStore.PayPal.CreateNow"))! + +   + ! @Html.ValidationMessageFor(model => model.ExperienceProfileId) @@ -160,16 +162,11 @@ \ No newline at end of file From 72f70a8a354d7067ff16261b1cba014c1466dc0e Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 14 Apr 2016 00:04:56 +0200 Subject: [PATCH 031/285] Renamed a method parameter --- .../Shipping/ShippingService.cs | 5 ++-- .../ByWeightShippingComputationMethod.cs | 24 +++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs b/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs index 51f0a22966..4f6db7fa9c 100644 --- a/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs +++ b/src/Libraries/SmartStore.Services/Shipping/ShippingService.cs @@ -317,11 +317,11 @@ public virtual decimal GetShoppingCartTotalWeight(IList(SystemCustomerAttributeNames.CheckoutAttributes, _genericAttributeService); @@ -332,6 +332,7 @@ public virtual decimal GetShoppingCartTotalWeight(IList /// Gets available shipping options /// - /// A request for getting shipping options + /// A request for getting shipping options /// Represents a response of getting shipping rate options - public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest getShippingOptionRequest) + public GetShippingOptionResponse GetShippingOptions(GetShippingOptionRequest request) { - if (getShippingOptionRequest == null) + if (request == null) throw new ArgumentNullException("getShippingOptionRequest"); var response = new GetShippingOptionResponse(); - if (getShippingOptionRequest.Items == null || getShippingOptionRequest.Items.Count == 0) + if (request.Items == null || request.Items.Count == 0) { response.AddError(T("Admin.System.Warnings.NoShipmentItems")); return response; } - - int storeId = _storeContext.CurrentStore.Id; + + int storeId = request.StoreId > 0 ? request.StoreId : _storeContext.CurrentStore.Id; decimal subTotal = decimal.Zero; int countryId = 0; string zip = null; - if (getShippingOptionRequest.ShippingAddress != null) + if (request.ShippingAddress != null) { - countryId = getShippingOptionRequest.ShippingAddress.CountryId ?? 0; - zip = getShippingOptionRequest.ShippingAddress.ZipPostalCode; + countryId = request.ShippingAddress.CountryId ?? 0; + zip = request.ShippingAddress.ZipPostalCode; } - foreach (var shoppingCartItem in getShippingOptionRequest.Items) + foreach (var shoppingCartItem in request.Items) { if (shoppingCartItem.Item.IsFreeShipping || !shoppingCartItem.Item.IsShipEnabled) continue; subTotal += _priceCalculationService.GetSubTotal(shoppingCartItem, true); } - decimal weight = _shippingService.GetShoppingCartTotalWeight(getShippingOptionRequest.Items); + decimal weight = _shippingService.GetShoppingCartTotalWeight(request.Items); - var shippingMethods = _shippingService.GetAllShippingMethods(getShippingOptionRequest); + var shippingMethods = _shippingService.GetAllShippingMethods(request); foreach (var shippingMethod in shippingMethods) { var record = _shippingByWeightService.FindRecord(shippingMethod.Id, storeId, countryId, weight, zip); From fe07c6e883ad2ed3bfe7a3d0c33e54c933a11f3a Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 14 Apr 2016 00:44:40 +0200 Subject: [PATCH 032/285] Minor UI fix in backend --- .../Administration/Views/ScheduleTask/List.cshtml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/List.cshtml index 3723fa5819..c7e498cf2f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/List.cshtml @@ -12,13 +12,13 @@ - +
- + @@ -39,13 +39,13 @@ } - - @@ -76,6 +75,7 @@ @Html.SettingEditorFor(model => model.ExperienceProfileId)   ! diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml index 92bedd7d1b..72a42fc395 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml @@ -11,11 +11,6 @@ @Html.Action("CheckoutProgress", "Checkout", new { step = CheckoutProgressStep.Payment }) } -@if (!ViewData.ModelState.IsValid) -{ - @Html.ValidationSummary() -} - @Html.Partial("PaymentWallScripting")
@@ -27,6 +22,15 @@ { @Html.Widget("checkout_payment_method_top") + if (Model.ErrorMessage.HasValue()) + { +
+ @Html.Raw(T("Plugins.Payments.PayPalPlus.MethodUnavailable")) +
+ @Model.ErrorMessage +
+ } +
diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml index c66219b54f..b9848cc9f0 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml @@ -2,42 +2,45 @@ @model PayPalPlusCheckoutModel + \ No newline at end of file From 67b9f807ba49c3866b852bb41f2b46c29956f95b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 18 Apr 2016 10:21:56 +0200 Subject: [PATCH 040/285] PayPal Plus: Integrating third party payment methods --- .../Controllers/PayPalPlusController.cs | 162 ++++++++++++++++-- .../Localization/resources.de-de.xml | 32 +++- .../Localization/resources.en-us.xml | 32 +++- .../Models/ApiConfigurationModels.cs | 39 +++-- .../Models/PayPalPlusCheckoutModel.cs | 13 +- .../Services/IPayPalService.cs | 8 +- .../Services/PayPalService.cs | 17 +- .../Settings/PayPalSettings.cs | 27 +++ .../SmartStore.PayPal.csproj | 1 + .../Validators/PayPalPlusConfigValidator.cs | 33 ++++ .../Views/PayPalPlus/Configure.cshtml | 38 ++-- .../PayPalPlus/PaymentWallScripting.cshtml | 48 ++++-- .../Validators/SmartBaseValidator.cs | 7 +- 13 files changed, 374 insertions(+), 83 deletions(-) create mode 100644 src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index 49bb7304da..a573f02158 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -1,13 +1,25 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; +using SmartStore.Core.Domain.Customers; +using SmartStore.Core.Domain.Logging; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Html; +using SmartStore.Core.Plugins; using SmartStore.PayPal.Models; using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; +using SmartStore.PayPal.Validators; +using SmartStore.Services.Common; +using SmartStore.Services.Customers; +using SmartStore.Services.Localization; using SmartStore.Services.Orders; using SmartStore.Services.Payments; using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Plugins; using SmartStore.Web.Framework.Security; using SmartStore.Web.Framework.Settings; @@ -16,21 +28,91 @@ namespace SmartStore.PayPal.Controllers public class PayPalPlusController : PayPalControllerBase { private readonly HttpContextBase _httpContext; + private readonly PluginMediator _pluginMediator; private readonly IPayPalService _payPalService; + private readonly IGenericAttributeService _genericAttributeService; public PayPalPlusController( HttpContextBase httpContext, IPaymentService paymentService, IOrderService orderService, IOrderProcessingService orderProcessingService, - IPayPalService payPalService) : base( + PluginMediator pluginMediator, + IPayPalService payPalService, + IGenericAttributeService genericAttributeService) : base( PayPalPlusProvider.SystemName, paymentService, orderService, orderProcessingService) { _httpContext = httpContext; + _pluginMediator = pluginMediator; _payPalService = payPalService; + _genericAttributeService = genericAttributeService; + } + + private string GetPaymentMethodName(Provider provider) + { + var name = _pluginMediator.GetLocalizedFriendlyName(provider.Metadata); + + if (name.IsEmpty()) + name = provider.Metadata.FriendlyName; + + if (name.IsEmpty()) + name = provider.Metadata.SystemName; + + return name; + } + + private PayPalPlusCheckoutModel.ThirdPartyPaymentMethod GetThirdPartyPaymentMethodModel( + Provider provider, + PayPalPlusPaymentSettings settings, + Store store) + { + var model = new PayPalPlusCheckoutModel.ThirdPartyPaymentMethod(); + model.MethodName = GetPaymentMethodName(provider).EncodeJsString(); + model.RedirectUrl = Url.Action("CheckoutReturn", "PayPalPlus", new { area = Plugin.SystemName }, store.SslEnabled ? "https" : "http"); + + try + { + if (settings.DisplayPaymentMethodDescription) + { + // not the short description, the full description is intended for frontend + var paymentMethod = PaymentService.GetPaymentMethodBySystemName(provider.Metadata.SystemName); + if (paymentMethod != null) + { + var description = paymentMethod.GetLocalized(x => x.FullDescription); + if (description.HasValue()) + { + description = HtmlUtils.ConvertHtmlToPlainText(description); + description = HtmlUtils.StripTags(HttpUtility.HtmlDecode(description)); + + if (description.HasValue()) + model.Description = description.EncodeJsString(); + } + } + } + } + catch { } + + try + { + if (settings.DisplayPaymentMethodLogo && provider.Metadata.PluginDescriptor != null && store.SslEnabled) + { + var brandImageUrl = _pluginMediator.GetBrandImageUrl(provider.Metadata); + if (brandImageUrl.HasValue()) + { + if (brandImageUrl.StartsWith("~")) + brandImageUrl = brandImageUrl.Substring(1); + + var uri = new UriBuilder(Uri.UriSchemeHttps, Request.Url.Host, -1, brandImageUrl); + model.ImageUrl = uri.ToString(); + } + } + } + catch { } + + return model; } [NonAction] @@ -58,17 +140,22 @@ public ActionResult Configure() ConfigGroups = T("Plugins.SmartStore.PayPal.ConfigGroups").Text.SplitSafe(";") }; - model.TransactModeValues = new SelectList(new List - { - new { ID = (int)TransactMode.Authorize, Name = T("Plugins.SmartStore.PayPal.ModeAuth") }, - new { ID = (int)TransactMode.AuthorizeAndCapture, Name = T("Plugins.SmartStore.PayPal.ModeAuthAndCapture") } - }, - "ID", "Name", (int)settings.TransactMode); - model.AvailableSecurityProtocols = GetSecurityProtocols() .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) .ToList(); + // it's better to also offer inactive methods here but filter them out in frontend + var methods = PaymentService.LoadAllPaymentMethods(storeScope); + + model.AvailableThirdPartyPaymentMethods = methods + .Where(x => + x.Metadata.PluginDescriptor.SystemName != Plugin.SystemName && + !x.Value.RequiresInteraction && + (x.Metadata.PluginDescriptor.SystemName == "SmartStore.OfflinePayment" || x.Value.PaymentMethodType == PaymentMethodType.Redirection)) + .Select(x => new SelectListItem { Value = x.Metadata.SystemName, Text = GetPaymentMethodName(x) }) + .ToList(); + + model.Copy(settings, true); var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); @@ -80,20 +167,26 @@ public ActionResult Configure() [HttpPost, AdminAuthorize, ChildActionOnly] public ActionResult Configure(PayPalPlusConfigurationModel model, FormCollection form) { + var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + + var validator = new PayPalPlusConfigValidator(Services.Localization, x => + { + return storeScope == 0 || storeDependingSettingHelper.IsOverrideChecked(settings, x, form); + }); + + validator.Validate(model, ModelState); + if (!ModelState.IsValid) return Configure(); ModelState.Clear(); - var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); - var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); - var settings = Services.Settings.LoadSetting(storeScope); - model.Copy(settings, false); storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); - // multistore context not possible, see IPN handling Services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); Services.Settings.ClearCache(); @@ -143,9 +236,11 @@ public ActionResult PaymentWall() { var store = Services.StoreContext.CurrentStore; var customer = Services.WorkContext.CurrentCustomer; + var language = Services.WorkContext.WorkingLanguage; var settings = Services.Settings.LoadSetting(store.Id); + var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); - var model = new PayPalPlusCheckoutModel(); + var methods = PaymentService.LoadActivePaymentMethods(customer, cart, store.Id, null, false); var state = _httpContext.GetCheckoutState(); if (!state.CustomProperties.ContainsKey(PayPalPlusProvider.SystemName)) @@ -153,15 +248,28 @@ public ActionResult PaymentWall() var session = state.CustomProperties[PayPalPlusProvider.SystemName] as PayPalSessionData; + var model = new PayPalPlusCheckoutModel(); + model.ThirdPartyPaymentMethods = new List(); model.UseSandbox = settings.UseSandbox; model.HasPaymentFee = (settings.AdditionalFee > decimal.Zero); - model.LanguageCulture = (Services.WorkContext.WorkingLanguage.LanguageCulture ?? "de_DE").Replace("-", "_"); + model.LanguageCulture = (language.LanguageCulture ?? "de_DE").Replace("-", "_"); if (customer.BillingAddress != null && customer.BillingAddress.Country != null) { model.BillingAddressCountryCode = customer.BillingAddress.Country.TwoLetterIsoCode; } + foreach (var systemName in settings.ThirdPartyPaymentMethods) + { + var provider = methods.FirstOrDefault(x => x.Metadata.SystemName == systemName); + if (provider != null) + { + var methodModel = GetThirdPartyPaymentMethodModel(provider, settings, store); + model.ThirdPartyPaymentMethods.Add(methodModel); + } + } + + if (session.PaymentId.IsEmpty() || session.ApprovalUrl.IsEmpty()) { var result = _payPalService.EnsureAccessToken(session, settings); @@ -171,7 +279,7 @@ public ActionResult PaymentWall() var returnUrl = Url.Action("CheckoutReturn", "PayPalPlus", new { area = Plugin.SystemName }, protocol); var cancelUrl = Url.Action("CheckoutCancel", "PayPalPlus", new { area = Plugin.SystemName }, protocol); - result = _payPalService.CreatePayment(settings, session, PayPalPlusProvider.SystemName, returnUrl, cancelUrl); + result = _payPalService.CreatePayment(settings, session, cart, PayPalPlusProvider.SystemName, returnUrl, cancelUrl); if (result.Success && result.Json != null) { foreach (var link in result.Json.links) @@ -200,14 +308,32 @@ public ActionResult PaymentWall() return View(model); } + [ValidateInput(false)] public ActionResult CheckoutReturn() { + var customer = Services.WorkContext.CurrentCustomer; + var store = Services.StoreContext.CurrentStore; + + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, Plugin.SystemName, store.Id); + + // this is ugly... + var paymentRequest = _httpContext.Session["OrderPaymentInfo"] as ProcessPaymentRequest; + if (paymentRequest == null) + { + _httpContext.Session["OrderPaymentInfo"] = new ProcessPaymentRequest(); + } + + Logger.InsertLog(LogLevel.Information, "PayPal PLUS Return", string.Join(", ", Request.QueryString.AllKeys.Select(key => key + ": " + Request.QueryString[key]).ToArray())); + + return RedirectToAction("Confirm", "Checkout", new { area = "" }); } + [ValidateInput(false)] public ActionResult CheckoutCancel() { - return RedirectToAction("Confirm", "Checkout", new { area = "" }); + // back to where he came from + return RedirectToAction("PaymentMethod", "Checkout", new { area = "" }); } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index d4abb7441e..57af7f6676 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -13,12 +13,6 @@ {0}]]> - - Autorisierung sofort, Abbuchung später - - - Sofort abbuchen - Jetzt erstellen @@ -40,6 +34,9 @@ Legt die API Secret fest. + + Bitte geben Sie Ihre API Zugangsdaten ein. + Experience Profil-ID @@ -383,12 +380,33 @@ Zahlungsgebühr: {10}]]> PayPal PLUS bietet die Möglichkeit per PayPal, Kreditkarte, Lastschriftverfahren und per Rechnung zu bezahlen. -Die Bezahlung auf Rechnung steht möglicherweise nicht allen Händlern zur Verfügung. PayPal PLUS ist im Moment nur in Deutschland verfügbar.

]]> +Die Bezahlung auf Rechnung steht möglicherweise nicht allen Händlern zur Verfügung. PayPal PLUS unterstützt nur den Transaktionstyp der sofortigen Buchung und ist derzeit nur in Deutschland verfügbar.

]]>
Wir können Ihnen leider keine PayPal PLUS Zahlungsart anbieten. Bitte versuchen Sie er später erneut. + + Weitere Zahlarten + + + Sie können bis zu 5 weitere Zahlarten festlegen. Diese werden zusammen mit den PayPal PLUS Zahlarten im Kassenbereich angeboten. + + + Es können maximal 5 weitere Zahlarten festgelegt werden. + + + Logo der Zahlarten anzeigen + + + Legt fest, ob für die weiteren Zahlarten deren Logo angezeigt werden soll. SSL erforderlich. + + + Beschreibung der Zahlarten anzeigen + + + Legt fest, ob für die weiteren Zahlarten deren Beschreibung angezeigt werden soll. + diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index 8661213392..eeeab9e403 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -13,12 +13,6 @@ {0}]]> - - Authorize immediately, debit later - - - Immediately debit - Create now @@ -40,6 +34,9 @@ Specifies the API secret. + + Please enter your API credentials. + Experience profile ID @@ -383,12 +380,33 @@ Payment fee: {10}]]> PayPal PLUS is a solution where PayPal offers PayPal, Credit Card, Direct Debit (ELV) and pay upon invoice as individual payment options on the payment selection page. -Pay upon invoice may not be available for all merchants. PayPal PLUS is only available in Germany at the moment.

]]> +Pay upon invoice may not be available for all merchants. PayPal PLUS only supports the transaction type of instant settlement and is only available in Germany at the moment.

]]>
Unfortunately we cannot offer you a PayPal PLUS payment method. Please try again later. + + More payment methods + + + You can set up to 5 more payment methods. These are offered along with the PayPal PLUS payment methods during the checkout process. + + + Maximum of 5 third party payment methods can be specified. + + + Display logo of other payment methods + + + Specifies whether to display the logo for other payment methods. SSL required. + + + Display description of other payment methods + + + Specifies whether to display the description for other payment methods. + diff --git a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs index cb37dd4500..a6cfabcca0 100644 --- a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs +++ b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs @@ -37,6 +37,15 @@ public abstract class ApiConfigurationModel: ModelBase [SmartResourceDisplayName("Plugins.Payments.PayPal.Signature")] public string Signature { get; set; } + [SmartResourceDisplayName("Plugins.SmartStore.PayPal.ClientId")] + public string ClientId { get; set; } + + [SmartResourceDisplayName("Plugins.SmartStore.PayPal.Secret")] + public string Secret { get; set; } + + [SmartResourceDisplayName("Plugins.SmartStore.PayPal.ExperienceProfileId")] + public string ExperienceProfileId { get; set; } + [SmartResourceDisplayName("Plugins.Payments.PayPal.AdditionalFee")] public decimal AdditionalFee { get; set; } @@ -134,14 +143,16 @@ public void Copy(PayPalExpressPaymentSettings settings, bool fromSettings) public class PayPalPlusConfigurationModel : ApiConfigurationModel { - [SmartResourceDisplayName("Plugins.SmartStore.PayPal.ClientId")] - public string ClientId { get; set; } + [SmartResourceDisplayName("Plugins.Payments.PayPalPlus.ThirdPartyPaymentMethods")] + public List ThirdPartyPaymentMethods { get; set; } + public List AvailableThirdPartyPaymentMethods { get; set; } - [SmartResourceDisplayName("Plugins.SmartStore.PayPal.Secret")] - public string Secret { get; set; } + [SmartResourceDisplayName("Plugins.Payments.PayPalPlus.DisplayPaymentMethodLogo")] + public bool DisplayPaymentMethodLogo { get; set; } + + [SmartResourceDisplayName("Plugins.Payments.PayPalPlus.DisplayPaymentMethodDescription")] + public bool DisplayPaymentMethodDescription { get; set; } - [SmartResourceDisplayName("Plugins.SmartStore.PayPal.ExperienceProfileId")] - public string ExperienceProfileId { get; set; } public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) { @@ -149,33 +160,29 @@ public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) { SecurityProtocol = settings.SecurityProtocol; UseSandbox = settings.UseSandbox; - IpnChangesPaymentStatus = settings.IpnChangesPaymentStatus; - TransactMode = Convert.ToInt32(settings.TransactMode); - ApiAccountName = settings.ApiAccountName; - ApiAccountPassword = settings.ApiAccountPassword; - Signature = settings.Signature; AdditionalFee = settings.AdditionalFee; AdditionalFeePercentage = settings.AdditionalFeePercentage; ClientId = settings.ClientId; Secret = settings.Secret; ExperienceProfileId = settings.ExperienceProfileId; + ThirdPartyPaymentMethods = settings.ThirdPartyPaymentMethods; + DisplayPaymentMethodLogo = settings.DisplayPaymentMethodLogo; + DisplayPaymentMethodDescription = settings.DisplayPaymentMethodDescription; } else { settings.SecurityProtocol = SecurityProtocol; settings.UseSandbox = UseSandbox; - settings.IpnChangesPaymentStatus = IpnChangesPaymentStatus; - settings.TransactMode = (TransactMode)TransactMode; - settings.ApiAccountName = ApiAccountName; - settings.ApiAccountPassword = ApiAccountPassword; - settings.Signature = Signature; settings.AdditionalFee = AdditionalFee; settings.AdditionalFeePercentage = AdditionalFeePercentage; settings.ClientId = ClientId; settings.Secret = Secret; settings.ExperienceProfileId = ExperienceProfileId; + settings.ThirdPartyPaymentMethods = ThirdPartyPaymentMethods; + settings.DisplayPaymentMethodLogo = DisplayPaymentMethodLogo; + settings.DisplayPaymentMethodDescription = DisplayPaymentMethodDescription; } } } diff --git a/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs b/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs index dcb5b83bb2..b3b09094c5 100644 --- a/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs +++ b/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs @@ -1,4 +1,5 @@ -using SmartStore.Web.Framework.Modelling; +using System.Collections.Generic; +using SmartStore.Web.Framework.Modelling; namespace SmartStore.PayPal.Models { @@ -10,5 +11,15 @@ public class PayPalPlusCheckoutModel : ModelBase public string LanguageCulture { get; set; } public string ApprovalUrl { get; set; } public string ErrorMessage { get; set; } + + public List ThirdPartyPaymentMethods { get; set; } + + public class ThirdPartyPaymentMethod + { + public string RedirectUrl { get; set; } + public string MethodName { get; set; } + public string ImageUrl { get; set; } + public string Description { get; set; } + } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs index 7305762d68..874aea5672 100644 --- a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -18,6 +18,12 @@ public interface IPayPalService PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, PayPalSessionData session, Store store, string profileId); - PayPalResponse CreatePayment(PayPalApiSettingsBase settings, PayPalSessionData session, string providerSystemName, string returnUrl, string cancelUrl); + PayPalResponse CreatePayment( + PayPalApiSettingsBase settings, + PayPalSessionData session, + List cart, + string providerSystemName, + string returnUrl, + string cancelUrl); } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 5718a77bab..28fe09546c 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -341,12 +341,17 @@ public PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, P return result; } - public PayPalResponse CreatePayment(PayPalApiSettingsBase settings, PayPalSessionData session, string providerSystemName, string returnUrl, string cancelUrl) + public PayPalResponse CreatePayment( + PayPalApiSettingsBase settings, + PayPalSessionData session, + List cart, + string providerSystemName, + string returnUrl, + string cancelUrl) { var store = _services.StoreContext.CurrentStore; var customer = _services.WorkContext.CurrentCustomer; var language = _services.WorkContext.WorkingLanguage; - var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); var currencyCode = store.PrimaryStoreCurrency.CurrencyCode; Discount orderAppliedDiscount; @@ -377,7 +382,11 @@ public PayPalResponse CreatePayment(PayPalApiSettingsBase settings, PayPalSessio if (session.PaymentId.HasValue()) path = string.Concat(path, "/", HttpUtility.UrlPathEncode(session.PaymentId)); - data.Add("intent", settings.TransactMode == TransactMode.AuthorizeAndCapture ? "sale" : "authorize"); + // "PayPal PLUS only supports transaction type “Sale†(instant settlement)" + if (providerSystemName == PayPalPlusProvider.SystemName) + data.Add("intent", "sale"); + else + data.Add("intent", settings.TransactMode == TransactMode.AuthorizeAndCapture ? "sale" : "authorize"); if (settings.ExperienceProfileId.HasValue()) data.Add("experience_profile_id", settings.ExperienceProfileId); @@ -463,7 +472,7 @@ public PayPalResponse CreatePayment(PayPalApiSettingsBase settings, PayPalSessio { result.Id = (string)result.Json.id; - Logger.InsertLog(LogLevel.Information, "PayPal PLUS", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + result.Json.ToString()); + //Logger.InsertLog(LogLevel.Information, "PayPal PLUS", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + result.Json.ToString()); } return result; diff --git a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs index 77de820164..84bbe6ae59 100644 --- a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs +++ b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Net; using SmartStore.Core.Configuration; @@ -38,8 +39,19 @@ public abstract class PayPalApiSettingsBase : PayPalSettingsBase public string ApiAccountPassword { get; set; } public string Signature { get; set; } + /// + /// PayPal client id + /// public string ClientId { get; set; } + + /// + /// PayPal secret + /// public string Secret { get; set; } + + /// + /// PayPal experience profile id + /// public string ExperienceProfileId { get; set; } } @@ -98,6 +110,21 @@ public PayPalPlusPaymentSettings() { UseSandbox = true; } + + /// + /// Specifies other payment methods to be offered in payment wall + /// + public List ThirdPartyPaymentMethods { get; set; } + + /// + /// Specifies whether to display the logo of a third party payment method + /// + public bool DisplayPaymentMethodLogo { get; set; } + + /// + /// Specifies whether to display the description of a third party payment method + /// + public bool DisplayPaymentMethodDescription { get; set; } } public class PayPalStandardPaymentSettings : PayPalSettingsBase, ISettings diff --git a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj index 76fe8515dd..650be736b8 100644 --- a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj +++ b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj @@ -149,6 +149,7 @@ Settings.settings + diff --git a/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs b/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs new file mode 100644 index 0000000000..54fa987d14 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs @@ -0,0 +1,33 @@ +using System; +using FluentValidation; +using SmartStore.PayPal.Models; +using SmartStore.Services.Localization; +using SmartStore.Web.Framework.Validators; + +namespace SmartStore.PayPal.Validators +{ + public class PayPalPlusConfigValidator : SmartValidatorBase + { + public PayPalPlusConfigValidator(ILocalizationService localize, Func addRule) + { + if (addRule("ClientId")) + { + RuleFor(x => x.ClientId).NotEmpty() + .WithMessage(localize.GetResource("Plugins.SmartStore.PayPal.ValidateClientIdAndSecret")); + } + + if (addRule("Secret")) + { + RuleFor(x => x.Secret).NotEmpty() + .WithMessage(localize.GetResource("Plugins.SmartStore.PayPal.ValidateClientIdAndSecret")); + } + + if (addRule("ThirdPartyPaymentMethods")) + { + RuleFor(x => x.ThirdPartyPaymentMethods) + .Must(x => x.Count <= 5) + .WithMessage(localize.GetResource("Plugins.Payments.PayPalPlus.ValidateThirdPartyPaymentMethods")); + } + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml index 6b32a76ada..bdd59a8735 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml @@ -101,31 +101,43 @@ @Html.DropDownListFor(model => model.SecurityProtocol, Model.AvailableSecurityProtocols, T("Common.Unspecified"))
+ + + - - + + diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml index b9848cc9f0..8beadec2a2 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml @@ -5,26 +5,46 @@ try { var ppp = PAYPAL.apps.PPP({ - approvalUrl: '@Html.Raw(Model.ApprovalUrl)', - placeholder: 'PayPalPlusPaymentWall', - mode: '@(Model.UseSandbox ? "sandbox" : "live")', - country: '@(Model.BillingAddressCountryCode ?? "DE")', - buttonLocation: 'outside', - language: '@Html.Raw(Model.LanguageCulture ?? "de_DE")', - useraction: 'continue', - //surcharging: @Model.HasPaymentFee.ToString().ToLower(), - //showLoadingIndicator: true, - enableContinue: 'PayPalPlusNextCheckoutStep', - disableContinue: 'PayPalPlusNextCheckoutStep', + approvalUrl: "@Html.Raw(Model.ApprovalUrl)", + placeholder: "PayPalPlusPaymentWall", + mode: "@(Model.UseSandbox ? "sandbox" : "live")", + country: "@(Model.BillingAddressCountryCode ?? "DE")", + buttonLocation: "outside", + language: "@Html.Raw(Model.LanguageCulture ?? "de_DE")", + useraction: "continue", + surcharging: @Model.HasPaymentFee.ToString().ToLower(), + showLoadingIndicator: true, + //showPuiOnSandbox: true, + enableContinue: "PayPalPlusNextCheckoutStep", + disableContinue: "PayPalPlusNextCheckoutStep", //enableContinue: function () { // $('#PayPalPlusNextCheckoutStep').prop('disabled', false); //}, //disableContinue: function () { // $('#PayPalPlusNextCheckoutStep').prop('disabled', true); //}, - //onLoad: function () { - // $('#PayPalPlusNextCheckoutStep').fadeIn(); - //} + onLoad: function () { + $('#PayPalPlusNextCheckoutStep').fadeIn(); + }, + @if (Model.ThirdPartyPaymentMethods.Any()) + { + thirdPartyPaymentMethods: [ + foreach (var method in Model.ThirdPartyPaymentMethods) + { + { redirectUrl: "@Html.Raw(method.RedirectUrl)", + methodName: @Html.Raw(method.MethodName), + if (method.ImageUrl.HasValue()) + { + imageUrl: "@Html.Raw(method.ImageUrl)", + } + if (method.Description.HasValue()) + { + description: @Html.Raw(method.Description), + } + }, + } + ] + } }); } diff --git a/src/Presentation/SmartStore.Web.Framework/Validators/SmartBaseValidator.cs b/src/Presentation/SmartStore.Web.Framework/Validators/SmartBaseValidator.cs index 6f8c74b211..7d349f7e4a 100644 --- a/src/Presentation/SmartStore.Web.Framework/Validators/SmartBaseValidator.cs +++ b/src/Presentation/SmartStore.Web.Framework/Validators/SmartBaseValidator.cs @@ -16,10 +16,13 @@ public void Validate(T model, ModelStateDictionary modelState) if (!result.IsValid) { - int i = 0; foreach (var error in result.Errors) { - modelState.AddModelError(error.PropertyName + (++i).ToString(), error.ErrorMessage); + try + { + modelState.AddModelError(error.PropertyName, error.ErrorMessage); + } + catch { } } } } From 2384d470baca6b8cb7042dd0c4cc5a5d269aa949 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 18 Apr 2016 17:14:31 +0200 Subject: [PATCH 041/285] PayPal Plus: Newtonsoft.Json 8.0.3, rest API provider base, checkout return, PayPal Plus does not support IPN --- .../Controllers/PayPalControllerBase.cs | 32 ----- .../Controllers/PayPalDirectController.cs | 2 +- .../Controllers/PayPalExpressController.cs | 3 +- .../Controllers/PayPalPlusController.cs | 66 +++++---- .../Controllers/PayPalStandardController.cs | 2 +- .../Providers/PayPalPlusProvider.cs | 17 +-- .../Providers/PayPalRestApiProviderBase.cs | 134 +++++++++++++++++ .../Services/IPayPalService.cs | 4 + .../Services/PayPalService.cs | 66 ++++++++- .../SmartStore.PayPal.csproj | 5 +- src/Plugins/SmartStore.PayPal/packages.config | 2 +- src/Plugins/SmartStore.PayPal/web.config | 136 +++++++++--------- 12 files changed, 315 insertions(+), 154 deletions(-) create mode 100644 src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalControllerBase.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalControllerBase.cs index 203c28693d..06d2c94600 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalControllerBase.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalControllerBase.cs @@ -3,7 +3,6 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Net; using System.Text; using System.Web; using System.Web.Mvc; @@ -86,37 +85,6 @@ protected PaymentStatus GetPaymentStatus(string paymentStatus, string pendingRea return result; } - protected Dictionary GetSecurityProtocols() - { - var dic = new Dictionary(); - - foreach (SecurityProtocolType protocol in Enum.GetValues(typeof(SecurityProtocolType))) - { - string friendlyName = null; - switch (protocol) - { - case SecurityProtocolType.Ssl3: - friendlyName = "SSL 3.0"; - break; - case SecurityProtocolType.Tls: - friendlyName = "TLS 1.0"; - break; - case SecurityProtocolType.Tls11: - friendlyName = "TLS 1.1"; - break; - case SecurityProtocolType.Tls12: - friendlyName = "TLS 1.2"; - break; - default: - friendlyName = protocol.ToString().ToUpper(); - break; - } - - dic.Add(protocol, friendlyName); - } - return dic; - } - protected 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. diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs index 9460321d80..abdf2df189 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalDirectController.cs @@ -55,7 +55,7 @@ public ActionResult Configure() model.TransactModeValues = TransactModeValues(settings.TransactMode); - model.AvailableSecurityProtocols = GetSecurityProtocols() + model.AvailableSecurityProtocols = PayPalService.GetSecurityProtocols() .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) .ToList(); diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs index 7e647a9970..776d1a210a 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalExpressController.cs @@ -11,6 +11,7 @@ using SmartStore.Core.Domain.Shipping; using SmartStore.PayPal.Models; using SmartStore.PayPal.PayPalSvc; +using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.PayPal.Validators; using SmartStore.Services.Common; @@ -109,7 +110,7 @@ public ActionResult Configure() model.TransactModeValues = TransactModeValues(settings.TransactMode); - model.AvailableSecurityProtocols = GetSecurityProtocols() + model.AvailableSecurityProtocols = PayPalService.GetSecurityProtocols() .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) .ToList(); diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index a573f02158..fa9bf3724a 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -4,7 +4,6 @@ using System.Web; using System.Web.Mvc; using SmartStore.Core.Domain.Customers; -using SmartStore.Core.Domain.Logging; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Html; @@ -25,12 +24,13 @@ namespace SmartStore.PayPal.Controllers { - public class PayPalPlusController : PayPalControllerBase + public class PayPalPlusController : PaymentControllerBase { private readonly HttpContextBase _httpContext; private readonly PluginMediator _pluginMediator; private readonly IPayPalService _payPalService; private readonly IGenericAttributeService _genericAttributeService; + private readonly IPaymentService _paymentService; public PayPalPlusController( HttpContextBase httpContext, @@ -39,29 +39,30 @@ public PayPalPlusController( IOrderProcessingService orderProcessingService, PluginMediator pluginMediator, IPayPalService payPalService, - IGenericAttributeService genericAttributeService) : base( - PayPalPlusProvider.SystemName, - paymentService, - orderService, - orderProcessingService) + IGenericAttributeService genericAttributeService) { _httpContext = httpContext; _pluginMediator = pluginMediator; _payPalService = payPalService; _genericAttributeService = genericAttributeService; + _paymentService = paymentService; } private string GetPaymentMethodName(Provider provider) { - var name = _pluginMediator.GetLocalizedFriendlyName(provider.Metadata); + if (provider != null) + { + var name = _pluginMediator.GetLocalizedFriendlyName(provider.Metadata); - if (name.IsEmpty()) - name = provider.Metadata.FriendlyName; + if (name.IsEmpty()) + name = provider.Metadata.FriendlyName; - if (name.IsEmpty()) - name = provider.Metadata.SystemName; + if (name.IsEmpty()) + name = provider.Metadata.SystemName; - return name; + return name; + } + return ""; } private PayPalPlusCheckoutModel.ThirdPartyPaymentMethod GetThirdPartyPaymentMethodModel( @@ -71,14 +72,14 @@ private PayPalPlusCheckoutModel.ThirdPartyPaymentMethod GetThirdPartyPaymentMeth { var model = new PayPalPlusCheckoutModel.ThirdPartyPaymentMethod(); model.MethodName = GetPaymentMethodName(provider).EncodeJsString(); - model.RedirectUrl = Url.Action("CheckoutReturn", "PayPalPlus", new { area = Plugin.SystemName }, store.SslEnabled ? "https" : "http"); + model.RedirectUrl = Url.Action("CheckoutReturn", "PayPalPlus", new { area = Plugin.SystemName, systemName = provider.Metadata.SystemName }, store.SslEnabled ? "https" : "http"); try { if (settings.DisplayPaymentMethodDescription) { // not the short description, the full description is intended for frontend - var paymentMethod = PaymentService.GetPaymentMethodBySystemName(provider.Metadata.SystemName); + var paymentMethod = _paymentService.GetPaymentMethodBySystemName(provider.Metadata.SystemName); if (paymentMethod != null) { var description = paymentMethod.GetLocalized(x => x.FullDescription); @@ -140,12 +141,12 @@ public ActionResult Configure() ConfigGroups = T("Plugins.SmartStore.PayPal.ConfigGroups").Text.SplitSafe(";") }; - model.AvailableSecurityProtocols = GetSecurityProtocols() + model.AvailableSecurityProtocols = PayPalService.GetSecurityProtocols() .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) .ToList(); // it's better to also offer inactive methods here but filter them out in frontend - var methods = PaymentService.LoadAllPaymentMethods(storeScope); + var methods = _paymentService.LoadAllPaymentMethods(storeScope); model.AvailableThirdPartyPaymentMethods = methods .Where(x => @@ -240,7 +241,7 @@ public ActionResult PaymentWall() var settings = Services.Settings.LoadSetting(store.Id); var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); - var methods = PaymentService.LoadActivePaymentMethods(customer, cart, store.Id, null, false); + var methods = _paymentService.LoadActivePaymentMethods(customer, cart, store.Id, null, false); var state = _httpContext.GetCheckoutState(); if (!state.CustomProperties.ContainsKey(PayPalPlusProvider.SystemName)) @@ -309,23 +310,38 @@ public ActionResult PaymentWall() } [ValidateInput(false)] - public ActionResult CheckoutReturn() + public ActionResult CheckoutReturn(string systemName, string paymentId) { + // Request.QueryString: + // paymentId: PAY-0TC88803RP094490KK4KM6AI, token: EC-5P379249AL999154U, PayerID: 5L9K773HHJLPN + var customer = Services.WorkContext.CurrentCustomer; var store = Services.StoreContext.CurrentStore; + var settings = Services.Settings.LoadSetting(store.Id); + var state = _httpContext.GetCheckoutState(); - _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, Plugin.SystemName, store.Id); + if (!state.CustomProperties.ContainsKey(PayPalPlusProvider.SystemName)) + state.CustomProperties.Add(PayPalPlusProvider.SystemName, new PayPalSessionData()); + + var session = state.CustomProperties[PayPalPlusProvider.SystemName] as PayPalSessionData; + + if (systemName.IsEmpty()) + systemName = PayPalPlusProvider.SystemName; + + if (paymentId.HasValue() && session.PaymentId.IsEmpty()) + session.PaymentId = paymentId; + + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, systemName, store.Id); - // this is ugly... var paymentRequest = _httpContext.Session["OrderPaymentInfo"] as ProcessPaymentRequest; if (paymentRequest == null) { - _httpContext.Session["OrderPaymentInfo"] = new ProcessPaymentRequest(); + _httpContext.Session["OrderPaymentInfo"] = new ProcessPaymentRequest + { + PaymentMethodSystemName = systemName + }; } - Logger.InsertLog(LogLevel.Information, "PayPal PLUS Return", string.Join(", ", Request.QueryString.AllKeys.Select(key => key + ": " + Request.QueryString[key]).ToArray())); - - return RedirectToAction("Confirm", "Checkout", new { area = "" }); } diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs index b03f8ef7d4..e007d86dfc 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalStandardController.cs @@ -39,7 +39,7 @@ public ActionResult Configure() model.Copy(settings, true); - model.AvailableSecurityProtocols = GetSecurityProtocols() + model.AvailableSecurityProtocols = PayPalService.GetSecurityProtocols() .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) .ToList(); diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs index d18f0759dc..eba81fa500 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs @@ -10,7 +10,7 @@ namespace SmartStore.PayPal [SystemName("Payments.PayPalPlus")] [FriendlyName("PayPal Plus")] [DisplayOrder(1)] - public partial class PayPalPlusProvider : PayPalProviderBase + public partial class PayPalPlusProvider : PayPalRestApiProviderBase { public PayPalPlusProvider() { @@ -29,21 +29,6 @@ public override PaymentMethodType PaymentMethodType } } - protected override string GetResourceRootKey() - { - return "Plugins.Payments.PayPalPlus"; - } - - protected override string GetControllerName() - { - return "PayPalPlus"; - } - - /// - /// Process a payment - /// - /// Payment info required for an order processing - /// Process payment result public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest) { var result = new ProcessPaymentResult diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs new file mode 100644 index 0000000000..c3424d7193 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs @@ -0,0 +1,134 @@ +using System; +using System.Collections.Generic; +using System.Web.Routing; +using SmartStore.Core.Configuration; +using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Logging; +using SmartStore.Core.Plugins; +using SmartStore.PayPal.Settings; +using SmartStore.Services; +using SmartStore.Services.Orders; +using SmartStore.Services.Payments; + +namespace SmartStore.PayPal +{ + public abstract class PayPalRestApiProviderBase : PaymentMethodBase, IConfigurable where TSetting : PayPalApiSettingsBase, ISettings, new() + { + protected PayPalRestApiProviderBase() + { + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + public ICommonServices Services { get; set; } + public IOrderService OrderService { get; set; } + public IOrderTotalCalculationService OrderTotalCalculationService { get; set; } + + protected string GetControllerName() + { + return GetControllerType().Name.EmptyNull().Replace("Controller", ""); + } + + public override bool SupportCapture + { + get { return true; } + } + + public override bool SupportPartiallyRefund + { + get { return true; } + } + + public override bool SupportRefund + { + get { return true; } + } + + public override bool SupportVoid + { + get { return true; } + } + + public override decimal GetAdditionalHandlingFee(IList cart) + { + var result = decimal.Zero; + try + { + var settings = Services.Settings.LoadSetting(); + + result = this.CalculateAdditionalFee(OrderTotalCalculationService, cart, settings.AdditionalFee, settings.AdditionalFeePercentage); + } + catch (Exception) + { + } + return result; + } + + public override CapturePaymentResult Capture(CapturePaymentRequest capturePaymentRequest) + { + var result = new CapturePaymentResult + { + NewPaymentStatus = capturePaymentRequest.Order.PaymentStatus + }; + + var settings = Services.Settings.LoadSetting(capturePaymentRequest.Order.StoreId); + var currencyCode = Services.WorkContext.WorkingCurrency.CurrencyCode ?? "EUR"; + + var authorizationId = capturePaymentRequest.Order.AuthorizationTransactionId; + + // TODO + + return result; + } + + public override RefundPaymentResult Refund(RefundPaymentRequest request) + { + var result = new RefundPaymentResult + { + NewPaymentStatus = request.Order.PaymentStatus + }; + + var settings = Services.Settings.LoadSetting(request.Order.StoreId); + + var transactionId = request.Order.CaptureTransactionId; + + // TODO + + return result; + } + + public override VoidPaymentResult Void(VoidPaymentRequest request) + { + var result = new VoidPaymentResult + { + NewPaymentStatus = request.Order.PaymentStatus + }; + + var settings = Services.Settings.LoadSetting(request.Order.StoreId); + + var transactionId = request.Order.AuthorizationTransactionId; + + if (transactionId.IsEmpty()) + transactionId = request.Order.CaptureTransactionId; + + // TODO + + return result; + } + + public override void GetConfigurationRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) + { + actionName = "Configure"; + controllerName = GetControllerName(); + routeValues = new RouteValueDictionary { { "area", "SmartStore.PayPal" } }; + } + + public override void GetPaymentInfoRoute(out string actionName, out string controllerName, out RouteValueDictionary routeValues) + { + actionName = "PaymentInfo"; + controllerName = GetControllerName(); + routeValues = new RouteValueDictionary { { "area", "SmartStore.PayPal" } }; + } + } +} + diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs index 874aea5672..240d71c02a 100644 --- a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -18,6 +18,8 @@ public interface IPayPalService PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, PayPalSessionData session, Store store, string profileId); + PayPalResponse GetPayment(PayPalApiSettingsBase settings, PayPalSessionData session); + PayPalResponse CreatePayment( PayPalApiSettingsBase settings, PayPalSessionData session, @@ -25,5 +27,7 @@ PayPalResponse CreatePayment( string providerSystemName, string returnUrl, string cancelUrl); + + PayPalResponse PatchPayment(PayPalApiSettingsBase settings, PayPalSessionData session); } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 28fe09546c..cd5a05b7fb 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -65,6 +65,37 @@ public static string GetApiUrl(bool sandbox) return sandbox ? "https://api.sandbox.paypal.com" : "https://api.paypal.com"; } + public static Dictionary GetSecurityProtocols() + { + var dic = new Dictionary(); + + foreach (SecurityProtocolType protocol in Enum.GetValues(typeof(SecurityProtocolType))) + { + string friendlyName = null; + switch (protocol) + { + case SecurityProtocolType.Ssl3: + friendlyName = "SSL 3.0"; + break; + case SecurityProtocolType.Tls: + friendlyName = "TLS 1.0"; + break; + case SecurityProtocolType.Tls11: + friendlyName = "TLS 1.1"; + break; + case SecurityProtocolType.Tls12: + friendlyName = "TLS 1.2"; + break; + default: + friendlyName = protocol.ToString().ToUpper(); + break; + } + + dic.Add(protocol, friendlyName); + } + return dic; + } + public void AddOrderNote(PayPalSettingsBase settings, Order order, string anyString) { try @@ -185,6 +216,7 @@ public PayPalResponse CallApi(string method, string path, string accessToken, Pa catch (WebException wexc) { result.Success = false; + result.ErrorMessage = wexc.ToString(); webResponse = wexc.Response as HttpWebResponse; } catch (Exception exception) @@ -222,23 +254,24 @@ public PayPalResponse CallApi(string method, string path, string accessToken, Pa if (message.IsEmpty()) message = (string)result.Json.error_description; - if (message.IsEmpty()) - message = webResponse.StatusDescription; - result.ErrorMessage = "{0} ({1}).".FormatInvariant(message.NaIfEmpty(), name.NaIfEmpty()); - - LogError(null, result.ErrorMessage, result.Json.ToString(), false, errors); } } } else if (!result.Success) { result.ErrorMessage = rawResponse; - - LogError(null, result.ErrorMessage, null, false, errors); } } } + + if (!result.Success) + { + if (result.ErrorMessage.IsEmpty()) + result.ErrorMessage = webResponse.StatusDescription; + + LogError(null, result.ErrorMessage, result.Json == null ? null : result.Json.ToString(), false, errors); + } } } catch (Exception exception) @@ -341,6 +374,20 @@ public PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, P return result; } + public PayPalResponse GetPayment(PayPalApiSettingsBase settings, PayPalSessionData session) + { + var result = CallApi("GET", "/v1/payments/payment/" + session.PaymentId, session.AccessToken, settings, null); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + + Logger.InsertLog(LogLevel.Information, "PayPal PLUS", result.Json.ToString()); + } + + return result; + } + public PayPalResponse CreatePayment( PayPalApiSettingsBase settings, PayPalSessionData session, @@ -477,6 +524,11 @@ public PayPalResponse CreatePayment( return result; } + + public PayPalResponse PatchPayment(PayPalApiSettingsBase settings, PayPalSessionData session) + { + return null; + } } diff --git a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj index 650be736b8..e552740df4 100644 --- a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj +++ b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj @@ -68,8 +68,8 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll True @@ -135,6 +135,7 @@ + diff --git a/src/Plugins/SmartStore.PayPal/packages.config b/src/Plugins/SmartStore.PayPal/packages.config index 8480e349e2..eb91eb3011 100644 --- a/src/Plugins/SmartStore.PayPal/packages.config +++ b/src/Plugins/SmartStore.PayPal/packages.config @@ -7,5 +7,5 @@ - + \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/web.config b/src/Plugins/SmartStore.PayPal/web.config index add05adbda..f6daa0fb4b 100644 --- a/src/Plugins/SmartStore.PayPal/web.config +++ b/src/Plugins/SmartStore.PayPal/web.config @@ -1,11 +1,11 @@ - + -
+
- + @@ -14,131 +14,131 @@ - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + From 7aeb45ee94911d4d27ac2a92d04f41bc095e0a27 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 19 Apr 2016 14:27:33 +0200 Subject: [PATCH 042/285] PayPal Plus: Payment execution --- .../Controllers/PayPalPlusController.cs | 18 ++--- .../Extensions/MiscExtensions.cs | 16 +++- .../Localization/resources.de-de.xml | 3 + .../Localization/resources.en-us.xml | 3 + .../Providers/PayPalPlusProvider.cs | 68 +++++++++++++++-- .../Services/IPayPalService.cs | 2 +- .../Services/PayPalService.cs | 73 +++++++++++++++++-- 7 files changed, 154 insertions(+), 29 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index fa9bf3724a..6ee9e56bc4 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -242,12 +242,7 @@ public ActionResult PaymentWall() var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); var methods = _paymentService.LoadActivePaymentMethods(customer, cart, store.Id, null, false); - var state = _httpContext.GetCheckoutState(); - - if (!state.CustomProperties.ContainsKey(PayPalPlusProvider.SystemName)) - state.CustomProperties.Add(PayPalPlusProvider.SystemName, new PayPalSessionData()); - - var session = state.CustomProperties[PayPalPlusProvider.SystemName] as PayPalSessionData; + var session = _httpContext.GetPayPalSessionData(); var model = new PayPalPlusCheckoutModel(); model.ThirdPartyPaymentMethods = new List(); @@ -310,7 +305,7 @@ public ActionResult PaymentWall() } [ValidateInput(false)] - public ActionResult CheckoutReturn(string systemName, string paymentId) + public ActionResult CheckoutReturn(string systemName, string paymentId, string PayerID) { // Request.QueryString: // paymentId: PAY-0TC88803RP094490KK4KM6AI, token: EC-5P379249AL999154U, PayerID: 5L9K773HHJLPN @@ -318,12 +313,7 @@ public ActionResult CheckoutReturn(string systemName, string paymentId) var customer = Services.WorkContext.CurrentCustomer; var store = Services.StoreContext.CurrentStore; var settings = Services.Settings.LoadSetting(store.Id); - var state = _httpContext.GetCheckoutState(); - - if (!state.CustomProperties.ContainsKey(PayPalPlusProvider.SystemName)) - state.CustomProperties.Add(PayPalPlusProvider.SystemName, new PayPalSessionData()); - - var session = state.CustomProperties[PayPalPlusProvider.SystemName] as PayPalSessionData; + var session = _httpContext.GetPayPalSessionData(); if (systemName.IsEmpty()) systemName = PayPalPlusProvider.SystemName; @@ -331,6 +321,8 @@ public ActionResult CheckoutReturn(string systemName, string paymentId) if (paymentId.HasValue() && session.PaymentId.IsEmpty()) session.PaymentId = paymentId; + session.PayerId = PayerID; + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, systemName, store.Id); var paymentRequest = _httpContext.Session["OrderPaymentInfo"] as ProcessPaymentRequest; diff --git a/src/Plugins/SmartStore.PayPal/Extensions/MiscExtensions.cs b/src/Plugins/SmartStore.PayPal/Extensions/MiscExtensions.cs index 6903865152..352d70d951 100644 --- a/src/Plugins/SmartStore.PayPal/Extensions/MiscExtensions.cs +++ b/src/Plugins/SmartStore.PayPal/Extensions/MiscExtensions.cs @@ -1,9 +1,12 @@ using System.Net; +using System.Web; +using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; +using SmartStore.Services.Orders; namespace SmartStore.PayPal { - public static class MiscExtensions + internal static class MiscExtensions { public static string GetPayPalUrl(this PayPalSettingsBase settings) { @@ -22,5 +25,16 @@ public static HttpWebRequest GetPayPalWebRequest(this PayPalSettingsBase setting var request = (HttpWebRequest)WebRequest.Create(GetPayPalUrl(settings)); return request; } + + public static PayPalSessionData GetPayPalSessionData(this HttpContextBase httpContext, CheckoutState state = null) + { + if (state == null) + state = httpContext.GetCheckoutState(); + + if (!state.CustomProperties.ContainsKey(PayPalPlusProvider.SystemName)) + state.CustomProperties.Add(PayPalPlusProvider.SystemName, new PayPalSessionData()); + + return state.CustomProperties[PayPalPlusProvider.SystemName] as PayPalSessionData; + } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index 57af7f6676..7625a75def 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -43,6 +43,9 @@ Legt die Experience Profil-ID fest. Das Profil beinhaltet globale Daten wie z.B. Shop-Name und -Logo. Ein Profil braucht nur einmalig angelegt werden. Über den Button können Sie ein neues Profil erstellen oder ein vorhandenes aktualisieren. + + Die Ausführung der Zahlung wurde durch PayPal abgelehnt. + diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index eeeab9e403..386d428484 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -43,6 +43,9 @@ Specifies the experience profile ID. The profile contains global data like shop name and logo. A profile needs to be created only once. Using the button, you can create a new profile or update an existing one. + + The execution of the payment has been declined by PayPal. + diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs index eba81fa500..c2510fa340 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs @@ -1,7 +1,9 @@ using System; +using System.Web; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Plugins; using SmartStore.PayPal.Controllers; +using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Services.Payments; @@ -12,8 +14,15 @@ namespace SmartStore.PayPal [DisplayOrder(1)] public partial class PayPalPlusProvider : PayPalRestApiProviderBase { - public PayPalPlusProvider() + private readonly HttpContextBase _httpContext; + private readonly IPayPalService _payPalService; + + public PayPalPlusProvider( + HttpContextBase httpContext, + IPayPalService payPalService) { + _httpContext = httpContext; + _payPalService = payPalService; } public static string SystemName @@ -29,6 +38,11 @@ public override PaymentMethodType PaymentMethodType } } + public override Type GetControllerType() + { + return typeof(PayPalPlusController); + } + public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest) { var result = new ProcessPaymentResult @@ -36,12 +50,54 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces NewPaymentStatus = PaymentStatus.Pending }; - return result; - } + var settings = Services.Settings.LoadSetting(processPaymentRequest.StoreId); + var session = _httpContext.GetPayPalSessionData(); - public override Type GetControllerType() - { - return typeof(PayPalPlusController); + processPaymentRequest.OrderGuid = session.OrderGuid; + + var apiResult = _payPalService.ExecutePayment(settings, session); + + if (apiResult.Success && apiResult.Json != null) + { + var state = (string)apiResult.Json.state; + + if (!state.IsCaseInsensitiveEqual("failed")) + { + result.AuthorizationTransactionCode = apiResult.Id; // payment id + + var sale = apiResult.Json.transactions[0].related_resources[0].sale; + + if (sale != null) + { + state = (string)sale.state; + + result.AuthorizationTransactionResult = state; + result.AuthorizationTransactionId = (string)sale.id; + + if (state.IsCaseInsensitiveEqual("completed") || state.IsCaseInsensitiveEqual("processed")) + { + result.CaptureTransactionResult = state; + result.CaptureTransactionId = (string)sale.id; + + result.NewPaymentStatus = PaymentStatus.Paid; + } + else + { + result.NewPaymentStatus = PaymentStatus.Authorized; + } + } + } + else + { + result.Errors.Add(T("Plugins.SmartStore.PayPal.PaymentExecuteFailed")); + } + } + else + { + result.Errors.Add(apiResult.ErrorMessage); + } + + return result; } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs index 240d71c02a..993c49a024 100644 --- a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -28,6 +28,6 @@ PayPalResponse CreatePayment( string returnUrl, string cancelUrl); - PayPalResponse PatchPayment(PayPalApiSettingsBase settings, PayPalSessionData session); + PayPalResponse ExecutePayment(PayPalApiSettingsBase settings, PayPalSessionData session); } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index cd5a05b7fb..ecade23af1 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -7,6 +7,7 @@ using System.Web; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Logging; using SmartStore.Core.Domain.Orders; @@ -17,6 +18,7 @@ using SmartStore.PayPal.Settings; using SmartStore.Services; using SmartStore.Services.Catalog; +using SmartStore.Services.Common; using SmartStore.Services.Customers; using SmartStore.Services.Localization; using SmartStore.Services.Media; @@ -60,6 +62,36 @@ public PayPalService( public Localizer T { get; set; } public ILogger Logger { get; set; } + private Dictionary CreateAddress(Address addr) + { + var dic = new Dictionary(); + + dic.Add("line1", addr.Address1.Truncate(100)); + + if (addr.Address2.HasValue()) + { + dic.Add("line2", addr.Address2.Truncate(100)); + } + + dic.Add("city", addr.City.Truncate(50)); + + if (addr.CountryId != 0 && addr.Country != null) + { + dic.Add("country_code", addr.Country.TwoLetterIsoCode); + } + + dic.Add("postal_code", addr.ZipPostalCode.Truncate(20)); + + if (addr.StateProvinceId != 0 && addr.StateProvince != null) + { + dic.Add("state", addr.StateProvince.Abbreviation.Truncate(100)); + } + + dic.Add("recipient_name", addr.GetFullName().Truncate(50)); + + return dic; + } + public static string GetApiUrl(bool sandbox) { return sandbox ? "https://api.sandbox.paypal.com" : "https://api.paypal.com"; @@ -381,8 +413,6 @@ public PayPalResponse GetPayment(PayPalApiSettingsBase settings, PayPalSessionDa if (result.Success && result.Json != null) { result.Id = (string)result.Json.id; - - Logger.InsertLog(LogLevel.Information, "PayPal PLUS", result.Json.ToString()); } return result; @@ -425,6 +455,7 @@ public PayPalResponse CreatePayment( var amount = new Dictionary(); var amountDetails = new Dictionary(); var items = new List>(); + var itemList = new Dictionary(); if (session.PaymentId.HasValue()) path = string.Concat(path, "/", HttpUtility.UrlPathEncode(session.PaymentId)); @@ -482,17 +513,23 @@ public PayPalResponse CreatePayment( items.Add(line); totalOrderItems += (total - itemsPlusMisc); - } + } + + itemList.Add("items", items); + if (customer.ShippingAddress != null) + { + itemList.Add("shipping_address", CreateAddress(customer.ShippingAddress)); + } // transactions amountDetails.Add("shipping", Math.Round(shipping, 2)); amountDetails.Add("subtotal", Math.Round(totalOrderItems, 2)); if (!includingTax) { - // To avoid rounding errors we recommend not submitting tax amounts on line item basis. + // "To avoid rounding errors we recommend not submitting tax amounts on line item basis. // Calculated tax amounts for the entire shopping basket may be submitted in the amount objects. // In this case the item amounts will be treated as amounts excluding tax. - // In a B2C scenario, where taxes are included, no taxes should be submitted to PayPal. + // In a B2C scenario, where taxes are included, no taxes should be submitted to PayPal." SortedDictionary taxRates = null; var taxTotal = _orderTotalCalculationService.GetTaxTotal(cart, out taxRates); @@ -509,7 +546,8 @@ public PayPalResponse CreatePayment( amount.Add("details", amountDetails); transaction.Add("amount", amount); - transaction.Add("item_list", new Dictionary { { "items" , items } }); + transaction.Add("item_list", itemList); + transaction.Add("invoice_number", session.OrderGuid.ToString()); data.Add("transactions", new List> { transaction }); @@ -525,9 +563,21 @@ public PayPalResponse CreatePayment( return result; } - public PayPalResponse PatchPayment(PayPalApiSettingsBase settings, PayPalSessionData session) + public PayPalResponse ExecutePayment(PayPalApiSettingsBase settings, PayPalSessionData session) { - return null; + var data = new Dictionary(); + data.Add("payer_id", session.PayerId); + + var result = CallApi("POST", "/v1/payments/payment/{0}/execute".FormatInvariant(session.PaymentId), session.AccessToken, settings, JsonConvert.SerializeObject(data)); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + + //Logger.InsertLog(LogLevel.Information, "PayPal PLUS", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + result.Json.ToString()); + } + + return result; } } @@ -542,9 +592,16 @@ public class PayPalResponse public class PayPalSessionData { + public PayPalSessionData() + { + OrderGuid = Guid.NewGuid(); + } + public string AccessToken { get; set; } public DateTime TokenExpiration { get; set; } public string PaymentId { get; set; } + public string PayerId { get; set; } public string ApprovalUrl { get; set; } + public Guid OrderGuid { get; private set; } } } \ No newline at end of file From e14ae4c293795cbc80185c359290fedef351c25a Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 19 Apr 2016 22:01:36 +0200 Subject: [PATCH 043/285] Localization & SEO: language switcher now takes language specific SEO slugs into account when building links --- changelog.md | 1 + .../Controllers/CommonController.cs | 53 +++++++++++++++---- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/changelog.md b/changelog.md index 9b75e12d36..3c618df80f 100644 --- a/changelog.md +++ b/changelog.md @@ -10,6 +10,7 @@ ### Improvements * (Dev) Publishing SmartStore.Web from within Visual Studio now deploys the project correctly. No need to execute ClickToBuild.cmd anymore. Just publish directly to any target, including Azure. +* Localization & SEO: language switcher now takes language specific SEO slugs into account when building links * Exports the product detail link including the attribute query string when exporting attribute combinations * #918 Compare products: Display base price information diff --git a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs index 2734c4d8ee..54b76c07b9 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs @@ -43,6 +43,7 @@ using SmartStore.Web.Framework.UI; using SmartStore.Web.Infrastructure.Cache; using SmartStore.Web.Models.Common; +using SmartStore.Services.Seo; namespace SmartStore.Web.Controllers { @@ -50,8 +51,6 @@ public partial class CommonController : PublicControllerBase { private readonly static string[] s_hints = new string[] { "Shopsystem", "Onlineshop Software", "Shopsoftware", "E-Commerce Solution" }; - #region Fields - private readonly ICommonServices _services; private readonly ITopicService _topicService; private readonly Lazy _languageService; @@ -62,6 +61,7 @@ public partial class CommonController : PublicControllerBase private readonly Lazy _genericAttributeService; private readonly Lazy _mobileDeviceHelper; private readonly Lazy _compareProductsService; + private readonly Lazy _urlRecordService; private readonly StoreInformationSettings _storeInfoSettings; private readonly CustomerSettings _customerSettings; @@ -85,10 +85,6 @@ public partial class CommonController : PublicControllerBase private readonly Lazy _categoryService; private readonly Lazy _productService; - #endregion - - #region Constructors - public CommonController( ICommonServices services, ITopicService topicService, @@ -100,6 +96,7 @@ public CommonController( Lazy genericAttributeService, Lazy mobileDeviceHelper, Lazy compareProductsService, + Lazy urlRecordService, StoreInformationSettings storeInfoSettings, CustomerSettings customerSettings, TaxSettings taxSettings, @@ -132,6 +129,7 @@ public CommonController( this._genericAttributeService = genericAttributeService; this._mobileDeviceHelper = mobileDeviceHelper; this._compareProductsService = compareProductsService; + this._urlRecordService = urlRecordService; this._storeInfoSettings = storeInfoSettings; this._customerSettings = customerSettings; @@ -157,8 +155,6 @@ public CommonController( this._productService = productService; } - #endregion - #region Utilities [NonAction] @@ -183,7 +179,7 @@ protected LanguageSelectorModel PrepareLanguageSelectorModel() var workingLanguage = _services.WorkContext.WorkingLanguage; - var model = new LanguageSelectorModel() + var model = new LanguageSelectorModel { CurrentLanguageId = workingLanguage.Id, AvailableLanguages = availableLanguages, @@ -194,9 +190,10 @@ protected LanguageSelectorModel PrepareLanguageSelectorModel() foreach (var lang in model.AvailableLanguages) { - var helper = new LocalizedUrlHelper(HttpContext.Request, true); + //var helper = new LocalizedUrlHelper(HttpContext.Request, true); + var helper = CreateUrlHelperForLanguageSelector(lang, workingLanguage.Id); - if (_localizationSettings.SeoFriendlyUrlsForLanguagesEnabled) + if (_localizationSettings.SeoFriendlyUrlsForLanguagesEnabled) { if (lang.SeoCode == defaultSeoCode && (int)(_localizationSettings.DefaultLanguageRedirectBehaviour) > 0) { @@ -214,6 +211,40 @@ protected LanguageSelectorModel PrepareLanguageSelectorModel() return model; } + private LocalizedUrlHelper CreateUrlHelperForLanguageSelector(LanguageModel model, int currentLanguageId) + { + if (currentLanguageId != model.Id) + { + var routeValues = this.Request.RequestContext.RouteData.Values; + var controller = routeValues["controller"].ToString(); + + object val; + if (!routeValues.TryGetValue(controller + "id", out val)) + { + controller = routeValues["action"].ToString(); + routeValues.TryGetValue(controller + "id", out val); + } + + int entityId = 0; + if (val != null) + { + entityId = val.Convert(); + } + + if (entityId > 0) + { + var activeSlug = _urlRecordService.Value.GetActiveSlug(entityId, controller, model.Id); + if (activeSlug.HasValue()) + { + var helper = new LocalizedUrlHelper(Request.ApplicationPath, activeSlug, false); + return helper; + } + } + } + + return new LocalizedUrlHelper(HttpContext.Request, true); + } + [NonAction] protected CurrencySelectorModel PrepareCurrencySelectorModel() { From 1954599885522ed39aa70db0dd342e24236e8fc7 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 20 Apr 2016 10:48:58 +0200 Subject: [PATCH 044/285] PayPal Plus: Integrating pay upon invoice and manual bank transfer --- .../Controllers/PayPalPlusController.cs | 12 ++ .../SmartStore.PayPal/DependencyRegistrar.cs | 4 + .../Filters/PayPalPlusWidgetZoneFilter.cs | 48 +++++ .../Localization/resources.de-de.xml | 9 + .../Localization/resources.en-us.xml | 9 + .../Providers/PayPalPlusProvider.cs | 44 ++++- .../Services/IPayPalService.cs | 4 + .../SmartStore.PayPal/Services/PayPalEnums.cs | 17 ++ .../Services/PayPalService.cs | 172 +++++++++++++++++- .../SmartStore.PayPal.csproj | 2 + .../PayPalPlus/PaymentWallScripting.cshtml | 11 +- 11 files changed, 318 insertions(+), 14 deletions(-) create mode 100644 src/Plugins/SmartStore.PayPal/Filters/PayPalPlusWidgetZoneFilter.cs create mode 100644 src/Plugins/SmartStore.PayPal/Services/PayPalEnums.cs diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index 6ee9e56bc4..3459d82448 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -343,5 +343,17 @@ public ActionResult CheckoutCancel() // back to where he came from return RedirectToAction("PaymentMethod", "Checkout", new { area = "" }); } + + public ActionResult CheckoutCompleted() + { + var instruct = _httpContext.Session[PayPalPlusProvider.CheckoutCompletedKey] as string; + + if (instruct.HasValue()) + { + return Content(instruct); + } + + return new EmptyResult(); + } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/DependencyRegistrar.cs b/src/Plugins/SmartStore.PayPal/DependencyRegistrar.cs index 346a7ab86b..95e7e8f1c3 100644 --- a/src/Plugins/SmartStore.PayPal/DependencyRegistrar.cs +++ b/src/Plugins/SmartStore.PayPal/DependencyRegistrar.cs @@ -23,6 +23,10 @@ public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, b builder.RegisterType() .AsActionFilterFor(x => x.PaymentMethod()) .InstancePerRequest(); + + builder.RegisterType() + .AsActionFilterFor(x => x.Completed()) + .InstancePerRequest(); } } diff --git a/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusWidgetZoneFilter.cs b/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusWidgetZoneFilter.cs new file mode 100644 index 0000000000..9c6c603b03 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusWidgetZoneFilter.cs @@ -0,0 +1,48 @@ +using System; +using System.Web.Mvc; +using SmartStore.Web.Framework.UI; + +namespace SmartStore.PayPal.Filters +{ + public class PayPalPlusWidgetZoneFilter : IActionFilter, IResultFilter + { + private readonly Lazy _widgetProvider; + + public PayPalPlusWidgetZoneFilter(Lazy widgetProvider) + { + _widgetProvider = widgetProvider; + } + + public void OnActionExecuting(ActionExecutingContext filterContext) + { + } + + public void OnActionExecuted(ActionExecutedContext filterContext) + { + } + + public void OnResultExecuting(ResultExecutingContext filterContext) + { + if (filterContext.IsChildAction) + return; + + // should only run on a full view rendering result + var result = filterContext.Result as ViewResultBase; + if (result == null) + return; + + var controller = filterContext.RouteData.Values["controller"] as string; + var action = filterContext.RouteData.Values["action"] as string; + + if (action.IsCaseInsensitiveEqual("Completed") && controller.IsCaseInsensitiveEqual("Checkout")) + { + _widgetProvider.Value.RegisterAction("checkout_completed_top", "CheckoutCompleted", "PayPalPlus", new { area = Plugin.SystemName }); + _widgetProvider.Value.RegisterAction("mobile_checkout_completed_top", "CheckoutCompleted", "PayPalPlus", new { area = Plugin.SystemName }); + } + } + + public void OnResultExecuted(ResultExecutedContext filterContext) + { + } + } +} diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index 7625a75def..33212ddb48 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -46,6 +46,15 @@ Die Ausführung der Zahlung wurde durch PayPal abgelehnt. + + {0} hat die Forderung gegen Sie im Rahmen eines laufenden Factoringvertrages an die PayPal (Europe) S.àr.l. et Cie, S.C.A. abgetreten. Zahlungen mit schuldbefreiender Wirkung können nur an die PayPal (Europe) S.àr.l. et Cie, S.C.A. geleistet werden. + + + Bitte überweisen Sie den fälligen Betrag auf folgendes Konto. + + + Referenz;Bankleitzahl;Bank;BIC;IBAN;Kontoinhaber;Kontonummer;Betrag;Zahlung fällig am;Details + diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index 386d428484..ea419bd172 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -46,6 +46,15 @@ The execution of the payment has been declined by PayPal. + + {0} hat die Forderung gegen Sie im Rahmen eines laufenden Factoringvertrages an die PayPal (Europe) S.àr.l. et Cie, S.C.A. abgetreten. Zahlungen mit schuldbefreiender Wirkung können nur an die PayPal (Europe) S.àr.l. et Cie, S.C.A. geleistet werden. + + + Please transfer the due amount to the following bank account. + + + Reference;Bank routing number;Bank;BIC;IBAN;Account holder;Account number;Amount;Payment due date;Details + diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs index c2510fa340..67ef0f017a 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs @@ -10,7 +10,7 @@ namespace SmartStore.PayPal { [SystemName("Payments.PayPalPlus")] - [FriendlyName("PayPal Plus")] + [FriendlyName("PayPal PLUS")] [DisplayOrder(1)] public partial class PayPalPlusProvider : PayPalRestApiProviderBase { @@ -30,6 +30,11 @@ public static string SystemName get { return "Payments.PayPalPlus"; } } + public static string CheckoutCompletedKey + { + get { return SystemName + "CheckoutCompleted"; } + } + public override PaymentMethodType PaymentMethodType { get @@ -50,6 +55,8 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces NewPaymentStatus = PaymentStatus.Pending }; + _httpContext.Session.SafeRemove(CheckoutCompletedKey); + var settings = Services.Settings.LoadSetting(processPaymentRequest.StoreId); var session = _httpContext.GetPayPalSessionData(); @@ -63,7 +70,7 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces if (!state.IsCaseInsensitiveEqual("failed")) { - result.AuthorizationTransactionCode = apiResult.Id; // payment id + result.AuthorizationTransactionCode = apiResult.Id; // payment id, actually not required anymore var sale = apiResult.Json.transactions[0].related_resources[0].sale; @@ -74,6 +81,8 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces result.AuthorizationTransactionResult = state; result.AuthorizationTransactionId = (string)sale.id; + result.NewPaymentStatus = PaymentStatus.Authorized; + if (state.IsCaseInsensitiveEqual("completed") || state.IsCaseInsensitiveEqual("processed")) { result.CaptureTransactionResult = state; @@ -81,15 +90,23 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces result.NewPaymentStatus = PaymentStatus.Paid; } - else + else if (state.IsCaseInsensitiveEqual("pending")) { - result.NewPaymentStatus = PaymentStatus.Authorized; + var reasonCode = (string)sale.reason_code; + if (reasonCode.IsCaseInsensitiveEqual("ECHECK")) + { + result.NewPaymentStatus = PaymentStatus.Pending; + } } + + session.PaymentInstruction = _payPalService.ParsePaymentInstruction(apiResult.Json.payment_instruction) as PayPalPaymentInstruction; } } else { - result.Errors.Add(T("Plugins.SmartStore.PayPal.PaymentExecuteFailed")); + var failureReason = (string)apiResult.Json.failure_reason; + + result.Errors.Add(T("Plugins.SmartStore.PayPal.PaymentExecuteFailed").Text.Grow(failureReason, " ")); } } else @@ -99,5 +116,20 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces return result; } - } + + public override void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest) + { + if (postProcessPaymentRequest.Order.PaymentStatus == PaymentStatus.Paid) + return; + + var instruction = _payPalService.CreatePaymentInstruction(_httpContext.GetPayPalSessionData().PaymentInstruction); + + if (instruction.HasValue()) + { + _httpContext.Session[CheckoutCompletedKey] = instruction; + + OrderService.AddOrderNote(postProcessPaymentRequest.Order, instruction, true); + } + } + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs index 993c49a024..4d7ffedc67 100644 --- a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -12,6 +12,10 @@ public interface IPayPalService void LogError(Exception exception, string shortMessage = null, string fullMessage = null, bool notify = false, IList errors = null, bool isWarning = false); + PayPalPaymentInstruction ParsePaymentInstruction(dynamic json); + + string CreatePaymentInstruction(PayPalPaymentInstruction instruct); + PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data, IList errors = null); PayPalResponse EnsureAccessToken(PayPalSessionData session, PayPalApiSettingsBase settings); diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalEnums.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalEnums.cs new file mode 100644 index 0000000000..11e2210945 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalEnums.cs @@ -0,0 +1,17 @@ + +namespace SmartStore.PayPal.Services +{ + public enum PayPalPaymentInstructionItem + { + Reference = 0, + BankRoutingNumber, + Bank, + Bic, + Iban, + AccountHolder, + AccountNumber, + Amount, + PaymentDueDate, + Details + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index ecade23af1..d0f572d217 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Net; @@ -20,6 +21,7 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; +using SmartStore.Services.Directory; using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Orders; @@ -36,7 +38,9 @@ public class PayPalService : IPayPalService private readonly IPaymentService _paymentService; private readonly IPriceCalculationService _priceCalculationService; private readonly ITaxService _taxService; + private readonly Lazy _currencyService; private readonly Lazy _pictureService; + private readonly Lazy _companyInfoSettings; public PayPalService( ICommonServices services, @@ -45,7 +49,9 @@ public PayPalService( IPaymentService paymentService, IPriceCalculationService priceCalculationService, ITaxService taxService, - Lazy pictureService) + Lazy currencyService, + Lazy pictureService, + Lazy companyInfoSettings) { _services = services; _orderService = orderService; @@ -53,7 +59,9 @@ public PayPalService( _paymentService = paymentService; _priceCalculationService = priceCalculationService; _taxService = taxService; + _currencyService = currencyService; _pictureService = pictureService; + _companyInfoSettings = companyInfoSettings; T = NullLocalizer.Instance; Logger = NullLogger.Instance; @@ -182,6 +190,125 @@ public void LogError(Exception exception, string shortMessage = null, string ful } } + public PayPalPaymentInstruction ParsePaymentInstruction(dynamic json) + { + if (json == null) + return null; + + DateTime dt; + var result = new PayPalPaymentInstruction(); + + try + { + result.ReferenceNumber = (string)json.reference_number; + result.Type = (string)json.instruction_type; + result.Note = (string)json.note; + + if (DateTime.TryParse((string)json.payment_due_date, CultureInfo.InvariantCulture, DateTimeStyles.None, out dt)) + { + result.DueDate = dt; + } + + if (json.amount != null) + { + result.AmountCurrencyCode = (string)json.amount.currency; + result.Amount = decimal.Parse((string)json.amount.value, CultureInfo.InvariantCulture); + } + + var rbi = json.recipient_banking_instruction; + + if (rbi != null) + { + result.RecipientBanking = new PayPalPaymentInstruction.RecipientBankingInstruction(); + result.RecipientBanking.BankName = (string)rbi.bank_name; + result.RecipientBanking.AccountHolderName = (string)rbi.account_holder_name; + result.RecipientBanking.AccountNumber = (string)rbi.account_number; + result.RecipientBanking.RoutingNumber = (string)rbi.routing_number; + result.RecipientBanking.Iban = (string)rbi.international_bank_account_number; + result.RecipientBanking.Bic = (string)rbi.bank_identifier_code; + } + + if (json.links != null) + { + result.Link = (string)json.links[0].href; + } + } + catch { } + + return result; + } + + public string CreatePaymentInstruction(PayPalPaymentInstruction instruct) + { + if (instruct == null || instruct.RecipientBanking == null) + return null; + + if (!instruct.IsManualBankTransfer && !instruct.IsPayUponInvoice) + return null; + + var sb = new StringBuilder("
"); + var paragraphTemplate = "
{0}
"; + var rowTemplate = "{0}: {1}
"; + var instructStrings = T("Plugins.SmartStore.PayPal.PaymentInstructionStrings").Text.SplitSafe(";"); + + if (instruct.IsManualBankTransfer) + { + sb.AppendFormat(paragraphTemplate, T("Plugins.SmartStore.PayPal.ManualBankTransferNote")); + + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Reference), instruct.ReferenceNumber); + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.AccountNumber), instruct.RecipientBanking.AccountNumber); + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.AccountHolder), instruct.RecipientBanking.AccountHolderName); + + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Bank), instruct.RecipientBanking.BankName); + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Iban), instruct.RecipientBanking.Iban); + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Bic), instruct.RecipientBanking.Bic); + } + else if (instruct.IsPayUponInvoice) + { + string amount = null; + var culture = new CultureInfo(_services.WorkContext.WorkingLanguage.LanguageCulture ?? "de-DE"); + + try + { + var currency = _currencyService.Value.GetCurrencyByCode(instruct.AmountCurrencyCode); + var format = (currency != null && currency.CustomFormatting.HasValue() ? currency.CustomFormatting : "C"); + + amount = instruct.Amount.ToString(format, culture); + } + catch { } + + if (amount.IsEmpty()) + { + amount = string.Concat(instruct.Amount.ToString("N"), " ", instruct.AmountCurrencyCode); + } + + var intro = T("Plugins.SmartStore.PayPal.PayUponInvoiceLegalNote", _companyInfoSettings.Value.CompanyName.NaIfEmpty()); + + if (instruct.Link.HasValue()) + { + intro = "{0} {2}.".FormatInvariant(intro, instruct.Link, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Details)); + } + + sb.AppendFormat(paragraphTemplate, intro); + + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Bank), instruct.RecipientBanking.BankName); + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.AccountHolder), instruct.RecipientBanking.AccountHolderName); + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Iban), instruct.RecipientBanking.Iban); + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Bic), instruct.RecipientBanking.Bic); + sb.Append("
"); + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Amount), amount); + if (instruct.DueDate.HasValue) + { + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.PaymentDueDate), instruct.DueDate.Value.ToString("d", culture)); + } + sb.AppendFormat(rowTemplate, instructStrings.SafeGet((int)PayPalPaymentInstructionItem.Reference), instruct.ReferenceNumber); + } + + sb.Append("
"); + + return sb.ToString(); + } + public PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data, IList errors = null) { var isJson = (data.HasValue() && data.StartsWith("{")); @@ -451,6 +578,7 @@ public PayPalResponse CreatePayment( var data = new Dictionary(); var redirectUrls = new Dictionary(); var payer = new Dictionary(); + var payerInfo = new Dictionary(); var transaction = new Dictionary(); var amount = new Dictionary(); var amountDetails = new Dictionary(); @@ -481,6 +609,14 @@ public PayPalResponse CreatePayment( // payer payer.Add("payment_method", "paypal"); + + if (customer.BillingAddress != null) + { + payerInfo.Add("billing_address", CreateAddress(customer.BillingAddress)); + + payer.Add("payer_info", payerInfo); + } + data.Add("payer", payer); // line items @@ -603,5 +739,39 @@ public PayPalSessionData() public string PayerId { get; set; } public string ApprovalUrl { get; set; } public Guid OrderGuid { get; private set; } + public PayPalPaymentInstruction PaymentInstruction { get; set; } + } + + public class PayPalPaymentInstruction + { + public string ReferenceNumber { get; set; } + public string Type { get; set; } + public decimal Amount { get; set; } + public string AmountCurrencyCode { get; set; } + public DateTime? DueDate { get; set; } + public string Note { get; set; } + public string Link { get; set; } + + public RecipientBankingInstruction RecipientBanking { get; set; } + + public bool IsManualBankTransfer + { + get { return Type.IsCaseInsensitiveEqual("MANUAL_BANK_TRANSFER"); } + } + + public bool IsPayUponInvoice + { + get { return Type.IsCaseInsensitiveEqual("PAY_UPON_INVOICE"); } + } + + public class RecipientBankingInstruction + { + public string BankName { get; set; } + public string AccountHolderName { get; set; } + public string AccountNumber { get; set; } + public string RoutingNumber { get; set; } + public string Iban { get; set; } + public string Bic { get; set; } + } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj index e552740df4..a987ea30b6 100644 --- a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj +++ b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj @@ -128,6 +128,7 @@ + @@ -140,6 +141,7 @@ + diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml index 8beadec2a2..60a6047ef9 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml @@ -14,18 +14,15 @@ useraction: "continue", surcharging: @Model.HasPaymentFee.ToString().ToLower(), showLoadingIndicator: true, - //showPuiOnSandbox: true, enableContinue: "PayPalPlusNextCheckoutStep", disableContinue: "PayPalPlusNextCheckoutStep", - //enableContinue: function () { - // $('#PayPalPlusNextCheckoutStep').prop('disabled', false); - //}, - //disableContinue: function () { - // $('#PayPalPlusNextCheckoutStep').prop('disabled', true); - //}, onLoad: function () { $('#PayPalPlusNextCheckoutStep').fadeIn(); }, + @if (Model.UseSandbox) + { + showPuiOnSandbox: true, + } @if (Model.ThirdPartyPaymentMethods.Any()) { thirdPartyPaymentMethods: [ From ca6ea80a5579da60b2678ffcd3ac7b82dd424f96 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 20 Apr 2016 12:52:56 +0200 Subject: [PATCH 045/285] PayPal Plus: Refunding --- .../Filters/PayPalPlusWidgetZoneFilter.cs | 16 ++++++++-- .../Providers/PayPalPlusProvider.cs | 11 +++---- .../Providers/PayPalRestApiProviderBase.cs | 28 ++++++++++++++++-- .../Services/IPayPalService.cs | 3 ++ .../Services/PayPalService.cs | 29 +++++++++++++++++++ 5 files changed, 74 insertions(+), 13 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusWidgetZoneFilter.cs b/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusWidgetZoneFilter.cs index 9c6c603b03..cbe12c7aaf 100644 --- a/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusWidgetZoneFilter.cs +++ b/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusWidgetZoneFilter.cs @@ -1,4 +1,5 @@ using System; +using System.Web; using System.Web.Mvc; using SmartStore.Web.Framework.UI; @@ -6,10 +7,14 @@ namespace SmartStore.PayPal.Filters { public class PayPalPlusWidgetZoneFilter : IActionFilter, IResultFilter { + private readonly Lazy _httpContext; private readonly Lazy _widgetProvider; - public PayPalPlusWidgetZoneFilter(Lazy widgetProvider) + public PayPalPlusWidgetZoneFilter( + Lazy httpContext, + Lazy widgetProvider) { + _httpContext = httpContext; _widgetProvider = widgetProvider; } @@ -36,8 +41,13 @@ public void OnResultExecuting(ResultExecutingContext filterContext) if (action.IsCaseInsensitiveEqual("Completed") && controller.IsCaseInsensitiveEqual("Checkout")) { - _widgetProvider.Value.RegisterAction("checkout_completed_top", "CheckoutCompleted", "PayPalPlus", new { area = Plugin.SystemName }); - _widgetProvider.Value.RegisterAction("mobile_checkout_completed_top", "CheckoutCompleted", "PayPalPlus", new { area = Plugin.SystemName }); + var instruct = _httpContext.Value.Session[PayPalPlusProvider.CheckoutCompletedKey] as string; + + if (instruct.HasValue()) + { + _widgetProvider.Value.RegisterAction("checkout_completed_top", "CheckoutCompleted", "PayPalPlus", new { area = Plugin.SystemName }); + _widgetProvider.Value.RegisterAction("mobile_checkout_completed_top", "CheckoutCompleted", "PayPalPlus", new { area = Plugin.SystemName }); + } } } diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs index 67ef0f017a..835e58d364 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs @@ -15,14 +15,11 @@ namespace SmartStore.PayPal public partial class PayPalPlusProvider : PayPalRestApiProviderBase { private readonly HttpContextBase _httpContext; - private readonly IPayPalService _payPalService; public PayPalPlusProvider( - HttpContextBase httpContext, - IPayPalService payPalService) + HttpContextBase httpContext) { _httpContext = httpContext; - _payPalService = payPalService; } public static string SystemName @@ -62,7 +59,7 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces processPaymentRequest.OrderGuid = session.OrderGuid; - var apiResult = _payPalService.ExecutePayment(settings, session); + var apiResult = PayPalService.ExecutePayment(settings, session); if (apiResult.Success && apiResult.Json != null) { @@ -99,7 +96,7 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces } } - session.PaymentInstruction = _payPalService.ParsePaymentInstruction(apiResult.Json.payment_instruction) as PayPalPaymentInstruction; + session.PaymentInstruction = PayPalService.ParsePaymentInstruction(apiResult.Json.payment_instruction) as PayPalPaymentInstruction; } } else @@ -122,7 +119,7 @@ public override void PostProcessPayment(PostProcessPaymentRequest postProcessPay if (postProcessPaymentRequest.Order.PaymentStatus == PaymentStatus.Paid) return; - var instruction = _payPalService.CreatePaymentInstruction(_httpContext.GetPayPalSessionData().PaymentInstruction); + var instruction = PayPalService.CreatePaymentInstruction(_httpContext.GetPayPalSessionData().PaymentInstruction); if (instruction.HasValue()) { diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs index c3424d7193..9b0bf499d7 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs @@ -3,8 +3,10 @@ using System.Web.Routing; using SmartStore.Core.Configuration; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Payments; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; +using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Services; using SmartStore.Services.Orders; @@ -23,6 +25,7 @@ protected PayPalRestApiProviderBase() public ICommonServices Services { get; set; } public IOrderService OrderService { get; set; } public IOrderTotalCalculationService OrderTotalCalculationService { get; set; } + public IPayPalService PayPalService { get; set; } protected string GetControllerName() { @@ -89,10 +92,29 @@ public override RefundPaymentResult Refund(RefundPaymentRequest request) }; var settings = Services.Settings.LoadSetting(request.Order.StoreId); + var session = new PayPalSessionData(); - var transactionId = request.Order.CaptureTransactionId; - - // TODO + var apiResult = PayPalService.EnsureAccessToken(session, settings); + if (result.Success) + { + apiResult = PayPalService.Refund(settings, session, request); + + if (apiResult.Success && apiResult.Json != null) + { + if (request.IsPartialRefund) + result.NewPaymentStatus = PaymentStatus.PartiallyRefunded; + else + result.NewPaymentStatus = PaymentStatus.Refunded; + } + else + { + result.Errors.Add(apiResult.ErrorMessage); + } + } + else + { + result.Errors.Add(apiResult.ErrorMessage); + } return result; } diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs index 4d7ffedc67..c6e42c82ac 100644 --- a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -3,6 +3,7 @@ using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Stores; using SmartStore.PayPal.Settings; +using SmartStore.Services.Payments; namespace SmartStore.PayPal.Services { @@ -33,5 +34,7 @@ PayPalResponse CreatePayment( string cancelUrl); PayPalResponse ExecutePayment(PayPalApiSettingsBase settings, PayPalSessionData session); + + PayPalResponse Refund(PayPalApiSettingsBase settings, PayPalSessionData session, RefundPaymentRequest request); } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index d0f572d217..30c235517c 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -715,6 +715,35 @@ public PayPalResponse ExecutePayment(PayPalApiSettingsBase settings, PayPalSessi return result; } + + public PayPalResponse Refund(PayPalApiSettingsBase settings, PayPalSessionData session, RefundPaymentRequest request) + { + var data = new Dictionary(); + var saleId = request.Order.CaptureTransactionId; + + if (request.IsPartialRefund) + { + var store = _services.StoreService.GetStoreById(request.Order.StoreId); + + var amount = new Dictionary(); + amount.Add("total", Math.Round(request.AmountToRefund, 2)); + amount.Add("currency", store.PrimaryStoreCurrency.CurrencyCode); + + data.Add("amount", amount); + } + + var result = CallApi("POST", "/v1/payments/sale/{0}/refund".FormatInvariant(saleId), session.AccessToken, settings, + data.Any() ? JsonConvert.SerializeObject(data) : null); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + + //Logger.InsertLog(LogLevel.Information, "PayPal Refund", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + result.Json.ToString()); + } + + return result; + } } From a8be8ceb62ff1d2dfd7a3b4364055d6a62e8fbeb Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 20 Apr 2016 13:36:30 +0200 Subject: [PATCH 046/285] PayPal Plus: Rest API has different endpoints for different intents --- .../Providers/PayPalPlusProvider.cs | 100 ------------------ .../Providers/PayPalRestApiProviderBase.cs | 95 ++++++++++++++++- .../Services/PayPalService.cs | 13 ++- 3 files changed, 103 insertions(+), 105 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs index 835e58d364..45e251d2d0 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalPlusProvider.cs @@ -1,9 +1,6 @@ using System; -using System.Web; -using SmartStore.Core.Domain.Payments; using SmartStore.Core.Plugins; using SmartStore.PayPal.Controllers; -using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Services.Payments; @@ -14,24 +11,11 @@ namespace SmartStore.PayPal [DisplayOrder(1)] public partial class PayPalPlusProvider : PayPalRestApiProviderBase { - private readonly HttpContextBase _httpContext; - - public PayPalPlusProvider( - HttpContextBase httpContext) - { - _httpContext = httpContext; - } - public static string SystemName { get { return "Payments.PayPalPlus"; } } - public static string CheckoutCompletedKey - { - get { return SystemName + "CheckoutCompleted"; } - } - public override PaymentMethodType PaymentMethodType { get @@ -44,89 +28,5 @@ public override Type GetControllerType() { return typeof(PayPalPlusController); } - - public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest processPaymentRequest) - { - var result = new ProcessPaymentResult - { - NewPaymentStatus = PaymentStatus.Pending - }; - - _httpContext.Session.SafeRemove(CheckoutCompletedKey); - - var settings = Services.Settings.LoadSetting(processPaymentRequest.StoreId); - var session = _httpContext.GetPayPalSessionData(); - - processPaymentRequest.OrderGuid = session.OrderGuid; - - var apiResult = PayPalService.ExecutePayment(settings, session); - - if (apiResult.Success && apiResult.Json != null) - { - var state = (string)apiResult.Json.state; - - if (!state.IsCaseInsensitiveEqual("failed")) - { - result.AuthorizationTransactionCode = apiResult.Id; // payment id, actually not required anymore - - var sale = apiResult.Json.transactions[0].related_resources[0].sale; - - if (sale != null) - { - state = (string)sale.state; - - result.AuthorizationTransactionResult = state; - result.AuthorizationTransactionId = (string)sale.id; - - result.NewPaymentStatus = PaymentStatus.Authorized; - - if (state.IsCaseInsensitiveEqual("completed") || state.IsCaseInsensitiveEqual("processed")) - { - result.CaptureTransactionResult = state; - result.CaptureTransactionId = (string)sale.id; - - result.NewPaymentStatus = PaymentStatus.Paid; - } - else if (state.IsCaseInsensitiveEqual("pending")) - { - var reasonCode = (string)sale.reason_code; - if (reasonCode.IsCaseInsensitiveEqual("ECHECK")) - { - result.NewPaymentStatus = PaymentStatus.Pending; - } - } - - session.PaymentInstruction = PayPalService.ParsePaymentInstruction(apiResult.Json.payment_instruction) as PayPalPaymentInstruction; - } - } - else - { - var failureReason = (string)apiResult.Json.failure_reason; - - result.Errors.Add(T("Plugins.SmartStore.PayPal.PaymentExecuteFailed").Text.Grow(failureReason, " ")); - } - } - else - { - result.Errors.Add(apiResult.ErrorMessage); - } - - return result; - } - - public override void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest) - { - if (postProcessPaymentRequest.Order.PaymentStatus == PaymentStatus.Paid) - return; - - var instruction = PayPalService.CreatePaymentInstruction(_httpContext.GetPayPalSessionData().PaymentInstruction); - - if (instruction.HasValue()) - { - _httpContext.Session[CheckoutCompletedKey] = instruction; - - OrderService.AddOrderNote(postProcessPaymentRequest.Order, instruction, true); - } - } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs index 9b0bf499d7..c557c3dabf 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Web; using System.Web.Routing; using SmartStore.Core.Configuration; using SmartStore.Core.Domain.Orders; @@ -22,6 +23,7 @@ protected PayPalRestApiProviderBase() } public ILogger Logger { get; set; } + public HttpContextBase HttpContext { get; set; } public ICommonServices Services { get; set; } public IOrderService OrderService { get; set; } public IOrderTotalCalculationService OrderTotalCalculationService { get; set; } @@ -32,6 +34,11 @@ protected string GetControllerName() return GetControllerType().Name.EmptyNull().Replace("Controller", ""); } + public static string CheckoutCompletedKey + { + get { return "PayPalCheckoutCompleted"; } + } + public override bool SupportCapture { get { return true; } @@ -67,7 +74,93 @@ public override decimal GetAdditionalHandlingFee(IList(processPaymentRequest.StoreId); + var session = HttpContext.GetPayPalSessionData(); + + processPaymentRequest.OrderGuid = session.OrderGuid; + + var apiResult = PayPalService.ExecutePayment(settings, session); + + if (apiResult.Success && apiResult.Json != null) + { + var state = (string)apiResult.Json.state; + + if (!state.IsCaseInsensitiveEqual("failed")) + { + // intent: "sale" for immediate payment, "authorize" for pre-authorized payments and "order" for an order. + // info required cause API has different endpoints for different intents. + result.AuthorizationTransactionCode = (string)apiResult.Json.intent; + + var sale = apiResult.Json.transactions[0].related_resources[0].sale; + + if (sale != null) + { + state = (string)sale.state; + + result.AuthorizationTransactionResult = state; + result.AuthorizationTransactionId = (string)sale.id; + + result.NewPaymentStatus = PaymentStatus.Authorized; + + if (state.IsCaseInsensitiveEqual("completed") || state.IsCaseInsensitiveEqual("processed")) + { + result.CaptureTransactionResult = state; + result.CaptureTransactionId = (string)sale.id; + + result.NewPaymentStatus = PaymentStatus.Paid; + } + else if (state.IsCaseInsensitiveEqual("pending")) + { + var reasonCode = (string)sale.reason_code; + if (reasonCode.IsCaseInsensitiveEqual("ECHECK")) + { + result.NewPaymentStatus = PaymentStatus.Pending; + } + } + + session.PaymentInstruction = PayPalService.ParsePaymentInstruction(apiResult.Json.payment_instruction) as PayPalPaymentInstruction; + } + } + else + { + var failureReason = (string)apiResult.Json.failure_reason; + + result.Errors.Add(T("Plugins.SmartStore.PayPal.PaymentExecuteFailed").Text.Grow(failureReason, " ")); + } + } + else + { + result.Errors.Add(apiResult.ErrorMessage); + } + + return result; + } + + public override void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest) + { + if (postProcessPaymentRequest.Order.PaymentStatus == PaymentStatus.Paid) + return; + + var instruction = PayPalService.CreatePaymentInstruction(HttpContext.GetPayPalSessionData().PaymentInstruction); + + if (instruction.HasValue()) + { + HttpContext.Session[CheckoutCompletedKey] = instruction; + + OrderService.AddOrderNote(postProcessPaymentRequest.Order, instruction, true); + } + } + + public override CapturePaymentResult Capture(CapturePaymentRequest capturePaymentRequest) { var result = new CapturePaymentResult { diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 30c235517c..096b0a36e8 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -719,9 +719,15 @@ public PayPalResponse ExecutePayment(PayPalApiSettingsBase settings, PayPalSessi public PayPalResponse Refund(PayPalApiSettingsBase settings, PayPalSessionData session, RefundPaymentRequest request) { var data = new Dictionary(); - var saleId = request.Order.CaptureTransactionId; + var isSale = request.Order.AuthorizationTransactionCode.IsCaseInsensitiveEqual("sale"); - if (request.IsPartialRefund) + var path = "/v1/payments/{0}/{1}/refund".FormatInvariant(isSale ? "sale" : "capture", request.Order.CaptureTransactionId); + + if (isSale && !request.IsPartialRefund) + { + // no body required + } + else { var store = _services.StoreService.GetStoreById(request.Order.StoreId); @@ -732,8 +738,7 @@ public PayPalResponse Refund(PayPalApiSettingsBase settings, PayPalSessionData s data.Add("amount", amount); } - var result = CallApi("POST", "/v1/payments/sale/{0}/refund".FormatInvariant(saleId), session.AccessToken, settings, - data.Any() ? JsonConvert.SerializeObject(data) : null); + var result = CallApi("POST", path, session.AccessToken, settings, data.Any() ? JsonConvert.SerializeObject(data) : null); if (result.Success && result.Json != null) { From 6c3d3cb784d495d3f3b6887bcd0a12d0a627f6b2 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 20 Apr 2016 16:47:36 +0200 Subject: [PATCH 047/285] PayPal Plus: Void and capturing --- .../Providers/PayPalRestApiProviderBase.cs | 107 +++++++++++------- .../Services/IPayPalService.cs | 7 ++ .../Services/PayPalService.cs | 90 +++++++++++++++ 3 files changed, 164 insertions(+), 40 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs index c557c3dabf..d46f821d84 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs @@ -93,6 +93,8 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces if (apiResult.Success && apiResult.Json != null) { var state = (string)apiResult.Json.state; + string reasonCode = null; + dynamic relatedObject = null; if (!state.IsCaseInsensitiveEqual("failed")) { @@ -100,34 +102,32 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces // info required cause API has different endpoints for different intents. result.AuthorizationTransactionCode = (string)apiResult.Json.intent; - var sale = apiResult.Json.transactions[0].related_resources[0].sale; + if (result.AuthorizationTransactionCode.IsCaseInsensitiveEqual("sale")) + { + relatedObject = apiResult.Json.transactions[0].related_resources[0].sale; - if (sale != null) + session.PaymentInstruction = PayPalService.ParsePaymentInstruction(apiResult.Json.payment_instruction) as PayPalPaymentInstruction; + } + else { - state = (string)sale.state; + relatedObject = apiResult.Json.transactions[0].related_resources[0].authorization; + } - result.AuthorizationTransactionResult = state; - result.AuthorizationTransactionId = (string)sale.id; + if (relatedObject != null) + { + state = (string)relatedObject.state; + reasonCode = (string)relatedObject.reason_code; - result.NewPaymentStatus = PaymentStatus.Authorized; + result.AuthorizationTransactionResult = state; + result.AuthorizationTransactionId = (string)relatedObject.id; - if (state.IsCaseInsensitiveEqual("completed") || state.IsCaseInsensitiveEqual("processed")) - { - result.CaptureTransactionResult = state; - result.CaptureTransactionId = (string)sale.id; + result.NewPaymentStatus = PayPalService.GetPaymentStatus(state, reasonCode, PaymentStatus.Authorized); - result.NewPaymentStatus = PaymentStatus.Paid; - } - else if (state.IsCaseInsensitiveEqual("pending")) + if (result.NewPaymentStatus == PaymentStatus.Paid) { - var reasonCode = (string)sale.reason_code; - if (reasonCode.IsCaseInsensitiveEqual("ECHECK")) - { - result.NewPaymentStatus = PaymentStatus.Pending; - } + result.CaptureTransactionResult = result.AuthorizationTransactionResult; + result.CaptureTransactionId = result.AuthorizationTransactionId; } - - session.PaymentInstruction = PayPalService.ParsePaymentInstruction(apiResult.Json.payment_instruction) as PayPalPaymentInstruction; } } else @@ -168,33 +168,48 @@ public override CapturePaymentResult Capture(CapturePaymentRequest capturePaymen }; var settings = Services.Settings.LoadSetting(capturePaymentRequest.Order.StoreId); - var currencyCode = Services.WorkContext.WorkingCurrency.CurrencyCode ?? "EUR"; + var session = new PayPalSessionData(); + + var apiResult = PayPalService.EnsureAccessToken(session, settings); + if (result.Success) + { + apiResult = PayPalService.Capture(settings, session, capturePaymentRequest); - var authorizationId = capturePaymentRequest.Order.AuthorizationTransactionId; - - // TODO + if (apiResult.Success) + { + result.NewPaymentStatus = PaymentStatus.Paid; + } + else + { + result.Errors.Add(apiResult.ErrorMessage); + } + } + else + { + result.Errors.Add(apiResult.ErrorMessage); + } - return result; + return result; } - public override RefundPaymentResult Refund(RefundPaymentRequest request) + public override RefundPaymentResult Refund(RefundPaymentRequest refundPaymentRequest) { var result = new RefundPaymentResult { - NewPaymentStatus = request.Order.PaymentStatus + NewPaymentStatus = refundPaymentRequest.Order.PaymentStatus }; - var settings = Services.Settings.LoadSetting(request.Order.StoreId); + var settings = Services.Settings.LoadSetting(refundPaymentRequest.Order.StoreId); var session = new PayPalSessionData(); var apiResult = PayPalService.EnsureAccessToken(session, settings); if (result.Success) { - apiResult = PayPalService.Refund(settings, session, request); + apiResult = PayPalService.Refund(settings, session, refundPaymentRequest); - if (apiResult.Success && apiResult.Json != null) + if (apiResult.Success) { - if (request.IsPartialRefund) + if (refundPaymentRequest.IsPartialRefund) result.NewPaymentStatus = PaymentStatus.PartiallyRefunded; else result.NewPaymentStatus = PaymentStatus.Refunded; @@ -212,21 +227,33 @@ public override RefundPaymentResult Refund(RefundPaymentRequest request) return result; } - public override VoidPaymentResult Void(VoidPaymentRequest request) + public override VoidPaymentResult Void(VoidPaymentRequest voidPaymentRequest) { var result = new VoidPaymentResult { - NewPaymentStatus = request.Order.PaymentStatus + NewPaymentStatus = voidPaymentRequest.Order.PaymentStatus }; - var settings = Services.Settings.LoadSetting(request.Order.StoreId); - - var transactionId = request.Order.AuthorizationTransactionId; - - if (transactionId.IsEmpty()) - transactionId = request.Order.CaptureTransactionId; + var settings = Services.Settings.LoadSetting(voidPaymentRequest.Order.StoreId); + var session = new PayPalSessionData(); - // TODO + var apiResult = PayPalService.EnsureAccessToken(session, settings); + if (result.Success) + { + apiResult = PayPalService.Void(settings, session, voidPaymentRequest); + if (apiResult.Success) + { + result.NewPaymentStatus = PaymentStatus.Voided; + } + else + { + result.Errors.Add(apiResult.ErrorMessage); + } + } + else + { + result.Errors.Add(apiResult.ErrorMessage); + } return result; } diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs index c6e42c82ac..c625f4642c 100644 --- a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Payments; using SmartStore.Core.Domain.Stores; using SmartStore.PayPal.Settings; using SmartStore.Services.Payments; @@ -17,6 +18,8 @@ public interface IPayPalService string CreatePaymentInstruction(PayPalPaymentInstruction instruct); + PaymentStatus GetPaymentStatus(string state, string reasonCode, PaymentStatus defaultStatus); + PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data, IList errors = null); PayPalResponse EnsureAccessToken(PayPalSessionData session, PayPalApiSettingsBase settings); @@ -36,5 +39,9 @@ PayPalResponse CreatePayment( PayPalResponse ExecutePayment(PayPalApiSettingsBase settings, PayPalSessionData session); PayPalResponse Refund(PayPalApiSettingsBase settings, PayPalSessionData session, RefundPaymentRequest request); + + PayPalResponse Capture(PayPalApiSettingsBase settings, PayPalSessionData session, CapturePaymentRequest request); + + PayPalResponse Void(PayPalApiSettingsBase settings, PayPalSessionData session, VoidPaymentRequest request); } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 096b0a36e8..5da6195fdc 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -12,6 +12,7 @@ using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Logging; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Payments; using SmartStore.Core.Domain.Stores; using SmartStore.Core.Domain.Tax; using SmartStore.Core.Localization; @@ -309,6 +310,56 @@ public string CreatePaymentInstruction(PayPalPaymentInstruction instruct) return sb.ToString(); } + public PaymentStatus GetPaymentStatus(string state, string reasonCode, PaymentStatus defaultStatus) + { + var result = defaultStatus; + + if (state == null) + state = string.Empty; + + if (reasonCode == null) + reasonCode = string.Empty; + + switch (state.ToLowerInvariant()) + { + case "authorized": + result = PaymentStatus.Authorized; + break; + case "pending": + switch (reasonCode.ToLowerInvariant()) + { + case "authorization": + result = PaymentStatus.Authorized; + break; + default: + result = PaymentStatus.Pending; + break; + } + break; + case "completed": + case "captured": + case "partially_captured": + case "canceled_reversal": + result = PaymentStatus.Paid; + break; + case "denied": + case "expired": + case "failed": + case "voided": + result = PaymentStatus.Voided; + break; + case "reversed": + case "refunded": + result = PaymentStatus.Refunded; + break; + case "partially_refunded": + result = PaymentStatus.PartiallyRefunded; + break; + } + + return result; + } + public PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data, IList errors = null) { var isJson = (data.HasValue() && data.StartsWith("{")); @@ -749,6 +800,45 @@ public PayPalResponse Refund(PayPalApiSettingsBase settings, PayPalSessionData s return result; } + + public PayPalResponse Capture(PayPalApiSettingsBase settings, PayPalSessionData session, CapturePaymentRequest request) + { + var data = new Dictionary(); + //var isAuthorize = request.Order.AuthorizationTransactionCode.IsCaseInsensitiveEqual("authorize"); + + var path = "/v1/payments/authorization/{0}/capture".FormatInvariant(request.Order.AuthorizationTransactionId); + + var store = _services.StoreService.GetStoreById(request.Order.StoreId); + + var amount = new Dictionary(); + amount.Add("total", Math.Round(request.Order.OrderTotal, 2)); + amount.Add("currency", store.PrimaryStoreCurrency.CurrencyCode); + + data.Add("amount", amount); + + var result = CallApi("POST", path, session.AccessToken, settings, JsonConvert.SerializeObject(data)); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + } + + return result; + } + + public PayPalResponse Void(PayPalApiSettingsBase settings, PayPalSessionData session, VoidPaymentRequest request) + { + var path = "/v1/payments/authorization/{0}/void".FormatInvariant(request.Order.AuthorizationTransactionId); + + var result = CallApi("POST", path, session.AccessToken, settings, null); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + } + + return result; + } } From aaba7de99584aabf1b9ec4611fbd91023019f481 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 21 Apr 2016 17:44:23 +0200 Subject: [PATCH 048/285] PayPal Plus: Getting started with webhooks (IPN not supported by rest API) --- .../Controllers/PayPalPlusController.cs | 73 ++---- .../PayPalRestApiControllerBase.cs | 166 ++++++++++++ .../Localization/resources.de-de.xml | 38 ++- .../Localization/resources.en-us.xml | 38 ++- .../Models/ApiConfigurationModels.cs | 12 + src/Plugins/SmartStore.PayPal/Plugin.cs | 29 ++- .../Providers/PayPalRestApiProviderBase.cs | 41 +-- .../Services/IPayPalService.cs | 16 +- .../SmartStore.PayPal/Services/PayPalEnums.cs | 6 + .../Services/PayPalService.cs | 236 +++++++++++++----- .../Settings/PayPalSettings.cs | 16 ++ .../SmartStore.PayPal.csproj | 1 + .../Views/PayPalPlus/Configure.cshtml | 77 ++++-- 13 files changed, 568 insertions(+), 181 deletions(-) create mode 100644 src/Plugins/SmartStore.PayPal/Controllers/PayPalRestApiControllerBase.cs diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index 3459d82448..6d5b086ce9 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -17,6 +17,7 @@ using SmartStore.Services.Localization; using SmartStore.Services.Orders; using SmartStore.Services.Payments; +using SmartStore.Web.Framework; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Plugins; using SmartStore.Web.Framework.Security; @@ -24,11 +25,10 @@ namespace SmartStore.PayPal.Controllers { - public class PayPalPlusController : PaymentControllerBase + public class PayPalPlusController : PayPalRestApiControllerBase { private readonly HttpContextBase _httpContext; private readonly PluginMediator _pluginMediator; - private readonly IPayPalService _payPalService; private readonly IGenericAttributeService _genericAttributeService; private readonly IPaymentService _paymentService; @@ -39,11 +39,12 @@ public PayPalPlusController( IOrderProcessingService orderProcessingService, PluginMediator pluginMediator, IPayPalService payPalService, - IGenericAttributeService genericAttributeService) + IGenericAttributeService genericAttributeService) : base( + PayPalPlusProvider.SystemName, + payPalService) { _httpContext = httpContext; _pluginMediator = pluginMediator; - _payPalService = payPalService; _genericAttributeService = genericAttributeService; _paymentService = paymentService; } @@ -141,10 +142,12 @@ public ActionResult Configure() ConfigGroups = T("Plugins.SmartStore.PayPal.ConfigGroups").Text.SplitSafe(";") }; - model.AvailableSecurityProtocols = PayPalService.GetSecurityProtocols() + model.AvailableSecurityProtocols = PayPal.Services.PayPalService.GetSecurityProtocols() .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) .ToList(); + model.AvailableWebhookValidations = PayPalWebhookValidation.Simple.ToSelectList(false); + // it's better to also offer inactive methods here but filter them out in frontend var methods = _paymentService.LoadAllPaymentMethods(storeScope); @@ -196,38 +199,6 @@ public ActionResult Configure(PayPalPlusConfigurationModel model, FormCollection return Configure(); } - [AdminAuthorize] - public ActionResult UpsertExperienceProfile(string profileId) - { - var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); - var settings = Services.Settings.LoadSetting(storeScope); - - var store = Services.StoreService.GetStoreById(storeScope == 0 ? Services.StoreContext.CurrentStore.Id : storeScope); - var session = new PayPalSessionData(); - - var result = _payPalService.EnsureAccessToken(session, settings); - if (result.Success) - { - result = _payPalService.UpsertCheckoutExperience(settings, session, store, profileId); - if (result.Success && result.Id.HasValue()) - { - settings.ExperienceProfileId = result.Id; - - Services.Settings.SaveSetting(settings, storeScope); - } - else - { - NotifyError(result.ErrorMessage); - } - } - else - { - NotifyError(result.ErrorMessage); - } - - return RedirectToAction("ConfigureProvider", "Plugin", new { area = "admin", systemName = PayPalPlusProvider.SystemName }); - } - public ActionResult PaymentInfo() { return new EmptyResult(); @@ -268,14 +239,14 @@ public ActionResult PaymentWall() if (session.PaymentId.IsEmpty() || session.ApprovalUrl.IsEmpty()) { - var result = _payPalService.EnsureAccessToken(session, settings); + var result = PayPalService.EnsureAccessToken(session, settings); if (result.Success) { var protocol = (store.SslEnabled ? "https" : "http"); var returnUrl = Url.Action("CheckoutReturn", "PayPalPlus", new { area = Plugin.SystemName }, protocol); var cancelUrl = Url.Action("CheckoutCancel", "PayPalPlus", new { area = Plugin.SystemName }, protocol); - result = _payPalService.CreatePayment(settings, session, cart, PayPalPlusProvider.SystemName, returnUrl, cancelUrl); + result = PayPalService.CreatePayment(settings, session, cart, PayPalPlusProvider.SystemName, returnUrl, cancelUrl); if (result.Success && result.Json != null) { foreach (var link in result.Json.links) @@ -304,6 +275,18 @@ public ActionResult PaymentWall() return View(model); } + public ActionResult CheckoutCompleted() + { + var instruct = _httpContext.Session[PayPalPlusProvider.CheckoutCompletedKey] as string; + + if (instruct.HasValue()) + { + return Content(instruct); + } + + return new EmptyResult(); + } + [ValidateInput(false)] public ActionResult CheckoutReturn(string systemName, string paymentId, string PayerID) { @@ -343,17 +326,5 @@ public ActionResult CheckoutCancel() // back to where he came from return RedirectToAction("PaymentMethod", "Checkout", new { area = "" }); } - - public ActionResult CheckoutCompleted() - { - var instruct = _httpContext.Session[PayPalPlusProvider.CheckoutCompletedKey] as string; - - if (instruct.HasValue()) - { - return Content(instruct); - } - - return new EmptyResult(); - } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalRestApiControllerBase.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalRestApiControllerBase.cs new file mode 100644 index 0000000000..f3c02ada78 --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalRestApiControllerBase.cs @@ -0,0 +1,166 @@ +using System.IO; +using System.Web.Mvc; +using SmartStore.Core.Configuration; +using SmartStore.PayPal.Services; +using SmartStore.PayPal.Settings; +using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; + +namespace SmartStore.PayPal.Controllers +{ + public abstract class PayPalRestApiControllerBase : PaymentControllerBase where TSetting : PayPalApiSettingsBase, ISettings, new() + { + public PayPalRestApiControllerBase( + string systemName, + IPayPalService payPalService) + { + SystemName = systemName; + PayPalService = payPalService; + } + + private string GetControllerName() + { + return GetType().Name.Replace("Controller", ""); + } + + protected string SystemName { get; private set; } + protected IPayPalService PayPalService { get; private set; } + + [AdminAuthorize] + public ActionResult UpsertExperienceProfile() + { + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + + var store = Services.StoreService.GetStoreById(storeScope == 0 ? Services.StoreContext.CurrentStore.Id : storeScope); + var session = new PayPalSessionData(); + + var result = PayPalService.EnsureAccessToken(session, settings); + if (result.Success) + { + result = PayPalService.UpsertCheckoutExperience(settings, session, store); + if (result.Success && result.Id.HasValue()) + { + settings.ExperienceProfileId = result.Id; + Services.Settings.SaveSetting(settings, x => x.ExperienceProfileId, storeScope, false); + Services.Settings.ClearCache(); + } + } + + if (result.Success) + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + else + NotifyError(result.ErrorMessage); + + return RedirectToAction("ConfigureProvider", "Plugin", new { area = "admin", systemName = SystemName }); + } + + [AdminAuthorize] + public ActionResult DeleteExperienceProfile() + { + var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); + var settings = Services.Settings.LoadSetting(storeScope); + var session = new PayPalSessionData(); + + var result = PayPalService.EnsureAccessToken(session, settings); + if (result.Success) + { + result = PayPalService.DeleteCheckoutExperience(settings, session); + if (result.Success) + { + settings.ExperienceProfileId = null; + Services.Settings.SaveSetting(settings, x => x.ExperienceProfileId, storeScope, false); + Services.Settings.ClearCache(); + } + } + + if (result.Success) + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + else + NotifyError(result.ErrorMessage); + + return RedirectToAction("ConfigureProvider", "Plugin", new { area = "admin", systemName = SystemName }); + } + + [AdminAuthorize] + public ActionResult CreateWebhook() + { + var settings = Services.Settings.LoadSetting(); + var session = new PayPalSessionData(); + + if (settings.WebhookId.HasValue()) + { + var unused = PayPalService.DeleteWebhook(settings, session); + + Services.Settings.SaveSetting(settings, x => x.WebhookId, 0, false); + } + + var url = Url.Action("Webhook", GetControllerName(), new { area = Plugin.SystemName }, "https"); + + var result = PayPalService.EnsureAccessToken(session, settings); + if (result.Success) + { + result = PayPalService.CreateWebhook(settings, session, url); + if (result.Success) + { + settings.WebhookId = result.Id; + Services.Settings.SaveSetting(settings, x => x.WebhookId, 0, false); + } + } + + Services.Settings.ClearCache(); + + if (result.Success) + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + else + NotifyError(result.ErrorMessage); + + return RedirectToAction("ConfigureProvider", "Plugin", new { area = "admin", systemName = SystemName }); + } + + [AdminAuthorize] + public ActionResult DeleteWebhook() + { + var settings = Services.Settings.LoadSetting(); + var session = new PayPalSessionData(); + + if (settings.WebhookId.HasValue()) + { + var result = PayPalService.EnsureAccessToken(session, settings); + if (result.Success) + { + result = PayPalService.DeleteWebhook(settings, session); + if (result.Success) + { + settings.WebhookId = null; + Services.Settings.SaveSetting(settings, x => x.WebhookId, 0, false); + Services.Settings.ClearCache(); + } + } + + if (result.Success) + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); + else + NotifyError(result.ErrorMessage); + } + + return RedirectToAction("ConfigureProvider", "Plugin", new { area = "admin", systemName = SystemName }); + } + + [ValidateInput(false)] + public ActionResult Webhook() + { + string json = null; + using (var reader = new StreamReader(Request.InputStream)) + { + json = reader.ReadToEnd(); + } + + var settings = Services.Settings.LoadSetting(); + + var result = PayPalService.ProcessWebhook(settings, Request.Headers, json); + + return new HttpStatusCodeResult(result); + } + } +} \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index 33212ddb48..d0233e1d90 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -22,6 +22,19 @@ Sonstiges + + {0} hat die Forderung gegen Sie im Rahmen eines laufenden Factoringvertrages an die PayPal (Europe) S.àr.l. et Cie, S.C.A. abgetreten. Zahlungen mit schuldbefreiender Wirkung können nur an die PayPal (Europe) S.àr.l. et Cie, S.C.A. geleistet werden. + + + Bitte überweisen Sie den fälligen Betrag auf folgendes Konto. + + + Referenz;Bankleitzahl;Bank;BIC;IBAN;Kontoinhaber;Kontonummer;Betrag;Zahlung fällig am;Details + + + Validierung einer Webhook-Nachricht ist fehlgeschlagen + + Client-ID @@ -43,17 +56,26 @@ Legt die Experience Profil-ID fest. Das Profil beinhaltet globale Daten wie z.B. Shop-Name und -Logo. Ein Profil braucht nur einmalig angelegt werden. Über den Button können Sie ein neues Profil erstellen oder ein vorhandenes aktualisieren. - - Die Ausführung der Zahlung wurde durch PayPal abgelehnt. + + Experience Profil-ID - - {0} hat die Forderung gegen Sie im Rahmen eines laufenden Factoringvertrages an die PayPal (Europe) S.àr.l. et Cie, S.C.A. abgetreten. Zahlungen mit schuldbefreiender Wirkung können nur an die PayPal (Europe) S.àr.l. et Cie, S.C.A. geleistet werden. + + Webhook-ID - - Bitte überweisen Sie den fälligen Betrag auf folgendes Konto. + + Legt die Webhook-ID fest. Über einen Webhook sendet PayPal Statusänderungen von Zahlungen an Ihren Shop, wodurch der Zahlungsstatus Ihrer Aufträge automatisch aktualisert werden kann. - - Referenz;Bankleitzahl;Bank;BIC;IBAN;Kontoinhaber;Kontonummer;Betrag;Zahlung fällig am;Details + + Validierung von Nachrichten + + + Legt fest, ob und wie Webhook-Nachrichten validiert werden sollen. + + + Keine + + + Einfach diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index ea419bd172..7f8575af59 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -22,6 +22,22 @@ Miscellaneous + + The execution of the payment has been declined by PayPal. + + + {0} hat die Forderung gegen Sie im Rahmen eines laufenden Factoringvertrages an die PayPal (Europe) S.àr.l. et Cie, S.C.A. abgetreten. Zahlungen mit schuldbefreiender Wirkung können nur an die PayPal (Europe) S.àr.l. et Cie, S.C.A. geleistet werden. + + + Please transfer the due amount to the following bank account. + + + Reference;Bank routing number;Bank;BIC;IBAN;Account holder;Account number;Amount;Payment due date;Details + + + Validation of a webhook message has failed + + Client ID @@ -43,17 +59,23 @@ Specifies the experience profile ID. The profile contains global data like shop name and logo. A profile needs to be created only once. Using the button, you can create a new profile or update an existing one. - - The execution of the payment has been declined by PayPal. + + Webhook ID - - {0} hat die Forderung gegen Sie im Rahmen eines laufenden Factoringvertrages an die PayPal (Europe) S.àr.l. et Cie, S.C.A. abgetreten. Zahlungen mit schuldbefreiender Wirkung können nur an die PayPal (Europe) S.àr.l. et Cie, S.C.A. geleistet werden. + + Specifies the webhook ID. PayPal sends payment status changes via a webhook to your shop, whereby the payment status of your orders can be automatically synced. - - Please transfer the due amount to the following bank account. + + Message validation - - Reference;Bank routing number;Bank;BIC;IBAN;Account holder;Account number;Amount;Payment due date;Details + + Specifies if and how webhook messages should be validated. + + + None + + + Simple diff --git a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs index a6cfabcca0..59c4b69b54 100644 --- a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs +++ b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations; using System.Net; using System.Web.Mvc; +using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; @@ -46,6 +47,13 @@ public abstract class ApiConfigurationModel: ModelBase [SmartResourceDisplayName("Plugins.SmartStore.PayPal.ExperienceProfileId")] public string ExperienceProfileId { get; set; } + [SmartResourceDisplayName("Plugins.SmartStore.PayPal.WebhookId")] + public string WebhookId { get; set; } + + [SmartResourceDisplayName("Plugins.SmartStore.PayPal.WebhookValidation")] + public PayPalWebhookValidation WebhookValidation { get; set; } + public SelectList AvailableWebhookValidations { get; set; } + [SmartResourceDisplayName("Plugins.Payments.PayPal.AdditionalFee")] public decimal AdditionalFee { get; set; } @@ -166,6 +174,8 @@ public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) ClientId = settings.ClientId; Secret = settings.Secret; ExperienceProfileId = settings.ExperienceProfileId; + WebhookId = settings.WebhookId; + WebhookValidation = settings.WebhookValidation; ThirdPartyPaymentMethods = settings.ThirdPartyPaymentMethods; DisplayPaymentMethodLogo = settings.DisplayPaymentMethodLogo; DisplayPaymentMethodDescription = settings.DisplayPaymentMethodDescription; @@ -180,6 +190,8 @@ public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) settings.ClientId = ClientId; settings.Secret = Secret; settings.ExperienceProfileId = ExperienceProfileId; + settings.WebhookId = WebhookId; + settings.WebhookValidation = WebhookValidation; settings.ThirdPartyPaymentMethods = ThirdPartyPaymentMethods; settings.DisplayPaymentMethodLogo = DisplayPaymentMethodLogo; settings.DisplayPaymentMethodDescription = DisplayPaymentMethodDescription; diff --git a/src/Plugins/SmartStore.PayPal/Plugin.cs b/src/Plugins/SmartStore.PayPal/Plugin.cs index 336c38f253..472a7f2680 100644 --- a/src/Plugins/SmartStore.PayPal/Plugin.cs +++ b/src/Plugins/SmartStore.PayPal/Plugin.cs @@ -1,4 +1,6 @@ -using SmartStore.Core.Plugins; +using System; +using SmartStore.Core.Plugins; +using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Services.Configuration; using SmartStore.Services.Localization; @@ -9,13 +11,16 @@ public class Plugin : BasePlugin { private readonly ISettingService _settingService; private readonly ILocalizationService _localizationService; + private readonly Lazy _payPalService; public Plugin( ISettingService settingService, - ILocalizationService localizationService) + ILocalizationService localizationService, + Lazy payPalService) { _settingService = settingService; _localizationService = localizationService; + _payPalService = payPalService; } public static string SystemName @@ -37,6 +42,26 @@ public override void Install() public override void Uninstall() { + try + { + var settings = _settingService.LoadSetting(); + if (settings.WebhookId.HasValue()) + { + var session = new PayPalSessionData(); + var result = _payPalService.Value.EnsureAccessToken(session, settings); + + if (result.Success) + result = _payPalService.Value.DeleteWebhook(settings, session); + + if (!result.Success) + _payPalService.Value.LogError(null, result.ErrorMessage); + } + } + catch (Exception exception) + { + _payPalService.Value.LogError(exception); + } + _settingService.DeleteSetting(); _settingService.DeleteSetting(); _settingService.DeleteSetting(); diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs index d46f821d84..c48723fce6 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs @@ -137,10 +137,9 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces result.Errors.Add(T("Plugins.SmartStore.PayPal.PaymentExecuteFailed").Text.Grow(failureReason, " ")); } } - else - { + + if (!apiResult.Success) result.Errors.Add(apiResult.ErrorMessage); - } return result; } @@ -171,23 +170,16 @@ public override CapturePaymentResult Capture(CapturePaymentRequest capturePaymen var session = new PayPalSessionData(); var apiResult = PayPalService.EnsureAccessToken(session, settings); - if (result.Success) + if (apiResult.Success) { apiResult = PayPalService.Capture(settings, session, capturePaymentRequest); if (apiResult.Success) - { result.NewPaymentStatus = PaymentStatus.Paid; - } - else - { - result.Errors.Add(apiResult.ErrorMessage); - } } - else - { + + if (!apiResult.Success) result.Errors.Add(apiResult.ErrorMessage); - } return result; } @@ -203,10 +195,9 @@ public override RefundPaymentResult Refund(RefundPaymentRequest refundPaymentReq var session = new PayPalSessionData(); var apiResult = PayPalService.EnsureAccessToken(session, settings); - if (result.Success) + if (apiResult.Success) { apiResult = PayPalService.Refund(settings, session, refundPaymentRequest); - if (apiResult.Success) { if (refundPaymentRequest.IsPartialRefund) @@ -214,15 +205,10 @@ public override RefundPaymentResult Refund(RefundPaymentRequest refundPaymentReq else result.NewPaymentStatus = PaymentStatus.Refunded; } - else - { - result.Errors.Add(apiResult.ErrorMessage); - } } - else - { + + if (!apiResult.Success) result.Errors.Add(apiResult.ErrorMessage); - } return result; } @@ -238,22 +224,17 @@ public override VoidPaymentResult Void(VoidPaymentRequest voidPaymentRequest) var session = new PayPalSessionData(); var apiResult = PayPalService.EnsureAccessToken(session, settings); - if (result.Success) + if (apiResult.Success) { apiResult = PayPalService.Void(settings, session, voidPaymentRequest); if (apiResult.Success) { result.NewPaymentStatus = PaymentStatus.Voided; } - else - { - result.Errors.Add(apiResult.ErrorMessage); - } } - else - { + + if (!apiResult.Success) result.Errors.Add(apiResult.ErrorMessage); - } return result; } diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs index c625f4642c..ed74d18e17 100644 --- a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; +using System.Net; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Payments; using SmartStore.Core.Domain.Stores; @@ -20,12 +22,10 @@ public interface IPayPalService PaymentStatus GetPaymentStatus(string state, string reasonCode, PaymentStatus defaultStatus); - PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data, IList errors = null); + PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data); PayPalResponse EnsureAccessToken(PayPalSessionData session, PayPalApiSettingsBase settings); - PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, PayPalSessionData session, Store store, string profileId); - PayPalResponse GetPayment(PayPalApiSettingsBase settings, PayPalSessionData session); PayPalResponse CreatePayment( @@ -43,5 +43,15 @@ PayPalResponse CreatePayment( PayPalResponse Capture(PayPalApiSettingsBase settings, PayPalSessionData session, CapturePaymentRequest request); PayPalResponse Void(PayPalApiSettingsBase settings, PayPalSessionData session, VoidPaymentRequest request); + + PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, PayPalSessionData session, Store store); + + PayPalResponse DeleteCheckoutExperience(PayPalApiSettingsBase settings, PayPalSessionData session); + + PayPalResponse CreateWebhook(PayPalApiSettingsBase settings, PayPalSessionData session, string url); + + PayPalResponse DeleteWebhook(PayPalApiSettingsBase settings, PayPalSessionData session); + + HttpStatusCode ProcessWebhook(PayPalApiSettingsBase settings, NameValueCollection headers, string rawJson); } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalEnums.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalEnums.cs index 11e2210945..35561b4d1f 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalEnums.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalEnums.cs @@ -14,4 +14,10 @@ public enum PayPalPaymentInstructionItem PaymentDueDate, Details } + + public enum PayPalWebhookValidation + { + None = 0, + Simple + } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 5da6195fdc..c6dda81ff2 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Globalization; using System.IO; using System.Linq; @@ -101,6 +102,46 @@ private Dictionary CreateAddress(Address addr) return dic; } + private bool IsValidMessage(PayPalApiSettingsBase settings, dynamic json) + { + try + { + if (settings.WebhookValidation == PayPalWebhookValidation.Simple) + { + // own, low level validation + var paymentId = (string)json.resource.parent_payment; + + if (paymentId.IsEmpty() || settings.WebhookId.IsEmpty() || !settings.WebhookId.IsCaseInsensitiveEqual(settings.WebhookId)) + return false; + + + } + + // validating against PayPal SDK frequently failing: + //var webhookId = setting.WebhookId; // or (string)json.id for simulator + //var apiContext = new global::PayPal.Api.APIContext + //{ + // AccessToken = "I do not have one here", + // Config = new Dictionary + // { + // { "mode", settings.UseSandbox ? "sandbox" : "live" }, + // { "clientId", settings.ClientId }, + // { "clientSecret", settings.Secret }, + // { "webhook.id", webhookId }, + // } + //}; + //var result = global::PayPal.Api.WebhookEvent.ValidateReceivedEvent(apiContext, headers, rawJson, webhookId); + //} + } + catch (Exception exception) + { + LogError(exception, T("Plugins.SmartStore.PayPal.WebhookValidationFailed"), isWarning: true); + return false; + } + + return true; + } + public static string GetApiUrl(bool sandbox) { return sandbox ? "https://api.sandbox.paypal.com" : "https://api.paypal.com"; @@ -360,7 +401,7 @@ public PaymentStatus GetPaymentStatus(string state, string reasonCode, PaymentSt return result; } - public PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data, IList errors = null) + public PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data) { var isJson = (data.HasValue() && data.StartsWith("{")); var encoding = (isJson ? Encoding.UTF8 : Encoding.ASCII); @@ -432,7 +473,8 @@ public PayPalResponse CallApi(string method, string path, string accessToken, Pa catch (Exception exception) { result.Success = false; - LogError(exception, errors: errors); + result.ErrorMessage = exception.ToString(); + LogError(exception); } try @@ -480,13 +522,13 @@ public PayPalResponse CallApi(string method, string path, string accessToken, Pa if (result.ErrorMessage.IsEmpty()) result.ErrorMessage = webResponse.StatusDescription; - LogError(null, result.ErrorMessage, result.Json == null ? null : result.Json.ToString(), false, errors); + LogError(null, result.ErrorMessage, result.Json == null ? null : result.Json.ToString(), false); } } } catch (Exception exception) { - LogError(exception, errors: errors); + LogError(exception); } finally { @@ -526,64 +568,6 @@ public PayPalResponse EnsureAccessToken(PayPalSessionData session, PayPalApiSett }; } - public PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, PayPalSessionData session, Store store, string profileId) - { - PayPalResponse result; - var name = store.Name; - var logo = _pictureService.Value.GetPictureById(store.LogoPictureId); - var path = "/v1/payment-experience/web-profiles"; - - var data = new Dictionary(); - var presentation = new Dictionary(); - var inpuFields = new Dictionary(); - - if (profileId.IsEmpty()) - { - result = CallApi("GET", path, session.AccessToken, settings, null); - if (result.Success && result.Json != null) - { - foreach (var profile in result.Json) - { - var profileName = (string)profile.name; - if (profileName.IsCaseInsensitiveEqual(name)) - { - profileId = (string)profile.id; - break; - } - } - } - } - - presentation.Add("brand_name", name); - presentation.Add("locale_code", _services.WorkContext.WorkingLanguage.UniqueSeoCode.EmptyNull().ToUpper()); - - if (logo != null) - presentation.Add("logo_image", _pictureService.Value.GetPictureUrl(logo, showDefaultPicture: false, storeLocation: store.Url)); - - inpuFields.Add("allow_note", false); - inpuFields.Add("no_shipping", 0); - inpuFields.Add("address_override", 1); - - data.Add("name", name); - data.Add("presentation", presentation); - data.Add("input_fields", inpuFields); - - if (profileId.HasValue()) - path = string.Concat(path, "/", HttpUtility.UrlPathEncode(profileId)); - - result = CallApi(profileId.HasValue() ? "PUT" : "POST", path, session.AccessToken, settings, JsonConvert.SerializeObject(data)); - - if (result.Success) - { - if (result.Json != null) - result.Id = (string)result.Json.id; - else - result.Id = profileId; - } - - return result; - } - public PayPalResponse GetPayment(PayPalApiSettingsBase settings, PayPalSessionData session) { var result = CallApi("GET", "/v1/payments/payment/" + session.PaymentId, session.AccessToken, settings, null); @@ -839,6 +823,134 @@ public PayPalResponse Void(PayPalApiSettingsBase settings, PayPalSessionData ses return result; } + + public PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, PayPalSessionData session, Store store) + { + PayPalResponse result; + var name = store.Name; + var logo = _pictureService.Value.GetPictureById(store.LogoPictureId); + var path = "/v1/payment-experience/web-profiles"; + + var data = new Dictionary(); + var presentation = new Dictionary(); + var inpuFields = new Dictionary(); + + // find existing profile id, only one profile per profile name possible + if (settings.ExperienceProfileId.IsEmpty()) + { + result = CallApi("GET", path, session.AccessToken, settings, null); + if (result.Success && result.Json != null) + { + foreach (var profile in result.Json) + { + var profileName = (string)profile.name; + if (profileName.IsCaseInsensitiveEqual(name)) + { + settings.ExperienceProfileId = (string)profile.id; + break; + } + } + } + } + + presentation.Add("brand_name", name); + presentation.Add("locale_code", _services.WorkContext.WorkingLanguage.UniqueSeoCode.EmptyNull().ToUpper()); + + if (logo != null) + presentation.Add("logo_image", _pictureService.Value.GetPictureUrl(logo, showDefaultPicture: false, storeLocation: store.Url)); + + inpuFields.Add("allow_note", false); + inpuFields.Add("no_shipping", 0); + inpuFields.Add("address_override", 1); + + data.Add("name", name); + data.Add("presentation", presentation); + data.Add("input_fields", inpuFields); + + if (settings.ExperienceProfileId.HasValue()) + path = string.Concat(path, "/", HttpUtility.UrlPathEncode(settings.ExperienceProfileId)); + + result = CallApi(settings.ExperienceProfileId.HasValue() ? "PUT" : "POST", path, session.AccessToken, settings, JsonConvert.SerializeObject(data)); + + if (result.Success) + { + if (result.Json != null) + result.Id = (string)result.Json.id; + else + result.Id = settings.ExperienceProfileId; + } + + return result; + } + + public PayPalResponse DeleteCheckoutExperience(PayPalApiSettingsBase settings, PayPalSessionData session) + { + var result = CallApi("DELETE", "/v1/payment-experience/web-profiles/" + settings.ExperienceProfileId, session.AccessToken, settings, null); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + } + + return result; + } + + public PayPalResponse CreateWebhook(PayPalApiSettingsBase settings, PayPalSessionData session, string url) + { + var data = new Dictionary(); + var events = new List>(); + + events.Add(new Dictionary { { "name", "*" } }); + + data.Add("url", url); + data.Add("event_types", events); + + var result = CallApi("POST", "/v1/notifications/webhooks", session.AccessToken, settings, JsonConvert.SerializeObject(data)); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + } + + return result; + } + + public PayPalResponse DeleteWebhook(PayPalApiSettingsBase settings, PayPalSessionData session) + { + var result = CallApi("DELETE", "/v1/notifications/webhooks/" + settings.WebhookId, session.AccessToken, settings, null); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + } + + return result; + } + + /// return 503 (HttpStatusCode.ServiceUnavailable) to ask paypal to resend it at later time + public HttpStatusCode ProcessWebhook(PayPalApiSettingsBase settings, NameValueCollection headers, string rawJson) + { + if (rawJson.IsEmpty()) + return HttpStatusCode.OK; + + dynamic json = JObject.Parse(rawJson); + + //foreach (var key in headers.AllKeys)"{0}: {1}".FormatInvariant(key, headers[key]).Dump(); + //string data = JsonConvert.SerializeObject(json, Formatting.Indented);data.Dump(); + + var isValid = (bool)IsValidMessage(settings, json); + + if (!isValid) + { + LogError(null, T("Plugins.SmartStore.PayPal.WebhookValidationFailed"), rawJson, isWarning: true); + return HttpStatusCode.OK; + } + + // TODO + + + return HttpStatusCode.OK; + } } diff --git a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs index 84bbe6ae59..336c22945c 100644 --- a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs +++ b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Net; using SmartStore.Core.Configuration; +using SmartStore.PayPal.Services; namespace SmartStore.PayPal.Settings { @@ -34,6 +35,11 @@ public PayPalSettingsBase() public abstract class PayPalApiSettingsBase : PayPalSettingsBase { + public PayPalApiSettingsBase() + { + WebhookValidation = PayPalWebhookValidation.Simple; + } + public TransactMode TransactMode { get; set; } public string ApiAccountName { get; set; } public string ApiAccountPassword { get; set; } @@ -53,6 +59,16 @@ public abstract class PayPalApiSettingsBase : PayPalSettingsBase /// PayPal experience profile id /// public string ExperienceProfileId { get; set; } + + /// + /// PayPal webhook id + /// + public string WebhookId { get; set; } + + /// + /// Indicates if and how webhook messages should be validated + /// + public PayPalWebhookValidation WebhookValidation { get; set; } } diff --git a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj index a987ea30b6..2690f97cb2 100644 --- a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj +++ b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj @@ -118,6 +118,7 @@ Properties\AssemblyVersionInfo.cs + diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml index bdd59a8735..92707956bf 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml @@ -66,23 +66,6 @@ @Html.TextBoxFor(model => model.Secret, new { @class = "control-xlarge" }) @Html.ValidationMessageFor(model => model.Secret) -
- - - + + + + + + + + + + + + - - - -
@T("Admin.System.ScheduleTasks.Name") @T("Admin.System.ScheduleTasks.Enabled") @T("Admin.System.ScheduleTasks.CronExpression")@T("Admin.System.ScheduleTasks.LastStart")@T("Admin.System.ScheduleTasks.LastStart") @T("Admin.System.ScheduleTasks.NextRun") @T("Admin.Common.Actions")
+
@Html.Partial("_LastRun", task)
+
@Html.Partial("_NextRun", task)
From 2c0576b0e71e8ffcf15eaf232a5b06df5bf91c64 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 14 Apr 2016 00:53:32 +0200 Subject: [PATCH 033/285] Task scheduler should ignore bad ssl certificates --- src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs index b34fe37186..2ba9eb56e6 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskScheduler.cs @@ -168,7 +168,8 @@ protected internal virtual void CallEndpoint(string url) return; var req = (HttpWebRequest)WebRequest.Create(url); - req.UserAgent = "SmartStore.NET"; + req.ServerCertificateValidationCallback += (sender, cert, chain, errors) => true; + req.UserAgent = "SmartStore.NET"; req.Method = "POST"; req.ContentType = "text/plain"; req.ContentLength = 0; From 0de4d8dd3cd0eec7b95fcd86e9acda9c8dbe3f72 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 14 Apr 2016 20:10:42 +0200 Subject: [PATCH 034/285] PayPal Plus: Getting started with payment wall --- .../Controllers/PayPalPlusController.cs | 70 +++++++++++++++++-- .../Models/PayPalPlusCheckoutModel.cs | 5 ++ .../Services/IPayPalService.cs | 9 ++- .../Services/PayPalService.cs | 57 ++++++++++++--- .../Settings/PayPalSettings.cs | 3 +- .../SmartStore.PayPal.csproj | 3 + .../PayPalPlus/PaymentWall.Mobile.cshtml | 4 ++ .../Views/PayPalPlus/PaymentWall.cshtml | 26 +++---- .../PayPalPlus/PaymentWallScripting.cshtml | 43 ++++++++++++ 9 files changed, 192 insertions(+), 28 deletions(-) create mode 100644 src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index 539451b9b7..bdb302b649 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Web; using System.Web.Mvc; using SmartStore.PayPal.Models; using SmartStore.PayPal.Services; @@ -14,9 +15,11 @@ namespace SmartStore.PayPal.Controllers { public class PayPalPlusController : PayPalControllerBase { + private readonly HttpContextBase _httpContext; private readonly IPayPalService _payPalService; public PayPalPlusController( + HttpContextBase httpContext, IPaymentService paymentService, IOrderService orderService, IOrderProcessingService orderProcessingService, @@ -26,6 +29,7 @@ public PayPalPlusController( orderService, orderProcessingService) { + _httpContext = httpContext; _payPalService = payPalService; } @@ -105,10 +109,12 @@ public ActionResult UpsertExperienceProfile(string profileId) var settings = Services.Settings.LoadSetting(storeScope); var store = Services.StoreService.GetStoreById(storeScope == 0 ? Services.StoreContext.CurrentStore.Id : storeScope); + var session = new PayPalSessionData(); - var result = _payPalService.UpsertCheckoutExperience(settings, store, profileId); - if (result != null) + var result = _payPalService.EnsureAccessToken(session, settings); + if (result.Success) { + result = _payPalService.UpsertCheckoutExperience(settings, session, store, profileId); if (result.Success && result.Id.HasValue()) { settings.ExperienceProfileId = result.Id; @@ -120,6 +126,10 @@ public ActionResult UpsertExperienceProfile(string profileId) NotifyError(result.ErrorMessage); } } + else + { + NotifyError(result.ErrorMessage); + } return RedirectToAction("ConfigureProvider", "Plugin", new { area = "admin", systemName = PayPalPlusProvider.SystemName }); } @@ -131,15 +141,67 @@ public ActionResult PaymentInfo() public ActionResult PaymentWall() { + var store = Services.StoreContext.CurrentStore; + var customer = Services.WorkContext.CurrentCustomer; + var settings = Services.Settings.LoadSetting(store.Id); + var model = new PayPalPlusCheckoutModel(); + var state = _httpContext.GetCheckoutState(); + + if (!state.CustomProperties.ContainsKey(PayPalPlusProvider.SystemName)) + state.CustomProperties.Add(PayPalPlusProvider.SystemName, new PayPalSessionData()); + + var session = state.CustomProperties[PayPalPlusProvider.SystemName] as PayPalSessionData; + + model.UseSandbox = settings.UseSandbox; + model.HasPaymentFee = (settings.AdditionalFee > decimal.Zero); + model.LanguageCulture = (Services.WorkContext.WorkingLanguage.LanguageCulture ?? "de_DE").Replace("-", "_"); + + if (customer.BillingAddress != null && customer.BillingAddress.Country != null) + { + model.BillingAddressCountryCode = customer.BillingAddress.Country.TwoLetterIsoCode; + } + + var protocol = (store.SslEnabled ? "https" : "http"); + var returnUrl = Url.Action("CheckoutReturn", "PayPalPlus", new { area = Plugin.SystemName }, protocol); + var cancelUrl = Url.Action("CheckoutCancel", "PayPalPlus", new { area = Plugin.SystemName }, protocol); + + + var result = _payPalService.EnsureAccessToken(session, settings); + if (result.Success) + { + result = _payPalService.UpsertPayment(settings, session, null, returnUrl, cancelUrl); + if (result.Success && result.Json != null) + { + foreach (var link in result.Json.links) + { + if (((string)link.rel).IsCaseInsensitiveEqual("approval_url")) + { + model.ApprovalUrl = link.href; + break; + } + } + } + else if (result.ErrorMessage.HasValue()) + { + ModelState.AddModelError("", result.ErrorMessage); + } + } + else if (result.ErrorMessage.HasValue()) + { + ModelState.AddModelError("", result.ErrorMessage); + } return View(model); } - [HttpPost] - public ActionResult PaymentWall(FormCollection form) + public ActionResult CheckoutReturn() { + return RedirectToAction("Confirm", "Checkout", new { area = "" }); + } + public ActionResult CheckoutCancel() + { return RedirectToAction("Confirm", "Checkout", new { area = "" }); } } diff --git a/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs b/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs index 56c44dc329..f9fec802a2 100644 --- a/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs +++ b/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs @@ -4,5 +4,10 @@ namespace SmartStore.PayPal.Models { public class PayPalPlusCheckoutModel : ModelBase { + public bool UseSandbox { get; set; } + public bool HasPaymentFee { get; set; } + public string BillingAddressCountryCode { get; set; } + public string LanguageCulture { get; set; } + public string ApprovalUrl { get; set; } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs index 9678132a17..1dedce8e88 100644 --- a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -16,6 +16,13 @@ public interface IPayPalService PayPalResponse EnsureAccessToken(PayPalSessionData session, PayPalApiSettingsBase settings); - PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, Store store, string profileId); + PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, PayPalSessionData session, Store store, string profileId); + + PayPalResponse UpsertPayment( + PayPalApiSettingsBase settings, + PayPalSessionData session, + string paymentId, + string returnUrl, + string cancelUrl); } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 03df71f0cc..33a4d0d78a 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Net; using System.Text; using System.Web; @@ -263,14 +264,9 @@ public PayPalResponse EnsureAccessToken(PayPalSessionData session, PayPalApiSett }; } - public PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, Store store, string profileId) + public PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, PayPalSessionData session, Store store, string profileId) { - var session = new PayPalSessionData(); - var result = EnsureAccessToken(session, settings); - - if (!result.Success) - return result; - + PayPalResponse result; var name = store.Name; var logo = _pictureService.Value.GetPictureById(store.LogoPictureId); var path = "/v1/payment-experience/web-profiles"; @@ -325,6 +321,52 @@ public PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, S return result; } + + public PayPalResponse UpsertPayment( + PayPalApiSettingsBase settings, + PayPalSessionData session, + string paymentId, + string returnUrl, + string cancelUrl) + { + var path = "/v1/payments/payment"; + var data = new Dictionary(); + var redirectUrls = new Dictionary(); + + if (paymentId.HasValue()) + path = string.Concat(path, "/", HttpUtility.UrlPathEncode(paymentId)); + + data.Add("intent", settings.TransactMode == TransactMode.AuthorizeAndCapture ? "sale" : "authorize"); + + if (settings.ExperienceProfileId.HasValue()) + data.Add("experience_profile_id", settings.ExperienceProfileId); + + if (returnUrl.HasValue()) + redirectUrls.Add("return_url", returnUrl); + + if (cancelUrl.HasValue()) + redirectUrls.Add("cancel_url", cancelUrl); + + if (redirectUrls.Any()) + data.Add("redirect_urls", redirectUrls); + + if (paymentId.IsEmpty()) + { + // TODO: add line items + } + + var result = CallApi(paymentId.HasValue() ? "PATCH" : "POST", path, session.AccessToken, settings, JsonConvert.SerializeObject(data)); + + if (result.Success) + { + if (result.Json != null) + result.Id = (string)result.Json.id; + else + result.Id = paymentId; + } + + return result; + } } @@ -341,5 +383,4 @@ public class PayPalSessionData public string AccessToken { get; set; } public DateTime TokenExpiration { get; set; } } - } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs index e5509c8e7d..77de820164 100644 --- a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs +++ b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs @@ -40,6 +40,7 @@ public abstract class PayPalApiSettingsBase : PayPalSettingsBase public string ClientId { get; set; } public string Secret { get; set; } + public string ExperienceProfileId { get; set; } } @@ -93,8 +94,6 @@ public PayPalExpressPaymentSettings() public class PayPalPlusPaymentSettings : PayPalApiSettingsBase, ISettings { - public string ExperienceProfileId { get; set; } - public PayPalPlusPaymentSettings() { UseSandbox = true; diff --git a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj index 10e754fcef..76fe8515dd 100644 --- a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj +++ b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj @@ -288,6 +288,9 @@ PreserveNewest + + PreserveNewest + Designer diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml index 29ea3ec39e..d7d9e6e19e 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml @@ -6,10 +6,14 @@ Html.AddTitleParts(T("PageTitle.Checkout").Text); //Html.AddCssFileParts(true, Url.Content("~/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css")); } + +@Html.Partial("PaymentWallScripting") +

@T("Checkout.SelectPaymentMethod")

+
\ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml index b5b630756d..92bedd7d1b 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml @@ -10,27 +10,33 @@ { @Html.Action("CheckoutProgress", "Checkout", new { step = CheckoutProgressStep.Payment }) } -
+ +@if (!ViewData.ModelState.IsValid) +{ + @Html.ValidationSummary() +} + +@Html.Partial("PaymentWallScripting") + +

@T("Checkout.SelectPaymentMethod")

- hello world! @using (Html.BeginForm()) { @Html.Widget("checkout_payment_method_top") - - +
 @T("Common.Back") - + @*TODO: reward points*@ -
@@ -38,10 +44,4 @@ @Html.Widget("checkout_payment_method_bottom") }
-
- - +
\ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml new file mode 100644 index 0000000000..c66219b54f --- /dev/null +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml @@ -0,0 +1,43 @@ +@using SmartStore.PayPal.Models; +@model PayPalPlusCheckoutModel + + \ No newline at end of file From 246beb21cf19dcee8e9448b367d1aeb41e4e1864 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 14 Apr 2016 23:36:14 +0200 Subject: [PATCH 035/285] TaskScheduler should handle multi-server environments better --- .../Controllers/TaskSchedulerController.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs b/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs index 860da5fa85..1dd684ee5b 100644 --- a/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/TaskSchedulerController.cs @@ -44,14 +44,14 @@ public ActionResult Sweep() var prevTaskStart = DateTime.UtcNow; var count = 0; - foreach (var task in pendingTasks) + for (var i = 0; i < pendingTasks.Count; i++) { - var elapsedSincePrevTask = DateTime.UtcNow - prevTaskStart; - if (elapsedSincePrevTask >= TimeSpan.FromMinutes(_taskScheduler.SweepIntervalMinutes)) + var task = pendingTasks[i]; + + if (i > 0) { - // the previous Task execution in this loop took longer - // than the scheduler's sweep interval. Most likely a subsequent - // Sweep call executed it already in another HTTP request. + // Maybe a subsequent Sweep call or another machine in a webfarm executed + // successive tasks already. // To be able to determine this, we need to reload the entity from the database. // The TaskExecutor will exit when the task should be in running state then. _services.DbContext.ReloadEntity(task); From 1a54340fd2852233616a165ac2eb6d89a3198c59 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 15 Apr 2016 01:17:33 +0200 Subject: [PATCH 036/285] Added EF migration code that sets IsTransient = true to false (to prevent deletion of transient media files after TransientMediaClearTask gets enabled again) --- .../201603121451066_ThirdPartyEmailHandOver.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs index 75e785db86..1b64d01d4c 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs @@ -1,7 +1,9 @@ namespace SmartStore.Data.Migrations { using System.Data.Entity.Migrations; - using Setup; + using SmartStore.Data.Setup; + using SmartStore.Core.Domain.Media; + using System.Linq; public partial class ThirdPartyEmailHandOver : DbMigration, ILocaleResourcesProvider, IDataSeeder { @@ -23,6 +25,17 @@ public bool RollbackOnFailure public void Seed(SmartObjectContext context) { context.MigrateLocaleResources(MigrateLocaleResources); + + // Some users have disabled the "TransientMediaClearTask" due to a bug. + // When the task is enabled again, it would delete media files, which are marked as transient + // but are permanent actually. To avoid this, we have to set IsTransient to false. + var transientPictures = context.Set().Where(x => x.IsTransient == true).ToList(); + transientPictures.Each(x => x.IsTransient = false); + + var transientDownloads = context.Set().Where(x => x.IsTransient == true).ToList(); + transientDownloads.Each(x => x.IsTransient = false); + + context.SaveChanges(); } public void MigrateLocaleResources(LocaleResourcesBuilder builder) From 717c30804a4c4e8a28e5b955964dbae5c5917e71 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 15 Apr 2016 01:46:34 +0200 Subject: [PATCH 037/285] Several language resource fixes --- ...201603121451066_ThirdPartyEmailHandOver.cs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs index 1b64d01d4c..63e182a699 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs @@ -48,25 +48,25 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Orders.CheckoutThirdPartyEmailHandOver.Activated", "Show activated", "Aktiviert anzeigen"); builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.ThirdPartyEmailHandOver", - "Third party email hand over", - "E-Mail Weitergabe an Dritte", - "Specifies if customers can accept to hand over their email address to third party when ordering and if the checkbox is enabled by default.", - "Legt fest, ob Kunden bei einer Bestellung der Weitergabe Ihrer E-Mail Adresse an Dritte zustimmen können und ob die Checkbox standardmäßig aktiviert ist."); + "Consent for email transfer to third parties", + "Zustimmung zur E-Mail Weitergabe an Dritte", + "Specifies whether customers can agree to a transferring of their email address to third parties when ordering, and whether the checkbox is enabled by default during checkout.", + "Legt fest, ob Kunden bei einer Bestellung der Weitergabe ihrer E-Mail Adresse an Dritte zustimmen können und ob die Checkbox dafür standardmäßig aktiviert ist."); builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.ThirdPartyEmailHandOverLabel", - "Text for email hand over", + "Text for email transfer consent", "Text für E-Mail Weitergabe", - "Specifies the text to accept to hand over the email address to third party. Please choose a certain reason for the hand over, e.g. 'I agree with the transmission and storage of my email address for the trusted shops buyer protection.'", + "Specifies the text to be displayed to the customer. Please choose a specific reason, e.g. 'I agree to the transfer and storage of my email address for TrustedShops buyer protection.'", "Legt den Text für die Zustimmung zur Weitergabe der E-Mail Adresse an Dritte fest. Wählen Sie bitte einen konkreten Grund für die Weitergabe, z.B. 'Mit der Übermittlung und Speicherung meiner E-Mail-Adresse zur Abwicklung des Käuferschutzes durch Trusted Shops bin ich einverstanden.'"); builder.AddOrUpdate("Admin.Configuration.Settings.ShoppingCart.ThirdPartyEmailHandOverLabel.Default", - "I agree with the transmission and storage of my email address by third parties.", + "I agree to the transfer and storage of my email address by third parties.", "Mit der Übermittlung und Speicherung meiner E-Mail-Adresse durch dritte Parteien bin ich einverstanden."); builder.AddOrUpdate("Admin.Orders.Fields.AcceptThirdPartyEmailHandOver", - "Accepts hand over of email", + "Accepts transfer of email", "Akzeptiert Weitergabe der E-Mail", - "Indicates whether the customer has accepted to hand over his email address to third party.", + "Indicates whether the customer has agreed to a transfer of his email address to third parties.", "Gibt an, ob der Kunde bei der Bestellung einer Weitergabe seiner E-Mail Adresse an Dritte zugestimmt hat oder nicht."); builder.AddOrUpdate("Admin.OrderNotice.OrderCaptureError", @@ -89,17 +89,17 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Legt fest, ob das Standardbild bei Herstellern ausgeblendet werden soll. Das Standardbild wird angezeigt, wenn dem Hersteller kein Bild zugeordnet ist."); builder.AddOrUpdate("Admin.DataExchange.Export.Partition.Note", - "You can partition the data to be exported with the following settings. This includes
  • Skipping the first n records
  • The maximum number of records to be exported
  • The number of records per export file
  • Export data for each shop in a separate file
By default, all data of a store will be exported into one file.", + "With the following settings you can partition the data to be exported. This includes
  • Skipping the first n records
  • The maximum number of records to be exported
  • The number of records per export file
  • Export data for each shop in a separate file
By default, all data of a store will be exported into one file.", "Mit den folgenden Einstellungen lassen sich die zu exportierenden Daten aufteilen. Dazu zählt
  • Das Überspringen der ersten n Datensätze
  • Die maximale Anzahl zu exportierender Datensätze
  • Die Anzahl der Datensätze pro Exportdatei
  • Daten von jedem Shop in eine separate Datei exportieren
Standardmäßig werden alle Daten eines Shops in eine Datei exportiert."); builder.AddOrUpdate("Admin.DataExchange.Export.Partition.Validate", - "Partitioning settings must be greater than or equal to 0.", + "Partitioning setting values must be greater than or equal to 0.", "Einstellungen zur Aufteilung müssen größer oder gleich 0 sein."); builder.AddOrUpdate("Admin.DataExchange.Export.Filter.IsActiveSubscriber", - "Only active subscriber", + "Only active subscribers", "Nur aktive Abonnenten", - "Filter by active or inactive newsletter subscriber.", + "Filter by active or inactive newsletter subscribers.", "Nach aktiven bzw. inaktiven Newsletter Abonnenten filtern."); builder.AddOrUpdate("Admin.DataExchange.Export.Filter.IsActiveCustomer", @@ -133,19 +133,19 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Nach dem Datum der letzten Shop Aktivität filtern."); builder.AddOrUpdate("Admin.DataExchange.Export.Filter.LastActivityTo", - "Last activity to", + "Last active until", "Zuletzt aktiv bis", "Filter by date of last store activity.", - "Nach dem Datum der letzten Shop Aktivität filtern."); + "Nach dem Datum der letzten Shop-Aktivität filtern."); builder.AddOrUpdate("Admin.DataExchange.Export.Filter.HasSpentAtLeastAmount", - "Has spent x amount", + "Has spent amount x", "Hat Betrag x ausgegeben", "Filter by spent amount.", "Nach dem insgesamt ausgegebenen Betrag filtern."); builder.AddOrUpdate("Admin.DataExchange.Export.Filter.HasPlacedAtLeastOrders", - "Has x placed orders", + "Has placed x orders", "Hat x Bestellungen", "Filter by number of placed orders.", "Nach der Anzahl der getätigten Bestellungen filtern."); @@ -165,13 +165,13 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.Delete("Admin.DataExchange.Import.ProfileEntitySelectNote"); builder.AddOrUpdate("Admin.Configuration.Restriction.SaveBeforeEdit", - "You need to save the data before you can specify restrictions.", + "You need to save before you can specify restrictions.", "Sie müssen zunächst speichern, bevor Sie Einschränkungen festlegen können."); builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.CustomerNumberMethod", "Customer numbers", "Kundennummern", - "Specifies whether to assign customer numbers and whether these should be created automatically.", + "Specifies whether to assign customer numbers and whether they should be created automatically.", "Legt fest, ob Kundennummern vergeben werden und ob diese automatisch vergeben werden sollen."); builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.CustomerNumberVisibility", @@ -187,11 +187,11 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberVisibility.None", "Do not display", "Nicht anzeigen"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberVisibility.Display", "Display", "Anzeigen"); builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberVisibility.EditableIfEmpty", "Editable if empty", "Editierbar falls leer"); - builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberVisibility.Editable", "Always editable", "Stets editierbar"); + builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Customers.CustomerNumberVisibility.Editable", "Always editable", "Immer editierbar"); builder.AddOrUpdate("Admin.Common.FileInUse", "The file is in use and cannot be opened.", - "Die Datei ist in Benutzung und kann nicht geöffnet werden."); + "Die Datei ist in Benutzung und kann daher nicht geöffnet werden."); builder.AddOrUpdate("Admin.DataExchange.Export.UserProfilesTitle", From 13a68bffbe79e1304aefd43ceedecb347e6463f5 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 15 Apr 2016 23:29:16 +0200 Subject: [PATCH 038/285] Updated Newtonsoft.Json package to version 8.0.3 --- .../SmartStore.Core/SmartStore.Core.csproj | 6 +- src/Libraries/SmartStore.Core/packages.config | 2 +- .../SmartStore.Data/SmartStore.Data.csproj | 6 +- src/Libraries/SmartStore.Data/packages.config | 2 +- .../SmartStore.Services.csproj | 6 +- .../SmartStore.Services/packages.config | 2 +- src/Plugins/SmartStore.AmazonPay/web.config | 132 ++++++++--------- src/Plugins/SmartStore.Clickatell/web.config | 116 +++++++-------- src/Plugins/SmartStore.DevTools/Web.config | 130 ++++++++--------- .../SmartStore.DiscountRules.csproj | 5 +- .../SmartStore.DiscountRules/packages.config | 2 +- .../SmartStore.DiscountRules/web.config | 116 +++++++-------- .../SmartStore.FacebookAuth.csproj | 5 +- .../SmartStore.FacebookAuth/packages.config | 2 +- .../SmartStore.FacebookAuth/web.config | 120 ++++++++-------- .../SmartStore.GoogleAnalytics/web.config | 116 +++++++-------- .../SmartStore.GoogleMerchantCenter.csproj | 5 +- .../packages.config | 2 +- .../web.config | 116 +++++++-------- .../SmartStore.OfflinePayment.csproj | 5 +- .../SmartStore.OfflinePayment/packages.config | 2 +- .../SmartStore.OfflinePayment/web.config | 116 +++++++-------- src/Plugins/SmartStore.PayPal/web.config | 136 +++++++++--------- src/Plugins/SmartStore.Shipping/web.config | 116 +++++++-------- .../SmartStore.ShippingByWeight/web.config | 116 +++++++-------- .../SmartStore.Tax/SmartStore.Tax.csproj | 5 +- src/Plugins/SmartStore.Tax/packages.config | 2 +- src/Plugins/SmartStore.Tax/web.config | 116 +++++++-------- .../SmartStore.WebApi.csproj | 5 +- src/Plugins/SmartStore.WebApi/packages.config | 2 +- src/Plugins/SmartStore.WebApi/web.config | 2 +- .../SmartStore.Web.Framework.csproj | 6 +- .../SmartStore.Web.Framework/app.config | 2 +- .../SmartStore.Web.Framework/packages.config | 2 +- .../Administration/SmartStore.Admin.csproj | 6 +- .../SmartStore.Web/Administration/Web.config | 2 +- .../Administration/packages.config | 2 +- .../SmartStore.Web/SmartStore.Web.csproj | 6 +- src/Presentation/SmartStore.Web/Web.config | 2 +- .../SmartStore.Web/packages.config | 2 +- src/Tests/SmartStore.Web.MVC.Tests/App.config | 2 +- .../SmartStore.WebApi.Client.csproj | 4 +- .../SmartStore.WebApi.Client/packages.config | 2 +- 43 files changed, 779 insertions(+), 773 deletions(-) diff --git a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj index 7aba1fc589..a14afb2ba4 100644 --- a/src/Libraries/SmartStore.Core/SmartStore.Core.csproj +++ b/src/Libraries/SmartStore.Core/SmartStore.Core.csproj @@ -85,9 +85,9 @@ ..\..\packages\Microsoft.Web.Xdt.1.0.0\lib\net40\Microsoft.Web.XmlTransform.dll - - False - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True False diff --git a/src/Libraries/SmartStore.Core/packages.config b/src/Libraries/SmartStore.Core/packages.config index 5765e490f2..61985f795a 100644 --- a/src/Libraries/SmartStore.Core/packages.config +++ b/src/Libraries/SmartStore.Core/packages.config @@ -8,7 +8,7 @@ - + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index b91c46a83d..69c108466d 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -93,9 +93,9 @@ True ..\..\packages\Microsoft.SqlServer.Scripting.11.0.2100.61\lib\Microsoft.SqlServer.Smo.dll - - False - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True diff --git a/src/Libraries/SmartStore.Data/packages.config b/src/Libraries/SmartStore.Data/packages.config index b8d63c5067..b1c169951c 100644 --- a/src/Libraries/SmartStore.Data/packages.config +++ b/src/Libraries/SmartStore.Data/packages.config @@ -6,5 +6,5 @@ - + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 3cd734dd9b..056e552845 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -104,9 +104,9 @@ ..\..\packages\ncrontab.2.0.0\lib\net20\NCrontab.dll - - False - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True False diff --git a/src/Libraries/SmartStore.Services/packages.config b/src/Libraries/SmartStore.Services/packages.config index 7f5b29df78..d078b22719 100644 --- a/src/Libraries/SmartStore.Services/packages.config +++ b/src/Libraries/SmartStore.Services/packages.config @@ -13,7 +13,7 @@ - + diff --git a/src/Plugins/SmartStore.AmazonPay/web.config b/src/Plugins/SmartStore.AmazonPay/web.config index 0e57dde0c1..3fd5b09a23 100644 --- a/src/Plugins/SmartStore.AmazonPay/web.config +++ b/src/Plugins/SmartStore.AmazonPay/web.config @@ -1,131 +1,131 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.Clickatell/web.config b/src/Plugins/SmartStore.Clickatell/web.config index 3e1b370f5d..ba87d0f098 100644 --- a/src/Plugins/SmartStore.Clickatell/web.config +++ b/src/Plugins/SmartStore.Clickatell/web.config @@ -1,116 +1,116 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.DevTools/Web.config b/src/Plugins/SmartStore.DevTools/Web.config index e1ac213bd8..b076d239ac 100644 --- a/src/Plugins/SmartStore.DevTools/Web.config +++ b/src/Plugins/SmartStore.DevTools/Web.config @@ -1,135 +1,135 @@ - + -
+
- - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - + diff --git a/src/Plugins/SmartStore.DiscountRules/SmartStore.DiscountRules.csproj b/src/Plugins/SmartStore.DiscountRules/SmartStore.DiscountRules.csproj index 813caac6a0..dc162de8da 100644 --- a/src/Plugins/SmartStore.DiscountRules/SmartStore.DiscountRules.csproj +++ b/src/Plugins/SmartStore.DiscountRules/SmartStore.DiscountRules.csproj @@ -85,8 +85,9 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True diff --git a/src/Plugins/SmartStore.DiscountRules/packages.config b/src/Plugins/SmartStore.DiscountRules/packages.config index 669284b1aa..0325f33297 100644 --- a/src/Plugins/SmartStore.DiscountRules/packages.config +++ b/src/Plugins/SmartStore.DiscountRules/packages.config @@ -4,5 +4,5 @@ - + \ No newline at end of file diff --git a/src/Plugins/SmartStore.DiscountRules/web.config b/src/Plugins/SmartStore.DiscountRules/web.config index 3e1b370f5d..ba87d0f098 100644 --- a/src/Plugins/SmartStore.DiscountRules/web.config +++ b/src/Plugins/SmartStore.DiscountRules/web.config @@ -1,116 +1,116 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.FacebookAuth/SmartStore.FacebookAuth.csproj b/src/Plugins/SmartStore.FacebookAuth/SmartStore.FacebookAuth.csproj index 734ef16423..3709846816 100644 --- a/src/Plugins/SmartStore.FacebookAuth/SmartStore.FacebookAuth.csproj +++ b/src/Plugins/SmartStore.FacebookAuth/SmartStore.FacebookAuth.csproj @@ -121,8 +121,9 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True diff --git a/src/Plugins/SmartStore.FacebookAuth/packages.config b/src/Plugins/SmartStore.FacebookAuth/packages.config index 7d02692d69..2cd39833bf 100644 --- a/src/Plugins/SmartStore.FacebookAuth/packages.config +++ b/src/Plugins/SmartStore.FacebookAuth/packages.config @@ -15,5 +15,5 @@ - + \ No newline at end of file diff --git a/src/Plugins/SmartStore.FacebookAuth/web.config b/src/Plugins/SmartStore.FacebookAuth/web.config index 6b304e839a..68523a15bc 100644 --- a/src/Plugins/SmartStore.FacebookAuth/web.config +++ b/src/Plugins/SmartStore.FacebookAuth/web.config @@ -1,120 +1,120 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.GoogleAnalytics/web.config b/src/Plugins/SmartStore.GoogleAnalytics/web.config index 32347fc005..c0f82db177 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/web.config +++ b/src/Plugins/SmartStore.GoogleAnalytics/web.config @@ -1,115 +1,115 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj index f027f7dd39..f743627997 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/SmartStore.GoogleMerchantCenter.csproj @@ -102,8 +102,9 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/packages.config b/src/Plugins/SmartStore.GoogleMerchantCenter/packages.config index 4966c1bd2c..3527aa9a5c 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/packages.config +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/packages.config @@ -8,5 +8,5 @@ - + \ No newline at end of file diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/web.config b/src/Plugins/SmartStore.GoogleMerchantCenter/web.config index 3e1b370f5d..ba87d0f098 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/web.config +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/web.config @@ -1,116 +1,116 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj b/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj index 2f5089d229..9083d62e99 100644 --- a/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj +++ b/src/Plugins/SmartStore.OfflinePayment/SmartStore.OfflinePayment.csproj @@ -91,8 +91,9 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True diff --git a/src/Plugins/SmartStore.OfflinePayment/packages.config b/src/Plugins/SmartStore.OfflinePayment/packages.config index ff044a8c8f..5669aa5ceb 100644 --- a/src/Plugins/SmartStore.OfflinePayment/packages.config +++ b/src/Plugins/SmartStore.OfflinePayment/packages.config @@ -6,5 +6,5 @@ - + \ No newline at end of file diff --git a/src/Plugins/SmartStore.OfflinePayment/web.config b/src/Plugins/SmartStore.OfflinePayment/web.config index 3e1b370f5d..ba87d0f098 100644 --- a/src/Plugins/SmartStore.OfflinePayment/web.config +++ b/src/Plugins/SmartStore.OfflinePayment/web.config @@ -1,116 +1,116 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.PayPal/web.config b/src/Plugins/SmartStore.PayPal/web.config index d9badc0f86..add05adbda 100644 --- a/src/Plugins/SmartStore.PayPal/web.config +++ b/src/Plugins/SmartStore.PayPal/web.config @@ -1,11 +1,11 @@ - + -
+
- + @@ -14,131 +14,131 @@ - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.Shipping/web.config b/src/Plugins/SmartStore.Shipping/web.config index 32347fc005..c0f82db177 100644 --- a/src/Plugins/SmartStore.Shipping/web.config +++ b/src/Plugins/SmartStore.Shipping/web.config @@ -1,115 +1,115 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.ShippingByWeight/web.config b/src/Plugins/SmartStore.ShippingByWeight/web.config index 32347fc005..c0f82db177 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/web.config +++ b/src/Plugins/SmartStore.ShippingByWeight/web.config @@ -1,115 +1,115 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.Tax/SmartStore.Tax.csproj b/src/Plugins/SmartStore.Tax/SmartStore.Tax.csproj index 692427666d..3f4f325f97 100644 --- a/src/Plugins/SmartStore.Tax/SmartStore.Tax.csproj +++ b/src/Plugins/SmartStore.Tax/SmartStore.Tax.csproj @@ -96,8 +96,9 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True diff --git a/src/Plugins/SmartStore.Tax/packages.config b/src/Plugins/SmartStore.Tax/packages.config index cc784b42f4..7f794ac209 100644 --- a/src/Plugins/SmartStore.Tax/packages.config +++ b/src/Plugins/SmartStore.Tax/packages.config @@ -6,5 +6,5 @@ - + \ No newline at end of file diff --git a/src/Plugins/SmartStore.Tax/web.config b/src/Plugins/SmartStore.Tax/web.config index 3e1b370f5d..ba87d0f098 100644 --- a/src/Plugins/SmartStore.Tax/web.config +++ b/src/Plugins/SmartStore.Tax/web.config @@ -1,116 +1,116 @@ - + - - + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + diff --git a/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj b/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj index 2961aa6848..73e315200d 100644 --- a/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj +++ b/src/Plugins/SmartStore.WebApi/SmartStore.WebApi.csproj @@ -107,8 +107,9 @@ ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True diff --git a/src/Plugins/SmartStore.WebApi/packages.config b/src/Plugins/SmartStore.WebApi/packages.config index 4b574c3ff9..9e4fbc38a8 100644 --- a/src/Plugins/SmartStore.WebApi/packages.config +++ b/src/Plugins/SmartStore.WebApi/packages.config @@ -16,6 +16,6 @@ - + \ No newline at end of file diff --git a/src/Plugins/SmartStore.WebApi/web.config b/src/Plugins/SmartStore.WebApi/web.config index 71013386a2..95ed427e0a 100644 --- a/src/Plugins/SmartStore.WebApi/web.config +++ b/src/Plugins/SmartStore.WebApi/web.config @@ -50,7 +50,7 @@ - + diff --git a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj index 729a38d055..b37e9baf66 100644 --- a/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj +++ b/src/Presentation/SmartStore.Web.Framework/SmartStore.Web.Framework.csproj @@ -122,9 +122,9 @@ True ..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - False - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True diff --git a/src/Presentation/SmartStore.Web.Framework/app.config b/src/Presentation/SmartStore.Web.Framework/app.config index 5d4773d412..751632b38f 100644 --- a/src/Presentation/SmartStore.Web.Framework/app.config +++ b/src/Presentation/SmartStore.Web.Framework/app.config @@ -24,7 +24,7 @@ - + diff --git a/src/Presentation/SmartStore.Web.Framework/packages.config b/src/Presentation/SmartStore.Web.Framework/packages.config index 06b8ceab7c..cbc00e31fa 100644 --- a/src/Presentation/SmartStore.Web.Framework/packages.config +++ b/src/Presentation/SmartStore.Web.Framework/packages.config @@ -24,7 +24,7 @@ - + \ 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 ed9af3db99..7d95256bb5 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -92,9 +92,9 @@ True ..\..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll - - False - ..\..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True False diff --git a/src/Presentation/SmartStore.Web/Administration/Web.config b/src/Presentation/SmartStore.Web/Administration/Web.config index 82720de3ee..7f12a993a8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Web.config +++ b/src/Presentation/SmartStore.Web/Administration/Web.config @@ -64,7 +64,7 @@ - + diff --git a/src/Presentation/SmartStore.Web/Administration/packages.config b/src/Presentation/SmartStore.Web/Administration/packages.config index 2f861314d9..7e1b1648ae 100644 --- a/src/Presentation/SmartStore.Web/Administration/packages.config +++ b/src/Presentation/SmartStore.Web/Administration/packages.config @@ -15,6 +15,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 186177349f..2bd35ba917 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -135,9 +135,9 @@ ..\..\packages\MsieJavaScriptEngine.1.5.6\lib\net40\MsieJavaScriptEngine.dll True - - False - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll + True diff --git a/src/Presentation/SmartStore.Web/Web.config b/src/Presentation/SmartStore.Web/Web.config index 40c1ea7c79..a08b655642 100644 --- a/src/Presentation/SmartStore.Web/Web.config +++ b/src/Presentation/SmartStore.Web/Web.config @@ -265,7 +265,7 @@ - + diff --git a/src/Presentation/SmartStore.Web/packages.config b/src/Presentation/SmartStore.Web/packages.config index 72c6be3548..23a46f19b7 100644 --- a/src/Presentation/SmartStore.Web/packages.config +++ b/src/Presentation/SmartStore.Web/packages.config @@ -27,7 +27,7 @@ - + \ No newline at end of file diff --git a/src/Tests/SmartStore.Web.MVC.Tests/App.config b/src/Tests/SmartStore.Web.MVC.Tests/App.config index 47411d9ac8..c359ffa2b2 100644 --- a/src/Tests/SmartStore.Web.MVC.Tests/App.config +++ b/src/Tests/SmartStore.Web.MVC.Tests/App.config @@ -15,7 +15,7 @@ - + diff --git a/src/Tools/SmartStore.WebApi.Client/SmartStore.WebApi.Client.csproj b/src/Tools/SmartStore.WebApi.Client/SmartStore.WebApi.Client.csproj index 8d8be73c77..68e94b3d54 100644 --- a/src/Tools/SmartStore.WebApi.Client/SmartStore.WebApi.Client.csproj +++ b/src/Tools/SmartStore.WebApi.Client/SmartStore.WebApi.Client.csproj @@ -59,8 +59,8 @@ true - - ..\..\packages\Newtonsoft.Json.6.0.6\lib\net45\Newtonsoft.Json.dll + + ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll True diff --git a/src/Tools/SmartStore.WebApi.Client/packages.config b/src/Tools/SmartStore.WebApi.Client/packages.config index 5cb1720db6..583acbdf9c 100644 --- a/src/Tools/SmartStore.WebApi.Client/packages.config +++ b/src/Tools/SmartStore.WebApi.Client/packages.config @@ -1,4 +1,4 @@  - + \ No newline at end of file From 2ff963ab4280f059ae3e5ed8e93a94a6266cc97b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 16 Apr 2016 17:19:47 +0200 Subject: [PATCH 039/285] PayPal Plus: Rendering payment wall --- .../Controllers/PayPalPlusController.cs | 43 +++--- .../Localization/resources.de-de.xml | 27 ++-- .../Localization/resources.en-us.xml | 28 ++-- .../Models/PayPalPlusCheckoutModel.cs | 1 + .../Services/IPayPalService.cs | 7 +- .../Services/PayPalService.cs | 135 +++++++++++++++--- .../Views/PayPalPlus/Configure.cshtml | 2 +- .../Views/PayPalPlus/PaymentWall.cshtml | 14 +- .../PayPalPlus/PaymentWallScripting.cshtml | 69 ++++----- 9 files changed, 207 insertions(+), 119 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index bdb302b649..49bb7304da 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -162,35 +162,40 @@ public ActionResult PaymentWall() model.BillingAddressCountryCode = customer.BillingAddress.Country.TwoLetterIsoCode; } - var protocol = (store.SslEnabled ? "https" : "http"); - var returnUrl = Url.Action("CheckoutReturn", "PayPalPlus", new { area = Plugin.SystemName }, protocol); - var cancelUrl = Url.Action("CheckoutCancel", "PayPalPlus", new { area = Plugin.SystemName }, protocol); - - - var result = _payPalService.EnsureAccessToken(session, settings); - if (result.Success) + if (session.PaymentId.IsEmpty() || session.ApprovalUrl.IsEmpty()) { - result = _payPalService.UpsertPayment(settings, session, null, returnUrl, cancelUrl); - if (result.Success && result.Json != null) + var result = _payPalService.EnsureAccessToken(session, settings); + if (result.Success) { - foreach (var link in result.Json.links) + var protocol = (store.SslEnabled ? "https" : "http"); + var returnUrl = Url.Action("CheckoutReturn", "PayPalPlus", new { area = Plugin.SystemName }, protocol); + var cancelUrl = Url.Action("CheckoutCancel", "PayPalPlus", new { area = Plugin.SystemName }, protocol); + + result = _payPalService.CreatePayment(settings, session, PayPalPlusProvider.SystemName, returnUrl, cancelUrl); + if (result.Success && result.Json != null) { - if (((string)link.rel).IsCaseInsensitiveEqual("approval_url")) + foreach (var link in result.Json.links) { - model.ApprovalUrl = link.href; - break; + if (((string)link.rel).IsCaseInsensitiveEqual("approval_url")) + { + session.PaymentId = result.Id; + session.ApprovalUrl = link.href; + break; + } } } + else + { + model.ErrorMessage = result.ErrorMessage; + } } - else if (result.ErrorMessage.HasValue()) + else { - ModelState.AddModelError("", result.ErrorMessage); + model.ErrorMessage = result.ErrorMessage; } } - else if (result.ErrorMessage.HasValue()) - { - ModelState.AddModelError("", result.ErrorMessage); - } + + model.ApprovalUrl = session.ApprovalUrl; return View(model); } diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index 43419425fe..d4abb7441e 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -25,6 +25,9 @@ Jetzt aktualisieren + + Sonstiges + Client-ID @@ -379,27 +382,13 @@ Zahlungsgebühr: {10}]]> - Stellen Sie bitte sicher, dass PayPal die Primärwährung Ihres Shops unterstützt.

-
    -
  1. Loggen Sie sich in Ihr PayPal-Konto ein.
  2. -
  3. Klicken Sie auf den Register Mein Profil.
  4. -
  5. Klicken Sie unter Verkäufer/Händler auf API-Zugriff.
  6. -
  7. Klicken Sie auf API-Berechtigung anfordern.
  8. -
  9. Wählen Sie die Option Fordern Sie eine API-Signature an.
  10. -
  11. Klicken Sie auf Zustimmen und senden.
  12. -
-

Aktivieren Sie optional den PayPal-Service zum Empfang von IPN-Nachrichten (Instant Payment Notification):

-
    -
  1. In Ihr Premier- oder Business-Konto einloggen.
  2. -
  3. Auf den Register Mein Profil klicken.
  4. -
  5. Unter Sprach-Kodierung > Weitere Einstellungen wählen Sie bitte UTF-8.
  6. -
  7. Sofortige Zahlungsbestätigung klicken.
  8. -
  9. Einstellungen für sofortige Zahlungsbestätigungen wählen klicken.
  10. -
  11. Bei Benachrichtigungs-URL die URL Ihres IPN-Handlers (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalPlus/IPNHandler) eingeben.
  12. -
  13. Speichern klicken. Danach sollten Sie eine Nachricht über die erfolgreiche Aktivierung von IPN erhalten.
  14. -
]]> + PayPal PLUS bietet die Möglichkeit per PayPal, Kreditkarte, Lastschriftverfahren und per Rechnung zu bezahlen. +Die Bezahlung auf Rechnung steht möglicherweise nicht allen Händlern zur Verfügung. PayPal PLUS ist im Moment nur in Deutschland verfügbar.

]]>
+ + Wir können Ihnen leider keine PayPal PLUS Zahlungsart anbieten. Bitte versuchen Sie er später erneut. +
diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index 392835a2b9..8661213392 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -25,6 +25,9 @@ Update now + + Miscellaneous + Client ID @@ -379,28 +382,13 @@ Payment fee: {10}]]> - If you're using PayPal ensure that your primary store currency is supported by Paypal.

-
    -
  1. Log into your PayPal account.
  2. -
  3. Click on Profile from the Home Menu.
  4. -
  5. Under Account Information choose API Access.
  6. -
  7. Choose Request API credentials.
  8. -
  9. Choose API Signature as the credential type.
  10. -
  11. Enable the checkbox and agree to the license Terms-I agree to the API Certificate Terms of Use and terms incorporated therein and press the Submit button.
  12. -
-

Before being able to receive IPN (Instant Payment Notification) messages (optional), you'll need 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 Language Encoding and More options and select UTF-8.
  6. -
  7. Click Instant Payment Notification in the Selling Preferences column.
  8. -
  9. Click the Edit IPN Settings button to update your settings.
  10. -
  11. Select Receive IPN messages (enabled) and enter the URL of your IPN handler (https://www.yourStore.com/Plugins/SmartStore.PayPal/PayPalPlus/IPNHandler).
  12. -
  13. Click Save, and you should get a message that you have successfully activated IPN.
  14. -
]]> + PayPal PLUS is a solution where PayPal offers PayPal, Credit Card, Direct Debit (ELV) and pay upon invoice as individual payment options on the payment selection page. +Pay upon invoice may not be available for all merchants. PayPal PLUS is only available in Germany at the moment.

]]>
+ + Unfortunately we cannot offer you a PayPal PLUS payment method. Please try again later. +
diff --git a/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs b/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs index f9fec802a2..dcb5b83bb2 100644 --- a/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs +++ b/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs @@ -9,5 +9,6 @@ public class PayPalPlusCheckoutModel : ModelBase public string BillingAddressCountryCode { get; set; } public string LanguageCulture { get; set; } public string ApprovalUrl { get; set; } + public string ErrorMessage { get; set; } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs index 1dedce8e88..7305762d68 100644 --- a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -18,11 +18,6 @@ public interface IPayPalService PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, PayPalSessionData session, Store store, string profileId); - PayPalResponse UpsertPayment( - PayPalApiSettingsBase settings, - PayPalSessionData session, - string paymentId, - string returnUrl, - string cancelUrl); + PayPalResponse CreatePayment(PayPalApiSettingsBase settings, PayPalSessionData session, string providerSystemName, string returnUrl, string cancelUrl); } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 33a4d0d78a..5718a77bab 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -7,15 +7,22 @@ using System.Web; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Logging; using SmartStore.Core.Domain.Orders; using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Domain.Tax; using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.PayPal.Settings; using SmartStore.Services; +using SmartStore.Services.Catalog; +using SmartStore.Services.Customers; +using SmartStore.Services.Localization; using SmartStore.Services.Media; using SmartStore.Services.Orders; +using SmartStore.Services.Payments; +using SmartStore.Services.Tax; namespace SmartStore.PayPal.Services { @@ -23,15 +30,27 @@ public class PayPalService : IPayPalService { private readonly ICommonServices _services; private readonly IOrderService _orderService; + private readonly IOrderTotalCalculationService _orderTotalCalculationService; + private readonly IPaymentService _paymentService; + private readonly IPriceCalculationService _priceCalculationService; + private readonly ITaxService _taxService; private readonly Lazy _pictureService; public PayPalService( ICommonServices services, IOrderService orderService, + IOrderTotalCalculationService orderTotalCalculationService, + IPaymentService paymentService, + IPriceCalculationService priceCalculationService, + ITaxService taxService, Lazy pictureService) { _services = services; _orderService = orderService; + _orderTotalCalculationService = orderTotalCalculationService; + _paymentService = paymentService; + _priceCalculationService = priceCalculationService; + _taxService = taxService; _pictureService = pictureService; T = NullLocalizer.Instance; @@ -322,25 +341,48 @@ public PayPalResponse UpsertCheckoutExperience(PayPalApiSettingsBase settings, P return result; } - public PayPalResponse UpsertPayment( - PayPalApiSettingsBase settings, - PayPalSessionData session, - string paymentId, - string returnUrl, - string cancelUrl) + public PayPalResponse CreatePayment(PayPalApiSettingsBase settings, PayPalSessionData session, string providerSystemName, string returnUrl, string cancelUrl) { + var store = _services.StoreContext.CurrentStore; + var customer = _services.WorkContext.CurrentCustomer; + var language = _services.WorkContext.WorkingLanguage; + var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); + var currencyCode = store.PrimaryStoreCurrency.CurrencyCode; + + Discount orderAppliedDiscount; + List appliedGiftCards; + int redeemedRewardPoints = 0; + decimal redeemedRewardPointsAmount; + decimal orderDiscountInclTax; + decimal totalOrderItems = decimal.Zero; + + var includingTax = (_services.WorkContext.GetTaxDisplayTypeFor(customer, store.Id) == TaxDisplayType.IncludingTax); + + var shipping = (_orderTotalCalculationService.GetShoppingCartShippingTotal(cart) ?? decimal.Zero); + + var paymentFee = _paymentService.GetAdditionalHandlingFee(cart, providerSystemName); + + var total = (_orderTotalCalculationService.GetShoppingCartTotal(cart, out orderDiscountInclTax, out orderAppliedDiscount, out appliedGiftCards, + out redeemedRewardPoints, out redeemedRewardPointsAmount) ?? decimal.Zero); + var path = "/v1/payments/payment"; var data = new Dictionary(); var redirectUrls = new Dictionary(); + var payer = new Dictionary(); + var transaction = new Dictionary(); + var amount = new Dictionary(); + var amountDetails = new Dictionary(); + var items = new List>(); - if (paymentId.HasValue()) - path = string.Concat(path, "/", HttpUtility.UrlPathEncode(paymentId)); + if (session.PaymentId.HasValue()) + path = string.Concat(path, "/", HttpUtility.UrlPathEncode(session.PaymentId)); data.Add("intent", settings.TransactMode == TransactMode.AuthorizeAndCapture ? "sale" : "authorize"); if (settings.ExperienceProfileId.HasValue()) data.Add("experience_profile_id", settings.ExperienceProfileId); + // redirect urls if (returnUrl.HasValue()) redirectUrls.Add("return_url", returnUrl); @@ -350,19 +392,78 @@ public PayPalResponse UpsertPayment( if (redirectUrls.Any()) data.Add("redirect_urls", redirectUrls); - if (paymentId.IsEmpty()) + // payer + payer.Add("payment_method", "paypal"); + data.Add("payer", payer); + + // line items + foreach (var item in cart) { - // TODO: add line items + decimal unitPriceTaxRate = decimal.Zero; + decimal unitPrice = _priceCalculationService.GetUnitPrice(item, true); + decimal productPrice = _taxService.GetProductPrice(item.Item.Product, unitPrice, includingTax, customer, out unitPriceTaxRate); + + var line = new Dictionary(); + line.Add("quantity", item.Item.Quantity); + line.Add("name", item.Item.Product.GetLocalized(x => x.Name, language.Id, true, false).Truncate(127)); + line.Add("price", Math.Round(productPrice, 2)); + line.Add("currency", currencyCode); + line.Add("sku", item.Item.Product.Sku.Truncate(50)); + items.Add(line); + + totalOrderItems += (productPrice * item.Item.Quantity); } - var result = CallApi(paymentId.HasValue() ? "PATCH" : "POST", path, session.AccessToken, settings, JsonConvert.SerializeObject(data)); + var itemsPlusMisc = (totalOrderItems + shipping + paymentFee); - if (result.Success) + if (total != itemsPlusMisc) { - if (result.Json != null) - result.Id = (string)result.Json.id; - else - result.Id = paymentId; + var line = new Dictionary(); + line.Add("quantity", "1"); + line.Add("name", T("Plugins.SmartStore.PayPal.Other").Text.Truncate(127)); + line.Add("price", Math.Round(total - itemsPlusMisc, 2)); + line.Add("currency", currencyCode); + items.Add(line); + + totalOrderItems += (total - itemsPlusMisc); + } + + // transactions + amountDetails.Add("shipping", Math.Round(shipping, 2)); + amountDetails.Add("subtotal", Math.Round(totalOrderItems, 2)); + if (!includingTax) + { + // To avoid rounding errors we recommend not submitting tax amounts on line item basis. + // Calculated tax amounts for the entire shopping basket may be submitted in the amount objects. + // In this case the item amounts will be treated as amounts excluding tax. + // In a B2C scenario, where taxes are included, no taxes should be submitted to PayPal. + + SortedDictionary taxRates = null; + var taxTotal = _orderTotalCalculationService.GetTaxTotal(cart, out taxRates); + + amountDetails.Add("tax", Math.Round(taxTotal, 2)); + } + if (paymentFee != decimal.Zero) + { + amountDetails.Add("handling_fee", Math.Round(paymentFee, 2)); + } + + amount.Add("total", Math.Round(total, 2)); + amount.Add("currency", currencyCode); + amount.Add("details", amountDetails); + + transaction.Add("amount", amount); + transaction.Add("item_list", new Dictionary { { "items" , items } }); + + data.Add("transactions", new List> { transaction }); + + var result = CallApi("POST", path, session.AccessToken, settings, JsonConvert.SerializeObject(data)); + + if (result.Success && result.Json != null) + { + result.Id = (string)result.Json.id; + + Logger.InsertLog(LogLevel.Information, "PayPal PLUS", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + result.Json.ToString()); } return result; @@ -382,5 +483,7 @@ public class PayPalSessionData { public string AccessToken { get; set; } public DateTime TokenExpiration { get; set; } + public string PaymentId { get; set; } + public string ApprovalUrl { get; set; } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml index 45c7a157f3..6b32a76ada 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml @@ -43,7 +43,6 @@ @Html.SmartLabelFor(model => model.UseSandbox)
- @*multistore context not possible, see IPN handling*@ @Html.EditorFor(model => model.UseSandbox) @Html.ValidationMessageFor(model => model.UseSandbox)
+
+
+ @Model.ConfigGroups.SafeGet(2) +
+
+
- @Html.SmartLabelFor(model => model.TransactMode) + @Html.SmartLabelFor(model => model.ThirdPartyPaymentMethods) - @Html.SettingOverrideCheckbox(model => model.TransactMode) - @Html.DropDownListFor(model => model.TransactMode, Model.TransactModeValues) + @Html.SettingOverrideCheckbox(model => model.ThirdPartyPaymentMethods) + @Html.ListBoxFor(x => x.ThirdPartyPaymentMethods, + new MultiSelectList(Model.AvailableThirdPartyPaymentMethods, "Value", "Text"), + new { multiple = "multiple", @class = "control-xlarge" }) + @Html.ValidationMessageFor(model => model.ThirdPartyPaymentMethods)
- @Html.SmartLabelFor(model => model.IpnChangesPaymentStatus) + @Html.SmartLabelFor(model => model.DisplayPaymentMethodLogo) - @Html.SettingEditorFor(model => model.IpnChangesPaymentStatus) - @Html.ValidationMessageFor(model => model.IpnChangesPaymentStatus) + @Html.SettingEditorFor(model => model.DisplayPaymentMethodLogo) + @Html.ValidationMessageFor(model => model.DisplayPaymentMethodLogo)
-
-
- @Model.ConfigGroups.SafeGet(2) -
-
+
+ @Html.SmartLabelFor(model => model.DisplayPaymentMethodDescription) + + @Html.SettingEditorFor(model => model.DisplayPaymentMethodDescription) + @Html.ValidationMessageFor(model => model.DisplayPaymentMethodDescription)
- @Html.SmartLabelFor(model => model.ExperienceProfileId) - - @Html.SettingEditorFor(model => model.ExperienceProfileId) - - -   - ! - - - @Html.ValidationMessageFor(model => model.ExperienceProfileId) -
@@ -101,6 +84,66 @@ @Html.DropDownListFor(model => model.SecurityProtocol, Model.AvailableSecurityProtocols, T("Common.Unspecified"))
+ @Html.SmartLabelFor(model => model.ExperienceProfileId) + + @Html.SettingEditorFor(model => model.ExperienceProfileId) + + + +  ! + + + @if (Model.ExperienceProfileId.HasValue()) + { + +  @T("Admin.Common.Delete") + + } + + @Html.ValidationMessageFor(model => model.ExperienceProfileId) +
+ @Html.SmartLabelFor(model => model.WebhookId) + + @* IPNs and webhook messages have no store context, so multistore configuration not possible here *@ + @Html.EditorFor(model => model.WebhookId) + + @if (Model.WebhookId.HasValue()) + { + +  @T("Admin.Common.Delete") + + } + else + { + +  @T("Common.AddNew") + + } + + @Html.ValidationMessageFor(model => model.WebhookId) +
+ @Html.SmartLabelFor(model => model.WebhookValidation) + + @Html.DropDownListFor(model => model.WebhookValidation, Model.AvailableWebhookValidations) + @Html.ValidationMessageFor(model => model.WebhookValidation) +
From 2332bc0a6381d4853d6b91891a23abe1a52b23e9 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 22 Apr 2016 10:51:58 +0200 Subject: [PATCH 049/285] PayPal Plus: Webhook implementation --- .../Controllers/PayPalPlusController.cs | 2 - .../PayPalRestApiControllerBase.cs | 25 ++- .../Localization/resources.de-de.xml | 25 ++- .../Localization/resources.en-us.xml | 25 ++- .../Models/ApiConfigurationModels.cs | 6 - .../Providers/PayPalRestApiProviderBase.cs | 10 +- .../Services/IPayPalService.cs | 6 +- .../SmartStore.PayPal/Services/PayPalEnums.cs | 10 +- .../Services/PayPalService.cs | 165 ++++++++++++++---- .../Settings/PayPalSettings.cs | 10 -- .../Views/PayPalPlus/Configure.cshtml | 9 - 11 files changed, 188 insertions(+), 105 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index 6d5b086ce9..4a5154fde3 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -146,8 +146,6 @@ public ActionResult Configure() .Select(x => new SelectListItem { Value = ((int)x.Key).ToString(), Text = x.Value }) .ToList(); - model.AvailableWebhookValidations = PayPalWebhookValidation.Simple.ToSelectList(false); - // it's better to also offer inactive methods here but filter them out in frontend var methods = _paymentService.LoadAllPaymentMethods(storeScope); diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalRestApiControllerBase.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalRestApiControllerBase.cs index f3c02ada78..8d5818e5d5 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalRestApiControllerBase.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalRestApiControllerBase.cs @@ -1,4 +1,6 @@ -using System.IO; +using System; +using System.IO; +using System.Net; using System.Web.Mvc; using SmartStore.Core.Configuration; using SmartStore.PayPal.Services; @@ -150,15 +152,24 @@ public ActionResult DeleteWebhook() [ValidateInput(false)] public ActionResult Webhook() { - string json = null; - using (var reader = new StreamReader(Request.InputStream)) + HttpStatusCode result = HttpStatusCode.OK; + + try { - json = reader.ReadToEnd(); - } + string json = null; + using (var reader = new StreamReader(Request.InputStream)) + { + json = reader.ReadToEnd(); + } - var settings = Services.Settings.LoadSetting(); + var settings = Services.Settings.LoadSetting(); - var result = PayPalService.ProcessWebhook(settings, Request.Headers, json); + result = PayPalService.ProcessWebhook(settings, Request.Headers, json, PayPalPlusProvider.SystemName); + } + catch (Exception exception) + { + PayPalService.LogError(exception, isWarning: true); + } return new HttpStatusCodeResult(result); } diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index d0233e1d90..43a70d229d 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -13,6 +13,9 @@ {0}]]> + + Mitteilung;Ereignis;Ereignis-ID;Status;Betrag;Zahlungs-ID + Jetzt erstellen @@ -31,8 +34,14 @@ Referenz;Bankleitzahl;Bank;BIC;IBAN;Kontoinhaber;Kontonummer;Betrag;Zahlung fällig am;Details - - Validierung einer Webhook-Nachricht ist fehlgeschlagen + + Fehlende Payment-ID. + + + Keine Statusaktualisierung, da {0} Aufträge mit der Payment-ID {1} gefunden wurden. + + + Währung {0} entspricht nicht der Leitwährung des Shops {1}. @@ -65,18 +74,6 @@ Legt die Webhook-ID fest. Über einen Webhook sendet PayPal Statusänderungen von Zahlungen an Ihren Shop, wodurch der Zahlungsstatus Ihrer Aufträge automatisch aktualisert werden kann. - - Validierung von Nachrichten - - - Legt fest, ob und wie Webhook-Nachrichten validiert werden sollen. - - - Keine - - - Einfach - diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index 7f8575af59..b1005615cc 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -13,6 +13,9 @@ {0}]]> + + Message;Event;Event-ID;State;Amount;Payment-ID + Create now @@ -34,8 +37,14 @@ Reference;Bank routing number;Bank;BIC;IBAN;Account holder;Account number;Amount;Payment due date;Details - - Validation of a webhook message has failed + + Missing payment ID. + + + No status update because {0} orders with payment ID {1} found. + + + Currency {0} does not equal primary store currency {1}. @@ -65,18 +74,6 @@ Specifies the webhook ID. PayPal sends payment status changes via a webhook to your shop, whereby the payment status of your orders can be automatically synced. - - Message validation - - - Specifies if and how webhook messages should be validated. - - - None - - - Simple - diff --git a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs index 59c4b69b54..d7752e61f5 100644 --- a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs +++ b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs @@ -50,10 +50,6 @@ public abstract class ApiConfigurationModel: ModelBase [SmartResourceDisplayName("Plugins.SmartStore.PayPal.WebhookId")] public string WebhookId { get; set; } - [SmartResourceDisplayName("Plugins.SmartStore.PayPal.WebhookValidation")] - public PayPalWebhookValidation WebhookValidation { get; set; } - public SelectList AvailableWebhookValidations { get; set; } - [SmartResourceDisplayName("Plugins.Payments.PayPal.AdditionalFee")] public decimal AdditionalFee { get; set; } @@ -175,7 +171,6 @@ public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) Secret = settings.Secret; ExperienceProfileId = settings.ExperienceProfileId; WebhookId = settings.WebhookId; - WebhookValidation = settings.WebhookValidation; ThirdPartyPaymentMethods = settings.ThirdPartyPaymentMethods; DisplayPaymentMethodLogo = settings.DisplayPaymentMethodLogo; DisplayPaymentMethodDescription = settings.DisplayPaymentMethodDescription; @@ -191,7 +186,6 @@ public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) settings.Secret = Secret; settings.ExperienceProfileId = ExperienceProfileId; settings.WebhookId = WebhookId; - settings.WebhookValidation = WebhookValidation; settings.ThirdPartyPaymentMethods = ThirdPartyPaymentMethods; settings.DisplayPaymentMethodLogo = DisplayPaymentMethodLogo; settings.DisplayPaymentMethodDescription = DisplayPaymentMethodDescription; diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs index c48723fce6..a9208ac82b 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs @@ -98,11 +98,14 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces if (!state.IsCaseInsensitiveEqual("failed")) { + // the payment id is required to find the order during webhook message processing + result.AuthorizationTransactionCode = apiResult.Id; + // intent: "sale" for immediate payment, "authorize" for pre-authorized payments and "order" for an order. // info required cause API has different endpoints for different intents. - result.AuthorizationTransactionCode = (string)apiResult.Json.intent; + var intent = (string)apiResult.Json.intent; - if (result.AuthorizationTransactionCode.IsCaseInsensitiveEqual("sale")) + if (intent.IsCaseInsensitiveEqual("sale")) { relatedObject = apiResult.Json.transactions[0].related_resources[0].sale; @@ -118,7 +121,8 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces state = (string)relatedObject.state; reasonCode = (string)relatedObject.reason_code; - result.AuthorizationTransactionResult = state; + // see PayPalService.Refund() + result.AuthorizationTransactionResult = "{0} ({1})".FormatInvariant(state.NaIfEmpty(), intent.NaIfEmpty()); result.AuthorizationTransactionId = (string)relatedObject.id; result.NewPaymentStatus = PayPalService.GetPaymentStatus(state, reasonCode, PaymentStatus.Authorized); diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs index ed74d18e17..cfb377fabb 100644 --- a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -52,6 +52,10 @@ PayPalResponse CreatePayment( PayPalResponse DeleteWebhook(PayPalApiSettingsBase settings, PayPalSessionData session); - HttpStatusCode ProcessWebhook(PayPalApiSettingsBase settings, NameValueCollection headers, string rawJson); + HttpStatusCode ProcessWebhook( + PayPalApiSettingsBase settings, + NameValueCollection headers, + string rawJson, + string providerSystemName); } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalEnums.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalEnums.cs index 35561b4d1f..d5bba1b72c 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalEnums.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalEnums.cs @@ -15,9 +15,13 @@ public enum PayPalPaymentInstructionItem Details } - public enum PayPalWebhookValidation + public enum PayPalMessage { - None = 0, - Simple + Message = 0, + Event, + EventId, + State, + Amount, + PaymentId } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index c6dda81ff2..1c648b68e1 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -9,6 +9,7 @@ using System.Web; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using SmartStore.Core.Data; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Logging; @@ -34,8 +35,10 @@ namespace SmartStore.PayPal.Services { public class PayPalService : IPayPalService { + private readonly Lazy> _orderRepository; private readonly ICommonServices _services; private readonly IOrderService _orderService; + private readonly IOrderProcessingService _orderProcessingService; private readonly IOrderTotalCalculationService _orderTotalCalculationService; private readonly IPaymentService _paymentService; private readonly IPriceCalculationService _priceCalculationService; @@ -45,8 +48,10 @@ public class PayPalService : IPayPalService private readonly Lazy _companyInfoSettings; public PayPalService( + Lazy> orderRepository, ICommonServices services, IOrderService orderService, + IOrderProcessingService orderProcessingService, IOrderTotalCalculationService orderTotalCalculationService, IPaymentService paymentService, IPriceCalculationService priceCalculationService, @@ -55,8 +60,10 @@ public PayPalService( Lazy pictureService, Lazy companyInfoSettings) { + _orderRepository = orderRepository; _services = services; _orderService = orderService; + _orderProcessingService = orderProcessingService; _orderTotalCalculationService = orderTotalCalculationService; _paymentService = paymentService; _priceCalculationService = priceCalculationService; @@ -102,44 +109,39 @@ private Dictionary CreateAddress(Address addr) return dic; } - private bool IsValidMessage(PayPalApiSettingsBase settings, dynamic json) + private string ToInfoString(dynamic json) { + var sb = new StringBuilder(); + try { - if (settings.WebhookValidation == PayPalWebhookValidation.Simple) - { - // own, low level validation - var paymentId = (string)json.resource.parent_payment; - - if (paymentId.IsEmpty() || settings.WebhookId.IsEmpty() || !settings.WebhookId.IsCaseInsensitiveEqual(settings.WebhookId)) - return false; + string[] strings = T("Plugins.SmartStore.PayPal.MessageStrings").Text.SplitSafe(";"); + var message = (string)json.summary; + var eventType = (string)json.event_type; + var eventId = (string)json.id; + string state = null; + string amount = null; + string paymentId = null; + if (json.resource != null) + { + state = (string)json.resource.state; + paymentId = (string)json.resource.parent_payment; + if (json.resource.amount != null) + amount = string.Concat((string)json.resource.amount.total, " ", (string)json.resource.amount.currency); } - // validating against PayPal SDK frequently failing: - //var webhookId = setting.WebhookId; // or (string)json.id for simulator - //var apiContext = new global::PayPal.Api.APIContext - //{ - // AccessToken = "I do not have one here", - // Config = new Dictionary - // { - // { "mode", settings.UseSandbox ? "sandbox" : "live" }, - // { "clientId", settings.ClientId }, - // { "clientSecret", settings.Secret }, - // { "webhook.id", webhookId }, - // } - //}; - //var result = global::PayPal.Api.WebhookEvent.ValidateReceivedEvent(apiContext, headers, rawJson, webhookId); - //} - } - catch (Exception exception) - { - LogError(exception, T("Plugins.SmartStore.PayPal.WebhookValidationFailed"), isWarning: true); - return false; + sb.AppendLine("{0}: {1}".FormatInvariant(strings.SafeGet((int)PayPalMessage.Message), message.NaIfEmpty())); + sb.AppendLine("{0}: {1}".FormatInvariant(strings.SafeGet((int)PayPalMessage.Event), eventType.NaIfEmpty())); + sb.AppendLine("{0}: {1}".FormatInvariant(strings.SafeGet((int)PayPalMessage.EventId), eventId.NaIfEmpty())); + sb.AppendLine("{0}: {1}".FormatInvariant(strings.SafeGet((int)PayPalMessage.PaymentId), paymentId.NaIfEmpty())); + sb.AppendLine("{0}: {1}".FormatInvariant(strings.SafeGet((int)PayPalMessage.State), state.NaIfEmpty())); + sb.AppendLine("{0}: {1}".FormatInvariant(strings.SafeGet((int)PayPalMessage.Amount), amount.NaIfEmpty())); } + catch { } - return true; + return sb.ToString(); } public static string GetApiUrl(bool sandbox) @@ -754,7 +756,7 @@ public PayPalResponse ExecutePayment(PayPalApiSettingsBase settings, PayPalSessi public PayPalResponse Refund(PayPalApiSettingsBase settings, PayPalSessionData session, RefundPaymentRequest request) { var data = new Dictionary(); - var isSale = request.Order.AuthorizationTransactionCode.IsCaseInsensitiveEqual("sale"); + var isSale = request.Order.AuthorizationTransactionResult.Contains("(sale)"); var path = "/v1/payments/{0}/{1}/refund".FormatInvariant(isSale ? "sale" : "capture", request.Order.CaptureTransactionId); @@ -900,7 +902,17 @@ public PayPalResponse CreateWebhook(PayPalApiSettingsBase settings, PayPalSessio var data = new Dictionary(); var events = new List>(); - events.Add(new Dictionary { { "name", "*" } }); + events.Add(new Dictionary { { "name", "PAYMENT.AUTHORIZATION.VOIDED" } }); + events.Add(new Dictionary { { "name", "PAYMENT.CAPTURE.COMPLETED" } }); + events.Add(new Dictionary { { "name", "PAYMENT.CAPTURE.DENIED" } }); + events.Add(new Dictionary { { "name", "PAYMENT.CAPTURE.PENDING" } }); + events.Add(new Dictionary { { "name", "PAYMENT.CAPTURE.REFUNDED" } }); + events.Add(new Dictionary { { "name", "PAYMENT.CAPTURE.REVERSED" } }); + events.Add(new Dictionary { { "name", "PAYMENT.SALE.COMPLETED" } }); + events.Add(new Dictionary { { "name", "PAYMENT.SALE.DENIED" } }); + events.Add(new Dictionary { { "name", "PAYMENT.SALE.PENDING" } }); + events.Add(new Dictionary { { "name", "PAYMENT.SALE.REFUNDED" } }); + events.Add(new Dictionary { { "name", "PAYMENT.SALE.REVERSED" } }); data.Add("url", url); data.Add("event_types", events); @@ -928,26 +940,107 @@ public PayPalResponse DeleteWebhook(PayPalApiSettingsBase settings, PayPalSessio } /// return 503 (HttpStatusCode.ServiceUnavailable) to ask paypal to resend it at later time - public HttpStatusCode ProcessWebhook(PayPalApiSettingsBase settings, NameValueCollection headers, string rawJson) + public HttpStatusCode ProcessWebhook( + PayPalApiSettingsBase settings, + NameValueCollection headers, + string rawJson, + string providerSystemName) { if (rawJson.IsEmpty()) return HttpStatusCode.OK; dynamic json = JObject.Parse(rawJson); + var eventType = (string)json.event_type; //foreach (var key in headers.AllKeys)"{0}: {1}".FormatInvariant(key, headers[key]).Dump(); //string data = JsonConvert.SerializeObject(json, Formatting.Indented);data.Dump(); - var isValid = (bool)IsValidMessage(settings, json); + if (eventType.IsEmpty() || json.resource == null) + return HttpStatusCode.OK; - if (!isValid) + // validating against PayPal SDK failing using sandbox, so better we do not use it: + //var apiContext = new global::PayPal.Api.APIContext + //{ + // AccessToken = "I do not have one here", + // Config = new Dictionary + // { + // { "mode", settings.UseSandbox ? "sandbox" : "live" }, + // { "clientId", settings.ClientId }, + // { "clientSecret", settings.Secret }, + // { "webhook.id", setting.WebhookId }, + // } + //}; + //var result = global::PayPal.Api.WebhookEvent.ValidateReceivedEvent(apiContext, headers, rawJson, webhookId); + //} + + var paymentId = (string)json.resource.parent_payment; + if (paymentId.IsEmpty()) { - LogError(null, T("Plugins.SmartStore.PayPal.WebhookValidationFailed"), rawJson, isWarning: true); + LogError(null, T("Plugins.SmartStore.PayPal.MissingPaymentId"), JsonConvert.SerializeObject(json, Formatting.Indented), isWarning: true); return HttpStatusCode.OK; } - // TODO + var orders = _orderRepository.Value.Table + .Where(x => x.PaymentMethodSystemName == providerSystemName && x.AuthorizationTransactionCode == paymentId) + .ToList(); + + if (orders.Count != 1) + { + LogError(null, T("Plugins.SmartStore.PayPal.FoundOrderForPayment", orders.Count, paymentId), JsonConvert.SerializeObject(json, Formatting.Indented), isWarning: true); + return HttpStatusCode.OK; + } + + var order = orders.First(); + var store = _services.StoreService.GetStoreById(order.StoreId); + + var total = decimal.Zero; + var currency = (string)json.resource.amount.currency; + var primaryCurrency = store.PrimaryStoreCurrency.CurrencyCode; + + if (!primaryCurrency.IsCaseInsensitiveEqual(currency)) + { + LogError(null, T("Plugins.SmartStore.PayPal.CurrencyNotEqual", currency.NaIfEmpty(), primaryCurrency), JsonConvert.SerializeObject(json, Formatting.Indented), isWarning: true); + return HttpStatusCode.OK; + } + + eventType = eventType.Substring(eventType.LastIndexOf('.') + 1); + + var newPaymentStatus = GetPaymentStatus(eventType, "authorization", order.PaymentStatus); + + var isValidTotal = decimal.TryParse((string)json.resource.amount.total, NumberStyles.Currency, CultureInfo.InvariantCulture, out total); + + if (newPaymentStatus == PaymentStatus.Refunded && (Math.Abs(order.OrderTotal) - Math.Abs(total)) > decimal.Zero) + { + newPaymentStatus = PaymentStatus.PartiallyRefunded; + } + + switch (newPaymentStatus) + { + case PaymentStatus.Pending: + break; + case PaymentStatus.Authorized: + if (_orderProcessingService.CanMarkOrderAsAuthorized(order)) + _orderProcessingService.MarkAsAuthorized(order); + break; + case PaymentStatus.Paid: + if (_orderProcessingService.CanMarkOrderAsPaid(order)) + _orderProcessingService.MarkOrderAsPaid(order); + break; + case PaymentStatus.Refunded: + if (_orderProcessingService.CanRefundOffline(order)) + _orderProcessingService.RefundOffline(order); + break; + case PaymentStatus.PartiallyRefunded: + if (_orderProcessingService.CanPartiallyRefundOffline(order, Math.Abs(total))) + _orderProcessingService.PartiallyRefundOffline(order, Math.Abs(total)); + break; + case PaymentStatus.Voided: + if (_orderProcessingService.CanVoidOffline(order)) + _orderProcessingService.VoidOffline(order); + break; + } + AddOrderNote(settings, order, (string)ToInfoString(json)); return HttpStatusCode.OK; } diff --git a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs index 336c22945c..8109422c68 100644 --- a/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs +++ b/src/Plugins/SmartStore.PayPal/Settings/PayPalSettings.cs @@ -35,11 +35,6 @@ public PayPalSettingsBase() public abstract class PayPalApiSettingsBase : PayPalSettingsBase { - public PayPalApiSettingsBase() - { - WebhookValidation = PayPalWebhookValidation.Simple; - } - public TransactMode TransactMode { get; set; } public string ApiAccountName { get; set; } public string ApiAccountPassword { get; set; } @@ -64,11 +59,6 @@ public PayPalApiSettingsBase() /// PayPal webhook id /// public string WebhookId { get; set; } - - /// - /// Indicates if and how webhook messages should be validated - /// - public PayPalWebhookValidation WebhookValidation { get; set; } } diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml index 92707956bf..993f9e14f8 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml @@ -135,15 +135,6 @@ @Html.ValidationMessageFor(model => model.WebhookId)
- @Html.SmartLabelFor(model => model.WebhookValidation) - - @Html.DropDownListFor(model => model.WebhookValidation, Model.AvailableWebhookValidations) - @Html.ValidationMessageFor(model => model.WebhookValidation) -
From 6e8982e469b411fa95359f0b1167187fa68677d7 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 22 Apr 2016 12:57:41 +0200 Subject: [PATCH 050/285] PayPal Plus: Setup instruction on configuration page --- .../Localization/resources.de-de.xml | 17 ++++++----- .../Localization/resources.en-us.xml | 17 ++++++----- .../Services/PayPalService.cs | 4 ++- .../Views/PayPalPlus/Configure.cshtml | 29 ++++++------------- 4 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index 43a70d229d..a006f88e01 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -16,12 +16,6 @@ Mitteilung;Ereignis;Ereignis-ID;Status;Betrag;Zahlungs-ID - - Jetzt erstellen - - - Jetzt aktualisieren - Sonstiges @@ -411,7 +405,16 @@ Zahlungsgebühr: {10}]]> PayPal PLUS bietet die Möglichkeit per PayPal, Kreditkarte, Lastschriftverfahren und per Rechnung zu bezahlen. -Die Bezahlung auf Rechnung steht möglicherweise nicht allen Händlern zur Verfügung. PayPal PLUS unterstützt nur den Transaktionstyp der sofortigen Buchung und ist derzeit nur in Deutschland verfügbar.

]]> +Die Bezahlung auf Rechnung steht möglicherweise nicht allen Händlern zur Verfügung. PayPal PLUS unterstützt nur den Transaktionstyp der sofortigen Buchung und ist derzeit nur in Deutschland verfügbar.

+

PayPal PLUS ersetzt die Zahlartenliste im Kassenbereich durch die PayPal Payment Wall. Sie können der Payment Wall jedoch bis zu 5 weitere Offline- bzw. Weiterleitungszahlarten hinzufügen.

+

So richten Sie PayPal PLUS ein: +

    +
  1. Legen Sie unter My Apps & Credentials eine neue REST API Anwendung an. +Unter Manage your applications finden Sie weitere Informationen zum Thema PayPal Anwendungen (Englisch).
  2. +
  3. Klicken Sie auf den Namen der Anwendung und tragen Sie Client-ID und Secret weiter unten auf dieser Seite ein. Speichern.
  4. +
  5. Optional: Klicken Sie unten bei Experience Profil-ID auf Hinzufügen, um ein Profil zu erstellen. Ihre Shop-Daten (Name, Logo etc.) werden Käufern dadurch beim Bezahlen auf den PayPal-Seiten angezeigt.
  6. +
  7. Optional: Klicken Sie unten bei Webhook-ID auf Hinzufügen, um einen Webhook zu erstellen. Über einen Webhook wird der Zahlungsstatus Ihrer Aufträge aktualisiert.
  8. +

]]>
diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index b1005615cc..33e100097d 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -16,12 +16,6 @@ Message;Event;Event-ID;State;Amount;Payment-ID - - Create now - - - Update now - Miscellaneous @@ -411,7 +405,16 @@ Payment fee: {10}]]> PayPal PLUS is a solution where PayPal offers PayPal, Credit Card, Direct Debit (ELV) and pay upon invoice as individual payment options on the payment selection page. -Pay upon invoice may not be available for all merchants. PayPal PLUS only supports the transaction type of instant settlement and is only available in Germany at the moment.

]]> +Pay upon invoice may not be available for all merchants. PayPal PLUS only supports the transaction type of instant settlement and is only available in Germany at the moment.

+

PayPal PLUS replaces the list of payment methods in checkout by the PayPal payment wall. You can add up to 5 offline or redirection payment methods to the payment wall.

+

How to set up PayPal PLUS: +

    +
  1. Create a new REST API application under My Apps & Credentials. +Go to Manage your applications to get more information about PayPal applications.
  2. +
  3. Click the name of the application and enter Client-ID and Secret below on this page. Save.
  4. +
  5. Optional: Click Add next to Experience profile ID to create a new profile. Your shop data (name, logo etc.) can now be displayed to customers when paying on PayPal pages.
  6. +
  7. Optional: Click Add next to Webhook-ID to create a webhook. The payment state of your orders can be updated through a webhook.
  8. +

]]>
diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 1c648b68e1..3f04320047 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -939,7 +939,7 @@ public PayPalResponse DeleteWebhook(PayPalApiSettingsBase settings, PayPalSessio return result; } - /// return 503 (HttpStatusCode.ServiceUnavailable) to ask paypal to resend it at later time + /// return 503 (HttpStatusCode.ServiceUnavailable) to ask paypal to resend it at later time again public HttpStatusCode ProcessWebhook( PayPalApiSettingsBase settings, NameValueCollection headers, @@ -1040,6 +1040,8 @@ public HttpStatusCode ProcessWebhook( break; } + order.HasNewPaymentNotification = true; + AddOrderNote(settings, order, (string)ToInfoString(json)); return HttpStatusCode.OK; diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml index 993f9e14f8..129922ca27 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/Configure.cshtml @@ -6,6 +6,8 @@ @{ Layout = ""; Html.AddCssFileParts(true, "~/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css"); + + var hasCredentials = (Model.ClientId.HasValue() && Model.Secret.HasValue()); }
@@ -91,16 +93,15 @@
@Html.SettingEditorFor(model => model.ExperienceProfileId) - - -  ! +   + @T(Model.ExperienceProfileId.HasValue() ? "Common.Refresh" : "Common.AddNew") @if (Model.ExperienceProfileId.HasValue()) { -  @T("Admin.Common.Delete") @@ -119,14 +120,14 @@ @if (Model.WebhookId.HasValue()) { -  @T("Admin.Common.Delete") } else { -  @T("Common.AddNew") @@ -203,16 +204,4 @@
-} - - \ No newline at end of file +} \ No newline at end of file From 6045fc4d3bf18b4515a295dcb3caa6d102fe2a28 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 23 Apr 2016 11:59:44 +0200 Subject: [PATCH 051/285] PayPal Plus: Testing... Cancel URL ambiguous. Getting undocumented pseudo message flag over cancel URL when buyer clicks 'continue' at PayPal (ELV or CC in sandbox). --- .../Controllers/PayPalPlusController.cs | 12 ++++ .../Localization/resources.de-de.xml | 15 +++-- .../Localization/resources.en-us.xml | 15 +++-- .../Models/PayPalPlusCheckoutModel.cs | 1 + .../Services/PayPalService.cs | 58 +++++++++---------- .../Views/PayPalPlus/PaymentWall.cshtml | 13 ++++- 6 files changed, 67 insertions(+), 47 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index 4a5154fde3..54c1046903 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -218,6 +218,7 @@ public ActionResult PaymentWall() model.UseSandbox = settings.UseSandbox; model.HasPaymentFee = (settings.AdditionalFee > decimal.Zero); model.LanguageCulture = (language.LanguageCulture ?? "de_DE").Replace("-", "_"); + model.PayPalPlusPseudoMessageFlag = TempData["PayPalPlusPseudoMessageFlag"] as string; if (customer.BillingAddress != null && customer.BillingAddress.Country != null) { @@ -321,6 +322,17 @@ public ActionResult CheckoutReturn(string systemName, string paymentId, string P [ValidateInput(false)] public ActionResult CheckoutCancel() { + // Request.QueryString: + // token: EC-6JM38216F6718012L, ppp_msg: 1 + + // undocumented + var pseudoMessageFlag = Request.QueryString["ppp_msg"] as string; + + if (pseudoMessageFlag.HasValue()) + { + TempData["PayPalPlusPseudoMessageFlag"] = pseudoMessageFlag; + } + // back to where he came from return RedirectToAction("PaymentMethod", "Checkout", new { area = "" }); } diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index a006f88e01..21bd4bed2a 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -11,7 +11,7 @@ API Zugang;Datenaustausch;Sonstiges
- {0}]]> + {0}]]> Mitteilung;Ereignis;Ereignis-ID;Status;Betrag;Zahlungs-ID @@ -28,15 +28,15 @@ Referenz;Bankleitzahl;Bank;BIC;IBAN;Kontoinhaber;Kontonummer;Betrag;Zahlung fällig am;Details - - Fehlende Payment-ID. - - Keine Statusaktualisierung, da {0} Aufträge mit der Payment-ID {1} gefunden wurden. + Mitteilung wurde ignoriert, da {0} Aufträge mit der Payment-ID {1} gefunden wurden. Währung {0} entspricht nicht der Leitwährung des Shops {1}. + + Keine Weiterleitungs-URL von PayPal erhalten. + Client-ID @@ -413,13 +413,16 @@ Die Bezahlung auf Rechnung steht möglicherweise nicht allen Händlern zur Verf Unter Manage your applications finden Sie weitere Informationen zum Thema PayPal Anwendungen (Englisch).
  • Klicken Sie auf den Namen der Anwendung und tragen Sie Client-ID und Secret weiter unten auf dieser Seite ein. Speichern.
  • Optional: Klicken Sie unten bei Experience Profil-ID auf Hinzufügen, um ein Profil zu erstellen. Ihre Shop-Daten (Name, Logo etc.) werden Käufern dadurch beim Bezahlen auf den PayPal-Seiten angezeigt.
  • -
  • Optional: Klicken Sie unten bei Webhook-ID auf Hinzufügen, um einen Webhook zu erstellen. Über einen Webhook wird der Zahlungsstatus Ihrer Aufträge aktualisiert.
  • +
  • Optional: Klicken Sie unten bei Webhook-ID auf Hinzufügen, um einen Webhook zu erstellen. Über einen Webhook empfängt der Shop PayPal-Mitteilungen und aktualisiert u.U. den Zahlungsstatus Ihrer Aufträge.
  • ]]>
    Wir können Ihnen leider keine PayPal PLUS Zahlungsart anbieten. Bitte versuchen Sie er später erneut. + + Entschuldigung, das hat gerade nicht funktioniert. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode. + Weitere Zahlarten diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index 33e100097d..5dfc52a4b4 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -11,7 +11,7 @@ API access;Data exchange;Miscellaneous
    - {0}]]> + {0}]]> Message;Event;Event-ID;State;Amount;Payment-ID @@ -31,15 +31,15 @@ Reference;Bank routing number;Bank;BIC;IBAN;Account holder;Account number;Amount;Payment due date;Details - - Missing payment ID. - - No status update because {0} orders with payment ID {1} found. + Message has been ignored because {0} orders with payment ID {1} found. Currency {0} does not equal primary store currency {1}. + + No redirect URL received from PayPal. + Client ID @@ -413,13 +413,16 @@ Pay upon invoice may not be available for all merchants. PayPal PLUS only suppor Go to Manage your applications to get more information about PayPal applications.
  • Click the name of the application and enter Client-ID and Secret below on this page. Save.
  • Optional: Click Add next to Experience profile ID to create a new profile. Your shop data (name, logo etc.) can now be displayed to customers when paying on PayPal pages.
  • -
  • Optional: Click Add next to Webhook-ID to create a webhook. The payment state of your orders can be updated through a webhook.
  • +
  • Optional: Click Add next to Webhook-ID to create a webhook. Through a webhook the shop receives PayPal messages und possibly updates the payment state of your orders.
  • ]]>
    Unfortunately we cannot offer you a PayPal PLUS payment method. Please try again later. + + I'm sorry, that just didn't work. Please try again or select another payment method. + More payment methods diff --git a/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs b/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs index b3b09094c5..2b6e7fff2b 100644 --- a/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs +++ b/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs @@ -11,6 +11,7 @@ public class PayPalPlusCheckoutModel : ModelBase public string LanguageCulture { get; set; } public string ApprovalUrl { get; set; } public string ErrorMessage { get; set; } + public string PayPalPlusPseudoMessageFlag { get; set; } public List ThirdPartyPaymentMethods { get; set; } diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 3f04320047..9fd33cfcc9 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -11,6 +11,7 @@ using Newtonsoft.Json.Linq; using SmartStore.Core.Data; using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Discounts; using SmartStore.Core.Domain.Logging; using SmartStore.Core.Domain.Orders; @@ -79,7 +80,7 @@ public PayPalService( public Localizer T { get; set; } public ILogger Logger { get; set; } - private Dictionary CreateAddress(Address addr) + private Dictionary CreateAddress(Address addr, bool addRecipientName) { var dic = new Dictionary(); @@ -104,7 +105,10 @@ private Dictionary CreateAddress(Address addr) dic.Add("state", addr.StateProvince.Abbreviation.Truncate(100)); } - dic.Add("recipient_name", addr.GetFullName().Truncate(50)); + if (addRecipientName) + { + dic.Add("recipient_name", addr.GetFullName().Truncate(50)); + } return dic; } @@ -595,6 +599,8 @@ public PayPalResponse CreatePayment( var language = _services.WorkContext.WorkingLanguage; var currencyCode = store.PrimaryStoreCurrency.CurrencyCode; + var dateOfBirth = customer.GetAttribute(SystemCustomerAttributeNames.DateOfBirth); + Discount orderAppliedDiscount; List appliedGiftCards; int redeemedRewardPoints = 0; @@ -611,7 +617,6 @@ public PayPalResponse CreatePayment( var total = (_orderTotalCalculationService.GetShoppingCartTotal(cart, out orderDiscountInclTax, out orderAppliedDiscount, out appliedGiftCards, out redeemedRewardPoints, out redeemedRewardPointsAmount) ?? decimal.Zero); - var path = "/v1/payments/payment"; var data = new Dictionary(); var redirectUrls = new Dictionary(); var payer = new Dictionary(); @@ -622,9 +627,6 @@ public PayPalResponse CreatePayment( var items = new List>(); var itemList = new Dictionary(); - if (session.PaymentId.HasValue()) - path = string.Concat(path, "/", HttpUtility.UrlPathEncode(session.PaymentId)); - // "PayPal PLUS only supports transaction type “Sale†(instant settlement)" if (providerSystemName == PayPalPlusProvider.SystemName) data.Add("intent", "sale"); @@ -644,16 +646,18 @@ public PayPalResponse CreatePayment( if (redirectUrls.Any()) data.Add("redirect_urls", redirectUrls); - // payer - payer.Add("payment_method", "paypal"); - + // payer, payer_info + if (dateOfBirth.HasValue) + { + payerInfo.Add("birth_date", dateOfBirth.Value.ToString("yyyy-MM-dd")); + } if (customer.BillingAddress != null) { - payerInfo.Add("billing_address", CreateAddress(customer.BillingAddress)); - - payer.Add("payer_info", payerInfo); + payerInfo.Add("billing_address", CreateAddress(customer.BillingAddress, false)); } + payer.Add("payment_method", "paypal"); + payer.Add("payer_info", payerInfo); data.Add("payer", payer); // line items @@ -691,7 +695,7 @@ public PayPalResponse CreatePayment( itemList.Add("items", items); if (customer.ShippingAddress != null) { - itemList.Add("shipping_address", CreateAddress(customer.ShippingAddress)); + itemList.Add("shipping_address", CreateAddress(customer.ShippingAddress, true)); } // transactions @@ -724,15 +728,15 @@ public PayPalResponse CreatePayment( data.Add("transactions", new List> { transaction }); - var result = CallApi("POST", path, session.AccessToken, settings, JsonConvert.SerializeObject(data)); + var result = CallApi("POST", "/v1/payments/payment", session.AccessToken, settings, JsonConvert.SerializeObject(data)); if (result.Success && result.Json != null) { result.Id = (string)result.Json.id; - - //Logger.InsertLog(LogLevel.Information, "PayPal PLUS", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + result.Json.ToString()); } + Logger.InsertLog(LogLevel.Information, "PayPal PLUS", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + (result.Json != null ? result.Json.ToString() : "")); + return result; } @@ -756,24 +760,16 @@ public PayPalResponse ExecutePayment(PayPalApiSettingsBase settings, PayPalSessi public PayPalResponse Refund(PayPalApiSettingsBase settings, PayPalSessionData session, RefundPaymentRequest request) { var data = new Dictionary(); + var store = _services.StoreService.GetStoreById(request.Order.StoreId); var isSale = request.Order.AuthorizationTransactionResult.Contains("(sale)"); var path = "/v1/payments/{0}/{1}/refund".FormatInvariant(isSale ? "sale" : "capture", request.Order.CaptureTransactionId); - if (isSale && !request.IsPartialRefund) - { - // no body required - } - else - { - var store = _services.StoreService.GetStoreById(request.Order.StoreId); - - var amount = new Dictionary(); - amount.Add("total", Math.Round(request.AmountToRefund, 2)); - amount.Add("currency", store.PrimaryStoreCurrency.CurrencyCode); + var amount = new Dictionary(); + amount.Add("total", Math.Round(request.AmountToRefund, 2)); + amount.Add("currency", store.PrimaryStoreCurrency.CurrencyCode); - data.Add("amount", amount); - } + data.Add("amount", amount); var result = CallApi("POST", path, session.AccessToken, settings, data.Any() ? JsonConvert.SerializeObject(data) : null); @@ -955,8 +951,6 @@ public HttpStatusCode ProcessWebhook( //foreach (var key in headers.AllKeys)"{0}: {1}".FormatInvariant(key, headers[key]).Dump(); //string data = JsonConvert.SerializeObject(json, Formatting.Indented);data.Dump(); - if (eventType.IsEmpty() || json.resource == null) - return HttpStatusCode.OK; // validating against PayPal SDK failing using sandbox, so better we do not use it: //var apiContext = new global::PayPal.Api.APIContext @@ -976,7 +970,7 @@ public HttpStatusCode ProcessWebhook( var paymentId = (string)json.resource.parent_payment; if (paymentId.IsEmpty()) { - LogError(null, T("Plugins.SmartStore.PayPal.MissingPaymentId"), JsonConvert.SerializeObject(json, Formatting.Indented), isWarning: true); + LogError(null, T("Plugins.SmartStore.PayPal.FoundOrderForPayment", 0, "".NaIfEmpty()), JsonConvert.SerializeObject(json, Formatting.Indented), isWarning: true); return HttpStatusCode.OK; } diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml index 72a42fc395..5889bb0abd 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml @@ -4,7 +4,6 @@ @{ Layout = "~/Views/Shared/_Checkout.cshtml"; Html.AddTitleParts(T("PageTitle.Checkout").Text); - //Html.AddCssFileParts(true, Url.Content("~/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css")); } @section orderProgress { @@ -27,9 +26,19 @@
    @Html.Raw(T("Plugins.Payments.PayPalPlus.MethodUnavailable"))
    + @if (!Model.ApprovalUrl.HasValue()) + { + @T("Plugins.SmartStore.PayPal.NoApprovalUrlReturned") + } @Model.ErrorMessage
    } + else if (Model.PayPalPlusPseudoMessageFlag.HasValue() && Model.PayPalPlusPseudoMessageFlag == "1") + { +
    + @Html.Raw(T("Plugins.Payments.PayPalPlus.SorryFailure")) +
    + }
    @@ -38,8 +47,6 @@  @T("Common.Back") - @*TODO: reward points*@ - From 750eeafa4c4648003f7f40db16987357b48e698c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 23 Apr 2016 12:48:39 +0200 Subject: [PATCH 052/285] PayPal Plus: Render full description on top of the payment wall --- .../SmartStore.PayPal/Controllers/PayPalPlusController.cs | 7 +++++++ .../SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs | 1 + src/Plugins/SmartStore.PayPal/Services/PayPalService.cs | 2 +- .../SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml | 7 +++++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index 54c1046903..e059de2b48 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -210,6 +210,8 @@ public ActionResult PaymentWall() var settings = Services.Settings.LoadSetting(store.Id); var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); + var pppMethod = _paymentService.GetPaymentMethodBySystemName(PayPalPlusProvider.SystemName); + var methods = _paymentService.LoadActivePaymentMethods(customer, cart, store.Id, null, false); var session = _httpContext.GetPayPalSessionData(); @@ -220,6 +222,11 @@ public ActionResult PaymentWall() model.LanguageCulture = (language.LanguageCulture ?? "de_DE").Replace("-", "_"); model.PayPalPlusPseudoMessageFlag = TempData["PayPalPlusPseudoMessageFlag"] as string; + if (pppMethod != null) + { + model.FullDescription = pppMethod.GetLocalized(x => x.FullDescription, language.Id); + } + if (customer.BillingAddress != null && customer.BillingAddress.Country != null) { model.BillingAddressCountryCode = customer.BillingAddress.Country.TwoLetterIsoCode; diff --git a/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs b/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs index 2b6e7fff2b..282036b039 100644 --- a/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs +++ b/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs @@ -12,6 +12,7 @@ public class PayPalPlusCheckoutModel : ModelBase public string ApprovalUrl { get; set; } public string ErrorMessage { get; set; } public string PayPalPlusPseudoMessageFlag { get; set; } + public string FullDescription { get; set; } public List ThirdPartyPaymentMethods { get; set; } diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 9fd33cfcc9..5a10fc6e12 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -735,7 +735,7 @@ public PayPalResponse CreatePayment( result.Id = (string)result.Json.id; } - Logger.InsertLog(LogLevel.Information, "PayPal PLUS", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + (result.Json != null ? result.Json.ToString() : "")); + //Logger.InsertLog(LogLevel.Information, "PayPal PLUS", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + (result.Json != null ? result.Json.ToString() : "")); return result; } diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml index 5889bb0abd..b1737ebd0a 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml @@ -40,6 +40,13 @@ } + if (Model.FullDescription.HasValue()) + { +
    + @Html.Raw(Model.FullDescription) +
    + } +
    From dbdbd4eda3b83e7f7e57ee6257796e6f62917a7e Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 23 Apr 2016 14:10:19 +0200 Subject: [PATCH 053/285] PayPal Plus: Payment wall for mobile devices --- .../Filters/PayPalPlusCheckoutFilter.cs | 2 +- .../Services/PayPalService.cs | 22 +++--- .../PayPalPlus/PaymentWall.Mobile.cshtml | 46 +++++++++++- .../Views/PayPalPlus/PaymentWall.cshtml | 75 +++++++++---------- 4 files changed, 91 insertions(+), 54 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusCheckoutFilter.cs b/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusCheckoutFilter.cs index db1bb64542..0d49130da7 100644 --- a/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusCheckoutFilter.cs +++ b/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusCheckoutFilter.cs @@ -28,7 +28,7 @@ public void OnActionExecuting(ActionExecutingContext filterContext) if (!_paymentService.IsPaymentMethodActive(PayPalPlusProvider.SystemName, store.Id)) return; - var routeValues = new RouteValueDictionary(new { action = "PaymentWall", controller = "PayPalPlus" }); + var routeValues = new RouteValueDictionary(new { action = "PaymentWall", controller = "PayPalPlus", area = Plugin.SystemName }); filterContext.Result = new RedirectToRouteResult("SmartStore.PayPalPlus", routeValues); } diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 5a10fc6e12..eeab9f531b 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -670,7 +670,7 @@ public PayPalResponse CreatePayment( var line = new Dictionary(); line.Add("quantity", item.Item.Quantity); line.Add("name", item.Item.Product.GetLocalized(x => x.Name, language.Id, true, false).Truncate(127)); - line.Add("price", Math.Round(productPrice, 2)); + line.Add("price", productPrice.FormatInvariant()); line.Add("currency", currencyCode); line.Add("sku", item.Item.Product.Sku.Truncate(50)); items.Add(line); @@ -685,7 +685,7 @@ public PayPalResponse CreatePayment( var line = new Dictionary(); line.Add("quantity", "1"); line.Add("name", T("Plugins.SmartStore.PayPal.Other").Text.Truncate(127)); - line.Add("price", Math.Round(total - itemsPlusMisc, 2)); + line.Add("price", (total - itemsPlusMisc).FormatInvariant()); line.Add("currency", currencyCode); items.Add(line); @@ -699,8 +699,8 @@ public PayPalResponse CreatePayment( } // transactions - amountDetails.Add("shipping", Math.Round(shipping, 2)); - amountDetails.Add("subtotal", Math.Round(totalOrderItems, 2)); + amountDetails.Add("shipping", shipping.FormatInvariant()); + amountDetails.Add("subtotal", totalOrderItems.FormatInvariant()); if (!includingTax) { // "To avoid rounding errors we recommend not submitting tax amounts on line item basis. @@ -711,14 +711,14 @@ public PayPalResponse CreatePayment( SortedDictionary taxRates = null; var taxTotal = _orderTotalCalculationService.GetTaxTotal(cart, out taxRates); - amountDetails.Add("tax", Math.Round(taxTotal, 2)); + amountDetails.Add("tax", taxTotal.FormatInvariant()); } if (paymentFee != decimal.Zero) { - amountDetails.Add("handling_fee", Math.Round(paymentFee, 2)); + amountDetails.Add("handling_fee", paymentFee.FormatInvariant()); } - amount.Add("total", Math.Round(total, 2)); + amount.Add("total", total.FormatInvariant()); amount.Add("currency", currencyCode); amount.Add("details", amountDetails); @@ -766,7 +766,7 @@ public PayPalResponse Refund(PayPalApiSettingsBase settings, PayPalSessionData s var path = "/v1/payments/{0}/{1}/refund".FormatInvariant(isSale ? "sale" : "capture", request.Order.CaptureTransactionId); var amount = new Dictionary(); - amount.Add("total", Math.Round(request.AmountToRefund, 2)); + amount.Add("total", request.AmountToRefund.FormatInvariant()); amount.Add("currency", store.PrimaryStoreCurrency.CurrencyCode); data.Add("amount", amount); @@ -776,10 +776,10 @@ public PayPalResponse Refund(PayPalApiSettingsBase settings, PayPalSessionData s if (result.Success && result.Json != null) { result.Id = (string)result.Json.id; - - //Logger.InsertLog(LogLevel.Information, "PayPal Refund", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + result.Json.ToString()); } + //Logger.InsertLog(LogLevel.Information, "PayPal Refund", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + (result.Json != null ? result.Json.ToString() : "")); + return result; } @@ -793,7 +793,7 @@ public PayPalResponse Capture(PayPalApiSettingsBase settings, PayPalSessionData var store = _services.StoreService.GetStoreById(request.Order.StoreId); var amount = new Dictionary(); - amount.Add("total", Math.Round(request.Order.OrderTotal, 2)); + amount.Add("total", request.Order.OrderTotal.FormatInvariant()); amount.Add("currency", store.PrimaryStoreCurrency.CurrencyCode); data.Add("amount", amount); diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml index d7d9e6e19e..bde5eba855 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml @@ -4,16 +4,56 @@ @{ Layout = "~/Views/Shared/_Root.cshtml"; Html.AddTitleParts(T("PageTitle.Checkout").Text); - //Html.AddCssFileParts(true, Url.Content("~/Plugins/SmartStore.PayPal/Content/smartstore.paypal.css")); } @Html.Partial("PaymentWallScripting") -
    +

    @T("Checkout.SelectPaymentMethod")

    -
    +
    + @Html.Widget("mobile_checkout_payment_method_top") + + @if (Model.ErrorMessage.HasValue()) + { +
    +
      +
    • @Html.Raw(T("Plugins.Payments.PayPalPlus.MethodUnavailable"))
    • +
    • + @if (!Model.ApprovalUrl.HasValue()) + { + @T("Plugins.SmartStore.PayPal.NoApprovalUrlReturned") + } + @Model.ErrorMessage +
    • +
    +
    + } + else if (Model.PayPalPlusPseudoMessageFlag.HasValue() && Model.PayPalPlusPseudoMessageFlag == "1") + { +
    +
      +
    • @Html.Raw(T("Plugins.Payments.PayPalPlus.SorryFailure"))
    • +
    +
    + } + + @if (Model.FullDescription.HasValue()) + { +
    + @Html.Raw(Model.FullDescription) +
    + } +
    + +
    + +
    + +
    + + @Html.Widget("mobile_checkout_payment_method_bottom")
    \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml index b1737ebd0a..e0bec78408 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml @@ -16,50 +16,47 @@

    @T("Checkout.SelectPaymentMethod")

    -
    - @using (Html.BeginForm()) - { - @Html.Widget("checkout_payment_method_top") +
    + @Html.Widget("checkout_payment_method_top") - if (Model.ErrorMessage.HasValue()) - { -
    - @Html.Raw(T("Plugins.Payments.PayPalPlus.MethodUnavailable")) -
    - @if (!Model.ApprovalUrl.HasValue()) - { - @T("Plugins.SmartStore.PayPal.NoApprovalUrlReturned") - } - @Model.ErrorMessage -
    - } - else if (Model.PayPalPlusPseudoMessageFlag.HasValue() && Model.PayPalPlusPseudoMessageFlag == "1") - { -
    - @Html.Raw(T("Plugins.Payments.PayPalPlus.SorryFailure")) -
    - } + @if (Model.ErrorMessage.HasValue()) + { +
    + @Html.Raw(T("Plugins.Payments.PayPalPlus.MethodUnavailable")) +
    + @if (!Model.ApprovalUrl.HasValue()) + { + @T("Plugins.SmartStore.PayPal.NoApprovalUrlReturned") + } + @Model.ErrorMessage +
    + } + else if (Model.PayPalPlusPseudoMessageFlag.HasValue() && Model.PayPalPlusPseudoMessageFlag == "1") + { +
    + @Html.Raw(T("Plugins.Payments.PayPalPlus.SorryFailure")) +
    + } - if (Model.FullDescription.HasValue()) - { -
    - @Html.Raw(Model.FullDescription) -
    - } + @if (Model.FullDescription.HasValue()) + { +
    + @Html.Raw(Model.FullDescription) +
    + } -
    +
    -
    - -  @T("Common.Back") - +
    + +  @T("Common.Back") + - -
    + +
    - @Html.Widget("checkout_payment_method_bottom") - } + @Html.Widget("checkout_payment_method_bottom")
    \ No newline at end of file From d72cc1b3dd90710696dc675e7ac3cf0d5aedf8ac Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 25 Apr 2016 10:47:43 +0200 Subject: [PATCH 054/285] PayPal Plus: Display payment fee above payment wall --- changelog.md | 5 +- .../Controllers/PayPalPlusController.cs | 49 +++++++++++--- src/Plugins/SmartStore.PayPal/Description.txt | 2 +- .../Filters/PayPalPlusCheckoutFilter.cs | 2 +- .../Localization/resources.de-de.xml | 3 + .../Localization/resources.en-us.xml | 3 + .../Models/PayPalPlusCheckoutModel.cs | 5 +- .../PayPalPlus/PaymentWall.Mobile.cshtml | 7 ++ .../Views/PayPalPlus/PaymentWall.cshtml | 7 ++ .../PayPalPlus/PaymentWallScripting.cshtml | 64 +++++++++++++++---- src/Plugins/SmartStore.PayPal/changelog.md | 2 +- 11 files changed, 124 insertions(+), 25 deletions(-) diff --git a/changelog.md b/changelog.md index 9b75e12d36..b91674b12d 100644 --- a/changelog.md +++ b/changelog.md @@ -1,7 +1,10 @@ -# Release Notes +# Release Notes ## SmartStore.NET 2.5.1 +### Highlights +* #637 Integrate PayPal PLUS Provider + ### New Features * #571 Option to display another checkbox on confirm page to let the customer accept that his email address can be handed over to a third party * #870 Implement address import for customers (both billing & shipping address) diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index e059de2b48..1c20f4b032 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using System.Web; using System.Web.Mvc; using SmartStore.Core.Domain.Customers; @@ -12,12 +13,13 @@ using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.PayPal.Validators; +using SmartStore.Services.Catalog; using SmartStore.Services.Common; using SmartStore.Services.Customers; +using SmartStore.Services.Directory; using SmartStore.Services.Localization; -using SmartStore.Services.Orders; using SmartStore.Services.Payments; -using SmartStore.Web.Framework; +using SmartStore.Services.Tax; using SmartStore.Web.Framework.Controllers; using SmartStore.Web.Framework.Plugins; using SmartStore.Web.Framework.Security; @@ -31,15 +33,19 @@ public class PayPalPlusController : PayPalRestApiControllerBase provider) @@ -66,13 +75,26 @@ private string GetPaymentMethodName(Provider provider) return ""; } + private string GetPaymentFee(Provider provider, List cart) + { + var paymentMethodAdditionalFee = provider.Value.GetAdditionalHandlingFee(cart); + var rateBase = _taxService.GetPaymentMethodAdditionalFee(paymentMethodAdditionalFee, Services.WorkContext.CurrentCustomer); + var rate = _currencyService.ConvertFromPrimaryStoreCurrency(rateBase, Services.WorkContext.WorkingCurrency); + + if (rate != decimal.Zero) + { + return _priceFormatter.FormatPaymentMethodAdditionalFee(rate, true); + } + return ""; + } + private PayPalPlusCheckoutModel.ThirdPartyPaymentMethod GetThirdPartyPaymentMethodModel( Provider provider, PayPalPlusPaymentSettings settings, Store store) { var model = new PayPalPlusCheckoutModel.ThirdPartyPaymentMethod(); - model.MethodName = GetPaymentMethodName(provider).EncodeJsString(); + model.MethodName = GetPaymentMethodName(provider); model.RedirectUrl = Url.Action("CheckoutReturn", "PayPalPlus", new { area = Plugin.SystemName, systemName = provider.Metadata.SystemName }, store.SslEnabled ? "https" : "http"); try @@ -204,6 +226,7 @@ public ActionResult PaymentInfo() public ActionResult PaymentWall() { + var sb = new StringBuilder(); var store = Services.StoreContext.CurrentStore; var customer = Services.WorkContext.CurrentCustomer; var language = Services.WorkContext.WorkingLanguage; @@ -211,6 +234,7 @@ public ActionResult PaymentWall() var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); var pppMethod = _paymentService.GetPaymentMethodBySystemName(PayPalPlusProvider.SystemName); + var pppProvider = _paymentService.LoadPaymentMethodBySystemName(PayPalPlusProvider.SystemName, false, store.Id); var methods = _paymentService.LoadActivePaymentMethods(customer, cart, store.Id, null, false); var session = _httpContext.GetPayPalSessionData(); @@ -218,9 +242,10 @@ public ActionResult PaymentWall() var model = new PayPalPlusCheckoutModel(); model.ThirdPartyPaymentMethods = new List(); model.UseSandbox = settings.UseSandbox; - model.HasPaymentFee = (settings.AdditionalFee > decimal.Zero); model.LanguageCulture = (language.LanguageCulture ?? "de_DE").Replace("-", "_"); model.PayPalPlusPseudoMessageFlag = TempData["PayPalPlusPseudoMessageFlag"] as string; + model.PayPalFee = GetPaymentFee(pppProvider, cart); + model.HasAnyFees = model.PayPalFee.HasValue(); if (pppMethod != null) { @@ -239,9 +264,17 @@ public ActionResult PaymentWall() { var methodModel = GetThirdPartyPaymentMethodModel(provider, settings, store); model.ThirdPartyPaymentMethods.Add(methodModel); + + var fee = GetPaymentFee(provider, cart); + if (fee.HasValue()) + model.HasAnyFees = true; + if (sb.Length > 0) + sb.Append(", "); + sb.AppendFormat("['{0}','{1}']", methodModel.MethodName.Replace("'", ""), fee); } } + model.ThirdPartyFees = sb.ToString(); if (session.PaymentId.IsEmpty() || session.ApprovalUrl.IsEmpty()) { diff --git a/src/Plugins/SmartStore.PayPal/Description.txt b/src/Plugins/SmartStore.PayPal/Description.txt index 67e7b90636..c78ff1bd32 100644 --- a/src/Plugins/SmartStore.PayPal/Description.txt +++ b/src/Plugins/SmartStore.PayPal/Description.txt @@ -1,5 +1,5 @@ FriendlyName: PayPal -Description: Provides the PayPal payment methods PayPal Standard, PayPal Direct, PayPal Express and PayPal Plus. +Description: Provides the PayPal payment methods PayPal Standard, PayPal Direct, PayPal Express and PayPal PLUS. SystemName: SmartStore.PayPal Group: Payment Version: 2.5.0.2 diff --git a/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusCheckoutFilter.cs b/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusCheckoutFilter.cs index 0d49130da7..db1bb64542 100644 --- a/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusCheckoutFilter.cs +++ b/src/Plugins/SmartStore.PayPal/Filters/PayPalPlusCheckoutFilter.cs @@ -28,7 +28,7 @@ public void OnActionExecuting(ActionExecutingContext filterContext) if (!_paymentService.IsPaymentMethodActive(PayPalPlusProvider.SystemName, store.Id)) return; - var routeValues = new RouteValueDictionary(new { action = "PaymentWall", controller = "PayPalPlus", area = Plugin.SystemName }); + var routeValues = new RouteValueDictionary(new { action = "PaymentWall", controller = "PayPalPlus" }); filterContext.Result = new RedirectToRouteResult("SmartStore.PayPalPlus", routeValues); } diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index 21bd4bed2a..5c1f2562c2 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -423,6 +423,9 @@ Unter Entschuldigung, das hat gerade nicht funktioniert. Bitte versuchen Sie es erneut oder wählen Sie eine andere Zahlungsmethode. + + .]]> + Weitere Zahlarten diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index 5dfc52a4b4..78f376947d 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -423,6 +423,9 @@ Go to I'm sorry, that just didn't work. Please try again or select another payment method. + + .]]> + More payment methods diff --git a/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs b/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs index 282036b039..280d76ec24 100644 --- a/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs +++ b/src/Plugins/SmartStore.PayPal/Models/PayPalPlusCheckoutModel.cs @@ -6,13 +6,15 @@ namespace SmartStore.PayPal.Models public class PayPalPlusCheckoutModel : ModelBase { public bool UseSandbox { get; set; } - public bool HasPaymentFee { get; set; } public string BillingAddressCountryCode { get; set; } public string LanguageCulture { get; set; } public string ApprovalUrl { get; set; } public string ErrorMessage { get; set; } public string PayPalPlusPseudoMessageFlag { get; set; } public string FullDescription { get; set; } + public string PayPalFee { get; set; } + public string ThirdPartyFees { get; set; } + public bool HasAnyFees { get; set; } public List ThirdPartyPaymentMethods { get; set; } @@ -22,6 +24,7 @@ public class ThirdPartyPaymentMethod public string MethodName { get; set; } public string ImageUrl { get; set; } public string Description { get; set; } + public string PaymentFee { get; set; } } } } \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml index bde5eba855..266ae25887 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.Mobile.cshtml @@ -46,6 +46,13 @@
    } + @if (Model.HasAnyFees) + { + + } +
    diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml index e0bec78408..461b02a3ab 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWall.cshtml @@ -45,6 +45,13 @@
    } + @if (Model.HasAnyFees) + { + + } +
    diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml index 60a6047ef9..212ae653cc 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml @@ -2,9 +2,9 @@ @model PayPalPlusCheckoutModel - \ No newline at end of file diff --git a/src/Plugins/SmartStore.PayPal/changelog.md b/src/Plugins/SmartStore.PayPal/changelog.md index f27fa62f5d..70ed0765b7 100644 --- a/src/Plugins/SmartStore.PayPal/changelog.md +++ b/src/Plugins/SmartStore.PayPal/changelog.md @@ -2,7 +2,7 @@ ##Paypal 2.5.0.2 ###New Features -* PayPal Plus payment provider +* PayPal PLUS payment provider ##Paypal 2.5.0.1 ###Bugfixes From ee2dc12f22cfcd52911d212c7e79c70e7884c9f9 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 25 Apr 2016 11:25:44 +0200 Subject: [PATCH 055/285] Fixed: Export deployment emails were always send manually --- changelog.md | 1 + .../DataExchange/Deployment/EmailFilePublisher.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index e5055e6999..c2db717a81 100644 --- a/changelog.md +++ b/changelog.md @@ -26,6 +26,7 @@ * Adding new shipping method throws an exception * Attribute Values: Assigning IsPreselected to more than one value causes an error * BizImporter: fixed redirection bug when default file extension in .biz wasn't .html +* Fixed: Export deployment emails were always send manually ## SmartStore.NET 2.5 diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs index 0294229103..3ce555f766 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs @@ -35,7 +35,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de { From = emailAccount.Email, FromName = emailAccount.DisplayName, - SendManually = true, + SendManually = false, To = email, Subject = deployment.EmailSubject.NaIfEmpty(), CreatedOnUtc = DateTime.UtcNow, From b64540f4aadeaf937a3201ad89458f9d06de6994 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 25 Apr 2016 11:58:19 +0200 Subject: [PATCH 056/285] Spelling correction --- .../Migrations/201508091512101_ExportFramework.cs | 4 ++-- .../Migrations/201509021536425_ExportFramework1.cs | 4 ++-- .../201603121451066_ThirdPartyEmailHandOver.cs | 12 ++++++++++++ .../Localization/resources.de-de.xml | 2 +- .../Localization/resources.en-us.xml | 2 +- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs index fdad717e6c..5203e70451 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201508091512101_ExportFramework.cs @@ -465,9 +465,9 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.EmailAddresses", "Email addresses to", - "E-Mail-Addressen an", + "E-Mail Adressen an", "Specifies the email addresses where to send the data.", - "Legt die E-Mail Addressen fest, an die die Daten verschickt werden soll."); + "Legt die E-Mail Adressen fest, an die die Daten verschickt werden soll."); builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.EmailSubject", "Email subject", diff --git a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs index 8313baa6a1..09f2dc6314 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201509021536425_ExportFramework1.cs @@ -81,9 +81,9 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.DataExchange.Export.CompletedEmailAddresses", "Email addresses to", - "E-Mail-Addressen an", + "E-Mail Adressen an", "Specifies the email addresses where to send the notification message.", - "Legt die E-Mail Addressen fest, an die die Benachrichtigung geschickt werden soll."); + "Legt die E-Mail Adressen fest, an die die Benachrichtigung geschickt werden soll."); builder.AddOrUpdate("Admin.DataExchange.Export.CompletedEmail.Subject", "Export of profile \"{0}\" has been finished", diff --git a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs index 63e182a699..ae4a5e7d4e 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs @@ -200,6 +200,18 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.DataExchange.Export.SystemProfilesTitle", "System profiles", "Systemprofile"); + + builder.AddOrUpdate("Admin.DataExchange.Export.CompletedEmailAddresses", + "Email addresses to", + "E-Mail Adressen an", + "Specifies the email addresses where to send the notification message.", + "Legt die E-Mail Adressen fest, an die die Benachrichtigung geschickt werden soll."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.EmailAddresses", + "Email addresses to", + "E-Mail Adressen an", + "Specifies the email addresses where to send the data.", + "Legt die E-Mail Adressen fest, an die die Daten verschickt werden soll."); } } } diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml index 5c1f2562c2..0568ab1572 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.de-de.xml @@ -11,7 +11,7 @@ API Zugang;Datenaustausch;Sonstiges - {0}]]> + {0}]]> Mitteilung;Ereignis;Ereignis-ID;Status;Betrag;Zahlungs-ID diff --git a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml index 78f376947d..700b806aa7 100644 --- a/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml +++ b/src/Plugins/SmartStore.PayPal/Localization/resources.en-us.xml @@ -11,7 +11,7 @@ API access;Data exchange;Miscellaneous - {0}]]> + {0}]]> Message;Event;Event-ID;State;Amount;Payment-ID From 9cc3066e417b8e855dc44a43e2c2617a86ae0cd0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 25 Apr 2016 12:42:41 +0200 Subject: [PATCH 057/285] PayPal Plus: Payment instruction not displayed --- .../SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs | 3 --- src/Plugins/SmartStore.PayPal/Services/PayPalService.cs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs index a9208ac82b..2330c9d336 100644 --- a/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs +++ b/src/Plugins/SmartStore.PayPal/Providers/PayPalRestApiProviderBase.cs @@ -150,9 +150,6 @@ public override ProcessPaymentResult ProcessPayment(ProcessPaymentRequest proces public override void PostProcessPayment(PostProcessPaymentRequest postProcessPaymentRequest) { - if (postProcessPaymentRequest.Order.PaymentStatus == PaymentStatus.Paid) - return; - var instruction = PayPalService.CreatePaymentInstruction(HttpContext.GetPayPalSessionData().PaymentInstruction); if (instruction.HasValue()) diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index eeab9f531b..0b96289c19 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -294,7 +294,7 @@ public string CreatePaymentInstruction(PayPalPaymentInstruction instruct) if (!instruct.IsManualBankTransfer && !instruct.IsPayUponInvoice) return null; - var sb = new StringBuilder("
    "); + var sb = new StringBuilder("
    "); var paragraphTemplate = "
    {0}
    "; var rowTemplate = "{0}: {1}
    "; var instructStrings = T("Plugins.SmartStore.PayPal.PaymentInstructionStrings").Text.SplitSafe(";"); From 067d0eabf1f229a17992bbdfbbee6e45e03373e3 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 25 Apr 2016 14:06:20 +0200 Subject: [PATCH 058/285] Fixed exception when downloading mail attachment of a file that does not exist --- .../201603121451066_ThirdPartyEmailHandOver.cs | 2 ++ .../Controllers/QueuedEmailController.cs | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs index ae4a5e7d4e..0e7b6ad44b 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs @@ -212,6 +212,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "E-Mail Adressen an", "Specifies the email addresses where to send the data.", "Legt die E-Mail Adressen fest, an die die Daten verschickt werden soll."); + + builder.AddOrUpdate("Admin.Common.FileNotFound", "File not found", "Datei nicht gefunden"); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/QueuedEmailController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/QueuedEmailController.cs index 6bc007e811..7814196042 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/QueuedEmailController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/QueuedEmailController.cs @@ -279,6 +279,18 @@ public ActionResult DownloadAttachment(int id) { path = CommonHelper.MapPath(VirtualPathUtility.ToAppRelative(path), false); } + + if (!System.IO.File.Exists(path)) + { + NotifyError(string.Concat(T("Admin.Common.FileNotFound"), ": ", path)); + + var referrer = Services.WebHelper.GetUrlReferrer(); + if (referrer.HasValue()) + return Redirect(referrer); + + return RedirectToAction("List"); + } + return File(path, qea.MimeType, qea.Name); } @@ -290,7 +302,6 @@ public ActionResult DownloadAttachment(int id) NotifyError(T("Admin.System.QueuedEmails.CouldNotDownloadAttachment")); return RedirectToAction("List"); - } } } From 81eb4659a911c8dacf425f5a2b635c1442b1e415 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 25 Apr 2016 14:17:12 +0200 Subject: [PATCH 059/285] Export email attachments needs to be stored in database because the temp file may not exist anymore when sending the email --- changelog.md | 1 + .../DataExchange/Deployment/EmailFilePublisher.cs | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/changelog.md b/changelog.md index c2db717a81..b21b7c9094 100644 --- a/changelog.md +++ b/changelog.md @@ -16,6 +16,7 @@ * Localization & SEO: language switcher now takes language specific SEO slugs into account when building links * Exports the product detail link including the attribute query string when exporting attribute combinations * #918 Compare products: Display base price information +* Export email attachments needs to be stored in database because the temp file may not exist anymore when sending the email ### Bugfixes * Fixed ajax cache issue when saving payment or shipping restrictions. Internet Explorer showed the old data state (before storage). diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs index 3ce555f766..8a9beeeea0 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs @@ -42,14 +42,14 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de EmailAccountId = deployment.EmailAccountId }; - foreach (string path in context.DeploymentFiles) + foreach (var path in context.DeploymentFiles) { - string name = Path.GetFileName(path); + var name = Path.GetFileName(path); queuedEmail.Attachments.Add(new QueuedEmailAttachment { - StorageLocation = EmailAttachmentStorageLocation.Path, - Path = path, + StorageLocation = EmailAttachmentStorageLocation.Blob, + Data = File.ReadAllBytes(path), Name = name, MimeType = MimeTypes.MapNameToMimeType(name) }); From f648a637bf89b6842bf725f2edaf4cf8d5b232b5 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 25 Apr 2016 17:28:08 +0200 Subject: [PATCH 060/285] Manually notifying gift card recipient threw an exception --- changelog.md | 3 ++- .../Messages/MessageTokenProvider.cs | 15 ++++++++++++--- .../Controllers/GiftCardController.cs | 2 ++ 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/changelog.md b/changelog.md index b21b7c9094..ed70da672f 100644 --- a/changelog.md +++ b/changelog.md @@ -24,10 +24,11 @@ * Bundles without selected attributes could throw an exception on product detail page * GMC feed did not export the product type (category path) * The error message of a payment provider when capturing a payment was not displayed -* Adding new shipping method throws an exception +* Adding new shipping method threw an exception * Attribute Values: Assigning IsPreselected to more than one value causes an error * BizImporter: fixed redirection bug when default file extension in .biz wasn't .html * Fixed: Export deployment emails were always send manually +* Manually notifying gift card recipient threw an exception ## SmartStore.NET 2.5 diff --git a/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs b/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs index 6a86297fe7..d0ff917259 100644 --- a/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs +++ b/src/Libraries/SmartStore.Services/Messages/MessageTokenProvider.cs @@ -927,15 +927,24 @@ public virtual void AddReturnRequestTokens(IList tokens, ReturnRequest re public virtual void AddGiftCardTokens(IList tokens, GiftCard giftCard) { - var order = giftCard.PurchasedWithOrderItem.Order; - var remainingAmount = _currencyService.ConvertCurrency(giftCard.GetGiftCardRemainingAmount(), order.CurrencyRate); + var order = (giftCard.PurchasedWithOrderItem != null ? giftCard.PurchasedWithOrderItem.Order : null); + + if (order != null) + { + var remainingAmount = _currencyService.ConvertCurrency(giftCard.GetGiftCardRemainingAmount(), order.CurrencyRate); + + tokens.Add(new Token("GiftCard.RemainingAmount", _priceFormatter.FormatPrice(remainingAmount, true, false))); + } + else + { + tokens.Add(new Token("GiftCard.RemainingAmount", "")); + } 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.RemainingAmount", _priceFormatter.FormatPrice(remainingAmount, true, false))); tokens.Add(new Token("GiftCard.CouponCode", giftCard.GiftCardCouponCode)); var giftCardMesage = !String.IsNullOrWhiteSpace(giftCard.Message) ? diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/GiftCardController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/GiftCardController.cs index 586be5bb45..0900557d31 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/GiftCardController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/GiftCardController.cs @@ -270,6 +270,8 @@ public ActionResult NotifyRecipient(GiftCardModel model) { giftCard.IsRecipientNotified = true; _giftCardService.UpdateGiftCard(giftCard); + + NotifySuccess(T("Admin.Common.TaskSuccessfullyProcessed")); } } catch (Exception exc) From cdebfbe76b404915929803032ec498949c6f1976 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 26 Apr 2016 16:48:41 +0200 Subject: [PATCH 061/285] Loading shipping by weight grid failed under SQL CE --- changelog.md | 1 + src/Plugins/SmartStore.ShippingByWeight/Description.txt | 2 +- .../Services/ShippingByWeightService.cs | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index ed70da672f..0eb401e4f8 100644 --- a/changelog.md +++ b/changelog.md @@ -29,6 +29,7 @@ * BizImporter: fixed redirection bug when default file extension in .biz wasn't .html * Fixed: Export deployment emails were always send manually * Manually notifying gift card recipient threw an exception +* Loading shipping by weight grid failed under SQL CE ## SmartStore.NET 2.5 diff --git a/src/Plugins/SmartStore.ShippingByWeight/Description.txt b/src/Plugins/SmartStore.ShippingByWeight/Description.txt index 03312a5943..76876783c9 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Description.txt +++ b/src/Plugins/SmartStore.ShippingByWeight/Description.txt @@ -1,7 +1,7 @@ FriendlyName: Shipping by weight SystemName: SmartStore.ShippingByWeight Group: Shipping -Version: 2.5.0 +Version: 2.5.0.1 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.ShippingByWeight.dll diff --git a/src/Plugins/SmartStore.ShippingByWeight/Services/ShippingByWeightService.cs b/src/Plugins/SmartStore.ShippingByWeight/Services/ShippingByWeightService.cs index 06d9b7ff55..f60212f80c 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Services/ShippingByWeightService.cs +++ b/src/Plugins/SmartStore.ShippingByWeight/Services/ShippingByWeightService.cs @@ -48,7 +48,7 @@ public virtual IQueryable GetShippingByWeightRecords() { var query = from x in _sbwRepository.Table - orderby x.StoreId, x.CountryId, x.ShippingMethodId, x.From, x.Zip + orderby x.StoreId, x.CountryId, x.ShippingMethodId, x.From select x; return query; @@ -83,7 +83,7 @@ public virtual IList GetShippingByWeightModels(int pageIn var shippingMethod = _shippingService.GetShippingMethodById(x.ShippingMethodId); var country = _countryService.GetCountryById(x.CountryId); - var model = new ShippingByWeightModel() + var model = new ShippingByWeightModel { Id = x.Id, StoreId = x.StoreId, From a0ab77b36c0b77d5f569959fd5deacff10db1a66 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 28 Apr 2016 11:08:34 +0200 Subject: [PATCH 062/285] Sending test email: Localization and output of full error message (popup dialog showed too little information) --- ...201603121451066_ThirdPartyEmailHandOver.cs | 12 +++++++ .../Controllers/EmailAccountController.cs | 33 ++++++++++--------- .../Models/Messages/EmailAccountModel.cs | 4 ++- .../Views/EmailAccount/_CreateOrUpdate.cshtml | 12 ++++++- 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs index 0e7b6ad44b..2e601a22e5 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs @@ -214,6 +214,18 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Legt die E-Mail Adressen fest, an die die Daten verschickt werden soll."); builder.AddOrUpdate("Admin.Common.FileNotFound", "File not found", "Datei nicht gefunden"); + + builder.AddOrUpdate("Admin.Common.EnterEmailAdress", + "Please enter an email address.", + "Bitte geben Sie eine E-Mail-Adresse ein."); + + builder.AddOrUpdate("Admin.Configuration.EmailAccounts.TestingEmail", + "Testing email functionality.", + "Test der E-Mail-Funktion."); + + builder.AddOrUpdate("Admin.Common.EmailSuccessfullySent", + "The email has been successfully sent.", + "Die E-Mail wurde erfolgreich versendet."); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/EmailAccountController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/EmailAccountController.cs index 2586a826cb..2d6716d284 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/EmailAccountController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/EmailAccountController.cs @@ -1,11 +1,8 @@ using System; using System.Linq; -using System.Net.Mail; -using System.Threading.Tasks; using System.Web.Mvc; using SmartStore.Admin.Models.Messages; using SmartStore.Core; -using SmartStore.Core.Domain; using SmartStore.Core.Domain.Messages; using SmartStore.Core.Email; using SmartStore.Services.Configuration; @@ -184,28 +181,32 @@ public ActionResult SendTestEmail(EmailAccountModel model) var emailAccount = _emailAccountService.GetEmailAccountById(model.Id); if (emailAccount == null) - //No email account found with the specified id return RedirectToAction("List"); try { - if (String.IsNullOrWhiteSpace(model.SendTestEmailTo)) - throw new SmartException("Enter test email address"); + if (model.SendTestEmailTo.IsEmpty()) + { + NotifyError(T("Admin.Common.EnterEmailAdress")); + } + else + { + var to = new EmailAddress(model.SendTestEmailTo); + var from = new EmailAddress(emailAccount.Email, emailAccount.DisplayName); + var subject = string.Concat(_storeContext.CurrentStore.Name, ". ", T("Admin.Configuration.EmailAccounts.TestingEmail")); + var body = T("Admin.Common.EmailSuccessfullySent"); - var to = new EmailAddress(model.SendTestEmailTo); - var from = new EmailAddress(emailAccount.Email, emailAccount.DisplayName); - string subject = _storeContext.CurrentStore.Name + ". Testing email functionality."; - string body = "Email works fine."; + var msg = new EmailMessage(to, subject, body, from); - var msg = new EmailMessage(to, subject, body, from); - - _emailSender.SendEmail(new SmtpContext(emailAccount), msg); + _emailSender.SendEmail(new SmtpContext(emailAccount), msg); - NotifySuccess(_localizationService.GetResource("Admin.Configuration.EmailAccounts.SendTestEmail.Success"), false); + NotifySuccess(T("Admin.Configuration.EmailAccounts.SendTestEmail.Success"), false); + } } - catch (Exception exc) + catch (Exception exception) { - NotifyError(exc.Message, false); + model.TestEmailShortErrorMessage = exception.ToAllMessages(); + model.TestEmailFullErrorMessage = exception.ToString(); } //If we got this far, something failed, redisplay form diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Messages/EmailAccountModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Messages/EmailAccountModel.cs index 0604e18a0c..8a3fa54a2d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Messages/EmailAccountModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Messages/EmailAccountModel.cs @@ -48,5 +48,7 @@ public class EmailAccountModel : EntityModelBase [AllowHtml] public string SendTestEmailTo { get; set; } - } + public string TestEmailShortErrorMessage { get; set; } + public string TestEmailFullErrorMessage { get; set; } + } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/EmailAccount/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/EmailAccount/_CreateOrUpdate.cshtml index 9028265175..64d56e6030 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/EmailAccount/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/EmailAccount/_CreateOrUpdate.cshtml @@ -100,6 +100,7 @@ +   - } + } + +@if (Model.TestEmailShortErrorMessage.HasValue()) +{ +

    +

    @Model.TestEmailShortErrorMessage
    +
    @Model.TestEmailFullErrorMessage
    +

    +} + From 413b2a8530b5136db881bd00264d89470ac76a7d Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 28 Apr 2016 21:50:23 +0200 Subject: [PATCH 063/285] CSS: reduced throbber title size a bit --- .../SmartStore.Web/Content/bootstrap/custom/throbber.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/throbber.less b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/throbber.less index 72d2b8f2d6..f579532a64 100644 --- a/src/Presentation/SmartStore.Web/Content/bootstrap/custom/throbber.less +++ b/src/Presentation/SmartStore.Web/Content/bootstrap/custom/throbber.less @@ -46,7 +46,7 @@ Version: 1, Timestamp: 18.08.2012 } &.large .throbber-content { - font-size: 42px; + font-size: 32px; font-weight: 100; } From 9f1aa390e8e3238b99557042537583ed18f7837f Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 28 Apr 2016 21:51:49 +0200 Subject: [PATCH 064/285] Prevent double init of import mapping select boxes --- .../Administration/Views/Import/_ColumnMappings.cshtml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml index 45f05961bb..742a0b6508 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml @@ -32,7 +32,7 @@ - @foreach (var property in Model.AvailableEntityProperties) { @@ -41,7 +41,7 @@ - @foreach (var column in Model.AvailableSourceColumns) { @@ -81,7 +81,7 @@ var index = Model.ColumnMappings.IndexOf(mapping) + 1; - @foreach (var property in Model.AvailableEntityProperties) { @@ -91,7 +91,7 @@ - @foreach (var column in Model.AvailableSourceColumns) { From 8ada5fb16f70d1b261b9c97ea3842fce1554d6ad Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 29 Apr 2016 11:31:25 +0200 Subject: [PATCH 065/285] Removed old, unused method --- .../Catalog/Importer/ProductImporter.cs | 71 ------------------- 1 file changed, 71 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index c63ea1fcf9..b848108384 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -225,77 +225,6 @@ private void ProcessProductPictures(IImportExecuteContext context, 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 count = -1; - var thumbPaths = row.GetDataValue>("PictureThumbPaths"); - - if (thumbPaths == null) - continue; - - foreach (var path in thumbPaths.Where(x => x.HasValue() && File.Exists(x))) - { - try - { - 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 (count == -1) - { - count = (currentProductPictures.Any() ? currentProductPictures.Select(x => x.DisplayOrder).Max() : 0); - } - - var pictureBinary = _pictureService.FindEqualPicture(path, currentPictures, out equalPictureId); - - if (pictureBinary != null && pictureBinary.Length > 0) - { - // no equal picture found in sequence - var newPicture = _pictureService.InsertPicture(pictureBinary, "image/jpeg", _pictureService.GetPictureSeName(row.EntityDisplayName), true, false, false); - if (newPicture != null) - { - var mapping = new ProductPicture - { - ProductId = row.Entity.Id, - PictureId = newPicture.Id, - DisplayOrder = ++count - }; - - _productPictureRepository.Insert(mapping); - lastInserted = mapping; - } - } - else - { - var idx = thumbPaths.IndexOf(path) + 1; - context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "PictureThumbPaths" + idx.ToString()); - } - } - catch (Exception exception) - { - var idx = thumbPaths.IndexOf(path) + 1; - context.Result.AddWarning(exception.Message, row.GetRowInfo(), "PictureThumbPaths" + idx.ToString()); - } - } - } - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - _services.EventPublisher.EntityInserted(lastInserted); - } - private int ProcessProductManufacturers(IImportExecuteContext context, ImportRow[] batch) { _productManufacturerRepository.AutoCommitEnabled = false; From 0808445ad17d48cab3a6a6114a0ac45551229001 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 29 Apr 2016 13:10:05 +0200 Subject: [PATCH 066/285] Resolves #940 Import mapping: SeName not available in AvailableEntityProperties select list --- .../Import/ImportProfileService.cs | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs index 76c5c87d32..2443bddfb6 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportProfileService.cs @@ -292,7 +292,6 @@ public virtual Dictionary GetImportableEntityProperties(ImportEn foreach (ImportEntityType type in Enum.GetValues(typeof(ImportEntityType))) { EntitySet entitySet = null; - var dic = new Dictionary(StringComparer.OrdinalIgnoreCase); try { @@ -306,23 +305,30 @@ public virtual Dictionary GetImportableEntityProperties(ImportEn throw new SmartException("There is no entity set for ImportEntityType {0}. Note, the enum value must equal the entity name.".FormatInvariant(type.ToString())); } - foreach (var member in entitySet.ElementType.Members) + var dic = entitySet.ElementType.Members + .Where(x => !x.Name.IsCaseInsensitiveEqual("Id") && x.BuiltInTypeKind.HasFlag(BuiltInTypeKind.EdmProperty)) + .ToDictionary(x => x.Name, x => "", StringComparer.OrdinalIgnoreCase); + + // lack of abstractness? + if ((type == ImportEntityType.Product || type == ImportEntityType.Category) && !dic.ContainsKey("SeName")) { - if (!member.Name.IsCaseInsensitiveEqual("Id") && member.BuiltInTypeKind.HasFlag(BuiltInTypeKind.EdmProperty)) - { - var localizedValue = GetLocalizedPropertyName(type, member.Name); + dic.Add("SeName", ""); + } - dic.Add(member.Name, localizedValue.NaIfEmpty()); + foreach (var key in dic.Keys.ToList()) + { + var localizedValue = GetLocalizedPropertyName(type, key); + + dic[key] = localizedValue.NaIfEmpty(); - if (localizableProperties[type].Contains(member.Name)) + if (localizableProperties[type].Contains(key)) + { + foreach (var language in allLanguages) { - foreach (var language in allLanguages) - { - dic.Add( - "{0}[{1}]".FormatInvariant(member.Name, language.UniqueSeoCode.EmptyNull().ToLower()), - "{0} {1}".FormatInvariant(localizedValue.NaIfEmpty(), allLanguageNames[language.UniqueSeoCode]) - ); - } + dic.Add( + "{0}[{1}]".FormatInvariant(key, language.UniqueSeoCode.EmptyNull().ToLower()), + "{0} {1}".FormatInvariant(localizedValue.NaIfEmpty(), allLanguageNames[language.UniqueSeoCode]) + ); } } } From df53d6816cdd0c705d323f3ef913f838ff178aa8 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 29 Apr 2016 13:45:12 +0200 Subject: [PATCH 067/285] Resolves #938 Import: Make both fields Skip & Take nullable --- .../Administration/Controllers/ImportController.cs | 8 ++++---- .../Models/DataExchange/ImportProfileModel.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index aa1b4c9c6a..1cc4de04d8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -60,8 +60,8 @@ private void PrepareProfileModel(ImportProfileModel model, ImportProfile profile model.Name = profile.Name; model.EntityType = profile.EntityType; model.Enabled = profile.Enabled; - model.Skip = profile.Skip; - model.Take = profile.Take; + model.Skip = (profile.Skip == 0 ? (int?)null : profile.Skip); + model.Take = (profile.Take == 0 ? (int?)null : profile.Take); model.UpdateOnly = profile.UpdateOnly; model.KeyFieldNames = profile.KeyFieldNames.SplitSafe(",").Distinct().ToArray(); model.ScheduleTaskId = profile.SchedulingTaskId; @@ -405,8 +405,8 @@ public ActionResult Edit(ImportProfileModel model, bool continueEditing, FormCol profile.Name = model.Name; profile.EntityType = model.EntityType; profile.Enabled = model.Enabled; - profile.Skip = model.Skip; - profile.Take = model.Take; + profile.Skip = model.Skip ?? 0; + profile.Take = model.Take ?? 0; profile.UpdateOnly = model.UpdateOnly; profile.KeyFieldNames = (model.KeyFieldNames == null ? null : string.Join(",", model.KeyFieldNames)); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs index 16bf2a9675..97c28e957d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs @@ -29,10 +29,10 @@ public partial class ImportProfileModel : EntityModelBase public bool Enabled { get; set; } [SmartResourceDisplayName("Admin.Common.RecordsSkip")] - public int Skip { get; set; } + public int? Skip { get; set; } [SmartResourceDisplayName("Admin.Common.RecordsTake")] - public int Take { get; set; } + public int? Take { get; set; } [SmartResourceDisplayName("Admin.DataExchange.Import.UpdateOnly")] public bool UpdateOnly { get; set; } From 3c5dd9b8168529d72fbef660139926b5ab9ab0d7 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 29 Apr 2016 14:10:12 +0200 Subject: [PATCH 068/285] Resolves #937 Impex UI: allow execution of import/export per button in top button-bar --- .../Controllers/ExportController.cs | 4 ++++ .../Controllers/ImportController.cs | 4 ++++ .../Administration/Views/Export/Edit.cshtml | 18 ++++++++++++++++++ .../Administration/Views/Export/List.cshtml | 2 +- .../Administration/Views/Import/Edit.cshtml | 8 ++++++++ .../Administration/Views/Import/List.cshtml | 2 +- .../Administration/Views/Import/_Update.cshtml | 11 ++++++++++- 7 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 0278aac4d0..36aee37104 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -1055,6 +1055,10 @@ public ActionResult Execute(int id, string selectedIds) NotifyInfo(T("Admin.System.ScheduleTasks.RunNow.Progress.DataExportTask")); + var referrer = Services.WebHelper.GetUrlReferrer(); + if (referrer.HasValue()) + return Redirect(referrer); + return RedirectToAction("List"); } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index 1cc4de04d8..5151c073ac 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -595,6 +595,10 @@ public ActionResult Execute(int id) NotifyInfo(T("Admin.System.ScheduleTasks.RunNow.Progress.DataImportTask")); + var referrer = Services.WebHelper.GetUrlReferrer(); + if (referrer.HasValue()) + return Redirect(referrer); + return RedirectToAction("List"); } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml index 26d12f432b..84f23091ff 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Edit.cshtml @@ -6,6 +6,8 @@ ViewBag.Title = title; } + + @using (Html.BeginForm()) {
    @@ -19,6 +21,13 @@
    @if (Model.HasActiveProvider) { + if (Model.Id != 0) + { + +  @T("Common.Export") + + } +  @T("Admin.Common.Preview") @@ -367,5 +376,14 @@ } }); }); + + // execute export + $('#ProfileExportButton').click(function (e) { + e.preventDefault(); + if (confirm('@T("Admin.Common.AskToProceed")')) { + $('#ProfileExportForm').submit(); + } + return false; + }); }); \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml index 224389951d..f99cc65c0e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/List.cshtml @@ -194,7 +194,7 @@ // start task $(document).on('click', '.btn-run-task', function (e) { e.preventDefault(); - if (confirm('@T("Admin.Common.AreYouSure")')) { + if (confirm('@T("Admin.Common.AskToProceed")')) { var form = $('#ProfileExportForm'); form.find('input[name=Id]').val($(this).attr('data-id')); form.submit(); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/Edit.cshtml index d0616dfc93..4b8970cb7d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/Edit.cshtml @@ -5,6 +5,8 @@ ViewBag.Title = title; } +
    + @using (Html.BeginForm()) {
    @@ -14,6 +16,12 @@ @title - @Model.Name @Html.ActionLink("(" + T("Admin.Common.BackToList") + ")", "List")
    + @if (Model.Id != 0) + { + +  @T("Common.Import") + + } @if (Model.LogFileExists) { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml index 0ec9e61acc..534198f5cb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/List.cshtml @@ -213,7 +213,7 @@ else // start task $(document).on('click', '.btn-run-task', function (e) { e.preventDefault(); - if (confirm('@T("Admin.Common.AreYouSure")')) { + if (confirm('@T("Admin.Common.AskToProceed")')) { var form = $('#ProfileImportForm'); form.find('input[name=Id]').val($(this).attr('data-id')); form.submit(); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml index 0ec0fc58b4..c9c50892a2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml @@ -234,6 +234,15 @@ return false; }); + // execute import + $('#ProfileImportButton').click(function (e) { + e.preventDefault(); + if (confirm('@T("Admin.Common.AskToProceed")')) { + $('#ProfileImportForm').submit(); + } + return false; + }); + // reset column mappings $('#ResetColumnMappings').click(function (e) { e.preventDefault(); @@ -284,7 +293,7 @@ $(this).closest('tr').remove(); }); - + initSelectBoxes($('#ImportColumnMappings')); appendNewMappingRow(); From 0da3f5180ec8148c26b99ab6c10c5f872b32e71a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 30 Apr 2016 01:19:56 +0200 Subject: [PATCH 069/285] Validate import skip and take: Should not be less zero --- .../Migrations/201603121451066_ThirdPartyEmailHandOver.cs | 4 ++++ .../Validators/DataExchange/ImportProfileValidator.cs | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs index 2e601a22e5..9b5970f35a 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201603121451066_ThirdPartyEmailHandOver.cs @@ -226,6 +226,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Common.EmailSuccessfullySent", "The email has been successfully sent.", "Die E-Mail wurde erfolgreich versendet."); + + builder.AddOrUpdate("Admin.Common.SkipAndTakeGreaterThanOrEqualZero", + "Values for skip and limit must be greater than or equal to 0.", + "Werte für Überspringen und Begrenzen müssen größer oder gleich 0 sein."); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ImportProfileValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ImportProfileValidator.cs index 15fa25f6f6..95277d0d85 100644 --- a/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ImportProfileValidator.cs +++ b/src/Presentation/SmartStore.Web/Administration/Validators/DataExchange/ImportProfileValidator.cs @@ -16,6 +16,14 @@ public ImportProfileValidator(ILocalizationService localization) .NotEmpty() .When(x => x.Id != 0) .WithMessage(localization.GetResource("Admin.DataExchange.Import.Validate.OneKeyFieldRequired")); + + RuleFor(x => x.Skip) + .GreaterThanOrEqualTo(0) + .WithMessage(localization.GetResource("Admin.Common.SkipAndTakeGreaterThanOrEqualZero")); + + RuleFor(x => x.Take) + .GreaterThanOrEqualTo(0) + .WithMessage(localization.GetResource("Admin.Common.SkipAndTakeGreaterThanOrEqualZero")); } } } \ No newline at end of file From d376d7f7d53858572f50fb3351cd49db3432aa1d Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 29 Apr 2016 22:58:37 +0200 Subject: [PATCH 070/285] Import: explicit NULL fields should be enclosed in brackets --- .../SmartStore.Services/DataExchange/Import/ImportRow.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs index 33279b7d7b..f49e047900 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -144,8 +144,9 @@ public bool SetProperty( { converted = converter(value, _segmenter.Culture); } - else if (value == DBNull.Value || value.ToString().IsCaseInsensitiveEqual("NULL")) + else if (value == DBNull.Value || value.ToString().IsCaseInsensitiveEqual("[NULL]")) { + // prop is set "explicitly" to null. converted = GetDefaultValue(mapping, propName, defaultValue, result); } else From d8655dce5a66bcc91bf2e86c5483642747499bbf Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Sat, 30 Apr 2016 01:26:06 +0200 Subject: [PATCH 071/285] Import: minor code cleanup --- .../Import/ColumnMapping/ColumnMap.cs | 59 ++++--------------- .../Import/ImportDataSegmenter.cs | 4 +- .../DataExchange/Import/ImportRow.cs | 41 +++++++------ 3 files changed, 36 insertions(+), 68 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs index 813f29b021..7e48cf8339 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs @@ -92,7 +92,18 @@ public bool AddMapping(string sourceColumn, string index, string entityProperty, /// Gets a mapped column value /// /// The name of the column to get a mapped value for. - /// The mapped column value OR - if the name is unmapped - a value with the passed + /// The column index, e.g. a language code (de, en etc.) + /// The mapped column value OR - if the name is unmapped - a value with the passed [] + public ColumnMappingValue GetMapping(string sourceColumn, string index) + { + return GetMapping(CreateSourceName(sourceColumn, index)); + } + + /// + /// Gets a mapped column value + /// + /// The name of the column to get a mapped value for. + /// The mapped column value OR - if the name is unmapped - the value of the passed public ColumnMappingValue GetMapping(string sourceColumn) { ColumnMappingValue result; @@ -118,52 +129,6 @@ public ColumnMappingValue GetMapping(string sourceColumn) // there is no mapping at all return new ColumnMappingValue { Property = sourceColumn }; } - - /// - /// Gets a mapped column value - /// - /// The name of the column to get a mapped value for. - /// The column index, e.g. a language code (de, en etc.) - /// The mapped column value OR - if the name is unmapped - a value with the passed [] - public ColumnMappingValue GetMapping(string sourceColumn, string index) - { - return GetMapping(CreateSourceName(sourceColumn, index)); - } - - /// - /// Gets a mapped property name - /// - /// The name of the column to get a mapped property name for. - /// The mapped property name OR - if the name is unmapped - the passed [] - public string GetMappedProperty(string sourceColumn) - { - ColumnMappingValue result; - - if (_map.TryGetValue(sourceColumn, out result)) - { - return result.Property; - } - - var crossPair = _map.FirstOrDefault(x => x.Value.Property.IsCaseInsensitiveEqual(sourceColumn)); - - if (crossPair.Key.HasValue()) - { - return crossPair.Key; - } - - return sourceColumn; - } - - /// - /// Gets a mapped property name - /// - /// The name of the column to get a mapped property name for. - /// The column index, e.g. a language code (de, en etc.) - /// The mapped property name OR - if the name is unmapped - the passed [] - public string GetMappedProperty(string sourceColumn, string index) - { - return GetMappedProperty(CreateSourceName(sourceColumn, index)); - } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs index 7ca1197f0f..be9cdee26c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs @@ -90,12 +90,12 @@ public int BatchSize public bool HasColumn(string name) { - return _table.HasColumn(_columnMap.GetMappedProperty(name)); + return _table.HasColumn(_columnMap.GetMapping(name).Property); } public bool HasColumn(string name, string index) { - return _table.HasColumn(_columnMap.GetMappedProperty(name, index)); + return _table.HasColumn(_columnMap.GetMapping(name, index).Property); } /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs index f49e047900..82d1f1f315 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -30,7 +30,7 @@ private TProp GetDefaultValue(ColumnMappingValue mapping, string columnNa { if (result != null) { - var msg = "Failed to convert default value '{0}'. Please specify a convertable default value. {1}"; + var msg = "Failed to convert default value '{0}'. Please specify a convertable default value. Column: {1}"; result.AddWarning(msg.FormatInvariant(mapping.Default, exception.Message), this.GetRowInfo(), columnName); } } @@ -135,28 +135,31 @@ public bool SetProperty( { object value; var mapping = _segmenter.ColumnMap.GetMapping(propName); - + if (_row.TryGetValue(mapping.Property, out value)) { - // source contains field value. Set it. - TProp converted; - if (converter != null) - { - converted = converter(value, _segmenter.Culture); - } - else if (value == DBNull.Value || value.ToString().IsCaseInsensitiveEqual("[NULL]")) + if (true /*value != null || value != DBNull.Value*/) { - // prop is set "explicitly" to null. - converted = GetDefaultValue(mapping, propName, defaultValue, result); - } - else - { - converted = value.Convert(_segmenter.Culture); - } + // source contains field value. Set it. + TProp converted; + if (converter != null) + { + converted = converter(value, _segmenter.Culture); + } + else if (value == DBNull.Value || value.ToString().IsCaseInsensitiveEqual("[NULL]")) + { + // prop is set "explicitly" to null. + converted = GetDefaultValue(mapping, propName, defaultValue, result); + } + else + { + converted = value.Convert(_segmenter.Culture); + } - var fastProp = FastProperty.GetProperty(target.GetUnproxiedType(), propName, PropertyCachingStrategy.EagerCached); - fastProp.SetValue(target, converted); - isPropertySet = true; + var fastProp = FastProperty.GetProperty(target.GetUnproxiedType(), propName, PropertyCachingStrategy.EagerCached); + fastProp.SetValue(target, converted); + isPropertySet = true; + } } else { From 79b03ca2c2de057374532a5fd122c9e7afdd091f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 30 Apr 2016 17:11:22 +0200 Subject: [PATCH 072/285] Improved column mapping: Explicitly ignored columns are now handled by ImportRow rather than ColumnMap (perf). Mappings are only stored in database if source column and entity property differs or a defaut value is set. --- .../Import/ColumnMapping/ColumnMap.cs | 15 +----- .../DataExchange/Import/ImportRow.cs | 45 +++++++++--------- .../Controllers/ImportController.cs | 47 ++++++++++--------- 3 files changed, 48 insertions(+), 59 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs index 7e48cf8339..1297a6cdcf 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs @@ -73,6 +73,7 @@ public bool AddMapping(string sourceColumn, string entityProperty, string defaul public bool AddMapping(string sourceColumn, string index, string entityProperty, string defaultValue = null) { Guard.ArgumentNotEmpty(() => sourceColumn); + Guard.ArgumentNotEmpty(() => entityProperty); var isAlreadyMapped = (entityProperty.HasValue() && _map.Any(x => x.Value.Property.IsCaseInsensitiveEqual(entityProperty))); @@ -110,23 +111,9 @@ public ColumnMappingValue GetMapping(string sourceColumn) if (_map.TryGetValue(sourceColumn, out result)) { - // a) source column and property are equal or b) property should be ignored return result; } - var crossPair = _map.FirstOrDefault(x => x.Value.Property.IsCaseInsensitiveEqual(sourceColumn)); - - if (crossPair.Key.HasValue()) - { - // source column and property are unequal - return new ColumnMappingValue - { - Property = crossPair.Key, - Default = (crossPair.Value != null ? crossPair.Value.Default : null) - }; - } - - // there is no mapping at all return new ColumnMappingValue { Property = sourceColumn }; } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs index 82d1f1f315..9b00f885b4 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -135,31 +135,32 @@ public bool SetProperty( { object value; var mapping = _segmenter.ColumnMap.GetMapping(propName); - - if (_row.TryGetValue(mapping.Property, out value)) + + if (mapping.Default != null && mapping.Default == "[NULL]") + { + // explicitly ignore this column + } + else if (_row.TryGetValue(mapping.Property, out value)) { - if (true /*value != null || value != DBNull.Value*/) + // source contains field value. Set it. + TProp converted; + if (converter != null) { - // source contains field value. Set it. - TProp converted; - if (converter != null) - { - converted = converter(value, _segmenter.Culture); - } - else if (value == DBNull.Value || value.ToString().IsCaseInsensitiveEqual("[NULL]")) - { - // prop is set "explicitly" to null. - converted = GetDefaultValue(mapping, propName, defaultValue, result); - } - else - { - converted = value.Convert(_segmenter.Culture); - } - - var fastProp = FastProperty.GetProperty(target.GetUnproxiedType(), propName, PropertyCachingStrategy.EagerCached); - fastProp.SetValue(target, converted); - isPropertySet = true; + converted = converter(value, _segmenter.Culture); + } + else if (value == DBNull.Value || value.ToString().IsCaseInsensitiveEqual("[NULL]")) + { + // prop is set "explicitly" to null. + converted = GetDefaultValue(mapping, propName, defaultValue, result); } + else + { + converted = value.Convert(_segmenter.Culture); + } + + var fastProp = FastProperty.GetProperty(target.GetUnproxiedType(), propName, PropertyCachingStrategy.EagerCached); + fastProp.SetValue(target, converted); + isPropertySet = true; } else { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index 5151c073ac..4d06df163f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -110,7 +110,7 @@ private void PrepareProfileModel(ImportProfileModel model, ImportProfile profile string[] availableKeyFieldNames = null; var mapConverter = new ColumnMapConverter(); var storedMap = mapConverter.ConvertFrom(profile.ColumnMapping); - var hasStoredMappings = (storedMap != null && storedMap.Mappings.Any()); + //var hasStoredMappings = (storedMap != null && storedMap.Mappings.Any()); var map = (invalidMap ?? storedMap) ?? new ColumnMap(); // property name to localized property name @@ -156,11 +156,18 @@ private void PrepareProfileModel(ImportProfileModel model, ImportProfile profile { var mapping = new ColumnMappingItemModel { - Column = (x.Value.Property.IsEmpty() ? null : x.Key), - Property = (x.Value.Property.IsEmpty() ? x.Key : x.Value.Property), + Column = x.Key, + Property = x.Value.Property, Default = x.Value.Default }; + if (mapping.Default != null && mapping.Default == "[NULL]") + { + // explicitly ignore this column + mapping.Column = null; + mapping.Default = null; + } + // add localized to make mappings sortable if (allProperties.ContainsKey(mapping.Property)) { @@ -198,11 +205,16 @@ 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)) + if (!model.ColumnMappings.Any(x => x.Column == column.Name)) { var kvp = allProperties.FirstOrDefault(x => x.Key.IsCaseInsensitiveEqual(column.Name)); + if (kvp.Key.IsEmpty()) + { + var alternativeName = LightweightDataTable.GetAlternativeColumnNameFor(column.Name); + kvp = allProperties.FirstOrDefault(x => x.Key.IsCaseInsensitiveEqual(alternativeName)); + } - if (kvp.Key.HasValue()) + if (kvp.Key.HasValue() && !model.ColumnMappings.Any(x => x.Property == kvp.Key)) { model.ColumnMappings.Add(new ColumnMappingItemModel { @@ -211,21 +223,6 @@ private void PrepareProfileModel(ImportProfileModel model, ImportProfile profile 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 - }); - } - } } } @@ -376,11 +373,15 @@ public ActionResult Edit(ImportProfileModel model, bool continueEditing, FormCol var defaultValue = form["ColumnMapping.Default." + index]; var result = true; - // ignored properties: column is empty means swap column and property (otherwise mapping impossible) if (column.IsEmpty()) - result = map.AddMapping(property, null, null); - else + { + // tell mapper to explicitly ignore this column + result = map.AddMapping(property, null, property, "[NULL]"); + } + else if (!column.IsCaseInsensitiveEqual(property) || defaultValue.HasValue()) + { result = map.AddMapping(column, null, property, defaultValue); + } if (!result) { From 088e6d154d61e6fd590fb6a61f3dfd684c59cb85 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 1 May 2016 10:53:29 +0200 Subject: [PATCH 073/285] Minor change to last commit --- .../Import/ColumnMapping/ColumnMap.cs | 8 ++++++++ .../DataExchange/Import/ImportRow.cs | 20 +++++++++---------- .../Controllers/ImportController.cs | 8 ++++---- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs index 1297a6cdcf..9480583caa 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs @@ -130,5 +130,13 @@ public class ColumnMappingValue /// An optional default value /// public string Default { get; set; } + + /// + /// Indicates whether to explicitly ignore this property + /// + public bool IgnoreProperty + { + get { return Default != null && Default == "[IGNOREPROPERTY]"; } + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs index 9b00f885b4..e1e65088c4 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -39,6 +39,14 @@ private TProp GetDefaultValue(ColumnMappingValue mapping, string columnNa return defaultValue; } + private void CheckInitialized() + { + if (_initialized) + { + throw Error.InvalidOperation("A row must be initialized before interacting with the entity or the data store"); + } + } + public ImportRow(ImportDataSegmenter parent, IDataRow row, int position) { _segmenter = parent; @@ -55,14 +63,6 @@ public void Initialize(T entity, string entityDisplayName) _initialized = true; } - private void CheckInitialized() - { - if (_initialized) - { - throw Error.InvalidOperation("A row must be initialized before interacting with the entity or the data store"); - } - } - public bool IsTransient { get { return _entity.Id == 0; } @@ -136,9 +136,9 @@ public bool SetProperty( object value; var mapping = _segmenter.ColumnMap.GetMapping(propName); - if (mapping.Default != null && mapping.Default == "[NULL]") + if (mapping.IgnoreProperty) { - // explicitly ignore this column + // explicitly ignore this property } else if (_row.TryGetValue(mapping.Property, out value)) { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index 4d06df163f..b76a7c8d31 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -161,9 +161,9 @@ private void PrepareProfileModel(ImportProfileModel model, ImportProfile profile Default = x.Value.Default }; - if (mapping.Default != null && mapping.Default == "[NULL]") + if (x.Value.IgnoreProperty) { - // explicitly ignore this column + // explicitly ignore the property mapping.Column = null; mapping.Default = null; } @@ -375,8 +375,8 @@ public ActionResult Edit(ImportProfileModel model, bool continueEditing, FormCol if (column.IsEmpty()) { - // tell mapper to explicitly ignore this column - result = map.AddMapping(property, null, property, "[NULL]"); + // tell mapper to explicitly ignore the property + result = map.AddMapping(property, null, property, "[IGNOREPROPERTY]"); } else if (!column.IsCaseInsensitiveEqual(property) || defaultValue.HasValue()) { From 87bf9c5bd5a2c070f6c5a43c2c82adf37129c4a8 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 2 May 2016 08:43:37 +0200 Subject: [PATCH 074/285] Resolves #945 Import: the DB columns 'Gtin' and 'ManufacturerPartNumber' should be indexed. --- .../SmartStore.Core/Domain/Catalog/Product.cs | 2 + .../201605020640016_GtinMpnIndex.Designer.cs | 29 ++++ .../201605020640016_GtinMpnIndex.cs | 19 +++ .../201605020640016_GtinMpnIndex.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 7 + 5 files changed, 183 insertions(+) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.resx diff --git a/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs b/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs index 50fd39fe5a..999f66bad3 100644 --- a/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs +++ b/src/Libraries/SmartStore.Core/Domain/Catalog/Product.cs @@ -191,6 +191,7 @@ public string Sku /// Gets or sets the manufacturer part number /// [DataMember] + [Index] public string ManufacturerPartNumber { [DebuggerStepThrough] @@ -208,6 +209,7 @@ public string ManufacturerPartNumber /// Gets or sets the Global Trade Item Number (GTIN). These identifiers include UPC (in North America), EAN (in Europe), JAN (in Japan), and ISBN (for books). /// [DataMember] + [Index] public string Gtin { [DebuggerStepThrough] diff --git a/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.Designer.cs new file mode 100644 index 0000000000..90e9ba6504 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.1.3-40302")] + public sealed partial class GtinMpnIndex : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(GtinMpnIndex)); + + string IMigrationMetadata.Id + { + get { return "201605020640016_GtinMpnIndex"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.cs b/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.cs new file mode 100644 index 0000000000..98f1936ecc --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.cs @@ -0,0 +1,19 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + + public partial class GtinMpnIndex : DbMigration + { + public override void Up() + { + CreateIndex("dbo.Product", "ManufacturerPartNumber"); + CreateIndex("dbo.Product", "Gtin"); + } + + public override void Down() + { + DropIndex("dbo.Product", new[] { "Gtin" }); + DropIndex("dbo.Product", new[] { "ManufacturerPartNumber" }); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.resx b/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.resx new file mode 100644 index 0000000000..15bcc90554 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605020640016_GtinMpnIndex.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index 69c108466d..fb9b3fae43 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -384,6 +384,10 @@ 201603121451066_ThirdPartyEmailHandOver.cs + + + 201605020640016_GtinMpnIndex.cs + @@ -717,6 +721,9 @@ 201603121451066_ThirdPartyEmailHandOver.cs + + 201605020640016_GtinMpnIndex.cs + From bbd55c30ba5d1eacf7277533e082a1fc3f99b04e Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 2 May 2016 12:09:07 +0200 Subject: [PATCH 075/285] Resolves #946 wrong weight display in shopping cart --- .../Controllers/ShoppingCartController.cs | 14 +++++++++++++- .../Models/ShoppingCart/ShoppingCartModel.cs | 1 + .../Views/ShoppingCart/OrderSummary.cshtml | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index eb34bfd641..18019458f4 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -93,6 +93,8 @@ public partial class ShoppingCartController : PublicControllerBase private readonly PluginMediator _pluginMediator; private readonly IQuantityUnitService _quantityUnitService; private readonly Lazy _topicService; + private readonly IMeasureService _measureService; + private readonly MeasureSettings _measureSettings; #endregion @@ -124,7 +126,8 @@ public ShoppingCartController(IProductService productService, CaptchaSettings captchaSettings, AddressSettings addressSettings, HttpContextBase httpContext, PluginMediator pluginMediator, IQuantityUnitService quantityUnitService, - Lazy topicService) + Lazy topicService, + IMeasureService measureService, MeasureSettings measureSettings) { this._productService = productService; this._workContext = workContext; @@ -171,6 +174,8 @@ public ShoppingCartController(IProductService productService, this._pluginMediator = pluginMediator; this._quantityUnitService = quantityUnitService; this._topicService = topicService; + this._measureService = measureService; + this._measureSettings = measureSettings; } #endregion @@ -639,6 +644,13 @@ protected void PrepareShoppingCartModel(ShoppingCartModel model, model.ShowProductBundleImages = _shoppingCartSettings.ShowProductBundleImagesOnShoppingCart; model.ShowSku = _catalogSettings.ShowProductSku; + var measure = _measureService.GetMeasureWeightById(_measureSettings.BaseWeightId); + if(measure != null) + { + model.MeasureUnitName = measure.Name; + } + + var checkoutAttributesXml = _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.CheckoutAttributes, _genericAttributeService); model.CheckoutAttributeInfo = HtmlUtils.ConvertPlainTextToTable(HtmlUtils.ConvertHtmlToPlainText( _checkoutAttributeFormatter.FormatAttributes(checkoutAttributesXml, _workContext.CurrentCustomer) diff --git a/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs b/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs index 4c7753f9fc..e8c08f9810 100644 --- a/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs +++ b/src/Presentation/SmartStore.Web/Models/ShoppingCart/ShoppingCartModel.cs @@ -51,6 +51,7 @@ public ShoppingCartModel() public bool DisplayCommentBox { get; set; } public string CustomerComment { get; set; } + public string MeasureUnitName { get; set; } public CheckoutNewsLetterSubscription NewsLetterSubscription { get; set; } public bool? SubscribeToNewsLetter { get; set; } diff --git a/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.cshtml b/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.cshtml index b2b686c260..da347fc184 100644 --- a/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.cshtml +++ b/src/Presentation/SmartStore.Web/Views/ShoppingCart/OrderSummary.cshtml @@ -101,7 +101,7 @@ @if (Model.DisplayWeight) { - @((item.Weight * item.Quantity).ToString("N2")) kg + @((item.Weight * item.Quantity).ToString("N2")) @Model.MeasureUnitName } From e31d37b140e9c2814fd56e2b02755fb27488db53 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 2 May 2016 14:09:52 +0200 Subject: [PATCH 076/285] Import mapping UI: don't generate all select elements on page init (part 1) --- .../Controllers/ImportController.cs | 4 - .../Models/DataExchange/ImportProfileModel.cs | 9 +- .../Views/Import/_ColumnMappings.cshtml | 134 +++++++++++++---- .../Views/Import/_Update.cshtml | 136 +++++++++++++----- 4 files changed, 210 insertions(+), 73 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index b76a7c8d31..b84a05f871 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -70,10 +70,6 @@ private void PrepareProfileModel(ImportProfileModel model, ImportProfile profile model.IsTaskEnabled = profile.ScheduleTask.Enabled; model.LogFileExists = System.IO.File.Exists(profile.GetImportLogPath()); model.EntityTypeName = profile.EntityType.GetLocalizedEnum(_services.Localization, _services.WorkContext); - model.UnspecifiedString = T("Common.Unspecified"); - model.AddNewString = T("Common.AddNew"); - model.DeleteString = T("Common.Delete"); - model.IgnoreString = T("Admin.Common.Ignore"); model.ExistingFileNames = profile.GetImportFiles() .Select(x => Path.GetFileName(x)) diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs index 97c28e957d..41b83136b7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs @@ -54,10 +54,6 @@ public partial class ImportProfileModel : EntityModelBase public bool LogFileExists { get; set; } public string TempFileName { get; set; } - public string UnspecifiedString { get; set; } - public string AddNewString { get; set; } - public string DeleteString { get; set; } - public string IgnoreString { get; set; } public CsvConfigurationModel CsvConfiguration { get; set; } @@ -78,5 +74,10 @@ public class ColumnMappingItemModel public string Property { get; set; } public string Default { get; set; } + + public string Text + { + get { return ColumnLocalized.HasValue() ? ColumnLocalized : Column; } + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml index 742a0b6508..2fb9921067 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml @@ -1,5 +1,10 @@ @using SmartStore.Admin.Models.DataExchange; @model ImportProfileModel +@{ + var delete = T("Common.Delete"); + var ignore = @T("Admin.Common.Ignore"); + var selectTargetProperty = T("Admin.DataExchange.Import.SelectTargetProperty"); +}
    @@ -27,37 +32,81 @@
    -
    +
    +
    + + + +
    +
    + +
    +
    + + + +
    +
    + +
    - @@ -69,8 +118,8 @@
    - +
    + +
    + + + + + +
    + +
    - +
    + +
    + +
    + +
    - + - + -
    - - + + @@ -81,17 +130,42 @@ var index = Model.ColumnMappings.IndexOf(mapping) + 1; diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml index c9c50892a2..9d66c7ae04 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml @@ -255,51 +255,123 @@ }); // mapping row handling - $('#ImportColumnMappings').on('change', '.mapping-control-property', function () { + $('#ImportColumnMappings').on('change', '.select-property', function () { var row = $(this).closest('tr'); if (row.hasClass('column-mapping-new')) { - var hasColumn = !_.isEmpty(row.find('.mapping-control-column').select2('val')), + var hasColumn = !_.isEmpty(row.find('.select-column').select2('val')), hasProperty = !_.isEmpty($(this).select2('val')); - row.find('.mapping-button-add').prop('disabled', !(hasProperty && hasColumn)); + row.find('.mapping-add').prop('disabled', !(hasProperty && hasColumn)); } - }).on('change', '.mapping-control-column', function () { + }).on('change', '.select-column', function () { var row = $(this).closest('tr'); if (row.hasClass('column-mapping-new')) { var hasColumn = !_.isEmpty($(this).select2('val')), - hasProperty = !_.isEmpty(row.find('.mapping-control-property').select2('val')); + hasProperty = !_.isEmpty(row.find('.select-property').select2('val')); - row.find('.mapping-button-add').prop('disabled', !(hasProperty && hasColumn)); + row.find('.mapping-add').prop('disabled', !(hasProperty && hasColumn)); } - }).on('click', '.mapping-button-add', function () { + }).on('click', '.item-inner', function () { + // start editing mapping item + var item = $(this).closest('.mapping-item'); + + closeAllMappingEdits(); + + if (item.find('select').length > 0) + return; + + var isProperty = item.hasClass('mapping-property'), + template = $(isProperty ? '#MappingTemplateProperty' : '#MappingTemplateColumn').children(':first'); + + // hide static label + item.find('.item-inner').hide(); + + // clone edit template into mapping item + var select = template.clone().appendTo(item).find('select'); + initSelectBox(select, isProperty); + }).on('click', '.mapping-add', function () { + // add mapping item var row = $(this).closest('tr'), table = row.closest('table'), index = parseInt(table.attr('data-maxindex')) + 1; - row.find('.mapping-control-property').attr('name', 'ColumnMapping.Property.' + index); - row.find('.mapping-control-column').attr('name', 'ColumnMapping.Column.' + index); - row.find('.mapping-control-default').attr('name', 'ColumnMapping.Default.' + index); + var property = row.find('.select-property').select2('val'), + column = row.find('.select-column').select2('val'); - table.attr('data-maxindex', index); + row.find('.input-property').attr('name', 'ColumnMapping.Property.' + index).val(property).removeClass('input-property'); + row.find('.input-column').attr('name', 'ColumnMapping.Column.' + index).val(column).removeClass('input-column'); + row.find('.input-default').attr('name', 'ColumnMapping.Default.' + index).removeClass('input-default'); + table.attr('data-maxindex', index); row.removeClass('column-mapping-new'); - row.find('.mapping-button-add').remove(); - row.find('.mapping-button-delete').show(); + + row.find('.item-inner').removeClass('hide'); + updatePropertyLabel(row.find('.mapping-property'), property); + row.find('.mapping-column .left-label').text(column); + + row.find('.select-property').select2('destroy').remove(); + row.find('.select-column').select2('destroy').remove(); + + row.find('.mapping-add').remove(); + row.find('.mapping-delete').show(); appendNewMappingRow(); - }).on('click', '.mapping-button-delete', function () { + }).on('click', '.mapping-apply', function () { + // apply mapping edit + var item = $(this).closest('.mapping-item'), + val = item.find('select').select2('val'); + + item.find('input[type=hidden]').val(val); + + if (item.hasClass('mapping-property')) { + updatePropertyLabel(item, val); + } + else { + item.find('.left-label').text(val); + } + + closeAllMappingEdits(); + }).on('click', '.mapping-cancel', function () { + closeAllMappingEdits(); + }).on('click', '.mapping-delete', function () { $(this).closest('tr').remove(); }); - initSelectBoxes($('#ImportColumnMappings')); + //initSelectBoxes($('#ImportColumnMappings')); appendNewMappingRow(); - function initSelectBoxes(context) { - var options = { + function closeAllMappingEdits() { + $('#ImportColumnMappings').find('.mapping-edit').each(function () { + $(this).closest('.mapping-item').find('.item-inner').show(); + $(this).remove(); + }); + } + + function updatePropertyLabel(context, val) { + context.find('.right-label').text(val); + + var leftLabel = context.find('.left-label'); + isLocalized = (val.indexOf(']', val.length - 1) !== -1), + text = context.find('select').find('option:selected').text(); + + if (text.length > 50) { + leftLabel.text(text.substring(0, 50) + '…'); + leftLabel.attr('title', $('
    ').html(text).text()); + } + else { + leftLabel.text(text); + leftLabel.attr('title', ''); + } + + context.find('i').removeClass('fa-globe fa-chain-broken').addClass(isLocalized ? 'fa-globe' : 'fa-chain-broken'); + } + + function initSelectBox(element, isProperty) { + element.select2({ allowClear: true, minimumResultsForSearch: 16, matcher: function (term, text, opt) { @@ -313,18 +385,10 @@ return true; return false; - } - }; - - context.find('.mapping-control-property').select2($.extend({}, options, { - formatResult: selectPropertyFormatting, - formatSelection: selectPropertyFormatting - })); - - context.find('.mapping-control-column').select2($.extend({}, options, { - formatResult: selectColumnFormatting, - formatSelection: selectColumnFormatting - })); + }, + formatResult: (isProperty ? selectPropertyFormatting : selectColumnFormatting), + formatSelection: (isProperty ? selectPropertyFormatting : selectColumnFormatting) + }); } function appendNewMappingRow() { @@ -332,12 +396,14 @@ if (!mappings.length) return; - var html = $('#ColumnMappingTemplate').find('table tbody').html(); + var html = $('#MappingAddTemplate').find('table tbody').html(); mappings.find('table:first tbody').append(html); var row = mappings.find('table:first tr:last'); - initSelectBoxes(row); + + initSelectBox(row.find('.select-property'), true); + initSelectBox(row.find('.select-column'), false); } function selectPropertyFormatting(item, container, query) { @@ -381,8 +447,8 @@ html += '' + item.id + ''; html += ' 50) { - html += ' title="' + $('
    ').text(item.text).html() + '">' + item.text.substring(0, 50) + '…'; + if (item.text.length > 40) { + html += ' title="' + $('
    ').text(item.text).html() + '">' + item.text.substring(0, 40) + '…'; } else { html += '>' + item.text; @@ -410,8 +476,8 @@ if (query && item.text !== item.id) { html += ' 50) { - html += ' title="' + $('
    ').text(item.text).html() + '">' + item.text.substring(0, 50) + '…'; + if (item.text.length > 40) { + html += ' title="' + $('
    ').text(item.text).html() + '">' + item.text.substring(0, 40) + '…'; } else { html += '>' + item.text; From 1335c6b94840d6c5b1c5fd2deec4c54b4c7ecb7d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 2 May 2016 21:31:37 +0200 Subject: [PATCH 077/285] Import mapping UI: don't generate all select elements on page init (part 2) --- .../Import/ColumnMapping/ColumnMap.cs | 4 ++ .../Administration/Content/admin.less | 33 +++++++++ .../Views/Import/_ColumnMappings.cshtml | 69 ++++++++----------- .../Views/Import/_Update.cshtml | 22 +++--- 4 files changed, 79 insertions(+), 49 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs index 9480583caa..1950b34749 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; namespace SmartStore.Services.DataExchange.Import { @@ -119,16 +120,19 @@ public ColumnMappingValue GetMapping(string sourceColumn) } + [JsonObject(MemberSerialization.OptIn)] public class ColumnMappingValue { /// /// The property name of the target entity /// + [JsonProperty] public string Property { get; set; } /// /// An optional default value /// + [JsonProperty] public string Default { get; set; } /// diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index d1dd3c54ad..d15f091e3e 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -1578,3 +1578,36 @@ table.payment-method-features td { display: inline-block; margin-left: 10px; } + + +/* Import column mapping +-------------------------------------------------------------- */ +.column-mapping { + .column-one, .column-two { + width: 38%; + } + + .select-property, .select-column, .input-default { + width: 98%; + } + + .mapping-item { + .item-inner { + cursor: pointer; + padding: 7px 0; + + .property-icon { + display: inline-block; + margin-right: 0; + } + .left-label { + display: inline-block; + } + .right-label { + float: right; + font-size: 0.9em; + margin-right: 20px; + } + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml index 2fb9921067..9827ce3f29 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml @@ -67,14 +67,14 @@
    @T("Admin.DataExchange.ColumnMapping.EntityProperty")@T("Admin.DataExchange.ColumnMapping.ImportField")@T("Admin.DataExchange.ColumnMapping.EntityProperty")@T("Admin.DataExchange.ColumnMapping.ImportField") @T("Admin.DataExchange.ColumnMapping.DefaultValue")  
    - +
    + + + + @if (mapping.Text.Length > 50) + { + @(mapping.Text.Substring(0, 50))… + } + else + { + @mapping.Text + } + @mapping.Column +
    + + + @* + *@
    - +
    + @mapping.Column +
    + + + @* + *@
    - + -
    -
    - +
    + - - + +
    - @foreach (var property in Model.AvailableEntityProperties) { @@ -86,10 +86,10 @@
    -
    - +
    +
    - @foreach (var column in Model.AvailableSourceColumns) { @@ -99,7 +99,7 @@
    - + @@ -60,7 +60,7 @@ -
    +
    @@ -93,7 +93,7 @@ @foreach (var column in Model.AvailableSourceColumns) { - + } @@ -134,17 +134,18 @@
    - + - @if (mapping.Text.Length > 50) + @if (mapping.PropertyDescription.Length > 50) { - @(mapping.Text.Substring(0, 50))… + @(mapping.PropertyDescription.Substring(0, 50))… } else { - @mapping.Text + @mapping.PropertyDescription } - @mapping.Column + @*@(mapping.Column.HasValue() ? mapping.Column : mapping.Property)*@ + @mapping.Property
    @@ -152,20 +153,22 @@
    - @*TODO: display ignored properties in a multi-select on top of mapping table. user must be able to revise the ignoring.*@ - @if (mapping.Column.HasValue()) - { - @mapping.Column - } - else - { - @ignore - } + + @if (mapping.Column.HasValue()) + { + @mapping.Column + } + else + { + @ignore + } +
    + @if (Model.EntityType == ImportEntityType.Product) + { + + + + + }
    - + @@ -78,7 +77,7 @@ @foreach (var property in Model.AvailableEntityProperties) { - + } @@ -99,7 +98,7 @@ - + + @Html.Attr("disabled", "disabled", mapping.IsDefaultDisabled || !mapping.Column.HasValue()) />
    + @Html.SmartLabelFor(x => x.ExtraData.NumberOfPictures) + + @Html.EditorFor(x => x.ExtraData.NumberOfPictures) + @Html.ValidationMessageFor(x => x.ExtraData.NumberOfPictures) +
    From 398b7735c9215501fa300dd34d707475483608aa Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 May 2016 17:24:23 +0200 Subject: [PATCH 129/285] build.bat: added new lookup folders for vcvarsall.bat --- build.bat | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build.bat b/build.bat index 2cee9dc2b5..b58175809a 100644 --- a/build.bat +++ b/build.bat @@ -5,7 +5,11 @@ FOR %%b in ( "%VS120COMNTOOLS%..\..\VC\vcvarsall.bat" "%ProgramFiles(x86)%\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" - "%ProgramFiles%\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" + "%ProgramFiles%\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" + + "%VS110COMNTOOLS%..\..\VC\vcvarsall.bat" + "%ProgramFiles(x86)%\Microsoft Visual Studio 11.0\VC\vcvarsall.bat" + "%ProgramFiles%\Microsoft Visual Studio 11.0\VC\vcvarsall.bat" ) do ( if exist %%b ( call %%b x86 From b9da0cd69219c4a3ba83087f43f294b63e1913fb Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 11 May 2016 17:31:19 +0200 Subject: [PATCH 130/285] Unit testing --- .../Catalog/PriceCalculationService.cs | 6 ++++-- .../Infrastructure/AutoMapperStartupTask.cs | 12 +++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs index 7a06368334..1aef3aeb63 100644 --- a/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs @@ -758,10 +758,12 @@ public virtual decimal GetUnitPrice(OrganizedShoppingCartItem shoppingCartItem, var attributesTotalPrice = decimal.Zero; - var pvaValues = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml).ToList(); + var pvaValuesEnum = _productAttributeParser.ParseProductVariantAttributeValues(shoppingCartItem.Item.AttributesXml); - if (pvaValues != null) + if (pvaValuesEnum != null) { + var pvaValues = pvaValuesEnum.ToList(); + foreach (var pvaValue in pvaValues) { attributesTotalPrice += GetProductVariantAttributeValuePriceAdjustment(pvaValue); diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs index a1b5004361..839bbc1f05 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs @@ -137,11 +137,13 @@ public void Execute() .ForMember(dest => dest.FlagFileNames, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.LocaleStringResources, mo => mo.Ignore()); - //email account - Mapper.CreateMap() - .ForMember(dest => dest.IsDefaultEmailAccount, mo => mo.Ignore()) - .ForMember(dest => dest.SendTestEmailTo, mo => mo.Ignore()); - Mapper.CreateMap(); + //email account + Mapper.CreateMap() + .ForMember(dest => dest.IsDefaultEmailAccount, mo => mo.Ignore()) + .ForMember(dest => dest.SendTestEmailTo, mo => mo.Ignore()) + .ForMember(dest => dest.TestEmailShortErrorMessage, mo => mo.Ignore()) + .ForMember(dest => dest.TestEmailFullErrorMessage, mo => mo.Ignore()); + Mapper.CreateMap(); //message template Mapper.CreateMap() .ForMember(dest => dest.TokensTree, mo => mo.Ignore()) From 917906fa6315901985d5d8e23247ab2bc5c070cf Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 May 2016 19:40:54 +0200 Subject: [PATCH 131/285] Fixes #961: "Open Redirection Vulnerability" --- .../Authentication/External/AuthorizeState.cs | 2 +- .../ExternalAuthFacebookController.cs | 6 +- .../Controllers/SmartController.cs | 47 ++++++++++++++-- .../Controllers/PackagingController.cs | 13 ++--- .../Controllers/PluginController.cs | 7 +-- .../Controllers/ScheduleTaskController.cs | 7 +-- .../Controllers/SettingController.cs | 16 ++---- .../Controllers/ThemeController.cs | 9 +-- .../Controllers/CommonController.cs | 55 +++++-------------- .../Controllers/CustomerController.cs | 28 ++++------ .../ExternalAuthenticationController.cs | 4 +- .../Controllers/ShoppingCartController.cs | 9 +-- .../Views/Customer/RegisterResult.cshtml | 22 +++----- 13 files changed, 98 insertions(+), 127 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Authentication/External/AuthorizeState.cs b/src/Libraries/SmartStore.Services/Authentication/External/AuthorizeState.cs index dcc6c9762c..35e853bbfc 100644 --- a/src/Libraries/SmartStore.Services/Authentication/External/AuthorizeState.cs +++ b/src/Libraries/SmartStore.Services/Authentication/External/AuthorizeState.cs @@ -17,7 +17,7 @@ public AuthorizeState(string returnUrl, OpenAuthenticationStatus openAuthenticat _returnUrl = returnUrl; AuthenticationStatus = openAuthenticationStatus; - //in way SEO friendly language URLs will be persisted + // in a way SEO friendly language URLs will be persisted if (AuthenticationStatus == OpenAuthenticationStatus.Authenticated) Result = new RedirectResult(!string.IsNullOrEmpty(_returnUrl) ? _returnUrl : "~/"); } diff --git a/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs b/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs index abc3a709bd..5a99884991 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs +++ b/src/Plugins/SmartStore.FacebookAuth/Controllers/ExternalAuthFacebookController.cs @@ -138,10 +138,10 @@ private ActionResult LoginInternal(string returnUrl, bool verifyResponse) if (result.Result != null) return result.Result; - return HttpContext.Request.IsAuthenticated ? - new RedirectResult(!string.IsNullOrEmpty(returnUrl) ? returnUrl : "~/") : + return HttpContext.Request.IsAuthenticated ? + RedirectToReferrer(returnUrl, "~/") : new RedirectResult(Url.LogOn(returnUrl)); - } + } public ActionResult Login(string returnUrl) { diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs index b5db948b54..c2127adf6b 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs @@ -113,14 +113,53 @@ protected virtual void NotifyAccessDenied(bool durable = true, bool log = true) Services.Notifier.Error(message, durable); } - protected virtual ActionResult RedirectToReferrer() + protected ActionResult RedirectToReferrer() { - if (Request.UrlReferrer != null && Request.UrlReferrer.ToString().HasValue()) + return RedirectToReferrer(null, () => RedirectToRoute("HomePage")); + } + + protected ActionResult RedirectToReferrer(string referrer) + { + return RedirectToReferrer(referrer, () => RedirectToRoute("HomePage")); + } + + protected ActionResult RedirectToReferrer(string referrer, string fallbackUrl) + { + // addressing "Open Redirection Vulnerability" (prevent cross-domain redirects / phishing) + if (fallbackUrl.HasValue() && !Url.IsLocalUrl(fallbackUrl)) { - return Redirect(Request.UrlReferrer.ToString()); + fallbackUrl = null; } - return RedirectToRoute("HomePage"); + return RedirectToReferrer( + referrer, + fallbackUrl.HasValue() ? () => Redirect(fallbackUrl) : (Func)null); + } + + protected virtual ActionResult RedirectToReferrer(string referrer, Func fallbackResult) + { + if (referrer.IsEmpty() && Request.UrlReferrer != null && Request.UrlReferrer.ToString().HasValue()) + { + referrer = Request.UrlReferrer.ToString(); + } + + // addressing "Open Redirection Vulnerability" (prevent cross-domain redirects / phishing) + if (referrer.HasValue() && !Url.IsLocalUrl(referrer)) + { + referrer = null; + } + + if (referrer.HasValue()) + { + return Redirect(referrer); + } + + if (fallbackResult != null) + { + return fallbackResult(); + } + + return HttpNotFound(); } protected virtual ActionResult RedirectToHomePageWithError(string reason, bool durable = true) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PackagingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PackagingController.cs index 9cbc47f5f6..12b1cb92dd 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PackagingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PackagingController.cs @@ -45,11 +45,6 @@ public ActionResult UploadPackage(bool isTheme) [HttpPost] public ActionResult UploadPackage(FormCollection form, string returnUrl = "") { - if (returnUrl.IsEmpty()) - { - returnUrl = _services.WebHelper.GetUrlReferrer(); - } - bool isTheme = false; try @@ -69,7 +64,7 @@ public ActionResult UploadPackage(FormCollection form, string returnUrl = "") if (!file.FileExtension.IsCaseInsensitiveEqual(".nupkg")) { NotifyError(T("Admin.Packaging.NotAPackage")); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } var location = CommonHelper.MapPath("~/App_Data"); @@ -102,7 +97,7 @@ public ActionResult UploadPackage(FormCollection form, string returnUrl = "") else { NotifyError(T("Admin.Common.UploadFile")); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } if (!isTheme) @@ -110,13 +105,13 @@ public ActionResult UploadPackage(FormCollection form, string returnUrl = "") _services.WebHelper.RestartAppDomain(); } NotifySuccess(T("Admin.Packaging.InstallSuccess")); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } catch (Exception exc) { NotifyError(exc); Logger.Error(exc); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs index aebeec2e49..971c96eaa9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/PluginController.cs @@ -574,12 +574,7 @@ public ActionResult UpdateStringResources(string systemName, string returnUrl = NotifySuccess(T("Admin.Configuration.Plugins.Resources.UpdateSuccess")); } - if (returnUrl.IsEmpty()) - { - return RedirectToAction("List"); - } - - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl, () => RedirectToAction("List")); } public ActionResult UpdateAllStringResources() diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs index 0239e8603f..453ae689c8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ScheduleTaskController.cs @@ -172,7 +172,7 @@ public ActionResult RunJob(int id, string returnUrl = "") } } - return Redirect(returnUrl.NullEmpty() ?? Request.UrlReferrer.ToString()); + return RedirectToReferrer(returnUrl); } public ActionResult CancelJob(int id /* scheduleTaskId */, string returnUrl = "") @@ -187,8 +187,7 @@ public ActionResult CancelJob(int id /* scheduleTaskId */, string returnUrl = "" NotifyWarning(T("Admin.System.ScheduleTasks.CancellationRequested")); } - returnUrl = returnUrl.NullEmpty() ?? Request.UrlReferrer.ToString(); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } public ActionResult Edit(int id /* taskId */, string returnUrl = null) @@ -223,7 +222,7 @@ public ActionResult Edit(ScheduleTaskModel model, bool continueEditing, string r } var reloadResult = RedirectToAction("Edit", new { id = model.Id, returnUrl = returnUrl }); - var returnResult = Redirect(returnUrl.NullEmpty() ?? Url.Action("List")); + var returnResult = RedirectToReferrer(returnUrl, () => RedirectToAction("List")); var scheduleTask = _scheduleTaskService.GetTaskById(model.Id); if (scheduleTask == null) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs index 4d23358a6c..d26e835c28 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs @@ -139,14 +139,14 @@ public ActionResult StoreScopeConfiguration() if (allStores.Count < 2) return Content(""); - var model = new StoreScopeConfigurationModel() + var model = new StoreScopeConfigurationModel { StoreId = this.GetActiveStoreScopeConfiguration(_services.StoreService, _services.WorkContext) }; foreach (var store in allStores) { - model.AllStores.Add(new SelectListItem() + model.AllStores.Add(new SelectListItem { Text = store.Name, Selected = (store.Id == model.StoreId), @@ -154,7 +154,7 @@ public ActionResult StoreScopeConfiguration() }); } - model.AllStores.Insert(0, new SelectListItem() + model.AllStores.Insert(0, new SelectListItem { Text = _services.Localization.GetResource("Admin.Common.StoresAll"), Selected = (0 == model.StoreId), @@ -172,16 +172,8 @@ public ActionResult ChangeStoreScopeConfiguration(int storeid, string returnUrl _genericAttributeService.SaveAttribute(_services.WorkContext.CurrentCustomer, SystemCustomerAttributeNames.AdminAreaStoreScopeConfiguration, storeid); } - - //url referrer - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = _services.WebHelper.GetUrlReferrer(); - - //home page - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = Url.Action("Index", "Home", new { area = "Admin" }); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl, () => RedirectToAction("Index", "Home", new { area = "Admin" })); } public ActionResult Blog() diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ThemeController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ThemeController.cs index 0e4129951d..9255a18675 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ThemeController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ThemeController.cs @@ -541,7 +541,7 @@ public ActionResult PreviewToolPost(string theme, int storeId, string returnUrl) _services.StoreContext.SetPreviewStore(storeId); } - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } [HttpPost, ActionName("PreviewTool"), FormValueRequired("PreviewMode.Exit")] @@ -556,12 +556,7 @@ public ActionResult ExitPreview() } var returnUrl = (string)TempData["PreviewModeReturnUrl"]; - if (returnUrl.IsEmpty()) - { - returnUrl = Url.Action("Index", "Home", new { area = (string)null }); - } - - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } [HttpPost, ActionName("PreviewTool"), FormValueRequired("PreviewMode.Apply")] diff --git a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs index 54b76c07b9..05cdc3bdc6 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CommonController.cs @@ -368,18 +368,6 @@ public ActionResult SetLanguage(int langid, string returnUrl = "") { _services.WorkContext.WorkingLanguage = language; } - - // url referrer - if (String.IsNullOrEmpty(returnUrl)) - { - returnUrl = _services.WebHelper.GetUrlReferrer(); - } - - // home page - if (String.IsNullOrEmpty(returnUrl)) - { - returnUrl = Url.RouteUrl("HomePage"); - } if (_localizationSettings.SeoFriendlyUrlsForLanguagesEnabled) { @@ -388,10 +376,9 @@ public ActionResult SetLanguage(int langid, string returnUrl = "") returnUrl = helper.GetAbsolutePath(); } - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } - //currency [ChildActionOnly] public ActionResult CurrencySelector() { @@ -402,40 +389,31 @@ public ActionResult CurrencySelector() return PartialView(model); } + public ActionResult CurrencySelected(int customerCurrency, string returnUrl = "") { var currency = _currencyService.Value.GetCurrencyById(customerCurrency); if (currency != null) + { _services.WorkContext.WorkingCurrency = currency; + } - //url referrer - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = _services.WebHelper.GetUrlReferrer(); - //home page - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = Url.RouteUrl("HomePage"); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } - //tax type [ChildActionOnly] public ActionResult TaxTypeSelector() { var model = PrepareTaxTypeSelectorModel(); return PartialView(model); } + public ActionResult TaxTypeSelected(int customerTaxType, string returnUrl = "") { var taxDisplayType = (TaxDisplayType)Enum.ToObject(typeof(TaxDisplayType), customerTaxType); _services.WorkContext.TaxDisplayType = taxDisplayType; - //url referrer - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = _services.WebHelper.GetUrlReferrer(); - //home page - if (String.IsNullOrEmpty(returnUrl)) - returnUrl = Url.RouteUrl("HomePage"); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } //Configuration page (used on mobile devices) @@ -763,12 +741,7 @@ public ActionResult ChangeTheme(string themeName, string returnUrl = null) return Json(new { Success = true }); } - if (returnUrl.IsEmpty()) - { - return RedirectToRoute("HomePage"); - } - - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } [ChildActionOnly] @@ -814,13 +787,13 @@ public ActionResult Favicon() [HttpPost] public ActionResult ChangeDevice(bool dontUseMobileVersion) { - _genericAttributeService.Value.SaveAttribute(_services.WorkContext.CurrentCustomer, - SystemCustomerAttributeNames.DontUseMobileVersion, dontUseMobileVersion, _services.StoreContext.CurrentStore.Id); + _genericAttributeService.Value.SaveAttribute( + _services.WorkContext.CurrentCustomer, + SystemCustomerAttributeNames.DontUseMobileVersion, + dontUseMobileVersion, + _services.StoreContext.CurrentStore.Id); - string returnurl = _services.WebHelper.GetUrlReferrer(); - if (String.IsNullOrEmpty(returnurl)) - returnurl = Url.RouteUrl("HomePage"); - return Redirect(returnurl); + return RedirectToReferrer(); } [ChildActionOnly] diff --git a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs index cd0f271796..9ff54c6482 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs @@ -462,10 +462,7 @@ public ActionResult Login(LoginModel model, string returnUrl, bool captchaValid) //activity log _customerActivityService.InsertActivity("PublicStore.Login", _localizationService.GetResource("ActivityLog.PublicStore.Login"), customer); - if (!String.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl)) - return Redirect(returnUrl); - else - return RedirectToRoute("HomePage"); + return RedirectToReferrer(returnUrl); } else { @@ -579,12 +576,12 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha var registrationResult = _customerRegistrationService.RegisterCustomer(registrationRequest); if (registrationResult.Success) { - //properties + // properties if (_dateTimeSettings.AllowCustomersToSetTimeZone) { _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.TimeZoneId, model.TimeZoneId); } - //VAT number + // VAT number if (_taxSettings.EuVatEnabled) { _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.VatNumber, model.VatNumber); @@ -595,12 +592,12 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.VatNumberStatusId, (int)vatNumberStatus); - //send VAT number admin notification + // send VAT number admin notification if (!String.IsNullOrEmpty(model.VatNumber) && _taxSettings.EuVatEmailAdminWhenNewVatSubmitted) _workflowMessageService.SendNewVatSubmittedStoreOwnerNotification(customer, model.VatNumber, vatAddress, _localizationSettings.DefaultAdminLanguageId); } - //form fields + // form fields if (_customerSettings.GenderEnabled) _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.Gender, model.Gender); _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.FirstName, model.FirstName); @@ -636,10 +633,10 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha if (_customerSettings.CustomerNumberMethod == CustomerNumberMethod.AutomaticallySet && String.IsNullOrEmpty(customer.GetAttribute(SystemCustomerAttributeNames.CustomerNumber))) _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.CustomerNumber, customer.Id); - //newsletter + // newsletter if (_customerSettings.NewsletterEnabled) { - //save newsletter value + // save newsletter value var newsletter = _newsLetterSubscriptionService.GetNewsLetterSubscriptionByEmail(model.Email, _storeContext.CurrentStore.Id); if (newsletter != null) { @@ -718,11 +715,10 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha { case UserRegistrationType.EmailValidation: { - //email validation message + // email validation message _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.AccountActivationToken, Guid.NewGuid().ToString()); _workflowMessageService.SendCustomerEmailValidationMessage(customer, _workContext.WorkingLanguage.Id); - //result return RedirectToRoute("RegisterResult", new { resultId = (int)UserRegistrationType.EmailValidation }); } case UserRegistrationType.AdminApproval: @@ -731,7 +727,7 @@ public ActionResult Register(RegisterModel model, string returnUrl, bool captcha } case UserRegistrationType.Standard: { - //send customer welcome message + // send customer welcome message _workflowMessageService.SendCustomerWelcomeMessage(customer, _workContext.WorkingLanguage.Id); var redirectUrl = Url.RouteUrl("RegisterResult", new { resultId = (int)UserRegistrationType.Standard }); @@ -828,10 +824,8 @@ public ActionResult RegisterResult(int resultId) default: break; } - var model = new RegisterResultModel - { - Result = resultText - }; + + var model = new RegisterResultModel { Result = resultText }; return View(model); } diff --git a/src/Presentation/SmartStore.Web/Controllers/ExternalAuthenticationController.cs b/src/Presentation/SmartStore.Web/Controllers/ExternalAuthenticationController.cs index f95f0ef4d2..3fc35cd8db 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ExternalAuthenticationController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ExternalAuthenticationController.cs @@ -31,10 +31,10 @@ public ExternalAuthenticationController(IOpenAuthenticationService openAuthentic #region Methods - public RedirectResult RemoveParameterAssociation(string returnUrl) + public ActionResult RemoveParameterAssociation(string returnUrl) { ExternalAuthorizerHelper.RemoveParameters(); - return Redirect(returnUrl); + return RedirectToReferrer(returnUrl); } [ChildActionOnly] diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index 18019458f4..a61c57d5ad 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -1749,14 +1749,7 @@ public ActionResult DeleteCartItem(int cartItemId, bool? wishlistItem) public ActionResult ContinueShopping() { string returnUrl = _workContext.CurrentCustomer.GetAttribute(SystemCustomerAttributeNames.LastContinueShoppingPage, _storeContext.CurrentStore.Id); - if (!String.IsNullOrEmpty(returnUrl)) - { - return Redirect(returnUrl); - } - else - { - return RedirectToRoute("HomePage"); - } + return RedirectToReferrer(returnUrl); } [ValidateInput(false)] diff --git a/src/Presentation/SmartStore.Web/Views/Customer/RegisterResult.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/RegisterResult.cshtml index a3a9ac9314..c4bcbfbfdb 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/RegisterResult.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/RegisterResult.cshtml @@ -4,21 +4,17 @@ @using SmartStore.Core.Infrastructure; @using SmartStore.Core.Infrastructure.DependencyManagement; @{ - Layout = "~/Views/Shared/_ColumnsThree.cshtml"; + Layout = "~/Views/Shared/_ColumnsThree.cshtml"; - //title - Html.AddTitleParts(T("PageTitle.Register").Text); + // title + Html.AddTitleParts(T("PageTitle.Register").Text); - //continue URL - var continueUrl = ""; - if (!String.IsNullOrEmpty(this.Context.Request.QueryString["returnUrl"])) - { - continueUrl = this.Context.Request.QueryString["returnUrl"]; - } - else - { - continueUrl = Url.RouteUrl("HomePage"); - } + // continue URL + var continueUrl = this.Context.Request.QueryString["returnUrl"]; + if (!continueUrl.HasValue() || (continueUrl.HasValue() && !Url.IsLocalUrl(continueUrl))) + { + continueUrl = Url.RouteUrl("HomePage"); + } }
    From e91fa05b9dc8b135a583bc283f436688c1e849a7 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 May 2016 20:00:35 +0200 Subject: [PATCH 132/285] Added shortcut extensions methods to ICommonServices for resolution of other services --- .../SmartStore.Services/ICommonServices.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/Libraries/SmartStore.Services/ICommonServices.cs b/src/Libraries/SmartStore.Services/ICommonServices.cs index ee16a33432..f9b3043bf0 100644 --- a/src/Libraries/SmartStore.Services/ICommonServices.cs +++ b/src/Libraries/SmartStore.Services/ICommonServices.cs @@ -86,4 +86,37 @@ IDateTimeHelper DateTimeHelper get; } } + + public static class ICommonServicesExtensions + { + public static TService Resolve(this ICommonServices services) + { + return services.Container.Resolve(); + } + + public static TService Resolve(this ICommonServices services, object serviceKey) + { + return services.Container.ResolveKeyed(serviceKey); + } + + public static TService ResolveNamed(this ICommonServices services, string serviceName) + { + return services.Container.ResolveNamed(serviceName); + } + + public static object Resolve(this ICommonServices services, Type serviceType) + { + return services.Resolve(null, serviceType); + } + + public static object Resolve(this ICommonServices services, object serviceKey, Type serviceType) + { + return services.Container.ResolveKeyed(serviceKey, serviceType); + } + + public static object ResolveNamed(this ICommonServices services, string serviceName, Type serviceType) + { + return services.Container.ResolveNamed(serviceName, serviceType); + } + } } From bb917685463f4674651044f7f28e10251e5e0471 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 11 May 2016 20:46:27 +0200 Subject: [PATCH 133/285] Mobile devices: Fixed "Uncaught Error: Syntax error, unrecognized expression: :nth-child" --- changelog.md | 1 + .../Views/Common/InfoBlock.Mobile.cshtml | 122 ++++++++++-------- 2 files changed, 68 insertions(+), 55 deletions(-) diff --git a/changelog.md b/changelog.md index d538dea8d6..8bd6ef243c 100644 --- a/changelog.md +++ b/changelog.md @@ -35,6 +35,7 @@ * #949 Import: ProcessSlugs does not process explicitly specified "SeName", if product name did not change in an update scenario * Customer import: Creates customer role duplicates for inserted customers * GMC feed does not generate the sale price if the sale price is set for a future date +* Mobile devices: Fixed "Uncaught Error: Syntax error, unrecognized expression: :nth-child" ## SmartStore.NET 2.5 diff --git a/src/Presentation/SmartStore.Web/Views/Common/InfoBlock.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Common/InfoBlock.Mobile.cshtml index 343176764c..4085b4d143 100644 --- a/src/Presentation/SmartStore.Web/Views/Common/InfoBlock.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Common/InfoBlock.Mobile.cshtml @@ -1,62 +1,74 @@ @model InfoBlockModel @using SmartStore.Web.Models.Common; @Html.Widget("mobile_infoblock_before") -
    - -
    -
    -
      - @if (Model.TopicPageUrls.ContainsKey("paymentinfo")) +@if (Model.TopicPageUrls.ContainsKey("contactus") || Model.TopicPageUrls.ContainsKey("aboutus") || Model.BlogEnabled || Model.ForumEnabled) { -
    • @T("Paymentinfo")
    • - } - @if (Model.TopicPageUrls.ContainsKey("shippinginfo")) - { -
    • @T("ShippingReturns")
    • - } -
    -
    -
    - -
    -
    -
      - @if (Model.TopicPageUrls.ContainsKey("imprint")) - { -
    • @T("Imprint")
    • - } - @if (Model.TopicPageUrls.ContainsKey("disclaimer")) - { -
    • @T("Disclaimer")
    • - } -
    -
    +
    + +
    +} +@if (Model.TopicPageUrls.ContainsKey("paymentinfo") || Model.TopicPageUrls.ContainsKey("shippinginfo")) +{ +
    + +
    +} +@if (Model.TopicPageUrls.ContainsKey("privacyinfo") || Model.TopicPageUrls.ContainsKey("conditionsofuse")) +{ +
    + +
    +} +@if (Model.TopicPageUrls.ContainsKey("imprint") || Model.TopicPageUrls.ContainsKey("disclaimer")) +{ +
    +
      + @if (Model.TopicPageUrls.ContainsKey("imprint")) + { +
    • @T("Imprint")
    • + } + @if (Model.TopicPageUrls.ContainsKey("disclaimer")) + { +
    • @T("Disclaimer")
    • + } +
    +
    +} @if (Model.AllowPrivateMessages) {
    From 78b5cf1836c6439d5e1a6ff3cae19a6f0257d76d Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 May 2016 22:14:00 +0200 Subject: [PATCH 134/285] Import refactoring: added DataExchangeSettings to ImportExecuteContext --- .../Catalog/Importer/CategoryImporter.cs | 9 +-- .../Catalog/Importer/ProductImporter.cs | 8 +- .../Customers/Importer/CustomerImporter.cs | 7 +- .../DataExchange/Import/DataImporter.cs | 9 ++- .../DataExchange/Import/EntityImporterBase.cs | 10 ++- .../Import/ImportExecuteContext.cs | 81 ++++++++++++++++--- 6 files changed, 87 insertions(+), 37 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index fb99e870f6..8a94dd461c 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -34,7 +34,6 @@ public class CategoryImporter : EntityImporterBase private readonly ILocalizedEntityService _localizedEntityService; private readonly FileDownloadManager _fileDownloadManager; private readonly SeoSettings _seoSettings; - private readonly DataExchangeSettings _dataExchangeSettings; private static readonly Dictionary>> _localizableProperties = new Dictionary>> { @@ -59,8 +58,7 @@ public CategoryImporter( IPictureService pictureService, ILocalizedEntityService localizedEntityService, FileDownloadManager fileDownloadManager, - SeoSettings seoSettings, - DataExchangeSettings dataExchangeSettings) + SeoSettings seoSettings) { _categoryRepository = categoryRepository; _urlRecordRepository = urlRecordRepository; @@ -74,7 +72,6 @@ public CategoryImporter( _localizedEntityService = localizedEntityService; _fileDownloadManager = fileDownloadManager; _seoSettings = seoSettings; - _dataExchangeSettings = dataExchangeSettings; } protected virtual int ProcessSlugs(IImportExecuteContext context, IEnumerable> batch) @@ -477,11 +474,11 @@ protected override void Import(IImportExecuteContext context) var templateViewPaths = _categoryTemplateService.GetAllCategoryTemplates().ToDictionarySafe(x => x.ViewPath, x => x.Id); - using (var scope = new DbContextScope(ctx: _categoryRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) + using (var scope = new DbContextScope(ctx: context.Services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { var segmenter = context.CreateSegmenter(); - Init(context, _dataExchangeSettings); + Initialize(context); var localizedProperties = ResolveLocalizedProperties(segmenter).ToArray(); diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 7f98b1d17a..6aab9d005a 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -39,7 +39,6 @@ public class ProductImporter : EntityImporterBase private readonly IStoreMappingService _storeMappingService; private readonly FileDownloadManager _fileDownloadManager; private readonly SeoSettings _seoSettings; - private readonly DataExchangeSettings _dataExchangeSettings; private static readonly Dictionary>> _localizableProperties = new Dictionary>> { @@ -69,8 +68,7 @@ public ProductImporter( IProductTemplateService productTemplateService, IStoreMappingService storeMappingService, FileDownloadManager fileDownloadManager, - SeoSettings seoSettings, - DataExchangeSettings dataExchangeSettings) + SeoSettings seoSettings) { _productPictureRepository = productPictureRepository; _productManufacturerRepository = productManufacturerRepository; @@ -88,9 +86,7 @@ public ProductImporter( _productTemplateService = productTemplateService; _storeMappingService = storeMappingService; _fileDownloadManager = fileDownloadManager; - _seoSettings = seoSettings; - _dataExchangeSettings = dataExchangeSettings; } private int? ZeroToNull(object value, CultureInfo culture) @@ -740,7 +736,7 @@ protected override void Import(IImportExecuteContext context) using (var scope = new DbContextScope(ctx: _productRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { var segmenter = context.CreateSegmenter(); - Init(context, _dataExchangeSettings); + Initialize(context); var localizedProperties = ResolveLocalizedProperties(segmenter).ToArray(); diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index fd949b85d9..528bc5d7d1 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -39,7 +39,6 @@ public class CustomerImporter : EntityImporterBase private readonly CustomerSettings _customerSettings; private readonly DateTimeSettings _dateTimeSettings; private readonly ForumSettings _forumSettings; - private readonly DataExchangeSettings _dataExchangeSettings; public CustomerImporter( IRepository customerRepository, @@ -54,8 +53,7 @@ public CustomerImporter( FileDownloadManager fileDownloadManager, CustomerSettings customerSettings, DateTimeSettings dateTimeSettings, - ForumSettings forumSettings, - DataExchangeSettings dataExchangeSettings) + ForumSettings forumSettings) { _customerRepository = customerRepository; _pictureRepository = pictureRepository; @@ -70,7 +68,6 @@ public CustomerImporter( _customerSettings = customerSettings; _dateTimeSettings = dateTimeSettings; _forumSettings = forumSettings; - _dataExchangeSettings = dataExchangeSettings; } private int? CountryCodeToId(Dictionary allCountries, string code) @@ -580,7 +577,7 @@ protected override void Import(IImportExecuteContext context) { var segmenter = context.CreateSegmenter(); - Init(context, _dataExchangeSettings); + Initialize(context); context.Result.TotalRecords = segmenter.TotalRows; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs index 9391d96c71..1739295b85 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs @@ -27,6 +27,7 @@ public partial class DataImporter : IDataImporter private readonly Lazy _emailAccountService; private readonly Lazy _emailSender; private readonly Lazy _contactDataSettings; + private readonly Lazy _dataExchangeSettings; public DataImporter( ICommonServices services, @@ -35,8 +36,8 @@ public DataImporter( Func importerFactory, Lazy emailAccountService, Lazy emailSender, - Lazy dataExchangeSettings, - Lazy contactDataSettings) + Lazy contactDataSettings, + Lazy dataExchangeSettings) { _services = services; _importProfileService = importProfileService; @@ -45,6 +46,7 @@ public DataImporter( _emailAccountService = emailAccountService; _emailSender = emailSender; _contactDataSettings = contactDataSettings; + _dataExchangeSettings = dataExchangeSettings; T = NullLocalizer.Instance; } @@ -253,6 +255,7 @@ private void ImportCoreOuter(DataImporterContext ctx) { ctx.Log = logger; + ctx.ExecuteContext.DataExchangeSettings = _dataExchangeSettings.Value; ctx.ExecuteContext.Services = _services; ctx.ExecuteContext.Log = logger; ctx.ExecuteContext.Languages = _languageService.GetAllLanguages(true); @@ -269,7 +272,7 @@ private void ImportCoreOuter(DataImporterContext ctx) var files = ctx.Request.Profile.GetImportFiles(); if (files.Count == 0) - throw new SmartException("No files to import found."); + throw new SmartException("No files to import."); if (!HasPermission(ctx)) throw new SmartException("You do not have permission to perform the selected import."); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index 4423364e73..e0e3cba1f5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -53,14 +53,16 @@ public void Execute(IImportExecuteContext context) protected abstract void Import(IImportExecuteContext context); - public void Init(IImportExecuteContext context, DataExchangeSettings dataExchangeSettings) + protected void Initialize(IImportExecuteContext context) { UtcNow = DateTime.UtcNow; DownloadedItems = new Dictionary(); ImageDownloadFolder = Path.Combine(context.ImportFolder, _imageDownloadFolder); - if (dataExchangeSettings.ImageImportFolder.HasValue()) - ImageFolder = Path.Combine(context.ImportFolder, dataExchangeSettings.ImageImportFolder); + var settings = context.DataExchangeSettings; + + if (settings.ImageImportFolder.HasValue()) + ImageFolder = Path.Combine(context.ImportFolder, settings.ImageImportFolder); else ImageFolder = context.ImportFolder; @@ -69,7 +71,7 @@ public void Init(IImportExecuteContext context, DataExchangeSettings dataExchang DownloaderContext = new FileDownloadManagerContext { - Timeout = TimeSpan.FromMinutes(dataExchangeSettings.ImageDownloadTimeout), + Timeout = TimeSpan.FromMinutes(settings.ImageDownloadTimeout), Logger = context.Log, CancellationToken = context.CancellationToken }; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index cc69ceec35..339e662706 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -8,6 +8,8 @@ namespace SmartStore.Services.DataExchange.Import { public interface IImportExecuteContext { + DataExchangeSettings DataExchangeSettings { get; } + /// /// Whether to only update existing records /// @@ -66,7 +68,6 @@ public interface IImportExecuteContext /// /// Creates a segmenter instance /// - /// /// ImportDataSegmenter CreateSegmenter(); @@ -98,32 +99,86 @@ public ImportExecuteContext( Result = new ImportResult(); } - public IDataTable DataTable { get; internal set; } + public DataExchangeSettings DataExchangeSettings + { + get; + internal set; + } - public ColumnMap ColumnMap { get; internal set; } + public IDataTable DataTable + { + get; + internal set; + } + + public ColumnMap ColumnMap + { + get; + internal set; + } - public bool UpdateOnly { get; internal set; } + public bool UpdateOnly + { + get; + internal set; + } - public string[] KeyFieldNames { get; internal set; } + public string[] KeyFieldNames + { + get; + internal set; + } - public IList Languages { get; internal set; } + public IList Languages + { + get; + internal set; + } - public ILogger Log { get; internal set; } + public ILogger Log + { + get; + internal set; + } - public ICommonServices Services { get; internal set; } + public ICommonServices Services + { + get; + internal set; + } - public CancellationToken CancellationToken { get; private set; } + public CancellationToken CancellationToken + { + get; + private set; + } - public string ImportFolder { get; internal set; } + public string ImportFolder + { + get; + internal set; + } /// /// Use this dictionary for any custom data required along the import /// - public Dictionary CustomProperties { get; set; } + public Dictionary CustomProperties + { + get; + set; + } - public ImportResult Result { get; set; } + public ImportResult Result + { + get; + set; + } - public ImportExtraData ExtraData { get; internal set; } + public ImportExtraData ExtraData + { + get; + internal set; + } public DataExchangeAbortion Abort { From 581496a40c0f2cd2473ff375c8961b20cc2bc89c Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 May 2016 22:24:37 +0200 Subject: [PATCH 135/285] Import refactoring: removed IImportExecuteContext interface (the concrete type is sufficient) --- .../Catalog/Importer/CategoryImporter.cs | 14 +- .../Catalog/Importer/ProductImporter.cs | 18 +-- .../Customers/Importer/CustomerImporter.cs | 12 +- .../DataExchange/Import/EntityImporterBase.cs | 6 +- .../DataExchange/Import/IEntityImporter.cs | 2 +- .../Import/ImportExecuteContext.cs | 120 +++++++----------- .../NewsLetterSubscriptionImporter.cs | 2 +- 7 files changed, 72 insertions(+), 102 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 8a94dd461c..84fb723e62 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -74,7 +74,7 @@ public CategoryImporter( _seoSettings = seoSettings; } - protected virtual int ProcessSlugs(IImportExecuteContext context, IEnumerable> batch) + protected virtual int ProcessSlugs(ImportExecuteContext context, IEnumerable> batch) { var entityName = typeof(Category).Name; var slugMap = new Dictionary(); @@ -148,7 +148,7 @@ protected virtual int ProcessSlugs(IImportExecuteContext context, IEnumerable> batch, string[] localizedProperties) { @@ -188,7 +188,7 @@ protected virtual int ProcessLocalizations( } protected virtual int ProcessParentMappings( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, Dictionary srcToDestId) { @@ -220,7 +220,7 @@ protected virtual int ProcessParentMappings( } protected virtual int ProcessPictures( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, Dictionary srcToDestId) { @@ -294,7 +294,7 @@ protected virtual int ProcessPictures( return num; } - protected virtual int ProcessStoreMappings(IImportExecuteContext context, IEnumerable> batch) + protected virtual int ProcessStoreMappings(ImportExecuteContext context, IEnumerable> batch) { _storeMappingRepository.AutoCommitEnabled = false; @@ -312,7 +312,7 @@ protected virtual int ProcessStoreMappings(IImportExecuteContext context, IEnume } protected virtual int ProcessCategories( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, Dictionary templateViewPaths, Dictionary srcToDestId) @@ -468,7 +468,7 @@ protected override IDictionary>> GetLo return _localizableProperties; } - protected override void Import(IImportExecuteContext context) + protected override void Import(ImportExecuteContext context) { var srcToDestId = new Dictionary(); diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 6aab9d005a..b3b8a5d3ec 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -101,7 +101,7 @@ public ProductImporter( } protected virtual int ProcessProductMappings( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, Dictionary srcToDestId) { @@ -132,7 +132,7 @@ protected virtual int ProcessProductMappings( return num; } - protected virtual void ProcessProductPictures(IImportExecuteContext context, IEnumerable> batch) + protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnumerable> batch) { // true, cause pictures must be saved and assigned an id prior adding a mapping. _productPictureRepository.AutoCommitEnabled = true; @@ -243,7 +243,7 @@ protected virtual void ProcessProductPictures(IImportExecuteContext context, IEn } } - protected virtual int ProcessProductManufacturers(IImportExecuteContext context, IEnumerable> batch) + protected virtual int ProcessProductManufacturers(ImportExecuteContext context, IEnumerable> batch) { _productManufacturerRepository.AutoCommitEnabled = false; @@ -294,7 +294,7 @@ protected virtual int ProcessProductManufacturers(IImportExecuteContext context, return num; } - protected virtual int ProcessProductCategories(IImportExecuteContext context, IEnumerable> batch) + protected virtual int ProcessProductCategories(ImportExecuteContext context, IEnumerable> batch) { _productCategoryRepository.AutoCommitEnabled = false; @@ -346,7 +346,7 @@ protected virtual int ProcessProductCategories(IImportExecuteContext context, IE } protected virtual int ProcessLocalizations( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, string[] localizedProperties) { @@ -386,7 +386,7 @@ protected virtual int ProcessLocalizations( } protected virtual int ProcessSlugs( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, string[] columnIndexes) { @@ -465,7 +465,7 @@ protected virtual int ProcessSlugs( return _urlRecordRepository.Context.SaveChanges(); } - protected virtual int ProcessStoreMappings(IImportExecuteContext context, IEnumerable> batch) + protected virtual int ProcessStoreMappings(ImportExecuteContext context, IEnumerable> batch) { _storeMappingRepository.AutoCommitEnabled = false; @@ -483,7 +483,7 @@ protected virtual int ProcessStoreMappings(IImportExecuteContext context, IEnume } protected virtual int ProcessProducts( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, Dictionary templateViewPaths, Dictionary srcToDestId) @@ -727,7 +727,7 @@ protected override IDictionary>> GetLoc return _localizableProperties; } - protected override void Import(IImportExecuteContext context) + protected override void Import(ImportExecuteContext context) { var srcToDestId = new Dictionary(); diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 528bc5d7d1..ed37970740 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -137,7 +137,7 @@ private void UpsertRole(ImportRow row, CustomerRole role, bool value) } protected virtual int ProcessAddresses( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, Dictionary allCountries, Dictionary, int> allStateProvinces) @@ -154,7 +154,7 @@ protected virtual int ProcessAddresses( private void ImportAddress( string fieldPrefix, ImportRow row, - IImportExecuteContext context, + ImportExecuteContext context, Dictionary allCountries, Dictionary, int> allStateProvinces) { @@ -227,7 +227,7 @@ private void ImportAddress( } protected virtual int ProcessGenericAttributes( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, Dictionary allCountries, Dictionary, int> allStateProvinces, @@ -313,7 +313,7 @@ protected virtual int ProcessGenericAttributes( } protected virtual int ProcessAvatars( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch) { foreach (var row in batch) @@ -376,7 +376,7 @@ protected virtual int ProcessAvatars( } protected virtual int ProcessCustomers( - IImportExecuteContext context, + ImportExecuteContext context, IEnumerable> batch, List allAffiliateIds) { @@ -547,7 +547,7 @@ protected override IDictionary>> GetLo throw new NotImplementedException(); } - protected override void Import(IImportExecuteContext context) + protected override void Import(ImportExecuteContext context) { var customer = _services.WorkContext.CurrentCustomer; var allowManagingCustomerRoles = _services.Permissions.Authorize(StandardPermissionProvider.ManageCustomerRoles, customer); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index e0e3cba1f5..44af3a266a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -46,14 +46,14 @@ public FileDownloadManagerContext DownloaderContext private set; } - public void Execute(IImportExecuteContext context) + public void Execute(ImportExecuteContext context) { Import(context); } - protected abstract void Import(IImportExecuteContext context); + protected abstract void Import(ImportExecuteContext context); - protected void Initialize(IImportExecuteContext context) + protected void Initialize(ImportExecuteContext context) { UtcNow = DateTime.UtcNow; DownloadedItems = new Dictionary(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/IEntityImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/IEntityImporter.cs index a33f2a3ccd..bc64b63531 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/IEntityImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/IEntityImporter.cs @@ -2,6 +2,6 @@ { public partial interface IEntityImporter { - void Execute(IImportExecuteContext context); + void Execute(ImportExecuteContext context); } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index 339e662706..8786b5e311 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -6,81 +6,7 @@ namespace SmartStore.Services.DataExchange.Import { - public interface IImportExecuteContext - { - DataExchangeSettings DataExchangeSettings { get; } - - /// - /// Whether to only update existing records - /// - bool UpdateOnly { get; } - - /// - /// Name of key fields to identify existing records for updating - /// - string[] KeyFieldNames { get; } - - /// - /// The import folder - /// - string ImportFolder { get; } - - /// - /// Use this dictionary for any custom data required along the export - /// - Dictionary CustomProperties { get; set; } - - /// - /// All languages - /// - IList Languages { get; } - - /// - /// To log information into the import log file - /// - ILogger Log { get; } - - /// - /// Common Services - /// - ICommonServices Services { get; } - - /// - /// Cancellation token - /// - CancellationToken CancellationToken { get; } - - /// - /// Result of the import - /// - ImportResult Result { get; set; } - - /// - /// Extra import configuration data - /// - ImportExtraData ExtraData { get; } - - /// - /// Indicates whether and how to abort the import - /// - DataExchangeAbortion Abort { get; set; } - - /// - /// Creates a segmenter instance - /// - /// - ImportDataSegmenter CreateSegmenter(); - - /// - /// Allows to set a progress message - /// - /// Progress value - /// /// Progress maximum - void SetProgress(int value, int maximum); - } - - - public class ImportExecuteContext : IImportExecuteContext + public class ImportExecuteContext { private DataExchangeAbortion _abortion; private ProgressValueSetter _progressValueSetter; @@ -99,60 +25,90 @@ public ImportExecuteContext( Result = new ImportResult(); } + /// + /// Import settings + /// public DataExchangeSettings DataExchangeSettings { get; internal set; } + /// + /// The data source (CSV, Excel etc.) + /// public IDataTable DataTable { get; internal set; } + /// + /// Mapping information between database and data source + /// public ColumnMap ColumnMap { get; internal set; } + /// + /// Whether to only update existing records + /// public bool UpdateOnly { get; internal set; } + /// + /// Name of key fields to identify existing records for updating + /// public string[] KeyFieldNames { get; internal set; } + /// + /// All active languages + /// public IList Languages { get; internal set; } + /// + /// To log information into the import log file + /// public ILogger Log { get; internal set; } + /// + /// Common Services + /// public ICommonServices Services { get; internal set; } + /// + /// Cancellation token + /// public CancellationToken CancellationToken { get; private set; } + /// + /// The import folder + /// public string ImportFolder { get; @@ -168,18 +124,27 @@ public Dictionary CustomProperties set; } + /// + /// Result of the import + /// public ImportResult Result { get; set; } + /// + /// Extra import configuration data + /// public ImportExtraData ExtraData { get; internal set; } + /// + /// Indicates whether and how to abort the import + /// public DataExchangeAbortion Abort { get @@ -208,6 +173,11 @@ public ImportDataSegmenter CreateSegmenter() return new ImportDataSegmenter(DataTable, ColumnMap); } + /// + /// Allows to set a progress message + /// + /// Progress value + /// /// Progress maximum public void SetProgress(int value, int maximum) { try diff --git a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs index cb89878d6b..263cb3dfc6 100644 --- a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs +++ b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs @@ -36,7 +36,7 @@ public static string[] DefaultKeyFields } } - public void Execute(IImportExecuteContext context) + public void Execute(ImportExecuteContext context) { var utcNow = DateTime.UtcNow; var currentStoreId = _services.StoreContext.CurrentStore.Id; From 18651fdef1eb529eae88858a7d3ac4074a873ee6 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 11 May 2016 22:40:26 +0200 Subject: [PATCH 136/285] Import refactoring: CreateDataSegmenter() > DataSegmenter --- .../Catalog/Importer/CategoryImporter.cs | 4 +-- .../Catalog/Importer/ProductImporter.cs | 4 +-- .../Customers/Importer/CustomerImporter.cs | 4 +-- .../DataExchange/Import/EntityImporterBase.cs | 2 ++ .../Import/ImportDataSegmenter.cs | 24 --------------- .../Import/ImportExecuteContext.cs | 30 ++++++++++++++++--- .../NewsLetterSubscriptionImporter.cs | 2 +- 7 files changed, 32 insertions(+), 38 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 84fb723e62..fae01e82ec 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -476,14 +476,12 @@ protected override void Import(ImportExecuteContext context) using (var scope = new DbContextScope(ctx: context.Services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { - var segmenter = context.CreateSegmenter(); + var segmenter = context.DataSegmenter; Initialize(context); var localizedProperties = ResolveLocalizedProperties(segmenter).ToArray(); - context.Result.TotalRecords = segmenter.TotalRows; - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { var batch = segmenter.GetCurrentBatch(); diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index b3b8a5d3ec..4197839911 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -735,13 +735,11 @@ protected override void Import(ImportExecuteContext context) using (var scope = new DbContextScope(ctx: _productRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { - var segmenter = context.CreateSegmenter(); + var segmenter = context.DataSegmenter; Initialize(context); var localizedProperties = ResolveLocalizedProperties(segmenter).ToArray(); - context.Result.TotalRecords = segmenter.TotalRows; - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { var batch = segmenter.GetCurrentBatch(); diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index ed37970740..3eec752b0b 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -575,12 +575,10 @@ protected override void Import(ImportExecuteContext context) using (var scope = new DbContextScope(ctx: _services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) { - var segmenter = context.CreateSegmenter(); + var segmenter = context.DataSegmenter; Initialize(context); - context.Result.TotalRecords = segmenter.TotalRows; - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { var batch = segmenter.GetCurrentBatch(); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index 44af3a266a..7d2f08871e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -75,6 +75,8 @@ protected void Initialize(ImportExecuteContext context) Logger = context.Log, CancellationToken = context.CancellationToken }; + + context.Result.TotalRecords = context.DataSegmenter.TotalRows; } public FileDownloadManagerItem CreateDownloadImage(string urlOrPath, string seoName, int displayOrder) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs index d4a1fc01d6..41129e24f8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportDataSegmenter.cs @@ -219,30 +219,6 @@ public bool ReadNextBatch() return false; } - //public IEnumerable> CurrentBatch - //{ - // get - // { - // if (_currentBatch == null) - // { - // int start = _pageable.FirstItemIndex - 1; - // int end = _pageable.LastItemIndex - 1; - - // _currentBatch = new ImportRow[(end - start) + 1]; - - // // Determine values per row - // int i = 0; - // for (int r = start; r <= end; r++) - // { - // _currentBatch[i] = new ImportRow(this, _table.Rows[r], r); - // i++; - // } - // } - - // return _currentBatch.Cast>(); - // } - //} - public IEnumerable> GetCurrentBatch() where T : BaseEntity { if (_currentBatch == null) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index 8786b5e311..67e3c090a1 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -12,6 +12,9 @@ public class ImportExecuteContext private ProgressValueSetter _progressValueSetter; private string _progressInfo; + private IDataTable _dataTable; + private ImportDataSegmenter _segmenter; + public ImportExecuteContext( CancellationToken cancellation, ProgressValueSetter progressValueSetter, @@ -39,8 +42,15 @@ public DataExchangeSettings DataExchangeSettings /// public IDataTable DataTable { - get; - internal set; + get + { + return _dataTable; + } + internal set + { + _dataTable = value; + _segmenter = null; + } } /// @@ -168,9 +178,21 @@ public bool IsMaxFailures } } - public ImportDataSegmenter CreateSegmenter() + public ImportDataSegmenter DataSegmenter { - return new ImportDataSegmenter(DataTable, ColumnMap); + get + { + if (_segmenter == null) + { + if (this.DataTable == null || this.ColumnMap == null) + { + throw new SmartException("A DataTable and a ColumnMap must be specified before accessing the DataSegmenter property."); + } + _segmenter = new ImportDataSegmenter(DataTable, ColumnMap); + } + + return _segmenter; + } } /// diff --git a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs index 263cb3dfc6..979051d1d6 100644 --- a/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs +++ b/src/Libraries/SmartStore.Services/Messages/Importer/NewsLetterSubscriptionImporter.cs @@ -43,7 +43,7 @@ public void Execute(ImportExecuteContext context) using (var scope = new DbContextScope(ctx: _services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) { - var segmenter = context.CreateSegmenter(); + var segmenter = context.DataSegmenter; context.Result.TotalRecords = segmenter.TotalRows; From 224b3c97514c1787d7ee017954e98a1009039afc Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 May 2016 00:52:53 +0200 Subject: [PATCH 137/285] Import: correct import handling of (localized) SeName --- .../Catalog/Importer/CategoryImporter.cs | 63 +++++++-------- .../Catalog/Importer/ProductImporter.cs | 81 +++++++++---------- .../Customers/Importer/CustomerImporter.cs | 2 +- .../Import/ColumnMapping/ColumnMap.cs | 12 ++- .../DataExchange/Import/DataImporter.cs | 3 +- .../DataExchange/Import/EntityImporterBase.cs | 1 + .../DataExchange/Import/ImportRow.cs | 4 +- .../Localization/ILocalizedEntityService.cs | 6 +- 8 files changed, 89 insertions(+), 83 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index fae01e82ec..a5010b2a72 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -74,9 +74,11 @@ public CategoryImporter( _seoSettings = seoSettings; } - protected virtual int ProcessSlugs(ImportExecuteContext context, IEnumerable> batch) + protected virtual int ProcessSlugs( + ImportExecuteContext context, + IEnumerable> batch) { - var entityName = typeof(Category).Name; + var entityName = typeof(Product).Name; var slugMap = new Dictionary(); UrlRecord urlRecord = null; @@ -89,47 +91,44 @@ protected virtual int ProcessSlugs(ImportExecuteContext context, IEnumerable("Name", lang.UniqueSeoCode); + seName = row.Entity.ValidateSeName(seName, localizedName, false, _urlRecordService, _seoSettings, lang.Id, slugLookup); urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, lang.Id); - if (urlRecord != null) { slugMap[seName] = urlRecord; diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 4197839911..954dbcb902 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -17,6 +17,7 @@ using SmartStore.Services.Seo; using SmartStore.Services.Stores; using SmartStore.Utilities; +using SmartStore.Core.Domain.Localization; namespace SmartStore.Services.Catalog.Importer { @@ -387,12 +388,10 @@ protected virtual int ProcessLocalizations( protected virtual int ProcessSlugs( ImportExecuteContext context, - IEnumerable> batch, - string[] columnIndexes) + IEnumerable> batch) { var entityName = typeof(Product).Name; var slugMap = new Dictionary(); - //var languageMap = context.Languages.ToDictionarySafe(x => x.UniqueSeoCode); UrlRecord urlRecord = null; Func slugLookup = ((s) => @@ -404,56 +403,50 @@ protected virtual int ProcessSlugs( { try { - string seName; - if (row.TryGetDataValue("SeName", out seName, row.IsNew)) + string seName = null; + if (row.IsNew || row.NameChanged || row.TryGetDataValue("SeName", out seName)) { - if (row.IsNew || row.NameChanged || seName.HasValue()) - { - seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); + seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); - if (row.IsNew) - { - // dont't bother validating SeName for new entities. - urlRecord = new UrlRecord - { - EntityId = row.Entity.Id, - EntityName = entityName, - Slug = seName, - LanguageId = 0, - IsActive = true, - }; - _urlRecordRepository.Insert(urlRecord); - } - else + if (row.IsNew) + { + // dont't bother validating SeName for new entities. + urlRecord = new UrlRecord { - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); - } + EntityId = row.Entity.Id, + EntityName = entityName, + Slug = seName, + LanguageId = 0, + IsActive = true, + }; + _urlRecordRepository.Insert(urlRecord); + } + else + { + urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); + } + + if (urlRecord != null) + { + // a new record was inserted to the store: keep track of it for this batch. + slugMap[seName] = urlRecord; + } + } + // process localized SeNames + foreach (var lang in context.Languages) + { + if (row.IsNew || row.NameChanged || row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName)) + { + var localizedName = row.GetDataValue("Name", lang.UniqueSeoCode); + seName = row.Entity.ValidateSeName(seName, localizedName, false, _urlRecordService, _seoSettings, lang.Id, slugLookup); + urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, lang.Id); if (urlRecord != null) { - // a new record was inserted to the store: keep track of it for this batch. slugMap[seName] = urlRecord; } } } - - //// TODO: (mc) Think about this VERY thoroughly!!!! - //foreach (var idx in columnIndexes) - //{ - // if (languageMap.TryGetValue(idx, out language)) - // { - // seName = row.GetDataValue("SeName", idx); - // if (seName.HasValue()) - // { - // seName = row.Entity.ValidateSeName(seName, null, false, _urlRecordService, _seoSettings, language.Id, slugLookup); - // urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, language.Id); - // if (urlRecord != null) - // { - // slugMap[seName] = urlRecord; - // } - // } - // } - //} } catch (Exception exception) { @@ -780,7 +773,7 @@ protected override void Import(ImportExecuteContext context) { _productRepository.Context.AutoDetectChangesEnabled = true; - ProcessSlugs(context, batch, segmenter.GetColumnIndexes("Name")); + ProcessSlugs(context, batch); } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 3eec752b0b..367d8dd854 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -544,7 +544,7 @@ public static string[] DefaultKeyFields protected override IDictionary>> GetLocalizableProperties() { - throw new NotImplementedException(); + throw new NotSupportedException(); } protected override void Import(ImportExecuteContext context) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs index d0d0df1d82..14a0ac77e9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ColumnMapping/ColumnMap.cs @@ -108,6 +108,8 @@ public ColumnMappingItem GetMapping(string sourceName) [JsonObject(MemberSerialization.OptIn)] public class ColumnMappingItem { + private bool? _ignored; + /// /// The source name /// @@ -131,7 +133,15 @@ public class ColumnMappingItem /// public bool IgnoreProperty { - get { return Default != null && Default == "[IGNOREPROPERTY]"; } + get + { + if (_ignored == null) + { + _ignored = Default != null && Default == "[IGNOREPROPERTY]"; + } + + return _ignored.Value; + } } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs index 1739295b85..aeb4b81897 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/DataImporter.cs @@ -2,6 +2,7 @@ using System.IO; using System.Text; using System.Threading; +using System.Linq; using SmartStore.Core; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Customers; @@ -200,7 +201,7 @@ private void ImportCoreInner(DataImporterContext ctx, string filePath) CsvConfiguration csvConfiguration = null; var extension = Path.GetExtension(filePath); - if (extension.IsCaseInsensitiveEqual(".csv")) + if ((new string[] { ".csv", ".txt", ".tab" }).Contains(extension, StringComparer.OrdinalIgnoreCase)) { var converter = new CsvConfigurationConverter(); csvConfiguration = converter.ConvertFrom(ctx.Request.Profile.FileTypeConfiguration); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index 7d2f08871e..6e037a3cdc 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -9,6 +9,7 @@ using SmartStore.Core; using Autofac; using SmartStore.Services.Localization; +using SmartStore.Core.Domain.Localization; namespace SmartStore.Services.DataExchange.Import { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs index ebe37a106f..1f213ee9e4 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportRow.cs @@ -184,7 +184,6 @@ public bool TryGetDataValue(string columnName, out TProp value, bool forc public bool TryGetDataValue(string columnName, string index, out TProp value, bool force = false) { - object rawValue; var mapping = _segmenter.ColumnMap.GetMapping(columnName, index); if (!force && mapping.IgnoreProperty) @@ -193,6 +192,7 @@ public bool TryGetDataValue(string columnName, string index, out TProp va return false; } + object rawValue; if (_row.TryGetValue(mapping.MappedName, out rawValue) && rawValue != null && rawValue != DBNull.Value) { value = rawValue.ToString().IsCaseInsensitiveEqual(ExplicitNull) @@ -209,7 +209,7 @@ public bool TryGetDataValue(string columnName, string index, out TProp va } value = default(TProp); - return true; + return false; } public bool SetProperty( diff --git a/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs b/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs index 871afe9887..070d1fbfe5 100644 --- a/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs +++ b/src/Libraries/SmartStore.Services/Localization/ILocalizedEntityService.cs @@ -62,12 +62,14 @@ public partial interface ILocalizedEntityService /// Key selector /// Locale value /// Language ID - void SaveLocalizedValue(T entity, + void SaveLocalizedValue( + T entity, Expression> keySelector, string localeValue, int languageId) where T : BaseEntity, ILocalizedEntity; - void SaveLocalizedValue(T entity, + void SaveLocalizedValue( + T entity, Expression> keySelector, TPropType localeValue, int languageId) where T : BaseEntity, ILocalizedEntity; From 87c7edb87da834170bd10034216fea59966ab1c9 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 May 2016 01:17:24 +0200 Subject: [PATCH 138/285] Further changes to fix for "Open Redirection Vulnerability" --- .../Controllers/SmartController.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs b/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs index c2127adf6b..29fdb90d52 100644 --- a/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs +++ b/src/Presentation/SmartStore.Web.Framework/Controllers/SmartController.cs @@ -138,13 +138,29 @@ protected ActionResult RedirectToReferrer(string referrer, string fallbackUrl) protected virtual ActionResult RedirectToReferrer(string referrer, Func fallbackResult) { + bool skipLocalCheck = false; + if (referrer.IsEmpty() && Request.UrlReferrer != null && Request.UrlReferrer.ToString().HasValue()) { referrer = Request.UrlReferrer.ToString(); + if (referrer.HasValue()) + { + var domain1 = (new Uri(referrer)).GetLeftPart(UriPartial.Authority); + var domain2 = this.Request.Url.GetLeftPart(UriPartial.Authority); + if (domain1.IsCaseInsensitiveEqual(domain2)) + { + // always allow fully qualified urls from local host + skipLocalCheck = true; + } + else + { + referrer = null; + } + } } // addressing "Open Redirection Vulnerability" (prevent cross-domain redirects / phishing) - if (referrer.HasValue() && !Url.IsLocalUrl(referrer)) + if (referrer.HasValue() && !skipLocalCheck && !Url.IsLocalUrl(referrer)) { referrer = null; } From 9cf5dea8fafb15c28ddc1508b86525848cb8b17c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 12 May 2016 10:23:34 +0200 Subject: [PATCH 139/285] Minor fix --- .../SmartStore.Services/Catalog/Importer/CategoryImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index a5010b2a72..4ab3ecc39e 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -78,7 +78,7 @@ protected virtual int ProcessSlugs( ImportExecuteContext context, IEnumerable> batch) { - var entityName = typeof(Product).Name; + var entityName = typeof(Category).Name; var slugMap = new Dictionary(); UrlRecord urlRecord = null; From 62dd2ef939e7fef23e93e227ba249833cb957bb3 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 12 May 2016 11:29:25 +0200 Subject: [PATCH 140/285] DevTools should generate an exception when calling installation page --- .../SmartStore.DevTools/DependencyRegistrar.cs | 11 ++--------- src/Plugins/SmartStore.DevTools/ProfilerHttpModule.cs | 6 ++++++ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Plugins/SmartStore.DevTools/DependencyRegistrar.cs b/src/Plugins/SmartStore.DevTools/DependencyRegistrar.cs index 91f0a57419..4e4cb6d96d 100644 --- a/src/Plugins/SmartStore.DevTools/DependencyRegistrar.cs +++ b/src/Plugins/SmartStore.DevTools/DependencyRegistrar.cs @@ -1,12 +1,5 @@ -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Web.Mvc; -using System.Reflection; using Autofac; -using Autofac.Core; using Autofac.Integration.Mvc; -using SmartStore.Core.Plugins; using SmartStore.Core.Data; using SmartStore.Core.Infrastructure; using SmartStore.Core.Infrastructure.DependencyManagement; @@ -16,13 +9,13 @@ namespace SmartStore.DevTools { - public class DependencyRegistrar : IDependencyRegistrar + public class DependencyRegistrar : IDependencyRegistrar { public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, bool isActiveModule) { builder.RegisterType().As().InstancePerRequest(); - if (isActiveModule) + if (isActiveModule && DataSettings.DatabaseIsInstalled()) { // intercept ALL public store controller actions builder.RegisterType().AsActionFilterFor(); diff --git a/src/Plugins/SmartStore.DevTools/ProfilerHttpModule.cs b/src/Plugins/SmartStore.DevTools/ProfilerHttpModule.cs index e1c5693558..c2e585d0eb 100644 --- a/src/Plugins/SmartStore.DevTools/ProfilerHttpModule.cs +++ b/src/Plugins/SmartStore.DevTools/ProfilerHttpModule.cs @@ -1,5 +1,6 @@ using System; using System.Web; +using SmartStore.Core.Data; using SmartStore.Core.Infrastructure; using StackExchange.Profiling; @@ -43,6 +44,11 @@ private static bool ShouldProfile(HttpApplication app) if (app.Context == null || app.Context.Request == null) return false; + if (!DataSettings.DatabaseIsInstalled()) + { + return false; + } + var url = app.Context.Request.AppRelativeCurrentExecutionFilePath; if (url.StartsWith("~/admin", StringComparison.InvariantCultureIgnoreCase) || url.StartsWith("~/mini-profiler", StringComparison.InvariantCultureIgnoreCase) || url.StartsWith("~/bundles", StringComparison.InvariantCultureIgnoreCase)) { From 9b3fdbb0edde030a1a132126e0ac46bad6f876eb Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 12 May 2016 12:22:36 +0200 Subject: [PATCH 141/285] Installation: Fixes "No constructor was found that had all the dependencies satisfied." --- .../Infrastructure/DependencyManagement/ContainerManager.cs | 2 +- .../SmartStore.Web.Framework/DependencyRegistrar.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs index 7b1bbc9292..f39bbeedeb 100644 --- a/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs +++ b/src/Libraries/SmartStore.Core/Infrastructure/DependencyManagement/ContainerManager.cs @@ -95,7 +95,7 @@ public object ResolveUnregistered(Type type, ILifetimeScope scope = null) } } - throw new SmartException("No contructor was found that had all the dependencies satisfied."); + throw new SmartException("No constructor for {0} was found that had all the dependencies satisfied.".FormatInvariant(type.Name.NaIfEmpty())); } private bool TryResolveAll(Type[] types, out object[] instances, ILifetimeScope scope = null) diff --git a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs index 046a1e8d4a..73a2c9bd9a 100644 --- a/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs +++ b/src/Presentation/SmartStore.Web.Framework/DependencyRegistrar.cs @@ -1094,11 +1094,12 @@ public DataExchangeModule(ITypeFinder typeFinder) protected override void Load(ContainerBuilder builder) { + builder.RegisterType().As().InstancePerRequest(); + builder.RegisterType().As().InstancePerRequest(); + if (!DataSettings.DatabaseIsInstalled()) return; - builder.RegisterType().As().InstancePerRequest(); - builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); builder.RegisterType().As().InstancePerRequest(); From 788e08d0c7f9b73867d112a4d1552f1795518fcf Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 12 May 2016 13:26:40 +0200 Subject: [PATCH 142/285] Minor fix --- .../SmartStore.Web/Themes/Alpha/Content/product.less | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less index 3aa0ecedd3..39a374f693 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less @@ -401,7 +401,9 @@ } .attributes .image-has-value { - margin-left: 28px; + margin-left: 8px; + vertical-align: top; + display: inline-block; } .mb-bundle-pictures .fa-plus { @@ -409,8 +411,7 @@ } .mb-bundle-pictures a, -.mb-bundle-pictures i, -.attributes .attribute-value-image-checkbox { +.mb-bundle-pictures i { float: left; } From 6abe5db27b2406c855bac68b277568715d59d05e Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 12 May 2016 13:44:41 +0200 Subject: [PATCH 143/285] Renamed AutoCompleteSearchThumbPictureSize as it was only used for variant value thumbs --- src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs | 4 ++-- .../Administration/Infrastructure/AutoMapperStartupTask.cs | 2 +- src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs b/src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs index cee3019aa8..b3072c0912 100644 --- a/src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Media/MediaSettings.cs @@ -18,7 +18,7 @@ public MediaSettings() CartThumbPictureSize = 80; CartThumbBundleItemPictureSize = 32; MiniCartThumbPictureSize = 32; - AutoCompleteSearchThumbPictureSize = 20; + VariantValueThumbPictureSize = 20; MaximumImageSize = 1280; DefaultPictureZoomEnabled = true; PictureZoomType = "window"; @@ -38,7 +38,7 @@ public MediaSettings() public int CartThumbPictureSize { get; set; } public int CartThumbBundleItemPictureSize { get; set; } public int MiniCartThumbPictureSize { get; set; } - public int AutoCompleteSearchThumbPictureSize { get; set; } + public int VariantValueThumbPictureSize { get; set; } public bool DefaultPictureZoomEnabled { get; set; } public string PictureZoomType { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs index 839bbc1f05..2f7c0f3339 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs @@ -668,7 +668,7 @@ public void Execute() //.ForMember(dest => dest.DefaultPictureZoomEnabled, mo => mo.Ignore()) .ForMember(dest => dest.DefaultImageQuality, mo => mo.Ignore()) .ForMember(dest => dest.MultipleThumbDirectories, mo => mo.Ignore()) - .ForMember(dest => dest.AutoCompleteSearchThumbPictureSize, mo => mo.Ignore()); + .ForMember(dest => dest.VariantValueThumbPictureSize, mo => mo.Ignore()); Mapper.CreateMap() .ForMember(dest => dest.AvailableCustomerNumberMethods, mo => mo.Ignore()) .ForMember(dest => dest.AvailableCustomerNumberVisibilities, mo => mo.Ignore()) diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index 68e7442173..2c0f3ef2be 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -571,7 +571,7 @@ public ProductDetailsModel PrepareProductDetailModel( { var linkagePicture = _pictureService.GetPicturesByProductId(pvaValue.LinkedProductId, 1).FirstOrDefault(); if (linkagePicture != null) - pvaValueModel.ImageUrl = _pictureService.GetPictureUrl(linkagePicture, _mediaSettings.AutoCompleteSearchThumbPictureSize, false); + pvaValueModel.ImageUrl = _pictureService.GetPictureUrl(linkagePicture, _mediaSettings.VariantValueThumbPictureSize, false); } pvaModel.Values.Add(pvaValueModel); From 37d67f7595c9b25981cc85b1cbbfc29825f3a755 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 12 May 2016 18:56:26 +0200 Subject: [PATCH 144/285] Refactored last commit --- .../201605111140288_ImportExtraData.cs | 6 ++ .../Themes/Alpha/Content/product.less | 19 ++-- .../Views/Product/_Attributes.cshtml | 90 ++++++++----------- 3 files changed, 53 insertions(+), 62 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs index 55bbdd6843..17d0564ce6 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs @@ -23,6 +23,12 @@ public bool RollbackOnFailure public void Seed(SmartObjectContext context) { context.MigrateLocaleResources(MigrateLocaleResources); + + context.MigrateSettings(x => + { + x.Add("MediaSettings.VariantValueThumbPictureSize", "20"); + x.Delete("MediaSettings.AutoCompleteSearchThumbPictureSize"); + }); } public void MigrateLocaleResources(LocaleResourcesBuilder builder) diff --git a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less index 39a374f693..0a0fde971a 100644 --- a/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less +++ b/src/Presentation/SmartStore.Web/Themes/Alpha/Content/product.less @@ -395,25 +395,28 @@ border-top: none !important; } +.attributes .image-has-value { + display: table-cell; + padding-top: 10px; +} + .attribute-value-image { margin-right: 5px; vertical-align: middle; } - -.attributes .image-has-value { - margin-left: 8px; - vertical-align: top; - display: inline-block; -} - .mb-bundle-pictures .fa-plus { padding: 4px 10px 0 10px; } .mb-bundle-pictures a, -.mb-bundle-pictures i { +.mb-bundle-pictures i, +.attributes .attribute-value-image-checkbox { float: left; } +.attributes .attribute-value-image-checkbox { + margin-right: 10px; + padding-top: 10px; +} .mb-bundle-pictures img { max-width: 24px; diff --git a/src/Presentation/SmartStore.Web/Views/Product/_Attributes.cshtml b/src/Presentation/SmartStore.Web/Views/Product/_Attributes.cshtml index bbe0a418d2..853aa09e75 100644 --- a/src/Presentation/SmartStore.Web/Views/Product/_Attributes.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Product/_Attributes.cshtml @@ -37,70 +37,52 @@ { case AttributeControlType.DropdownList: { - - } - break; - case AttributeControlType.RadioList: - { - foreach (var pvaValue in attribute.Values) - { - if (pvaValue.ImageUrl.HasValue()) + - @(pvaValue.Name)@(pvaValue.GetAttributeValueInfo()) - - } - + } break; + case AttributeControlType.RadioList: case AttributeControlType.Checkboxes: { foreach (var pvaValue in attribute.Values) { - if (pvaValue.ImageUrl.HasValue()) - { - if (!String.IsNullOrEmpty(pvaValue.SeName)) - { - - - - } - else + var inputType = attribute.AttributeControlType == AttributeControlType.RadioList ? "radio" : "checkbox"; + +
    + @if (pvaValue.ImageUrl.HasValue()) { - + if (!String.IsNullOrEmpty(pvaValue.SeName)) + { + + + + } + else + { + + } } - } - - + + +
    } } break; From 1f49bc0dc4783754d36ba7d37a283e9815d66a88 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 12 May 2016 20:34:50 +0200 Subject: [PATCH 145/285] Download nor sample download were removable when editing product --- changelog.md | 1 + .../Administration/Controllers/ProductController.cs | 2 +- .../Administration/Models/Catalog/ProductModel.cs | 4 ++-- .../Administration/Views/Product/_CreateOrUpdate.Info.cshtml | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/changelog.md b/changelog.md index 8bd6ef243c..a6a61a03fc 100644 --- a/changelog.md +++ b/changelog.md @@ -36,6 +36,7 @@ * Customer import: Creates customer role duplicates for inserted customers * GMC feed does not generate the sale price if the sale price is set for a future date * Mobile devices: Fixed "Uncaught Error: Syntax error, unrecognized expression: :nth-child" +* Download nor sample download were removable when editing product ## SmartStore.NET 2.5 diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 46fc08645f..8ea467ea2c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -215,7 +215,7 @@ protected void UpdateProductGeneralInfo(Product product, ProductModel model) p.GiftCardTypeId = m.GiftCardTypeId; p.IsDownload = m.IsDownload; - p.DownloadId = m.DownloadId; + p.DownloadId = m.DownloadId ?? 0; p.UnlimitedDownloads = m.UnlimitedDownloads; p.MaxNumberOfDownloads = m.MaxNumberOfDownloads; p.DownloadExpirationDays = m.DownloadExpirationDays; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs index d7f5f66e1f..d24020eaea 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Catalog/ProductModel.cs @@ -140,7 +140,7 @@ public ProductModel() [SmartResourceDisplayName("Admin.Catalog.Products.Fields.Download")] [UIHint("Download")] - public int DownloadId { get; set; } + public int? DownloadId { get; set; } [SmartResourceDisplayName("Admin.Catalog.Products.Fields.UnlimitedDownloads")] public bool UnlimitedDownloads { get; set; } @@ -159,7 +159,7 @@ public ProductModel() [SmartResourceDisplayName("Admin.Catalog.Products.Fields.SampleDownload")] [UIHint("Download")] - public int SampleDownloadId { get; set; } + public int? SampleDownloadId { get; set; } [SmartResourceDisplayName("Admin.Catalog.Products.Fields.HasUserAgreement")] public bool HasUserAgreement { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml index f180d223f8..8d295edfb2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Product/_CreateOrUpdate.Info.cshtml @@ -618,7 +618,7 @@
    @Html.EditorFor(model => model.SampleDownloadId) - @*@Html.ValidationMessageFor(model => model.SampleDownloadId)*@ + @Html.ValidationMessageFor(model => model.SampleDownloadId)
    From 52d2416371c231887e923031c48b65b42c5ada41 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 12 May 2016 21:06:20 +0200 Subject: [PATCH 146/285] Copied product must not share sample download of source product. Could produce "The DELETE statement conflicted with the REFERENCE constraint 'FK_dbo.Product_dbo.Download_SampleDownloadId'". --- changelog.md | 1 + .../SmartStore.Services/Catalog/CopyProductService.cs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index a6a61a03fc..8e188f25e4 100644 --- a/changelog.md +++ b/changelog.md @@ -37,6 +37,7 @@ * GMC feed does not generate the sale price if the sale price is set for a future date * Mobile devices: Fixed "Uncaught Error: Syntax error, unrecognized expression: :nth-child" * Download nor sample download were removable when editing product +* Copied product must not share sample download of source product. Could produce "The DELETE statement conflicted with the REFERENCE constraint 'FK_dbo.Product_dbo.Download_SampleDownloadId'". ## SmartStore.NET 2.5 diff --git a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs index 2dcb1cb673..f786bf9c78 100644 --- a/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs +++ b/src/Libraries/SmartStore.Services/Catalog/CopyProductService.cs @@ -84,8 +84,8 @@ public virtual Product CopyProduct(Product product, string newName, bool isPubli var utcNow = DateTime.UtcNow; // product download & sample download - int downloadId = product.DownloadId; - int? sampleDownloadId = product.SampleDownloadId; + int downloadId = 0; + int? sampleDownloadId = null; if (product.IsDownload) { From e7837c8b35e6c424f9df5961f4189b477317a98b Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 May 2016 19:16:03 +0200 Subject: [PATCH 147/285] Import: ProcessSlugs() does not always fetch SeName from source --- .../SmartStore.Services/Catalog/Importer/CategoryImporter.cs | 4 ++-- .../SmartStore.Services/Catalog/Importer/ProductImporter.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 4ab3ecc39e..2122938194 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -92,7 +92,7 @@ protected virtual int ProcessSlugs( try { string seName = null; - if (row.IsNew || row.NameChanged || row.TryGetDataValue("SeName", out seName)) + if (row.TryGetDataValue("SeName", out seName) || row.IsNew || row.NameChanged) { seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); @@ -124,7 +124,7 @@ protected virtual int ProcessSlugs( // process localized SeNames foreach (var lang in context.Languages) { - if (row.IsNew || row.NameChanged || row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName)) + if (row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName) || row.IsNew || row.NameChanged) { var localizedName = row.GetDataValue("Name", lang.UniqueSeoCode); seName = row.Entity.ValidateSeName(seName, localizedName, false, _urlRecordService, _seoSettings, lang.Id, slugLookup); diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 954dbcb902..c38cac828d 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -404,7 +404,7 @@ protected virtual int ProcessSlugs( try { string seName = null; - if (row.IsNew || row.NameChanged || row.TryGetDataValue("SeName", out seName)) + if (row.TryGetDataValue("SeName", out seName) || row.IsNew || row.NameChanged) { seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); @@ -436,7 +436,7 @@ protected virtual int ProcessSlugs( // process localized SeNames foreach (var lang in context.Languages) { - if (row.IsNew || row.NameChanged || row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName)) + if (row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName) || row.IsNew || row.NameChanged) { var localizedName = row.GetDataValue("Name", lang.UniqueSeoCode); seName = row.Entity.ValidateSeName(seName, localizedName, false, _urlRecordService, _seoSettings, lang.Id, slugLookup); From f0b0d80fdf6d04c3244f3db1c7549e9295ad4a37 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 May 2016 20:39:44 +0200 Subject: [PATCH 148/285] Importer refactoring: moved ProcessSlugs() and ProcessStoreMappings() to base class --- .../Catalog/Importer/CategoryImporter.cs | 108 +---------------- .../Catalog/Importer/ProductImporter.cs | 114 +----------------- .../DataExchange/Import/EntityImporterBase.cs | 111 ++++++++++++++++- 3 files changed, 113 insertions(+), 220 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 2122938194..de4d5a073f 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -23,17 +23,12 @@ namespace SmartStore.Services.Catalog.Importer public class CategoryImporter : EntityImporterBase { private readonly IRepository _categoryRepository; - private readonly IRepository _urlRecordRepository; private readonly IRepository _pictureRepository; private readonly ICommonServices _services; - private readonly IUrlRecordService _urlRecordService; - private readonly IRepository _storeMappingRepository; private readonly ICategoryTemplateService _categoryTemplateService; - private readonly IStoreMappingService _storeMappingService; private readonly IPictureService _pictureService; private readonly ILocalizedEntityService _localizedEntityService; private readonly FileDownloadManager _fileDownloadManager; - private readonly SeoSettings _seoSettings; private static readonly Dictionary>> _localizableProperties = new Dictionary>> { @@ -48,102 +43,20 @@ public class CategoryImporter : EntityImporterBase public CategoryImporter( IRepository categoryRepository, - IRepository urlRecordRepository, IRepository pictureRepository, - IRepository storeMappingRepository, ICommonServices services, - IUrlRecordService urlRecordService, ICategoryTemplateService categoryTemplateService, - IStoreMappingService storeMappingService, IPictureService pictureService, ILocalizedEntityService localizedEntityService, - FileDownloadManager fileDownloadManager, - SeoSettings seoSettings) + FileDownloadManager fileDownloadManager) { _categoryRepository = categoryRepository; - _urlRecordRepository = urlRecordRepository; _pictureRepository = pictureRepository; - _storeMappingRepository = storeMappingRepository; _services = services; - _urlRecordService = urlRecordService; _categoryTemplateService = categoryTemplateService; - _storeMappingService = storeMappingService; _pictureService = pictureService; _localizedEntityService = localizedEntityService; _fileDownloadManager = fileDownloadManager; - _seoSettings = seoSettings; - } - - protected virtual int ProcessSlugs( - ImportExecuteContext context, - IEnumerable> batch) - { - var entityName = typeof(Category).Name; - var slugMap = new Dictionary(); - UrlRecord urlRecord = null; - - Func slugLookup = ((s) => - { - return (slugMap.ContainsKey(s) ? slugMap[s] : null); - }); - - foreach (var row in batch) - { - try - { - string seName = null; - if (row.TryGetDataValue("SeName", out seName) || row.IsNew || row.NameChanged) - { - seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); - - if (row.IsNew) - { - // dont't bother validating SeName for new entities. - urlRecord = new UrlRecord - { - EntityId = row.Entity.Id, - EntityName = entityName, - Slug = seName, - LanguageId = 0, - IsActive = true, - }; - _urlRecordRepository.Insert(urlRecord); - } - else - { - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); - } - - if (urlRecord != null) - { - // a new record was inserted to the store: keep track of it for this batch. - slugMap[seName] = urlRecord; - } - } - - // process localized SeNames - foreach (var lang in context.Languages) - { - if (row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName) || row.IsNew || row.NameChanged) - { - var localizedName = row.GetDataValue("Name", lang.UniqueSeoCode); - seName = row.Entity.ValidateSeName(seName, localizedName, false, _urlRecordService, _seoSettings, lang.Id, slugLookup); - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, lang.Id); - if (urlRecord != null) - { - slugMap[seName] = urlRecord; - } - } - } - } - catch (Exception exception) - { - context.Result.AddWarning(exception.Message, row.GetRowInfo(), "SeName"); - } - } - - // commit whole batch at once - return _urlRecordRepository.Context.SaveChanges(); } protected virtual int ProcessLocalizations( @@ -293,23 +206,6 @@ protected virtual int ProcessPictures( return num; } - protected virtual int ProcessStoreMappings(ImportExecuteContext context, IEnumerable> batch) - { - _storeMappingRepository.AutoCommitEnabled = false; - - foreach (var row in batch) - { - var storeIds = row.GetDataValue>("StoreIds"); - if (!storeIds.IsNullOrEmpty()) - { - _storeMappingService.SaveStoreMappings(row.Entity, storeIds.ToArray()); - } - } - - // commit whole batch at once - return _services.DbContext.SaveChanges(); - } - protected virtual int ProcessCategories( ImportExecuteContext context, IEnumerable> batch, @@ -513,7 +409,7 @@ protected override void Import(ImportExecuteContext context) try { _categoryRepository.Context.AutoDetectChangesEnabled = true; - ProcessSlugs(context, batch); + ProcessSlugs(context, batch, typeof(Category).Name); } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index c38cac828d..bbb266ebfa 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -8,16 +8,11 @@ using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.DataExchange; -using SmartStore.Core.Domain.Seo; -using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; using SmartStore.Services.DataExchange.Import; using SmartStore.Services.Localization; using SmartStore.Services.Media; -using SmartStore.Services.Seo; -using SmartStore.Services.Stores; using SmartStore.Utilities; -using SmartStore.Core.Domain.Localization; namespace SmartStore.Services.Catalog.Importer { @@ -26,20 +21,15 @@ public class ProductImporter : EntityImporterBase private readonly IRepository _productPictureRepository; private readonly IRepository _productManufacturerRepository; private readonly IRepository _productCategoryRepository; - private readonly IRepository _urlRecordRepository; private readonly IRepository _productRepository; - private readonly IRepository _storeMappingRepository; private readonly ICommonServices _services; private readonly ILocalizedEntityService _localizedEntityService; private readonly IPictureService _pictureService; private readonly IManufacturerService _manufacturerService; private readonly ICategoryService _categoryService; private readonly IProductService _productService; - private readonly IUrlRecordService _urlRecordService; private readonly IProductTemplateService _productTemplateService; - private readonly IStoreMappingService _storeMappingService; private readonly FileDownloadManager _fileDownloadManager; - private readonly SeoSettings _seoSettings; private static readonly Dictionary>> _localizableProperties = new Dictionary>> { @@ -56,38 +46,28 @@ public ProductImporter( IRepository productPictureRepository, IRepository productManufacturerRepository, IRepository productCategoryRepository, - IRepository urlRecordRepository, IRepository productRepository, - IRepository storeMappingRepository, ICommonServices services, ILocalizedEntityService localizedEntityService, IPictureService pictureService, IManufacturerService manufacturerService, ICategoryService categoryService, IProductService productService, - IUrlRecordService urlRecordService, IProductTemplateService productTemplateService, - IStoreMappingService storeMappingService, - FileDownloadManager fileDownloadManager, - SeoSettings seoSettings) + FileDownloadManager fileDownloadManager) { _productPictureRepository = productPictureRepository; _productManufacturerRepository = productManufacturerRepository; _productCategoryRepository = productCategoryRepository; - _urlRecordRepository = urlRecordRepository; _productRepository = productRepository; - _storeMappingRepository = storeMappingRepository; _services = services; _localizedEntityService = localizedEntityService; _pictureService = pictureService; _manufacturerService = manufacturerService; _categoryService = categoryService; _productService = productService; - _urlRecordService = urlRecordService; _productTemplateService = productTemplateService; - _storeMappingService = storeMappingService; _fileDownloadManager = fileDownloadManager; - _seoSettings = seoSettings; } private int? ZeroToNull(object value, CultureInfo culture) @@ -386,95 +366,6 @@ protected virtual int ProcessLocalizations( return 0; } - protected virtual int ProcessSlugs( - ImportExecuteContext context, - IEnumerable> batch) - { - var entityName = typeof(Product).Name; - var slugMap = new Dictionary(); - UrlRecord urlRecord = null; - - Func slugLookup = ((s) => - { - return (slugMap.ContainsKey(s) ? slugMap[s] : null); - }); - - foreach (var row in batch) - { - try - { - string seName = null; - if (row.TryGetDataValue("SeName", out seName) || row.IsNew || row.NameChanged) - { - seName = row.Entity.ValidateSeName(seName, row.Entity.Name, true, _urlRecordService, _seoSettings, extraSlugLookup: slugLookup); - - if (row.IsNew) - { - // dont't bother validating SeName for new entities. - urlRecord = new UrlRecord - { - EntityId = row.Entity.Id, - EntityName = entityName, - Slug = seName, - LanguageId = 0, - IsActive = true, - }; - _urlRecordRepository.Insert(urlRecord); - } - else - { - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, 0); - } - - if (urlRecord != null) - { - // a new record was inserted to the store: keep track of it for this batch. - slugMap[seName] = urlRecord; - } - } - - // process localized SeNames - foreach (var lang in context.Languages) - { - if (row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName) || row.IsNew || row.NameChanged) - { - var localizedName = row.GetDataValue("Name", lang.UniqueSeoCode); - seName = row.Entity.ValidateSeName(seName, localizedName, false, _urlRecordService, _seoSettings, lang.Id, slugLookup); - urlRecord = _urlRecordService.SaveSlug(row.Entity, seName, lang.Id); - if (urlRecord != null) - { - slugMap[seName] = urlRecord; - } - } - } - } - catch (Exception exception) - { - context.Result.AddWarning(exception.Message, row.GetRowInfo(), "SeName"); - } - } - - // commit whole batch at once - return _urlRecordRepository.Context.SaveChanges(); - } - - protected virtual int ProcessStoreMappings(ImportExecuteContext context, IEnumerable> batch) - { - _storeMappingRepository.AutoCommitEnabled = false; - - foreach (var row in batch) - { - var storeIds = row.GetDataValue>("StoreIds"); - if (!storeIds.IsNullOrEmpty()) - { - _storeMappingService.SaveStoreMappings(row.Entity, storeIds.ToArray()); - } - } - - // commit whole batch at once - return _services.DbContext.SaveChanges(); - } - protected virtual int ProcessProducts( ImportExecuteContext context, IEnumerable> batch, @@ -772,8 +663,7 @@ protected override void Import(ImportExecuteContext context) try { _productRepository.Context.AutoDetectChangesEnabled = true; - - ProcessSlugs(context, batch); + ProcessSlugs(context, batch, typeof(Product).Name); } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index 6e037a3cdc..0108649f6c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -10,10 +10,15 @@ using Autofac; using SmartStore.Services.Localization; using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Seo; +using SmartStore.Services.Seo; +using SmartStore.Core.Data; +using SmartStore.Services.Stores; +using SmartStore.Core.Domain.Stores; namespace SmartStore.Services.DataExchange.Import { - public abstract class EntityImporterBase : IEntityImporter where TEntity : BaseEntity + public abstract class EntityImporterBase : IEntityImporter where T : BaseEntity { private const string _imageDownloadFolder = @"Content\DownloadedImages"; @@ -153,6 +158,108 @@ protected IEnumerable ResolveLocalizedProperties(ImportDataSegmenter seg } } - protected abstract IDictionary>> GetLocalizableProperties(); + protected abstract IDictionary>> GetLocalizableProperties(); + + protected virtual int ProcessStoreMappings( + ImportExecuteContext context, + IEnumerable> batch) where TEntity : BaseEntity, IStoreMappingSupported + { + var storeMappingService = context.Services.Resolve(); + var storeMappingRepository = context.Services.Resolve>(); + + storeMappingRepository.AutoCommitEnabled = false; + + foreach (var row in batch) + { + var storeIds = row.GetDataValue>("StoreIds"); + if (!storeIds.IsNullOrEmpty()) + { + storeMappingService.SaveStoreMappings(row.Entity, storeIds.ToArray()); + } + } + + // commit whole batch at once + return context.Services.DbContext.SaveChanges(); + } + + protected virtual int ProcessSlugs( + ImportExecuteContext context, + IEnumerable> batch, + string entityName) where TEntity : BaseEntity, ISlugSupported + { + var slugMap = new Dictionary(); + UrlRecord urlRecord = null; + + var urlRecordService = context.Services.Resolve(); + var urlRecordRepository = context.Services.Resolve>(); + var seoSettings = context.Services.Resolve(); + + Func slugLookup = ((s) => + { + return (slugMap.ContainsKey(s) ? slugMap[s] : null); + }); + + foreach (var row in batch) + { + try + { + string seName = null; + string localizedName = null; + + if (row.TryGetDataValue("SeName", out seName) || row.IsNew || row.NameChanged) + { + seName = row.Entity.ValidateSeName(seName, row.EntityDisplayName, true, urlRecordService, seoSettings, extraSlugLookup: slugLookup); + + if (row.IsNew) + { + // dont't bother validating SeName for new entities. + urlRecord = new UrlRecord + { + EntityId = row.Entity.Id, + EntityName = entityName, + Slug = seName, + LanguageId = 0, + IsActive = true, + }; + urlRecordRepository.Insert(urlRecord); + } + else + { + urlRecord = urlRecordService.SaveSlug(row.Entity, seName, 0); + } + + if (urlRecord != null) + { + // a new record was inserted to the store: keep track of it for this batch. + slugMap[seName] = urlRecord; + } + } + + // process localized SeNames + foreach (var lang in context.Languages) + { + var hasSeName = row.TryGetDataValue("SeName", lang.UniqueSeoCode, out seName); + var hasLocalizedName = row.TryGetDataValue("Name", lang.UniqueSeoCode, out localizedName); + + if (hasSeName || hasLocalizedName) + { + seName = row.Entity.ValidateSeName(seName, localizedName, false, urlRecordService, seoSettings, lang.Id, slugLookup); + urlRecord = urlRecordService.SaveSlug(row.Entity, seName, lang.Id); + if (urlRecord != null) + { + slugMap[seName] = urlRecord; + } + } + } + } + catch (Exception exception) + { + context.Result.AddWarning(exception.Message, row.GetRowInfo(), "SeName"); + } + } + + // commit whole batch at once + return context.Services.DbContext.SaveChanges(); + } } } From 1fc4648c0cccce2a54c60e2d45788954fc3bd68f Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 May 2016 21:19:32 +0200 Subject: [PATCH 149/285] Importer refactoring: moved ProcessLocalizations() to base class (DRY) --- .../Catalog/Importer/CategoryImporter.cs | 9 +--- .../Catalog/Importer/ProductImporter.cs | 50 +---------------- .../Customers/Importer/CustomerImporter.cs | 5 -- .../DataExchange/Import/EntityImporterBase.cs | 53 ++++++++++++++++--- 4 files changed, 48 insertions(+), 69 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index de4d5a073f..1d67735d05 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -358,11 +358,6 @@ public static string[] DefaultKeyFields } } - protected override IDictionary>> GetLocalizableProperties() - { - return _localizableProperties; - } - protected override void Import(ImportExecuteContext context) { var srcToDestId = new Dictionary(); @@ -375,8 +370,6 @@ protected override void Import(ImportExecuteContext context) Initialize(context); - var localizedProperties = ResolveLocalizedProperties(segmenter).ToArray(); - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { var batch = segmenter.GetCurrentBatch(); @@ -437,7 +430,7 @@ protected override void Import(ImportExecuteContext context) // localizations try { - ProcessLocalizations(context, batch, localizedProperties); + ProcessLocalizations(context, batch, _localizableProperties); } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index bbb266ebfa..06d527bc5f 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -326,46 +326,6 @@ protected virtual int ProcessProductCategories(ImportExecuteContext context, IEn return num; } - protected virtual int ProcessLocalizations( - ImportExecuteContext context, - IEnumerable> batch, - string[] localizedProperties) - { - if (localizedProperties.Length == 0) - { - return 0; - } - - bool shouldSave = false; - - foreach (var row in batch) - { - foreach (var prop in localizedProperties) - { - var lambda = _localizableProperties[prop]; - foreach (var lang in context.Languages) - { - var code = lang.UniqueSeoCode; - string value; - - if (row.TryGetDataValue(prop /* ColumnName */, code, out value)) - { - _localizedEntityService.SaveLocalizedValue(row.Entity, lambda, value, lang.Id); - shouldSave = true; - } - } - } - } - - if (shouldSave) - { - // commit whole batch at once - return context.Services.DbContext.SaveChanges(); - } - - return 0; - } - protected virtual int ProcessProducts( ImportExecuteContext context, IEnumerable> batch, @@ -606,11 +566,6 @@ public static string[] DefaultKeyFields } } - protected override IDictionary>> GetLocalizableProperties() - { - return _localizableProperties; - } - protected override void Import(ImportExecuteContext context) { var srcToDestId = new Dictionary(); @@ -620,9 +575,8 @@ protected override void Import(ImportExecuteContext context) using (var scope = new DbContextScope(ctx: _productRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { var segmenter = context.DataSegmenter; - Initialize(context); - var localizedProperties = ResolveLocalizedProperties(segmenter).ToArray(); + Initialize(context); while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { @@ -695,7 +649,7 @@ protected override void Import(ImportExecuteContext context) // =========================================================================== try { - ProcessLocalizations(context, batch, localizedProperties); + ProcessLocalizations(context, batch, _localizableProperties); } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 367d8dd854..25d6e70ad8 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -542,11 +542,6 @@ public static string[] DefaultKeyFields } } - protected override IDictionary>> GetLocalizableProperties() - { - throw new NotSupportedException(); - } - protected override void Import(ImportExecuteContext context) { var customer = _services.WorkContext.CurrentCustomer; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index 0108649f6c..347a72354c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Net.Mime; +using System.Linq; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.IO; using SmartStore.Utilities; @@ -143,22 +144,58 @@ public void Succeeded(FileDownloadManagerItem item) } } - protected IEnumerable ResolveLocalizedProperties(ImportDataSegmenter segmenter) + protected virtual int ProcessLocalizations( + ImportExecuteContext context, + IEnumerable> batch, + IDictionary>> localizableProperties) where TEntity : BaseEntity, ILocalizedEntity { + Guard.ArgumentNotNull(() => context); + Guard.ArgumentNotNull(() => batch); + Guard.ArgumentNotNull(() => localizableProperties); + // Perf: determine whether our localizable properties actually have - // counterparts in the source BEFORE import begins. This way we spare ourself + // counterparts in the source BEFORE import batch begins. This way we spare ourself // to query over and over for values. - var localizableProperties = GetLocalizableProperties(); - foreach (var kvp in localizableProperties) + var localizedProps = (from kvp in localizableProperties + where context.DataSegmenter.GetColumnIndexes(kvp.Key).Length > 0 + select kvp.Key).ToArray(); + + if (localizedProps.Length == 0) { - if (segmenter.GetColumnIndexes(kvp.Key).Length > 0) + return 0; + } + + var localizedEntityService = context.Services.Resolve(); + + bool shouldSave = false; + + foreach (var row in batch) + { + foreach (var prop in localizedProps) { - yield return kvp.Key; + var lambda = localizableProperties[prop]; + foreach (var lang in context.Languages) + { + var code = lang.UniqueSeoCode; + string value; + + if (row.TryGetDataValue(prop /* ColumnName */, code, out value)) + { + localizedEntityService.SaveLocalizedValue(row.Entity, lambda, value, lang.Id); + shouldSave = true; + } + } } } - } - protected abstract IDictionary>> GetLocalizableProperties(); + if (shouldSave) + { + // commit whole batch at once + return context.Services.DbContext.SaveChanges(); + } + + return 0; + } protected virtual int ProcessStoreMappings( ImportExecuteContext context, From f4d6733435cd4973f22657bfdf6e3035b327dc88 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 May 2016 22:49:48 +0200 Subject: [PATCH 150/285] Importer: removed generic type parameter from EntityImporterBase --- .../SmartStore.Services/Catalog/Importer/CategoryImporter.cs | 2 +- .../SmartStore.Services/Catalog/Importer/ProductImporter.cs | 2 +- .../SmartStore.Services/Customers/Importer/CustomerImporter.cs | 2 +- .../DataExchange/Import/EntityImporterBase.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 1d67735d05..98cef22f35 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -20,7 +20,7 @@ namespace SmartStore.Services.Catalog.Importer { - public class CategoryImporter : EntityImporterBase + public class CategoryImporter : EntityImporterBase { private readonly IRepository _categoryRepository; private readonly IRepository _pictureRepository; diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 06d527bc5f..756ca28d01 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -16,7 +16,7 @@ namespace SmartStore.Services.Catalog.Importer { - public class ProductImporter : EntityImporterBase + public class ProductImporter : EntityImporterBase { private readonly IRepository _productPictureRepository; private readonly IRepository _productManufacturerRepository; diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index 25d6e70ad8..da0d917059 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -22,7 +22,7 @@ namespace SmartStore.Services.Customers.Importer { - public class CustomerImporter : EntityImporterBase + public class CustomerImporter : EntityImporterBase { private const string _attributeKeyGroup = "Customer"; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs index 347a72354c..eff63d8431 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/EntityImporterBase.cs @@ -19,7 +19,7 @@ namespace SmartStore.Services.DataExchange.Import { - public abstract class EntityImporterBase : IEntityImporter where T : BaseEntity + public abstract class EntityImporterBase : IEntityImporter { private const string _imageDownloadFolder = @"Content\DownloadedImages"; From 042592d62df1cafcc57da729df5a99fa08fddeea Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 May 2016 23:03:39 +0200 Subject: [PATCH 151/285] Importer: reordered members in some files --- .../Catalog/Importer/CategoryImporter.cs | 340 ++++---- .../Catalog/Importer/ProductImporter.cs | 733 +++++++++--------- .../Customers/Importer/CustomerImporter.cs | 711 ++++++++--------- 3 files changed, 893 insertions(+), 891 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs index 98cef22f35..3f7315905b 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/CategoryImporter.cs @@ -59,76 +59,125 @@ public CategoryImporter( _fileDownloadManager = fileDownloadManager; } - protected virtual int ProcessLocalizations( - ImportExecuteContext context, - IEnumerable> batch, - string[] localizedProperties) + protected override void Import(ImportExecuteContext context) { - if (localizedProperties.Length == 0) - { - return 0; - } + var srcToDestId = new Dictionary(); - bool shouldSave = false; + var templateViewPaths = _categoryTemplateService.GetAllCategoryTemplates().ToDictionarySafe(x => x.ViewPath, x => x.Id); - foreach (var row in batch) + using (var scope = new DbContextScope(ctx: context.Services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { - foreach (var prop in localizedProperties) + var segmenter = context.DataSegmenter; + + Initialize(context); + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { - var lambda = _localizableProperties[prop]; - foreach (var lang in context.Languages) + var batch = segmenter.GetCurrentBatch(); + + // Perf: detach all entities + _categoryRepository.Context.DetachAll(false); + + context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); + + try { - var code = lang.UniqueSeoCode; - string value; + ProcessCategories(context, batch, templateViewPaths, srcToDestId); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessCategories"); + } - if (row.TryGetDataValue(prop /* ColumnName */, code, out value)) + // reduce batch to saved (valid) products. + // No need to perform import operations on errored products. + batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); + + // update result object + context.Result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); + context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); + + // process slugs + if (segmenter.HasColumn("SeName", true) || batch.Any(x => x.IsNew || x.NameChanged)) + { + try { - _localizedEntityService.SaveLocalizedValue(row.Entity, lambda, value, lang.Id); - shouldSave = true; + _categoryRepository.Context.AutoDetectChangesEnabled = true; + ProcessSlugs(context, batch, typeof(Category).Name); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessSlugs"); + } + finally + { + _categoryRepository.Context.AutoDetectChangesEnabled = false; } } - } - } - if (shouldSave) - { - // commit whole batch at once - return context.Services.DbContext.SaveChanges(); - } + // process store mappings + if (segmenter.HasColumn("StoreIds")) + { + try + { + ProcessStoreMappings(context, batch); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessStoreMappings"); + } + } - return 0; - } + // localizations + try + { + ProcessLocalizations(context, batch, _localizableProperties); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessLocalizedProperties"); + } - protected virtual int ProcessParentMappings( - ImportExecuteContext context, - IEnumerable> batch, - Dictionary srcToDestId) - { - foreach (var row in batch) - { - var id = row.GetDataValue("Id"); - var rawParentId = row.GetDataValue("ParentCategoryId"); - var parentId = rawParentId.ToInt(-1); + // process pictures + if (srcToDestId.Any() && segmenter.HasColumn("ImageUrl") && !segmenter.IsIgnored("PictureId")) + { + try + { + _categoryRepository.Context.AutoDetectChangesEnabled = true; + ProcessPictures(context, batch, srcToDestId); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessPictures"); + } + finally + { + _categoryRepository.Context.AutoDetectChangesEnabled = false; + } + } + } - if (id != 0 && parentId != -1 && srcToDestId.ContainsKey(id) && srcToDestId.ContainsKey(parentId)) + // map parent id of inserted categories + if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentCategoryId") && !segmenter.IsIgnored("ParentCategoryId")) { - // only touch hierarchical data if child and parent were inserted - if (srcToDestId[id].Inserted && srcToDestId[parentId].Inserted && srcToDestId[id].DestinationId != 0) + segmenter.Reset(); + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { - var category = _categoryRepository.GetById(srcToDestId[id].DestinationId); - if (category != null) - { - category.ParentCategoryId = srcToDestId[parentId].DestinationId; + var batch = segmenter.GetCurrentBatch(); + _categoryRepository.Context.DetachAll(false); - _categoryRepository.Update(category); + try + { + ProcessParentMappings(context, batch, srcToDestId); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessParentMappings"); } } } } - - var num = _categoryRepository.Context.SaveChanges(); - - return num; } protected virtual int ProcessPictures( @@ -206,6 +255,78 @@ protected virtual int ProcessPictures( return num; } + protected virtual int ProcessLocalizations( + ImportExecuteContext context, + IEnumerable> batch, + string[] localizedProperties) + { + if (localizedProperties.Length == 0) + { + return 0; + } + + bool shouldSave = false; + + foreach (var row in batch) + { + foreach (var prop in localizedProperties) + { + var lambda = _localizableProperties[prop]; + foreach (var lang in context.Languages) + { + var code = lang.UniqueSeoCode; + string value; + + if (row.TryGetDataValue(prop /* ColumnName */, code, out value)) + { + _localizedEntityService.SaveLocalizedValue(row.Entity, lambda, value, lang.Id); + shouldSave = true; + } + } + } + } + + if (shouldSave) + { + // commit whole batch at once + return context.Services.DbContext.SaveChanges(); + } + + return 0; + } + + protected virtual int ProcessParentMappings( + ImportExecuteContext context, + IEnumerable> batch, + Dictionary srcToDestId) + { + foreach (var row in batch) + { + var id = row.GetDataValue("Id"); + var rawParentId = row.GetDataValue("ParentCategoryId"); + var parentId = rawParentId.ToInt(-1); + + if (id != 0 && parentId != -1 && srcToDestId.ContainsKey(id) && srcToDestId.ContainsKey(parentId)) + { + // only touch hierarchical data if child and parent were inserted + if (srcToDestId[id].Inserted && srcToDestId[parentId].Inserted && srcToDestId[id].DestinationId != 0) + { + var category = _categoryRepository.GetById(srcToDestId[id].DestinationId); + if (category != null) + { + category.ParentCategoryId = srcToDestId[parentId].DestinationId; + + _categoryRepository.Update(category); + } + } + } + } + + var num = _categoryRepository.Context.SaveChanges(); + + return num; + } + protected virtual int ProcessCategories( ImportExecuteContext context, IEnumerable> batch, @@ -358,127 +479,6 @@ public static string[] DefaultKeyFields } } - protected override void Import(ImportExecuteContext context) - { - var srcToDestId = new Dictionary(); - - var templateViewPaths = _categoryTemplateService.GetAllCategoryTemplates().ToDictionarySafe(x => x.ViewPath, x => x.Id); - - using (var scope = new DbContextScope(ctx: context.Services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) - { - var segmenter = context.DataSegmenter; - - Initialize(context); - - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) - { - var batch = segmenter.GetCurrentBatch(); - - // Perf: detach all entities - _categoryRepository.Context.DetachAll(false); - - context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); - - try - { - ProcessCategories(context, batch, templateViewPaths, srcToDestId); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessCategories"); - } - - // reduce batch to saved (valid) products. - // No need to perform import operations on errored products. - batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); - - // update result object - context.Result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); - context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); - - // process slugs - if (segmenter.HasColumn("SeName", true) || batch.Any(x => x.IsNew || x.NameChanged)) - { - try - { - _categoryRepository.Context.AutoDetectChangesEnabled = true; - ProcessSlugs(context, batch, typeof(Category).Name); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessSlugs"); - } - finally - { - _categoryRepository.Context.AutoDetectChangesEnabled = false; - } - } - - // process store mappings - if (segmenter.HasColumn("StoreIds")) - { - try - { - ProcessStoreMappings(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessStoreMappings"); - } - } - - // localizations - try - { - ProcessLocalizations(context, batch, _localizableProperties); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessLocalizedProperties"); - } - - // process pictures - if (srcToDestId.Any() && segmenter.HasColumn("ImageUrl") && !segmenter.IsIgnored("PictureId")) - { - try - { - _categoryRepository.Context.AutoDetectChangesEnabled = true; - ProcessPictures(context, batch, srcToDestId); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessPictures"); - } - finally - { - _categoryRepository.Context.AutoDetectChangesEnabled = false; - } - } - } - - // map parent id of inserted categories - if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentCategoryId") && !segmenter.IsIgnored("ParentCategoryId")) - { - segmenter.Reset(); - - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) - { - var batch = segmenter.GetCurrentBatch(); - _categoryRepository.Context.DetachAll(false); - - try - { - ProcessParentMappings(context, batch, srcToDestId); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessParentMappings"); - } - } - } - } - } - public class ImportCategoryMapping { public int DestinationId { get; set; } diff --git a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs index 756ca28d01..6228f5b443 100644 --- a/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs +++ b/src/Libraries/SmartStore.Services/Catalog/Importer/ProductImporter.cs @@ -70,260 +70,166 @@ public ProductImporter( _fileDownloadManager = fileDownloadManager; } - private int? ZeroToNull(object value, CultureInfo culture) - { - int result; - if (CommonHelper.TryConvert(value, culture, out result) && result > 0) - { - return result; - } - - return (int?)null; - } - - protected virtual int ProcessProductMappings( - ImportExecuteContext context, - IEnumerable> batch, - Dictionary srcToDestId) - { - _productRepository.AutoCommitEnabled = false; - - foreach (var row in batch) - { - var id = row.GetDataValue("Id"); - var parentGroupedProductId = row.GetDataValue("ParentGroupedProductId"); - - if (id != 0 && parentGroupedProductId != 0 && srcToDestId.ContainsKey(id) && srcToDestId.ContainsKey(parentGroupedProductId)) - { - // only touch relationship if child and parent were inserted - if (srcToDestId[id].Inserted && srcToDestId[parentGroupedProductId].Inserted && srcToDestId[id].DestinationId != 0) - { - var product = _productRepository.GetById(srcToDestId[id].DestinationId); - if (product != null) - { - product.ParentGroupedProductId = srcToDestId[parentGroupedProductId].DestinationId; - _productRepository.Update(product); - } - } - } - } - - var num = _productRepository.Context.SaveChanges(); - - return num; - } - - protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnumerable> batch) + protected override void Import(ImportExecuteContext context) { - // true, cause pictures must be saved and assigned an id prior adding a mapping. - _productPictureRepository.AutoCommitEnabled = true; + var srcToDestId = new Dictionary(); - ProductPicture lastInserted = null; - var equalPictureId = 0; - var numberOfPictures = (context.ExtraData.NumberOfPictures ?? int.MaxValue); + var templateViewPaths = _productTemplateService.GetAllProductTemplates().ToDictionarySafe(x => x.ViewPath, x => x.Id); - foreach (var row in batch) + using (var scope = new DbContextScope(ctx: _productRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) { - var imageUrls = row.GetDataValue>("ImageUrls"); - if (imageUrls.IsNullOrEmpty()) - continue; + var segmenter = context.DataSegmenter; - var imageNumber = 0; - var displayOrder = -1; - var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); - var imageFiles = new List(); + Initialize(context); - // collect required image file infos - foreach (var urlOrPath in imageUrls) + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { - var image = CreateDownloadImage(urlOrPath, seoName, ++imageNumber); - - if (image != null) - imageFiles.Add(image); - - if (imageFiles.Count >= numberOfPictures) - break; - } + var batch = segmenter.GetCurrentBatch(); - // download images - if (imageFiles.Any(x => x.Url.HasValue())) - { - // async downloading in batch processing is inefficient cause only the image processing benefits from async, - // not the record processing itself. a per record processing may speed up the import. + // Perf: detach all entities + _productRepository.Context.DetachAll(false); - AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, imageFiles.Where(x => x.Url.HasValue() && !x.Success.HasValue))); - } + context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); - // import images - foreach (var image in imageFiles.OrderBy(x => x.DisplayOrder)) - { + // =========================================================================== + // 1.) Import products + // =========================================================================== try { - if ((image.Success ?? false) && File.Exists(image.Path)) - { - Succeeded(image); - var pictureBinary = File.ReadAllBytes(image.Path); - - if (pictureBinary != null && pictureBinary.Length > 0) - { - var currentProductPictures = _productPictureRepository.TableUntracked.Expand(x => x.Picture) - .Where(x => x.ProductId == row.Entity.Id) - .ToList(); - - var currentPictures = currentProductPictures - .Select(x => x.Picture) - .ToList(); + ProcessProducts(context, batch, templateViewPaths, srcToDestId); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProducts"); + } - if (displayOrder == -1) - { - displayOrder = (currentProductPictures.Any() ? currentProductPictures.Select(x => x.DisplayOrder).Max() : 0); - } + // reduce batch to saved (valid) products. + // No need to perform import operations on errored products. + batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); - pictureBinary = _pictureService.ValidatePicture(pictureBinary); - pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); + // update result object + context.Result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); + context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); - if (pictureBinary != null && pictureBinary.Length > 0) - { - // no equal picture found in sequence - var newPicture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false); - if (newPicture != null) - { - var mapping = new ProductPicture - { - ProductId = row.Entity.Id, - PictureId = newPicture.Id, - DisplayOrder = ++displayOrder - }; + // =========================================================================== + // 2.) Import SEO Slugs + // IMPORTANT: Unlike with Products AutoCommitEnabled must be TRUE, + // as Slugs are going to be validated against existing ones in DB. + // =========================================================================== + if (segmenter.HasColumn("SeName", true) || batch.Any(x => x.IsNew || x.NameChanged)) + { + try + { + _productRepository.Context.AutoDetectChangesEnabled = true; + ProcessSlugs(context, batch, typeof(Product).Name); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessSlugs"); + } + finally + { + _productRepository.Context.AutoDetectChangesEnabled = false; + } + } - _productPictureRepository.Insert(mapping); - lastInserted = mapping; - } - } - else - { - context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); - } - } + // =========================================================================== + // 3.) Import StoreMappings + // =========================================================================== + if (segmenter.HasColumn("StoreIds")) + { + try + { + ProcessStoreMappings(context, batch); } - else if (image.Url.HasValue()) + catch (Exception exception) { - context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessStoreMappings"); } } + + // =========================================================================== + // 4.) Import Localizations + // =========================================================================== + try + { + ProcessLocalizations(context, batch, _localizableProperties); + } catch (Exception exception) { - context.Result.AddWarning(exception.ToAllMessages(), row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessLocalizations"); } - } - } - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - { - _services.EventPublisher.EntityInserted(lastInserted); - } - } - - protected virtual int ProcessProductManufacturers(ImportExecuteContext context, IEnumerable> batch) - { - _productManufacturerRepository.AutoCommitEnabled = false; - ProductManufacturer lastInserted = null; + // =========================================================================== + // 5.) Import product category mappings + // =========================================================================== + if (segmenter.HasColumn("CategoryIds")) + { + try + { + ProcessProductCategories(context, batch); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductCategories"); + } + } - foreach (var row in batch) - { - var manufacturerIds = row.GetDataValue>("ManufacturerIds"); - if (!manufacturerIds.IsNullOrEmpty()) - { - try + // =========================================================================== + // 6.) Import product manufacturer mappings + // =========================================================================== + if (segmenter.HasColumn("ManufacturerIds")) { - foreach (var id in manufacturerIds) + try { - if (_productManufacturerRepository.TableUntracked.Where(x => x.ProductId == row.Entity.Id && x.ManufacturerId == id).FirstOrDefault() == null) - { - // ensure that manufacturer exists - var manufacturer = _manufacturerService.GetManufacturerById(id); - if (manufacturer != null) - { - var productManufacturer = new ProductManufacturer - { - ProductId = row.Entity.Id, - ManufacturerId = manufacturer.Id, - IsFeaturedProduct = false, - DisplayOrder = 1 - }; - _productManufacturerRepository.Insert(productManufacturer); - lastInserted = productManufacturer; - } - } + ProcessProductManufacturers(context, batch); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductManufacturers"); } } - catch (Exception exception) + + // =========================================================================== + // 7.) Import product picture mappings + // =========================================================================== + if (segmenter.HasColumn("ImageUrls")) { - context.Result.AddWarning(exception.Message, row.GetRowInfo(), "ManufacturerIds"); + try + { + ProcessProductPictures(context, batch); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductPictures"); + } } } - } - - // commit whole batch at once - var num = _productManufacturerRepository.Context.SaveChanges(); - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - _services.EventPublisher.EntityInserted(lastInserted); - return num; - } + // =========================================================================== + // 8.) Map parent id of inserted products + // =========================================================================== + if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentGroupedProductId") && !segmenter.IsIgnored("ParentGroupedProductId")) + { + segmenter.Reset(); - protected virtual int ProcessProductCategories(ImportExecuteContext context, IEnumerable> batch) - { - _productCategoryRepository.AutoCommitEnabled = false; + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + { + var batch = segmenter.GetCurrentBatch(); - ProductCategory lastInserted = null; + _productRepository.Context.DetachAll(false); - foreach (var row in batch) - { - var categoryIds = row.GetDataValue>("CategoryIds"); - if (!categoryIds.IsNullOrEmpty()) - { - try - { - foreach (var id in categoryIds) + try { - if (_productCategoryRepository.TableUntracked.Where(x => x.ProductId == row.Entity.Id && x.CategoryId == id).FirstOrDefault() == null) - { - // ensure that category exists - var category = _categoryService.GetCategoryById(id); - if (category != null) - { - var productCategory = new ProductCategory - { - ProductId = row.Entity.Id, - CategoryId = category.Id, - IsFeaturedProduct = false, - DisplayOrder = 1 - }; - _productCategoryRepository.Insert(productCategory); - lastInserted = productCategory; - } - } + ProcessProductMappings(context, batch, srcToDestId); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessParentMappings"); } - } - catch (Exception exception) - { - context.Result.AddWarning(exception.Message, row.GetRowInfo(), "CategoryIds"); } } } - - // commit whole batch at once - var num = _productCategoryRepository.Context.SaveChanges(); - - // Perf: notify only about LAST insertion and update - if (lastInserted != null) - _services.EventPublisher.EntityInserted(lastInserted); - - return num; } protected virtual int ProcessProducts( @@ -541,190 +447,285 @@ protected virtual int ProcessProducts( { _services.EventPublisher.EntityInserted(lastInserted); } - - if (lastUpdated != null) - { - _services.EventPublisher.EntityUpdated(lastUpdated); - } - - return num; - } - - public static string[] SupportedKeyFields - { - get - { - return new string[] { "Id", "Sku", "Gtin", "ManufacturerPartNumber", "Name" }; - } - } - - public static string[] DefaultKeyFields - { - get - { - return new string[] { "Sku", "Gtin", "ManufacturerPartNumber" }; - } + + if (lastUpdated != null) + { + _services.EventPublisher.EntityUpdated(lastUpdated); + } + + return num; + } + + protected virtual int ProcessProductMappings( + ImportExecuteContext context, + IEnumerable> batch, + Dictionary srcToDestId) + { + _productRepository.AutoCommitEnabled = false; + + foreach (var row in batch) + { + var id = row.GetDataValue("Id"); + var parentGroupedProductId = row.GetDataValue("ParentGroupedProductId"); + + if (id != 0 && parentGroupedProductId != 0 && srcToDestId.ContainsKey(id) && srcToDestId.ContainsKey(parentGroupedProductId)) + { + // only touch relationship if child and parent were inserted + if (srcToDestId[id].Inserted && srcToDestId[parentGroupedProductId].Inserted && srcToDestId[id].DestinationId != 0) + { + var product = _productRepository.GetById(srcToDestId[id].DestinationId); + if (product != null) + { + product.ParentGroupedProductId = srcToDestId[parentGroupedProductId].DestinationId; + _productRepository.Update(product); + } + } + } + } + + var num = _productRepository.Context.SaveChanges(); + + return num; + } + + protected virtual void ProcessProductPictures(ImportExecuteContext context, IEnumerable> batch) + { + // true, cause pictures must be saved and assigned an id prior adding a mapping. + _productPictureRepository.AutoCommitEnabled = true; + + ProductPicture lastInserted = null; + var equalPictureId = 0; + var numberOfPictures = (context.ExtraData.NumberOfPictures ?? int.MaxValue); + + foreach (var row in batch) + { + var imageUrls = row.GetDataValue>("ImageUrls"); + if (imageUrls.IsNullOrEmpty()) + continue; + + var imageNumber = 0; + var displayOrder = -1; + var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); + var imageFiles = new List(); + + // collect required image file infos + foreach (var urlOrPath in imageUrls) + { + var image = CreateDownloadImage(urlOrPath, seoName, ++imageNumber); + + if (image != null) + imageFiles.Add(image); + + if (imageFiles.Count >= numberOfPictures) + break; + } + + // download images + if (imageFiles.Any(x => x.Url.HasValue())) + { + // async downloading in batch processing is inefficient cause only the image processing benefits from async, + // not the record processing itself. a per record processing may speed up the import. + + AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, imageFiles.Where(x => x.Url.HasValue() && !x.Success.HasValue))); + } + + // import images + foreach (var image in imageFiles.OrderBy(x => x.DisplayOrder)) + { + try + { + if ((image.Success ?? false) && File.Exists(image.Path)) + { + Succeeded(image); + var pictureBinary = File.ReadAllBytes(image.Path); + + if (pictureBinary != null && pictureBinary.Length > 0) + { + var currentProductPictures = _productPictureRepository.TableUntracked.Expand(x => x.Picture) + .Where(x => x.ProductId == row.Entity.Id) + .ToList(); + + var currentPictures = currentProductPictures + .Select(x => x.Picture) + .ToList(); + + if (displayOrder == -1) + { + displayOrder = (currentProductPictures.Any() ? currentProductPictures.Select(x => x.DisplayOrder).Max() : 0); + } + + pictureBinary = _pictureService.ValidatePicture(pictureBinary); + pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); + + if (pictureBinary != null && pictureBinary.Length > 0) + { + // no equal picture found in sequence + var newPicture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false); + if (newPicture != null) + { + var mapping = new ProductPicture + { + ProductId = row.Entity.Id, + PictureId = newPicture.Id, + DisplayOrder = ++displayOrder + }; + + _productPictureRepository.Insert(mapping); + lastInserted = mapping; + } + } + else + { + context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + } + } + } + else if (image.Url.HasValue()) + { + context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + } + } + catch (Exception exception) + { + context.Result.AddWarning(exception.ToAllMessages(), row.GetRowInfo(), "ImageUrls" + image.DisplayOrder.ToString()); + } + } + } + + // Perf: notify only about LAST insertion and update + if (lastInserted != null) + { + _services.EventPublisher.EntityInserted(lastInserted); + } } - protected override void Import(ImportExecuteContext context) + protected virtual int ProcessProductManufacturers(ImportExecuteContext context, IEnumerable> batch) { - var srcToDestId = new Dictionary(); + _productManufacturerRepository.AutoCommitEnabled = false; - var templateViewPaths = _productTemplateService.GetAllProductTemplates().ToDictionarySafe(x => x.ViewPath, x => x.Id); + ProductManufacturer lastInserted = null; - using (var scope = new DbContextScope(ctx: _productRepository.Context, autoDetectChanges: false, proxyCreation: false, validateOnSave: false)) + foreach (var row in batch) { - var segmenter = context.DataSegmenter; - - Initialize(context); - - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + var manufacturerIds = row.GetDataValue>("ManufacturerIds"); + if (!manufacturerIds.IsNullOrEmpty()) { - var batch = segmenter.GetCurrentBatch(); - - // Perf: detach all entities - _productRepository.Context.DetachAll(false); - - context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); - - // =========================================================================== - // 1.) Import products - // =========================================================================== try { - ProcessProducts(context, batch, templateViewPaths, srcToDestId); + foreach (var id in manufacturerIds) + { + if (_productManufacturerRepository.TableUntracked.Where(x => x.ProductId == row.Entity.Id && x.ManufacturerId == id).FirstOrDefault() == null) + { + // ensure that manufacturer exists + var manufacturer = _manufacturerService.GetManufacturerById(id); + if (manufacturer != null) + { + var productManufacturer = new ProductManufacturer + { + ProductId = row.Entity.Id, + ManufacturerId = manufacturer.Id, + IsFeaturedProduct = false, + DisplayOrder = 1 + }; + _productManufacturerRepository.Insert(productManufacturer); + lastInserted = productManufacturer; + } + } + } } catch (Exception exception) { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProducts"); + context.Result.AddWarning(exception.Message, row.GetRowInfo(), "ManufacturerIds"); } + } + } - // reduce batch to saved (valid) products. - // No need to perform import operations on errored products. - batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); + // commit whole batch at once + var num = _productManufacturerRepository.Context.SaveChanges(); - // update result object - context.Result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); - context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); + // Perf: notify only about LAST insertion and update + if (lastInserted != null) + _services.EventPublisher.EntityInserted(lastInserted); - // =========================================================================== - // 2.) Import SEO Slugs - // IMPORTANT: Unlike with Products AutoCommitEnabled must be TRUE, - // as Slugs are going to be validated against existing ones in DB. - // =========================================================================== - if (segmenter.HasColumn("SeName", true) || batch.Any(x => x.IsNew || x.NameChanged)) - { - try - { - _productRepository.Context.AutoDetectChangesEnabled = true; - ProcessSlugs(context, batch, typeof(Product).Name); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessSlugs"); - } - finally - { - _productRepository.Context.AutoDetectChangesEnabled = false; - } - } + return num; + } - // =========================================================================== - // 3.) Import StoreMappings - // =========================================================================== - if (segmenter.HasColumn("StoreIds")) - { - try - { - ProcessStoreMappings(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessStoreMappings"); - } - } + protected virtual int ProcessProductCategories(ImportExecuteContext context, IEnumerable> batch) + { + _productCategoryRepository.AutoCommitEnabled = false; - // =========================================================================== - // 4.) Import Localizations - // =========================================================================== + ProductCategory lastInserted = null; + + foreach (var row in batch) + { + var categoryIds = row.GetDataValue>("CategoryIds"); + if (!categoryIds.IsNullOrEmpty()) + { try { - ProcessLocalizations(context, batch, _localizableProperties); + foreach (var id in categoryIds) + { + if (_productCategoryRepository.TableUntracked.Where(x => x.ProductId == row.Entity.Id && x.CategoryId == id).FirstOrDefault() == null) + { + // ensure that category exists + var category = _categoryService.GetCategoryById(id); + if (category != null) + { + var productCategory = new ProductCategory + { + ProductId = row.Entity.Id, + CategoryId = category.Id, + IsFeaturedProduct = false, + DisplayOrder = 1 + }; + _productCategoryRepository.Insert(productCategory); + lastInserted = productCategory; + } + } + } } catch (Exception exception) { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessLocalizations"); + context.Result.AddWarning(exception.Message, row.GetRowInfo(), "CategoryIds"); } + } + } - // =========================================================================== - // 5.) Import product category mappings - // =========================================================================== - if (segmenter.HasColumn("CategoryIds")) - { - try - { - ProcessProductCategories(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductCategories"); - } - } + // commit whole batch at once + var num = _productCategoryRepository.Context.SaveChanges(); - // =========================================================================== - // 6.) Import product manufacturer mappings - // =========================================================================== - if (segmenter.HasColumn("ManufacturerIds")) - { - try - { - ProcessProductManufacturers(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductManufacturers"); - } - } + // Perf: notify only about LAST insertion and update + if (lastInserted != null) + _services.EventPublisher.EntityInserted(lastInserted); - // =========================================================================== - // 7.) Import product picture mappings - // =========================================================================== - if (segmenter.HasColumn("ImageUrls")) - { - try - { - ProcessProductPictures(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessProductPictures"); - } - } - } + return num; + } - // =========================================================================== - // 8.) Map parent id of inserted products - // =========================================================================== - if (srcToDestId.Any() && segmenter.HasColumn("Id") && segmenter.HasColumn("ParentGroupedProductId") && !segmenter.IsIgnored("ParentGroupedProductId")) - { - segmenter.Reset(); - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) - { - var batch = segmenter.GetCurrentBatch(); + private int? ZeroToNull(object value, CultureInfo culture) + { + int result; + if (CommonHelper.TryConvert(value, culture, out result) && result > 0) + { + return result; + } - _productRepository.Context.DetachAll(false); + return (int?)null; + } - try - { - ProcessProductMappings(context, batch, srcToDestId); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessParentMappings"); - } - } - } + public static string[] SupportedKeyFields + { + get + { + return new string[] { "Id", "Sku", "Gtin", "ManufacturerPartNumber", "Name" }; + } + } + + public static string[] DefaultKeyFields + { + get + { + return new string[] { "Sku", "Gtin", "ManufacturerPartNumber" }; } } diff --git a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs index da0d917059..452f281fb8 100644 --- a/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs +++ b/src/Libraries/SmartStore.Services/Customers/Importer/CustomerImporter.cs @@ -70,70 +70,263 @@ public CustomerImporter( _forumSettings = forumSettings; } - private int? CountryCodeToId(Dictionary allCountries, string code) + + protected override void Import(ImportExecuteContext context) { - int countryId; - if (code.HasValue() && allCountries.TryGetValue(code, out countryId) && countryId != 0) + var customer = _services.WorkContext.CurrentCustomer; + var allowManagingCustomerRoles = _services.Permissions.Authorize(StandardPermissionProvider.ManageCustomerRoles, customer); + + var allAffiliateIds = _affiliateService.GetAllAffiliates(true) + .Select(x => x.Id) + .ToList(); + + var allCountries = new Dictionary(); + foreach (var country in _countryService.GetAllCountries(true)) { - return countryId; + if (!allCountries.ContainsKey(country.TwoLetterIsoCode)) + allCountries.Add(country.TwoLetterIsoCode, country.Id); + + if (!allCountries.ContainsKey(country.ThreeLetterIsoCode)) + allCountries.Add(country.ThreeLetterIsoCode, country.Id); } - return null; - } + var allStateProvinces = _stateProvinceService.GetAllStateProvinces(true) + .ToDictionarySafe(x => new Tuple(x.CountryId, x.Abbreviation), x => x.Id); - private int? StateAbbreviationToId(Dictionary, int> allStateProvinces, int? countryId, string abbreviation) - { - if (countryId.HasValue && abbreviation.HasValue()) + var allCustomerNumbers = new HashSet( + _genericAttributeService.GetAttributes(SystemCustomerAttributeNames.CustomerNumber, _attributeKeyGroup).Select(x => x.Value), + StringComparer.OrdinalIgnoreCase); + + using (var scope = new DbContextScope(ctx: _services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) { - var key = Tuple.Create(countryId.Value, abbreviation); + var segmenter = context.DataSegmenter; - int stateId; - if (allStateProvinces.TryGetValue(key, out stateId) && stateId != 0) + Initialize(context); + + while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) { - return stateId; + var batch = segmenter.GetCurrentBatch(); + + _customerRepository.Context.DetachAll(false); + + context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); + + // =========================================================================== + // Process customers + // =========================================================================== + try + { + ProcessCustomers(context, batch, allAffiliateIds); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessCustomers"); + } + + // reduce batch to saved (valid) records. + // No need to perform import operations on errored records. + batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); + + // update result object + context.Result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); + context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); + + // =========================================================================== + // Process generic attributes + // =========================================================================== + try + { + ProcessGenericAttributes(context, batch, allCountries, allStateProvinces, allCustomerNumbers); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessGenericAttributes"); + } + + // =========================================================================== + // Process avatars + // =========================================================================== + if (_customerSettings.AllowCustomersToUploadAvatars) + { + try + { + ProcessAvatars(context, batch); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessAvatars"); + } + } + + // =========================================================================== + // Process addresses + // =========================================================================== + try + { + _services.DbContext.AutoDetectChangesEnabled = true; + ProcessAddresses(context, batch, allCountries, allStateProvinces); + } + catch (Exception exception) + { + context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessAddresses"); + } + finally + { + _services.DbContext.AutoDetectChangesEnabled = false; + } } } - - return null; } - private void SaveAttribute(ImportRow row, string key) + protected virtual int ProcessCustomers( + ImportExecuteContext context, + IEnumerable> batch, + List allAffiliateIds) { - SaveAttribute(row, key, row.GetDataValue(key)); - } + _customerRepository.AutoCommitEnabled = true; - private void SaveAttribute(ImportRow row, string key) - { + Customer lastInserted = null; + Customer lastUpdated = null; + var currentCustomer = _services.WorkContext.CurrentCustomer; - SaveAttribute(row, key, row.GetDataValue(key)); - } + var guestRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Guests); + var registeredRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered); + var forumModeratorRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.ForumModerators); - private void SaveAttribute(ImportRow row, string key, TPropType value) - { - if (row.IsTransient) - return; + var customerQuery = _customerRepository.Table.Expand(x => x.Addresses); - if (row.IsNew || value!= null) + foreach (var row in batch) { - _genericAttributeService.SaveAttribute(row.Entity.Id, key, _attributeKeyGroup, value); - } - } + Customer customer = null; + var id = row.GetDataValue("Id"); + var email = row.GetDataValue("Email"); - private void UpsertRole(ImportRow row, CustomerRole role, bool value) - { - if (role == null) - return; + foreach (var keyName in context.KeyFieldNames) + { + switch (keyName) + { + case "Id": + if (id != 0) + { + customer = customerQuery.FirstOrDefault(x => x.Id == id); + } + break; + case "CustomerGuid": + var customerGuid = row.GetDataValue("CustomerGuid"); + if (customerGuid.HasValue()) + { + var guid = new Guid(customerGuid); + customer = customerQuery.FirstOrDefault(x => x.CustomerGuid == guid); + } + break; + case "Email": + if (email.HasValue()) + { + customer = customerQuery.FirstOrDefault(x => x.Email == email); + } + break; + case "Username": + var userName = row.GetDataValue("Username"); + if (userName.HasValue()) + { + customer = customerQuery.FirstOrDefault(x => x.Username == userName); + } + break; + } - var hasRole = row.Entity.CustomerRoles.Any(x => x.SystemName == role.SystemName); + if (customer != null) + break; + } - if (value && !hasRole) + if (customer == null) + { + if (context.UpdateOnly) + { + ++context.Result.SkippedRecords; + continue; + } + + customer = new Customer + { + CustomerGuid = new Guid(), + AffiliateId = 0, + Active = true + }; + } + else + { + _customerRepository.Context.LoadCollection(customer, (Customer x) => x.CustomerRoles); + } + + var isGuest = row.GetDataValue("IsGuest"); + var isRegistered = row.GetDataValue("IsRegistered"); + var isAdmin = row.GetDataValue("IsAdministrator"); + var isForumModerator = row.GetDataValue("IsForumModerator"); + var affiliateId = row.GetDataValue("AffiliateId"); + + row.Initialize(customer, email ?? id.ToString()); + + row.SetProperty(context.Result, (x) => x.CustomerGuid); + row.SetProperty(context.Result, (x) => x.Username); + row.SetProperty(context.Result, (x) => x.Email); + + if (email.HasValue() && currentCustomer.Email.IsCaseInsensitiveEqual(email)) + { + context.Result.AddInfo("Security. Ignored password of current customer (who started this import).", row.GetRowInfo(), "Password"); + } + else + { + row.SetProperty(context.Result, (x) => x.Password); + row.SetProperty(context.Result, (x) => x.PasswordFormatId); + row.SetProperty(context.Result, (x) => x.PasswordSalt); + } + + row.SetProperty(context.Result, (x) => x.AdminComment); + row.SetProperty(context.Result, (x) => x.IsTaxExempt); + row.SetProperty(context.Result, (x) => x.Active); + + row.SetProperty(context.Result, (x) => x.CreatedOnUtc, UtcNow); + row.SetProperty(context.Result, (x) => x.LastActivityDateUtc, UtcNow); + + if (affiliateId > 0 && allAffiliateIds.Contains(affiliateId)) + { + customer.AffiliateId = affiliateId; + } + + if (isAdmin) + { + context.Result.AddInfo("Security. Ignored administrator role.", row.GetRowInfo(), "IsAdministrator"); + } + + UpsertRole(row, guestRole, isGuest); + UpsertRole(row, registeredRole, isRegistered); + UpsertRole(row, forumModeratorRole, isForumModerator); + + if (row.IsTransient) + { + _customerRepository.Insert(customer); + lastInserted = customer; + } + else + { + _customerRepository.Update(customer); + lastUpdated = customer; + } + } + + var num = _customerRepository.Context.SaveChanges(); + + if (lastInserted != null) { - row.Entity.CustomerRoles.Add(role); + _services.EventPublisher.EntityInserted(lastInserted); } - else if (!value && hasRole) + + if (lastUpdated != null) { - row.Entity.CustomerRoles.Remove(role); + _services.EventPublisher.EntityUpdated(lastUpdated); } + + return num; } protected virtual int ProcessAddresses( @@ -272,258 +465,107 @@ protected virtual int ProcessGenericAttributes( SaveAttribute(row, SystemCustomerAttributeNames.Phone); if (_customerSettings.FaxEnabled) - SaveAttribute(row, SystemCustomerAttributeNames.Fax); - - if (_forumSettings.ForumsEnabled) - SaveAttribute(row, SystemCustomerAttributeNames.ForumPostCount); - - if (_forumSettings.SignaturesEnabled) - SaveAttribute(row, SystemCustomerAttributeNames.Signature); - - var countryId = CountryCodeToId(allCountries, row.GetDataValue("CountryCode")); - var stateId = StateAbbreviationToId(allStateProvinces, countryId, row.GetDataValue("StateAbbreviation")); - - if (countryId.HasValue) - { - SaveAttribute(row, SystemCustomerAttributeNames.CountryId, countryId.Value); - } - - if (stateId.HasValue) - { - SaveAttribute(row, SystemCustomerAttributeNames.StateProvinceId, stateId.Value); - } - - string customerNumber = null; - - if (_customerSettings.CustomerNumberMethod == CustomerNumberMethod.AutomaticallySet) - customerNumber = row.Entity.Id.ToString(); - else - customerNumber = row.GetDataValue("CustomerNumber"); - - if (customerNumber.IsEmpty() || !allCustomerNumbers.Contains(customerNumber)) - { - SaveAttribute(row, SystemCustomerAttributeNames.CustomerNumber, customerNumber); - - if (!customerNumber.IsEmpty()) - allCustomerNumbers.Add(customerNumber); - } - } - - return _services.DbContext.SaveChanges(); - } - - protected virtual int ProcessAvatars( - ImportExecuteContext context, - IEnumerable> batch) - { - foreach (var row in batch) - { - var urlOrPath = row.GetDataValue("AvatarPictureUrl"); - if (urlOrPath.IsEmpty()) - continue; - - Picture picture = null; - var equalPictureId = 0; - var currentPictures = new List(); - var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); - - var image = CreateDownloadImage(urlOrPath, seoName, 1); - if (image == null) - continue; - - if (image.Url.HasValue() && !image.Success.HasValue) - { - AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, new FileDownloadManagerItem[] { image })); - } - - if ((image.Success ?? false) && File.Exists(image.Path)) - { - Succeeded(image); - var pictureBinary = File.ReadAllBytes(image.Path); - - if (pictureBinary != null && pictureBinary.Length > 0) - { - var currentPictureId = row.Entity.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId); - if (currentPictureId != 0 && (picture = _pictureRepository.GetById(currentPictureId)) != null) - { - currentPictures.Add(picture); - } - - pictureBinary = _pictureService.ValidatePicture(pictureBinary); - pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); - - if (pictureBinary != null && pictureBinary.Length > 0) - { - if ((picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false)) != null) - { - _pictureRepository.Context.SaveChanges(); - SaveAttribute(row, SystemCustomerAttributeNames.AvatarPictureId, picture.Id); - } - } - else - { - context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "AvatarPictureUrl"); - } - } - } - else - { - context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "AvatarPictureUrl"); - } - } - - return _services.DbContext.SaveChanges(); - } - - protected virtual int ProcessCustomers( - ImportExecuteContext context, - IEnumerable> batch, - List allAffiliateIds) - { - _customerRepository.AutoCommitEnabled = true; - - Customer lastInserted = null; - Customer lastUpdated = null; - var currentCustomer = _services.WorkContext.CurrentCustomer; - - var guestRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Guests); - var registeredRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.Registered); - var forumModeratorRole = _customerService.GetCustomerRoleBySystemName(SystemCustomerRoleNames.ForumModerators); - - var customerQuery = _customerRepository.Table.Expand(x => x.Addresses); - - foreach (var row in batch) - { - Customer customer = null; - var id = row.GetDataValue("Id"); - var email = row.GetDataValue("Email"); - - foreach (var keyName in context.KeyFieldNames) - { - switch (keyName) - { - case "Id": - if (id != 0) - { - customer = customerQuery.FirstOrDefault(x => x.Id == id); - } - break; - case "CustomerGuid": - var customerGuid = row.GetDataValue("CustomerGuid"); - if (customerGuid.HasValue()) - { - var guid = new Guid(customerGuid); - customer = customerQuery.FirstOrDefault(x => x.CustomerGuid == guid); - } - break; - case "Email": - if (email.HasValue()) - { - customer = customerQuery.FirstOrDefault(x => x.Email == email); - } - break; - case "Username": - var userName = row.GetDataValue("Username"); - if (userName.HasValue()) - { - customer = customerQuery.FirstOrDefault(x => x.Username == userName); - } - break; - } - - if (customer != null) - break; - } - - if (customer == null) - { - if (context.UpdateOnly) - { - ++context.Result.SkippedRecords; - continue; - } - - customer = new Customer - { - CustomerGuid = new Guid(), - AffiliateId = 0, - Active = true - }; - } - else - { - _customerRepository.Context.LoadCollection(customer, (Customer x) => x.CustomerRoles); - } + SaveAttribute(row, SystemCustomerAttributeNames.Fax); - var isGuest = row.GetDataValue("IsGuest"); - var isRegistered = row.GetDataValue("IsRegistered"); - var isAdmin = row.GetDataValue("IsAdministrator"); - var isForumModerator = row.GetDataValue("IsForumModerator"); - var affiliateId = row.GetDataValue("AffiliateId"); + if (_forumSettings.ForumsEnabled) + SaveAttribute(row, SystemCustomerAttributeNames.ForumPostCount); - row.Initialize(customer, email ?? id.ToString()); + if (_forumSettings.SignaturesEnabled) + SaveAttribute(row, SystemCustomerAttributeNames.Signature); - row.SetProperty(context.Result, (x) => x.CustomerGuid); - row.SetProperty(context.Result, (x) => x.Username); - row.SetProperty(context.Result, (x) => x.Email); + var countryId = CountryCodeToId(allCountries, row.GetDataValue("CountryCode")); + var stateId = StateAbbreviationToId(allStateProvinces, countryId, row.GetDataValue("StateAbbreviation")); - if (email.HasValue() && currentCustomer.Email.IsCaseInsensitiveEqual(email)) + if (countryId.HasValue) { - context.Result.AddInfo("Security. Ignored password of current customer (who started this import).", row.GetRowInfo(), "Password"); + SaveAttribute(row, SystemCustomerAttributeNames.CountryId, countryId.Value); } - else + + if (stateId.HasValue) { - row.SetProperty(context.Result, (x) => x.Password); - row.SetProperty(context.Result, (x) => x.PasswordFormatId); - row.SetProperty(context.Result, (x) => x.PasswordSalt); + SaveAttribute(row, SystemCustomerAttributeNames.StateProvinceId, stateId.Value); } - row.SetProperty(context.Result, (x) => x.AdminComment); - row.SetProperty(context.Result, (x) => x.IsTaxExempt); - row.SetProperty(context.Result, (x) => x.Active); + string customerNumber = null; - row.SetProperty(context.Result, (x) => x.CreatedOnUtc, UtcNow); - row.SetProperty(context.Result, (x) => x.LastActivityDateUtc, UtcNow); + if (_customerSettings.CustomerNumberMethod == CustomerNumberMethod.AutomaticallySet) + customerNumber = row.Entity.Id.ToString(); + else + customerNumber = row.GetDataValue("CustomerNumber"); - if (affiliateId > 0 && allAffiliateIds.Contains(affiliateId)) + if (customerNumber.IsEmpty() || !allCustomerNumbers.Contains(customerNumber)) { - customer.AffiliateId = affiliateId; - } + SaveAttribute(row, SystemCustomerAttributeNames.CustomerNumber, customerNumber); - if (isAdmin) - { - context.Result.AddInfo("Security. Ignored administrator role.", row.GetRowInfo(), "IsAdministrator"); + if (!customerNumber.IsEmpty()) + allCustomerNumbers.Add(customerNumber); } + } - UpsertRole(row, guestRole, isGuest); - UpsertRole(row, registeredRole, isRegistered); - UpsertRole(row, forumModeratorRole, isForumModerator); + return _services.DbContext.SaveChanges(); + } - if (row.IsTransient) + protected virtual int ProcessAvatars( + ImportExecuteContext context, + IEnumerable> batch) + { + foreach (var row in batch) + { + var urlOrPath = row.GetDataValue("AvatarPictureUrl"); + if (urlOrPath.IsEmpty()) + continue; + + Picture picture = null; + var equalPictureId = 0; + var currentPictures = new List(); + var seoName = _pictureService.GetPictureSeName(row.EntityDisplayName); + + var image = CreateDownloadImage(urlOrPath, seoName, 1); + if (image == null) + continue; + + if (image.Url.HasValue() && !image.Success.HasValue) { - _customerRepository.Insert(customer); - lastInserted = customer; + AsyncRunner.RunSync(() => _fileDownloadManager.DownloadAsync(DownloaderContext, new FileDownloadManagerItem[] { image })); } - else + + if ((image.Success ?? false) && File.Exists(image.Path)) { - _customerRepository.Update(customer); - lastUpdated = customer; - } - } + Succeeded(image); + var pictureBinary = File.ReadAllBytes(image.Path); - var num = _customerRepository.Context.SaveChanges(); + if (pictureBinary != null && pictureBinary.Length > 0) + { + var currentPictureId = row.Entity.GetAttribute(SystemCustomerAttributeNames.AvatarPictureId); + if (currentPictureId != 0 && (picture = _pictureRepository.GetById(currentPictureId)) != null) + { + currentPictures.Add(picture); + } - if (lastInserted != null) - { - _services.EventPublisher.EntityInserted(lastInserted); - } + pictureBinary = _pictureService.ValidatePicture(pictureBinary); + pictureBinary = _pictureService.FindEqualPicture(pictureBinary, currentPictures, out equalPictureId); - if (lastUpdated != null) - { - _services.EventPublisher.EntityUpdated(lastUpdated); + if (pictureBinary != null && pictureBinary.Length > 0) + { + if ((picture = _pictureService.InsertPicture(pictureBinary, image.MimeType, seoName, true, false, false)) != null) + { + _pictureRepository.Context.SaveChanges(); + SaveAttribute(row, SystemCustomerAttributeNames.AvatarPictureId, picture.Id); + } + } + else + { + context.Result.AddInfo("Found equal picture in data store. Skipping field.", row.GetRowInfo(), "AvatarPictureUrl"); + } + } + } + else + { + context.Result.AddInfo("Download of an image failed.", row.GetRowInfo(), "AvatarPictureUrl"); + } } - return num; + return _services.DbContext.SaveChanges(); } public static string[] SupportedKeyFields @@ -542,110 +584,69 @@ public static string[] DefaultKeyFields } } - protected override void Import(ImportExecuteContext context) + private int? CountryCodeToId(Dictionary allCountries, string code) { - var customer = _services.WorkContext.CurrentCustomer; - var allowManagingCustomerRoles = _services.Permissions.Authorize(StandardPermissionProvider.ManageCustomerRoles, customer); - - var allAffiliateIds = _affiliateService.GetAllAffiliates(true) - .Select(x => x.Id) - .ToList(); - - var allCountries = new Dictionary(); - foreach (var country in _countryService.GetAllCountries(true)) + int countryId; + if (code.HasValue() && allCountries.TryGetValue(code, out countryId) && countryId != 0) { - if (!allCountries.ContainsKey(country.TwoLetterIsoCode)) - allCountries.Add(country.TwoLetterIsoCode, country.Id); - - if (!allCountries.ContainsKey(country.ThreeLetterIsoCode)) - allCountries.Add(country.ThreeLetterIsoCode, country.Id); + return countryId; } - var allStateProvinces = _stateProvinceService.GetAllStateProvinces(true) - .ToDictionarySafe(x => new Tuple(x.CountryId, x.Abbreviation), x => x.Id); - - var allCustomerNumbers = new HashSet( - _genericAttributeService.GetAttributes(SystemCustomerAttributeNames.CustomerNumber, _attributeKeyGroup).Select(x => x.Value), - StringComparer.OrdinalIgnoreCase); + return null; + } - using (var scope = new DbContextScope(ctx: _services.DbContext, autoDetectChanges: false, proxyCreation: false, validateOnSave: false, autoCommit: false)) + private int? StateAbbreviationToId(Dictionary, int> allStateProvinces, int? countryId, string abbreviation) + { + if (countryId.HasValue && abbreviation.HasValue()) { - var segmenter = context.DataSegmenter; - - Initialize(context); + var key = Tuple.Create(countryId.Value, abbreviation); - while (context.Abort == DataExchangeAbortion.None && segmenter.ReadNextBatch()) + int stateId; + if (allStateProvinces.TryGetValue(key, out stateId) && stateId != 0) { - var batch = segmenter.GetCurrentBatch(); + return stateId; + } + } - _customerRepository.Context.DetachAll(false); + return null; + } - context.SetProgress(segmenter.CurrentSegmentFirstRowIndex - 1, segmenter.TotalRows); + private void SaveAttribute(ImportRow row, string key) + { + SaveAttribute(row, key, row.GetDataValue(key)); + } - // =========================================================================== - // Process customers - // =========================================================================== - try - { - ProcessCustomers(context, batch, allAffiliateIds); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessCustomers"); - } + private void SaveAttribute(ImportRow row, string key) + { - // reduce batch to saved (valid) records. - // No need to perform import operations on errored records. - batch = batch.Where(x => x.Entity != null && !x.IsTransient).ToArray(); + SaveAttribute(row, key, row.GetDataValue(key)); + } - // update result object - context.Result.NewRecords += batch.Count(x => x.IsNew && !x.IsTransient); - context.Result.ModifiedRecords += batch.Count(x => !x.IsNew && !x.IsTransient); + private void SaveAttribute(ImportRow row, string key, TPropType value) + { + if (row.IsTransient) + return; - // =========================================================================== - // Process generic attributes - // =========================================================================== - try - { - ProcessGenericAttributes(context, batch, allCountries, allStateProvinces, allCustomerNumbers); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessGenericAttributes"); - } + if (row.IsNew || value != null) + { + _genericAttributeService.SaveAttribute(row.Entity.Id, key, _attributeKeyGroup, value); + } + } - // =========================================================================== - // Process avatars - // =========================================================================== - if (_customerSettings.AllowCustomersToUploadAvatars) - { - try - { - ProcessAvatars(context, batch); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessAvatars"); - } - } + private void UpsertRole(ImportRow row, CustomerRole role, bool value) + { + if (role == null) + return; - // =========================================================================== - // Process addresses - // =========================================================================== - try - { - _services.DbContext.AutoDetectChangesEnabled = true; - ProcessAddresses(context, batch, allCountries, allStateProvinces); - } - catch (Exception exception) - { - context.Result.AddError(exception, segmenter.CurrentSegment, "ProcessAddresses"); - } - finally - { - _services.DbContext.AutoDetectChangesEnabled = false; - } - } + var hasRole = row.Entity.CustomerRoles.Any(x => x.SystemName == role.SystemName); + + if (value && !hasRole) + { + row.Entity.CustomerRoles.Add(role); + } + else if (!value && hasRole) + { + row.Entity.CustomerRoles.Remove(role); } } } From 81a440a64830a63e1cc218ce4fd3a9ef20d0432c Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Thu, 12 May 2016 23:51:17 +0200 Subject: [PATCH 152/285] Updated changelog --- changelog.md | 6 +++++- src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index 8e188f25e4..c995ef0207 100644 --- a/changelog.md +++ b/changelog.md @@ -3,9 +3,12 @@ ## SmartStore.NET 2.5.1 ### Highlights -* #637 Integrate PayPal PLUS Provider +* #637 Integrated PayPal PLUS payment Provider +* Major improvements in Importer: better field mapping, higher performance, bug fixes etc. ### New Features +* #637 Integrated PayPal PLUS payment Provider +* #961 Fix "Open Redirection Vulnerability" * #571 Option to display another checkbox on confirm page to let the customer accept that his email address can be handed over to a third party * #870 Implement address import for customers (both billing & shipping address) * #886 Add setting to hide manufacturer images on product detail page and to hide default image for manufacturers @@ -13,6 +16,7 @@ * #477 Implement option to specify the number of exported and imported pictures ### Improvements +* Major improvements in Importer: better field mapping, higher performance, bug fixes etc. * (Dev) Publishing SmartStore.Web from within Visual Studio now deploys the project correctly. No need to execute ClickToBuild.cmd anymore. Just publish directly to any target, including Azure. * Localization & SEO: language switcher now takes language specific SEO slugs into account when building links * Exports the product detail link including the attribute query string when exporting attribute combinations diff --git a/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs b/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs index 67337bbb66..29c29d13ef 100644 --- a/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs +++ b/src/Libraries/SmartStore.Services/Seo/SeoExtensions.cs @@ -243,7 +243,7 @@ public static string ValidateSeName(this T entity, } } - // validate and alter sename if it could be interpreted as SEO code + // validate and alter SeName if it could be interpreted as SEO code if (LocalizationHelper.IsValidCultureCode(seName)) { if (seName.Length == 2) From 94038d8a2d816b3cb8d39f3a4031ba11f422791e Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 13 May 2016 10:35:26 +0200 Subject: [PATCH 153/285] Resolves #963 Import: ColumnMapping > lock SeName default value field --- .../Administration/Controllers/ImportController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs index fdb726fe6c..8de7f54e5c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ImportController.cs @@ -94,9 +94,9 @@ private string[] GetDisabledDefaultFieldNames(ImportProfile profile) switch (profile.EntityType) { case ImportEntityType.Product: - return new string[] { "Name", "Sku", "ManufacturerPartNumber", "Gtin" }; + return new string[] { "Name", "Sku", "ManufacturerPartNumber", "Gtin", "SeName" }; case ImportEntityType.Category: - return new string[] { "Name" }; + return new string[] { "Name", "SeName" }; case ImportEntityType.Customer: return new string[] { "CustomerGuid", "Email" }; case ImportEntityType.NewsLetterSubscription: From 8e207d6107436f5a6350a423d4f3657b9f56ea7d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 13 May 2016 13:40:41 +0200 Subject: [PATCH 154/285] Resolves #921 Specification attribute options with single quotation marks are causing a Javascript error --- changelog.md | 1 + .../Administration/Controllers/ProductController.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index c995ef0207..b5e154c8f0 100644 --- a/changelog.md +++ b/changelog.md @@ -42,6 +42,7 @@ * Mobile devices: Fixed "Uncaught Error: Syntax error, unrecognized expression: :nth-child" * Download nor sample download were removable when editing product * Copied product must not share sample download of source product. Could produce "The DELETE statement conflicted with the REFERENCE constraint 'FK_dbo.Product_dbo.Download_SampleDownloadId'". +* #921 Specification attribute options with single quotation marks are causing a Javascript error ## SmartStore.NET 2.5 diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs index 8ea467ea2c..752a19c41c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ProductController.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Data; using System.Linq; +using System.Web; using System.Web.Mvc; using Autofac; using Newtonsoft.Json; @@ -2313,7 +2314,7 @@ public ActionResult ProductSpecAttrList(GridCommand command, int productId) }); } - attr.SpecificationAttributeOptionsJsonString = JsonConvert.SerializeObject(attr.SpecificationAttributeOptions); + attr.SpecificationAttributeOptionsJsonString = HttpUtility.HtmlEncode(JsonConvert.SerializeObject(attr.SpecificationAttributeOptions)); } model.Data = productrSpecsModel; From 811246b73bf5622ce9a9f4058c60726edd363ac8 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 13 May 2016 19:34:46 +0200 Subject: [PATCH 155/285] Added paging to frontend order list --- changelog.md | 1 + .../Domain/Orders/OrderSettings.cs | 9 ++- .../201605111140288_ImportExtraData.cs | 13 ++++ .../Models/Settings/OrderSettingsModel.cs | 4 + .../Settings/OrderSettingsValidator.cs | 7 +- .../Administration/Views/Setting/Order.cshtml | 9 +++ .../Controllers/CustomerController.cs | 48 ++++++------ .../Models/Customer/CustomerOrderListModel.cs | 13 ++-- .../Views/Customer/Orders.Mobile.cshtml | 8 +- .../Views/Customer/Orders.cshtml | 75 +++++++++---------- 10 files changed, 116 insertions(+), 71 deletions(-) diff --git a/changelog.md b/changelog.md index b5e154c8f0..7327aee605 100644 --- a/changelog.md +++ b/changelog.md @@ -23,6 +23,7 @@ * #918 Compare products: Display base price information * Export email attachments needs to be stored in database because the temp file may not exist anymore when sending the email * #913 Use HTML5 Input types (tel, email) +* Added paging to frontend order list ### Bugfixes * Fixed ajax cache issue when saving payment or shipping restrictions. Internet Explorer showed the old data state (before storage). diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/OrderSettings.cs b/src/Libraries/SmartStore.Core/Domain/Orders/OrderSettings.cs index 996271de79..05e9a49fd4 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/OrderSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/OrderSettings.cs @@ -1,5 +1,4 @@ -using System.Collections.Generic; -using SmartStore.Core.Configuration; +using SmartStore.Core.Configuration; using SmartStore.Core.Domain.Localization; namespace SmartStore.Core.Domain.Orders @@ -16,6 +15,7 @@ public OrderSettings() ReturnRequestReasons = "Received Wrong Product,Wrong Product Ordered,There Was A Problem With The Product"; NumberOfDaysReturnRequestAvailable = 365; MinimumOrderPlacementInterval = 30; + OrderListPageSize = 10; } /// @@ -87,5 +87,10 @@ public OrderSettings() /// Gets or sets a value indicating whether to display all orders of all stores to a customer /// public bool DisplayOrdersOfAllStores { get; set; } + + /// + /// Page size of the order list + /// + public int OrderListPageSize { get; set; } } } \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs index 17d0564ce6..0d7b6fcc06 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs @@ -38,6 +38,19 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Anzahl der Bilder", "Specifies the number of images per object to be imported.", "Legt die Anzahl der zu importierenden Bilder pro Objekt fest."); + + builder.Update("Admin.Configuration.Settings.Catalog.DefaultPageSizeOptions") + .Value("en", "Number of displayed products per page"); + + builder.AddOrUpdate("Admin.Validation.ValueGreaterZero", + "The value must be greater than 0.", + "Der Wert muss größer 0 sein."); + + builder.AddOrUpdate("Admin.Configuration.Settings.Order.OrderListPageSize", + "Number of displayed orders per page", + "Anzahl der Aufträge pro Seite", + "Specifies the number of displayed orders per page.", + "Legt die Anzahl der dargestellten Aufträge pro Seite fest."); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/OrderSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/OrderSettingsModel.cs index c54a264eb5..c0e73f70fb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/OrderSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/OrderSettingsModel.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Admin.Validators.Settings; @@ -66,6 +67,9 @@ public OrderSettingsModel() [SmartResourceDisplayName("Admin.Configuration.Settings.Order.DisplayOrdersOfAllStores")] public bool DisplayOrdersOfAllStores { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.Order.OrderListPageSize")] + public int OrderListPageSize { get; set; } + public IList Locales { get; set; } } diff --git a/src/Presentation/SmartStore.Web/Administration/Validators/Settings/OrderSettingsValidator.cs b/src/Presentation/SmartStore.Web/Administration/Validators/Settings/OrderSettingsValidator.cs index eab92160eb..547872cac6 100644 --- a/src/Presentation/SmartStore.Web/Administration/Validators/Settings/OrderSettingsValidator.cs +++ b/src/Presentation/SmartStore.Web/Administration/Validators/Settings/OrderSettingsValidator.cs @@ -11,8 +11,13 @@ public OrderSettingsValidator(ILocalizationService localizationService) { RuleFor(x => x.GiftCards_Activated_OrderStatusId).NotEqual((int)OrderStatus.Pending) .WithMessage(localizationService.GetResource("Admin.Configuration.Settings.RewardPoints.PointsForPurchases_Awarded.Pending")); + RuleFor(x => x.GiftCards_Deactivated_OrderStatusId).NotEqual((int)OrderStatus.Pending) .WithMessage(localizationService.GetResource("Admin.Configuration.Settings.RewardPoints.PointsForPurchases_Canceled.Pending")); - } + + RuleFor(x => x.OrderListPageSize) + .GreaterThan(0) + .WithMessage(localizationService.GetResource("Admin.Validation.ValueGreaterZero")); + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml index 37cb664577..c10c4c8f52 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/Order.cshtml @@ -105,6 +105,15 @@ @Html.ValidationMessageFor(model => model.GiftCards_Deactivated_OrderStatusId) + + + @Html.SmartLabelFor(model => model.OrderListPageSize) + + + @Html.EditorFor(model => model.OrderListPageSize) + @Html.ValidationMessageFor(model => model.OrderListPageSize) + + @if (Model.OrderIdent.HasValue) { diff --git a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs index 9ff54c6482..cd3eb7dd5d 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CustomerController.cs @@ -352,35 +352,40 @@ protected void PrepareCustomerInfoModel(CustomerInfoModel model, Customer custom } [NonAction] - protected CustomerOrderListModel PrepareCustomerOrderListModel(Customer customer) + protected CustomerOrderListModel PrepareCustomerOrderListModel(Customer customer, int pageIndex) { if (customer == null) throw new ArgumentNullException("customer"); - var model = new CustomerOrderListModel(); + var storeScope = (_orderSettings.DisplayOrdersOfAllStores ? 0 : _storeContext.CurrentStore.Id); + + var model = new CustomerOrderListModel(); model.NavigationModel = GetCustomerNavigationModel(customer); model.NavigationModel.SelectedTab = CustomerNavigationEnum.Orders; - var storeScope = (_orderSettings.DisplayOrdersOfAllStores ? 0 : _storeContext.CurrentStore.Id); + var orders = _orderService.SearchOrders(storeScope, customer.Id, null, null, null, null, null, null, null, null, pageIndex, _orderSettings.OrderListPageSize); - var orders = _orderService.SearchOrders(storeScope, customer.Id, null, null, null, null, null, null, null, null, 0, int.MaxValue); + var orderModels = orders + .Select(x => + { + var orderModel = new CustomerOrderListModel.OrderDetailsModel + { + Id = x.Id, + OrderNumber = x.GetOrderNumber(), + CreatedOn = _dateTimeHelper.ConvertToUserTime(x.CreatedOnUtc, DateTimeKind.Utc), + OrderStatus = x.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), + IsReturnRequestAllowed = _orderProcessingService.IsReturnRequestAllowed(x) + }; - foreach (var order in orders) - { - var orderModel = new CustomerOrderListModel.OrderDetailsModel - { - Id = order.Id, - OrderNumber = order.GetOrderNumber(), - CreatedOn = _dateTimeHelper.ConvertToUserTime(order.CreatedOnUtc, DateTimeKind.Utc), - OrderStatus = order.OrderStatus.GetLocalizedEnum(_localizationService, _workContext), - IsReturnRequestAllowed = _orderProcessingService.IsReturnRequestAllowed(order) - }; + var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(x.OrderTotal, x.CurrencyRate); + orderModel.OrderTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, x.CustomerCurrencyCode, false, _workContext.WorkingLanguage); - var orderTotalInCustomerCurrency = _currencyService.ConvertCurrency(order.OrderTotal, order.CurrencyRate); - orderModel.OrderTotal = _priceFormatter.FormatPrice(orderTotalInCustomerCurrency, true, order.CustomerCurrencyCode, false, _workContext.WorkingLanguage); + return orderModel; + }) + .ToList(); + + model.Orders = new PagedList(orderModels, orders.PageIndex, orders.PageSize, orders.TotalCount); - model.Orders.Add(orderModel); - } var recurringPayments = _orderService.SearchRecurringPayments(_storeContext.CurrentStore.Id, customer.Id, 0, null); @@ -1256,13 +1261,12 @@ public ActionResult AddressEdit(CustomerAddressEditModel model, int id) #region Orders [RequireHttpsByConfigAttribute(SslRequirement.Yes)] - public ActionResult Orders() + public ActionResult Orders(int? page) { if (!IsCurrentUserRegistered()) return new HttpUnauthorizedResult(); - var customer = _workContext.CurrentCustomer; - var model = PrepareCustomerOrderListModel(customer); + var model = PrepareCustomerOrderListModel(_workContext.CurrentCustomer, Math.Max((page ?? 0) - 1, 0)); return View(model); } @@ -1295,7 +1299,7 @@ public ActionResult CancelRecurringPayment(FormCollection form) { var errors = _orderProcessingService.CancelRecurringPayment(recurringPayment); - var model = PrepareCustomerOrderListModel(customer); + var model = PrepareCustomerOrderListModel(customer, 0); model.CancelRecurringPaymentErrors = errors; return View(model); diff --git a/src/Presentation/SmartStore.Web/Models/Customer/CustomerOrderListModel.cs b/src/Presentation/SmartStore.Web/Models/Customer/CustomerOrderListModel.cs index d81be762d6..eebb536d65 100644 --- a/src/Presentation/SmartStore.Web/Models/Customer/CustomerOrderListModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Customer/CustomerOrderListModel.cs @@ -1,19 +1,19 @@ using System; using System.Collections.Generic; +using SmartStore.Core; using SmartStore.Web.Framework.Modelling; namespace SmartStore.Web.Models.Customer { - public partial class CustomerOrderListModel : ModelBase + public partial class CustomerOrderListModel : ModelBase { public CustomerOrderListModel() { - Orders = new List(); - RecurringOrders = new List(); - CancelRecurringPaymentErrors = new List(); + RecurringOrders = new List(); + CancelRecurringPaymentErrors = new List(); } - public IList Orders { get; set; } + public PagedList Orders { get; set; } public IList RecurringOrders { get; set; } public IList CancelRecurringPaymentErrors { get; set; } @@ -21,6 +21,7 @@ public CustomerOrderListModel() #region Nested classes + public partial class OrderDetailsModel : EntityModelBase { public string OrderNumber { get; set; } @@ -29,6 +30,7 @@ public partial class OrderDetailsModel : EntityModelBase public string OrderStatus { get; set; } public DateTime CreatedOn { get; set; } } + public partial class RecurringOrderModel : EntityModelBase { public string StartDate { get; set; } @@ -39,6 +41,7 @@ public partial class RecurringOrderModel : EntityModelBase public int InitialOrderId { get; set; } public bool CanCancel { get; set; } } + #endregion } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Views/Customer/Orders.Mobile.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/Orders.Mobile.cshtml index 554885a8c1..a0e67e4c08 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/Orders.Mobile.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/Orders.Mobile.cshtml @@ -1,11 +1,10 @@ -@model CustomerOrderListModel +@using SmartStore.Web.Models.Customer; +@model CustomerOrderListModel @{ Layout = "~/Views/Shared/_Root.cshtml"; - //title Html.AddTitleParts(T("PageTitle.Account").Text); } -@using SmartStore.Web.Models.Customer;
    diff --git a/src/Presentation/SmartStore.Web/Views/Customer/Orders.cshtml b/src/Presentation/SmartStore.Web/Views/Customer/Orders.cshtml index bc7a8f8606..94938bb554 100644 --- a/src/Presentation/SmartStore.Web/Views/Customer/Orders.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Customer/Orders.cshtml @@ -1,11 +1,10 @@ -@model CustomerOrderListModel +@using SmartStore.Web.Models.Customer; +@model CustomerOrderListModel @{ Layout = "~/Views/Shared/_ColumnsTwo.cshtml"; - //title Html.AddTitleParts(T("PageTitle.Account").Text); } -@using SmartStore.Web.Models.Customer; @section left { @Html.Partial("MyAccountNavigation", Model.NavigationModel, new ViewDataDictionary()) @@ -19,7 +18,6 @@
    From cd037cd38ea01722cd8b6d52d8f07d321c6d16bd Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 13 May 2016 20:37:00 +0200 Subject: [PATCH 156/285] Export refactoring: removed ExportExecuteContext interface (the concrete type is sufficient) --- .../DataExchange/Events/RowExportingEvent.cs | 29 ++- .../Export/ExportExecuteContext.cs | 193 +++++++----------- .../DataExchange/Export/ExportProviderBase.cs | 6 +- .../DataExchange/Export/IExportProvider.cs | 4 +- .../Providers/GmcXmlExportProvider.cs | 2 +- .../Views/Import/_ColumnMappings.cshtml | 4 +- 6 files changed, 107 insertions(+), 131 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs b/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs index 7459b9ee3f..0b96526e00 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs @@ -4,13 +4,32 @@ namespace SmartStore.Services.DataExchange.Export.Events { // TODO: Another event message must be implemented, say 'ColumnsBuildingEvent' // The consumer of this event (most likely a plugin) could push a list of specific column headers - // into global the export definition. + // into the global export definition. public class RowExportingEvent { - public dynamic Row { get; internal set; } - public ExportEntityType EntityType { get; internal set; } - public DataExportRequest ExportRequest { get; internal set; } - public IExportExecuteContext ExecuteContext { get; internal set; } + public dynamic Row + { + get; + internal set; + } + + public ExportEntityType EntityType + { + get; + internal set; + } + + public DataExportRequest ExportRequest + { + get; + internal set; + } + + public ExportExecuteContext ExecuteContext + { + get; + internal set; + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs index 514c223624..c6d52c746a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs @@ -7,131 +7,177 @@ namespace SmartStore.Services.DataExchange.Export { - public interface IExportExecuteContext + public class ExportExecuteContext { + private DataExportResult _result; + private CancellationToken _cancellation; + private DataExchangeAbortion _providerAbort; + + internal ExportExecuteContext(DataExportResult result, CancellationToken cancellation, string folder) + { + _result = result; + _cancellation = cancellation; + Folder = folder; + ExtraDataStreams = new List(); + CustomProperties = new Dictionary(); + } + /// /// Provides the data to be exported /// - IExportDataSegmenterConsumer Segmenter { get; } + public IExportDataSegmenterConsumer Segmenter { get; set; } /// /// The store context to be used for the export /// - dynamic Store { get; } + public dynamic Store { get; internal set; } /// /// The customer context to be used for the export /// - dynamic Customer { get; } + public dynamic Customer { get; internal set; } /// /// The currency context to be used for the export /// - dynamic Currency { get; } + public dynamic Currency { get; internal set; } /// /// The language context to be used for the export /// - dynamic Language { get; } + public dynamic Language { get; internal set; } /// /// Projection data /// - ExportProjection Projection { get; } + public ExportProjection Projection { get; internal set; } /// /// To log information into the export log file /// - ILogger Log { get; } + public ILogger Log { get; internal set; } /// /// Indicates whether and how to abort the export /// - DataExchangeAbortion Abort { get; set; } + public DataExchangeAbortion Abort + { + get + { + if (_cancellation.IsCancellationRequested || IsMaxFailures) + return DataExchangeAbortion.Hard; + + return _providerAbort; + } + set + { + _providerAbort = value; + } + } + public bool IsMaxFailures + { + get { return RecordsFailed > 11; } + } /// /// Identifier of current data stream. Can be null. /// - string DataStreamId { get; set; } + public string DataStreamId { get; set; } /// /// Stream used to write data to /// - Stream DataStream { get; } + public Stream DataStream { get; internal set; } /// /// List with extra data streams required by provider /// - List ExtraDataStreams { get; set; } - + public List ExtraDataStreams { get; set; } /// /// The maximum allowed file name length /// - int MaxFileNameLength { get; } + public int MaxFileNameLength { get; internal set; } /// /// The name of the current export file /// - string FileName { get; } + public string FileName { get; internal set; } /// /// The path of the export content folder /// - string Folder { get; } - + public string Folder { get; private set; } /// /// Whether the profile has a public deployment into "Exchange" folder /// - bool HasPublicDeployment { get; } + public bool HasPublicDeployment { get; internal set; } /// /// The local path to the public export folder "Exchange". null if the profile has no public deployment. /// - string PublicFolderPath { get; } + public string PublicFolderPath { get; internal set; } /// /// The public URL of the export file (accessible through the internet). null if the profile has no public deployment. /// - string PublicFileUrl { get; } - + public string PublicFileUrl { get; internal set; } /// /// Provider specific configuration data /// - object ConfigurationData { get; } + public object ConfigurationData { get; internal set; } /// /// Use this dictionary for any custom data required along the export /// - Dictionary CustomProperties { get; set; } + public Dictionary CustomProperties { get; set; } /// /// Number of successful processed records /// - int RecordsSucceeded { get; set; } + public int RecordsSucceeded { get; set; } /// /// Number of failed records /// - int RecordsFailed { get; set; } + public int RecordsFailed { get; set; } /// /// Processes an exception that occurred while exporting a record /// - /// Exception - void RecordException(Exception exc, int entityId); + /// Exception + public void RecordException(Exception exception, int entityId) + { + ++RecordsFailed; + + Log.Error("Error while processing record with id {0}: {1}".FormatInvariant(entityId, exception.ToAllMessages()), exception); + + if (IsMaxFailures) + _result.LastError = exception.ToString(); + } + + public ProgressValueSetter ProgressValueSetter { get; internal set; } /// /// Allows to set a progress message /// /// Output message - void SetProgress(string message); + public void SetProgress(string message) + { + if (ProgressValueSetter != null && message.HasValue()) + { + try + { + ProgressValueSetter.Invoke(0, 0, message); + } + catch { } + } + } } - public class ExportExtraStreams { /// @@ -149,93 +195,4 @@ public class ExportExtraStreams /// public string FileName { get; set; } } - - - public class ExportExecuteContext : IExportExecuteContext - { - private DataExportResult _result; - private CancellationToken _cancellation; - private DataExchangeAbortion _providerAbort; - - internal ExportExecuteContext(DataExportResult result, CancellationToken cancellation, string folder) - { - _result = result; - _cancellation = cancellation; - Folder = folder; - ExtraDataStreams = new List(); - CustomProperties = new Dictionary(); - } - - public IExportDataSegmenterConsumer Segmenter { get; set; } - - public dynamic Store { get; internal set; } - public dynamic Customer { get; internal set; } - public dynamic Currency { get; internal set; } - public dynamic Language { get; internal set; } - public ExportProjection Projection { get; internal set; } - - public ILogger Log { get; internal set; } - public ProgressValueSetter ProgressValueSetter { get; internal set; } - - public DataExchangeAbortion Abort - { - get - { - if (_cancellation.IsCancellationRequested || IsMaxFailures) - return DataExchangeAbortion.Hard; - - return _providerAbort; - } - set - { - _providerAbort = value; - } - } - - public bool IsMaxFailures - { - get { return RecordsFailed > 11; } - } - - public string DataStreamId { get; set; } - public Stream DataStream { get; internal set; } - public List ExtraDataStreams { get; set; } - - public int MaxFileNameLength { get; internal set; } - public string FileName { get; internal set; } - public string Folder { get; private set; } - - public bool HasPublicDeployment { get; internal set; } - public string PublicFolderPath { get; internal set; } - public string PublicFileUrl { get; internal set; } - - public object ConfigurationData { get; internal set; } - - public Dictionary CustomProperties { get; set; } - - public int RecordsSucceeded { get; set; } - public int RecordsFailed { get; set; } - - public void RecordException(Exception exc, int entityId) - { - ++RecordsFailed; - - Log.Error("Error while processing record with id {0}: {1}".FormatInvariant(entityId, exc.ToAllMessages()), exc); - - if (IsMaxFailures) - _result.LastError = exc.ToString(); - } - - public void SetProgress(string message) - { - if (ProgressValueSetter != null && message.HasValue()) - { - try - { - ProgressValueSetter.Invoke(0, 0, message); - } - catch { } - } - } - } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs index 2a2d9618f1..46c2bd0fc3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs @@ -32,9 +32,9 @@ public virtual ExportConfigurationInfo ConfigurationInfo /// Export data to a file /// /// Export execution context - protected abstract void Export(IExportExecuteContext context); + protected abstract void Export(ExportExecuteContext context); - public void Execute(IExportExecuteContext context) + public void Execute(ExportExecuteContext context) { Export(context); } @@ -43,7 +43,7 @@ public void Execute(IExportExecuteContext context) /// Called once per store when export execution ended /// /// Export execution context - public virtual void OnExecuted(IExportExecuteContext context) + public virtual void OnExecuted(ExportExecuteContext context) { } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProvider.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProvider.cs index e5248b20ba..60be2e11a9 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProvider.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProvider.cs @@ -24,12 +24,12 @@ public partial interface IExportProvider : IProvider, IUserEditable /// Export data to a file /// /// Export execution context - void Execute(IExportExecuteContext context); + void Execute(ExportExecuteContext context); /// /// Called once per store when export execution ended /// /// Export execution context - void OnExecuted(IExportExecuteContext context); + void OnExecuted(ExportExecuteContext context); } } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index 4fc2f1f305..3daf2af928 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -153,7 +153,7 @@ public override string FileExtension get { return "XML"; } } - protected override void Export(IExportExecuteContext context) + protected override void Export(ExportExecuteContext context) { dynamic currency = context.Currency; string measureWeightSystemKey = ""; diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml index a2c7b2f9be..8af26579a7 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml @@ -128,7 +128,7 @@ { var index = Model.ColumnMappings.IndexOf(mapping) + 1; - +
    @@ -147,7 +147,7 @@
    - +
    From 8b626c4da3b7647efcbd470c89301e39c61cd0e2 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 13 May 2016 20:39:25 +0200 Subject: [PATCH 157/285] Fixed a broken unit test --- .../Public/Infrastructure/RoutesTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tests/SmartStore.Web.MVC.Tests/Public/Infrastructure/RoutesTests.cs b/src/Tests/SmartStore.Web.MVC.Tests/Public/Infrastructure/RoutesTests.cs index b275d3afec..380578d32f 100644 --- a/src/Tests/SmartStore.Web.MVC.Tests/Public/Infrastructure/RoutesTests.cs +++ b/src/Tests/SmartStore.Web.MVC.Tests/Public/Infrastructure/RoutesTests.cs @@ -96,7 +96,7 @@ public void Customer_routes() "~/customer/myaccount/".ShouldMapTo(c => c.MyAccount()); "~/customer/info/".ShouldMapTo(c => c.Info()); "~/customer/addresses/".ShouldMapTo(c => c.Addresses()); - "~/customer/orders/".ShouldMapTo(c => c.Orders()); + "~/customer/orders/".ShouldMapTo(c => c.Orders(null)); "~/customer/returnrequests/".ShouldMapTo(c => c.ReturnRequests()); "~/customer/downloadableproducts/".ShouldMapTo(c => c.DownloadableProducts()); "~/customer/backinstocksubscriptions/".ShouldMapTo(c => c.BackInStockSubscriptions(null)); From ca6ea78f03f97abeaf4d9bacc65a97ce9d65e230 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 13 May 2016 20:52:43 +0200 Subject: [PATCH 158/285] Export refactoring: renamed ExportDataStreams to ExportDataUnit --- .../DataExchange/Export/DataExporter.cs | 9 ++++----- .../DataExchange/Export/ExportExecuteContext.cs | 8 ++++---- .../DataExchange/Export/ExportProviderBase.cs | 10 +++++----- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 09d444655a..bee1a48494 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -1116,17 +1116,16 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) if (ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard) { // always call OnExecuted - if (ctx.ExecuteContext.ExtraDataStreams.Count == 0) - ctx.ExecuteContext.ExtraDataStreams.Add(new ExportExtraStreams()); + if (ctx.ExecuteContext.ExtraDataUnits.Count == 0) + ctx.ExecuteContext.ExtraDataUnits.Add(new ExportDataUnit()); - ctx.ExecuteContext.ExtraDataStreams.ForEach(x => + ctx.ExecuteContext.ExtraDataUnits.ForEach(x => { var path = (x.FileName.HasValue() ? Path.Combine(ctx.ExecuteContext.Folder, x.FileName) : null); - CallProvider(ctx, x.Id, "OnExecuted", path); }); - ctx.ExecuteContext.ExtraDataStreams.Clear(); + ctx.ExecuteContext.ExtraDataUnits.Clear(); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs index c6d52c746a..a0de0334ea 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs @@ -18,7 +18,7 @@ internal ExportExecuteContext(DataExportResult result, CancellationToken cancell _result = result; _cancellation = cancellation; Folder = folder; - ExtraDataStreams = new List(); + ExtraDataUnits = new List(); CustomProperties = new Dictionary(); } @@ -91,9 +91,9 @@ public bool IsMaxFailures public Stream DataStream { get; internal set; } /// - /// List with extra data streams required by provider + /// List with extra data units/streams required by provider /// - public List ExtraDataStreams { get; set; } + public List ExtraDataUnits { get; private set; } /// /// The maximum allowed file name length @@ -178,7 +178,7 @@ public void SetProgress(string message) } } - public class ExportExtraStreams + public class ExportDataUnit { /// /// Your Id to identify this stream within a list of streams diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs index 46c2bd0fc3..c77638f60b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProviderBase.cs @@ -28,17 +28,17 @@ public virtual ExportConfigurationInfo ConfigurationInfo get { return null; } } + public void Execute(ExportExecuteContext context) + { + Export(context); + } + /// /// Export data to a file /// /// Export execution context protected abstract void Export(ExportExecuteContext context); - public void Execute(ExportExecuteContext context) - { - Export(context); - } - /// /// Called once per store when export execution ended /// From 8186b1e799edea300bee953c51d370a8595692fb Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Fri, 13 May 2016 20:56:38 +0200 Subject: [PATCH 159/285] Export refactoring: renamed ExportExecuteContext.Segmenter to DataSegmenter --- .../DataExchange/Export/DataExporter.cs | 16 ++++++++-------- .../DataExchange/Export/ExportExecuteContext.cs | 2 +- .../Providers/GmcXmlExportProvider.cs | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index bee1a48494..46c9a7d969 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -285,7 +285,7 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in switch (ctx.Request.Provider.Value.EntityType) { case ExportEntityType.Product: - ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( skip => GetProducts(ctx, skip), entities => @@ -310,7 +310,7 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in break; case ExportEntityType.Order: - ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( skip => GetOrders(ctx, skip), entities => @@ -329,7 +329,7 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in break; case ExportEntityType.Manufacturer: - ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( skip => GetManufacturers(ctx, skip), entities => @@ -345,7 +345,7 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in break; case ExportEntityType.Category: - ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( skip => GetCategories(ctx, skip), entities => @@ -361,7 +361,7 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in break; case ExportEntityType.Customer: - ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( skip => GetCustomers(ctx, skip), entities => @@ -376,7 +376,7 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in break; case ExportEntityType.NewsLetterSubscription: - ctx.ExecuteContext.Segmenter = new ExportDataSegmenter + ctx.ExecuteContext.DataSegmenter = new ExportDataSegmenter ( skip => GetNewsLetterSubscriptions(ctx, skip), null, @@ -386,11 +386,11 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in break; default: - ctx.ExecuteContext.Segmenter = null; + ctx.ExecuteContext.DataSegmenter = null; break; } - return ctx.ExecuteContext.Segmenter as IExportDataSegmenterProvider; + return ctx.ExecuteContext.DataSegmenter as IExportDataSegmenterProvider; } private bool CallProvider(DataExporterContext ctx, string streamId, string method, string path) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs index a0de0334ea..6c25d397bd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs @@ -25,7 +25,7 @@ internal ExportExecuteContext(DataExportResult result, CancellationToken cancell /// /// Provides the data to be exported /// - public IExportDataSegmenterConsumer Segmenter { get; set; } + public IExportDataSegmenterConsumer DataSegmenter { get; set; } /// /// The store context to be used for the export diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index 3daf2af928..bc9047d888 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -177,9 +177,9 @@ protected override void Export(ExportExecuteContext context) writer.WriteElementString("link", "http://base.google.com/base/"); writer.WriteElementString("description", "Information about products"); - while (context.Abort == DataExchangeAbortion.None && context.Segmenter.ReadNextSegment()) + while (context.Abort == DataExchangeAbortion.None && context.DataSegmenter.ReadNextSegment()) { - var segment = context.Segmenter.CurrentSegment; + var segment = context.DataSegmenter.CurrentSegment; int[] productIds = segment.Select(x => (int)((dynamic)x).Id).ToArray(); var googleProducts = _googleFeedService.GetGoogleProductRecords(productIds); From 97a6f80d2ee0f50e57dfe2d461fc01bef50c9683 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 14 May 2016 14:56:17 +0200 Subject: [PATCH 160/285] Resolves #964 Import: make column mapping UI perfectly fit into 1024px screen width --- .../Administration/Content/admin.less | 30 +++++++++++++++++-- .../Models/DataExchange/ImportProfileModel.cs | 2 ++ .../Views/Import/_ColumnMappings.cshtml | 22 +++++++------- .../Views/Import/_Update.cshtml | 28 ++++++++++++----- 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index d15f091e3e..b6bdce0dbf 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -1584,15 +1584,41 @@ table.payment-method-features td { -------------------------------------------------------------- */ .column-mapping { .column-one, .column-two { - width: 38%; + width: 42%; } - .select-property, .select-column, .input-default { + .select-column, .select-property, .input-default { width: 98%; } + th, td, .mapping-delete, .mapping-add, .mapping-apply, .mapping-cancel { + white-space: nowrap; + padding-left: 8px; + padding-right: 8px; + } + + .select2-container .select2-choice span { + margin-right: 12px; + white-space: nowrap; + } + + .mapping-edit { + white-space: nowrap; + + .select-column, .select-property { + width: 84%; + } + .select2-container { + float: left; + display: inline-block; + min-width: 220px; + margin-right: 4px; + } + } + .mapping-item { .item-inner { + white-space: nowrap; cursor: pointer; padding: 7px 0; diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs index 41f3c9f23f..5baf801477 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ImportProfileModel.cs @@ -68,6 +68,8 @@ public ImportProfileModel() public List AvailableSourceColumns { get; set; } public List AvailableEntityProperties { get; set; } + public int MaxMappingLabelTextLength { get { return 42; } } + public class ExtraDataModel { [SmartResourceDisplayName("Admin.DataExchange.Import.NumberOfPictures")] diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml index 8af26579a7..3aa2e9b9ef 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml @@ -34,7 +34,7 @@
    - @foreach (var property in Model.AvailableEntityProperties) { @@ -47,7 +47,7 @@
    - @foreach (var column in Model.AvailableSourceColumns) { @@ -102,10 +102,10 @@ @@ -119,8 +119,8 @@ @T("Admin.DataExchange.ColumnMapping.EntityProperty") @T("Admin.DataExchange.ColumnMapping.ImportField") - @T("Admin.DataExchange.ColumnMapping.DefaultValue") -   + @T("Admin.DataExchange.ColumnMapping.DefaultValue") +   @@ -128,16 +128,16 @@ { var index = Model.ColumnMappings.IndexOf(mapping) + 1; - +
    - @if (mapping.PropertyDescription.Length > 50) + @if (mapping.PropertyDescription.Length > Model.MaxMappingLabelTextLength) { - @(mapping.PropertyDescription.Substring(0, 50))… + @(mapping.PropertyDescription.Substring(0, Model.MaxMappingLabelTextLength))… } else { @@ -147,7 +147,7 @@
    - +
    @@ -170,7 +170,7 @@ diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml index 0693b5ed32..c87b1629c2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml @@ -212,6 +212,16 @@ var mappedProperties = []; jQuery(document).ready(function () { + var isNarrowScreen = ($(window).width() <= 1280), + maxMappingLableLength = @(Model.MaxMappingLabelTextLength), + maxEditTextLength = 40; + + // hide some mapping elements for narrow screen resolution + if (isNarrowScreen) { + maxMappingLableLength -= 10; + maxEditTextLength -= 10; + $('#AddMappingTemplate, #ImportColumnMappings').find('.mapping-button-label, .right-label').hide(); + } // upload import file $('#ImportFileUploader').fileupload({ @@ -382,8 +392,8 @@ isLocalized = (val.indexOf(']', val.length - 1) !== -1), text = context.find('select').find('option:selected').text(); - if (text.length > 50) { - leftLabel.text(text.substring(0, 50) + '…'); + if (text.length > maxMappingLableLength) { + leftLabel.text(text.substring(0, maxMappingLableLength) + '…'); leftLabel.attr('title', $('
    ').html(text).text()); } else { @@ -468,11 +478,13 @@ html += ''; } - html += '' + item.id + ''; + if (!isNarrowScreen) { + html += '' + item.id + ''; + } html += ' 40) { - html += ' title="' + $('
    ').text(item.text).html() + '">' + item.text.substring(0, 40) + '…'; + if (item.text.length > maxEditTextLength) { + html += ' title="' + $('
    ').text(item.text).html() + '">' + item.text.substring(0, maxEditTextLength) + '…'; } else { html += '>' + item.text; @@ -498,10 +510,10 @@ html += '
    '; - if (query && item.text !== item.id) { + if (query && item.text !== item.id && !isNarrowScreen) { html += ' 40) { - html += ' title="' + $('
    ').text(item.text).html() + '">' + item.text.substring(0, 40) + '…'; + if (item.text.length > maxEditTextLength) { + html += ' title="' + $('
    ').text(item.text).html() + '">' + item.text.substring(0, maxEditTextLength) + '…'; } else { html += '>' + item.text; From 66ec39c12f0260dedfe04eec90ff0b892a2399e7 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 15 May 2016 11:29:11 +0200 Subject: [PATCH 161/285] Resolves #965: Export abortion by user deletes the current (uncompleted) export file --- .../SmartStore.Services/DataExchange/Export/DataExporter.cs | 5 +++++ .../Administration/Views/Import/_ColumnMappings.cshtml | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 46c9a7d969..7ca9ea7936 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -443,6 +443,11 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho ctx.ExecuteContext.DataStream.Dispose(); ctx.ExecuteContext.DataStream = null; } + + if (ctx.ExecuteContext.Abort == DataExchangeAbortion.Hard && ctx.IsFileBasedExport && path.HasValue()) + { + FileSystemHelper.Delete(path); + } } return (ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard); diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml index 3aa2e9b9ef..be30b9c8e5 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_ColumnMappings.cshtml @@ -101,10 +101,10 @@ - - @@ -169,7 +169,7 @@ @Html.Attr("disabled", "disabled", mapping.IsDefaultDisabled || !mapping.Column.HasValue()) /> - From d5d1d2aef605fb359027c5805bdaa7a2d903a8e1 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sun, 15 May 2016 11:57:26 +0200 Subject: [PATCH 162/285] Localization of task executor exception log message --- .../201605111140288_ImportExtraData.cs | 8 +++++ .../SmartStore.Services/Tasks/TaskExecutor.cs | 30 ++++++++++++------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs index 0d7b6fcc06..8b06547f56 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs @@ -51,6 +51,14 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Anzahl der Aufträge pro Seite", "Specifies the number of displayed orders per page.", "Legt die Anzahl der dargestellten Aufträge pro Seite fest."); + + builder.AddOrUpdate("Admin.System.ScheduleTasks.RunningError", + "Error while running scheduled task \"{0}\"", + "Fehler beim Ausführen der Aufgabe \"{0}\""); + + builder.AddOrUpdate("Admin.System.ScheduleTasks.Cancellation", + "The scheduled task \"{0}\" has been canceled", + "Die geplante Aufgabe \"{0}\" wurde abgebrochen"); } } } diff --git a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs index fc7ce73e42..8fd93c2956 100644 --- a/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs +++ b/src/Libraries/SmartStore.Services/Tasks/TaskExecutor.cs @@ -1,21 +1,22 @@ using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Threading; using Autofac; +using SmartStore.Core; using SmartStore.Core.Async; using SmartStore.Core.Data; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Tasks; +using SmartStore.Core.Localization; using SmartStore.Core.Logging; using SmartStore.Core.Plugins; using SmartStore.Services.Customers; -using SmartStore.Core; -using SmartStore.Core.Domain.Customers; -using System.Diagnostics; -using System.Collections.Generic; namespace SmartStore.Services.Tasks { - public class TaskExecutor : ITaskExecutor + public class TaskExecutor : ITaskExecutor { private readonly IScheduleTaskService _scheduledTaskService; private readonly IDbContext _dbContext; @@ -42,11 +43,13 @@ public TaskExecutor( this._taskResolver = taskResolver; Logger = NullLogger.Instance; - } + T = NullLocalizer.Instance; + } public ILogger Logger { get; set; } + public Localizer T { get; set; } - public void Execute( + public void Execute( ScheduleTask task, IDictionary taskParameters = null, bool throwOnError = false) @@ -126,12 +129,17 @@ public void Execute( instance.Execute(ctx); } - catch (Exception ex) + catch (Exception exception) { faulted = true; - canceled = ex is OperationCanceledException; - Logger.Error(string.Format("Error while running scheduled task '{0}'. {1}", task.Name, ex.Message), ex); - lastError = ex.Message.Truncate(995, "..."); + canceled = exception is OperationCanceledException; + lastError = exception.Message.Truncate(995, "..."); + + if (canceled) + Logger.Warning(T("Admin.System.ScheduleTasks.Cancellation", task.Name), exception); + else + Logger.Error(string.Concat(T("Admin.System.ScheduleTasks.RunningError", task.Name), ": ", exception.Message), exception); + if (throwOnError) { throw; From b73c29eeef21330a8e9eecb7efea165e1fdbbe79 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 17 May 2016 17:57:06 +0200 Subject: [PATCH 163/285] Export all in preview displayed wrong message --- .../SmartStore.Web/Administration/Views/Export/Preview.cshtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml index 548863d36e..0c520cc1e8 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/Preview.cshtml @@ -41,10 +41,10 @@ $('#ExportAll, #ExportSelected').click(function (e) { e.preventDefault(); var form = $('#ProfileExportForm'), - exportAll = $(this).attr('name') === 'ExportAll'; + exportAll = $(this).attr('id') === 'ExportAll'; if (!exportAll && selectedIds.length === 0) { - EventBroker.publish("message", { title: '@T("Admin.Common.NoEntriesSelected")', type: 'warning' }); + displayNotification('@T("Admin.Common.NoEntriesSelected")', 'warning'); return false; } From 49b01a0a8c558dfdf7d69bc26dfb5fb39ab0699c Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Tue, 17 May 2016 20:09:42 +0200 Subject: [PATCH 164/285] Coding style --- .../DataExchange/Export/DataExporter.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 7ca9ea7936..bf39638dd6 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -182,9 +182,7 @@ private void SetProgress(DataExporterContext ctx, int loadedRecords) totalRecords = ctx.Request.Profile.Limit; ctx.RecordCount = Math.Min(ctx.RecordCount + loadedRecords, totalRecords); - var msg = ctx.ProgressInfo.FormatInvariant(ctx.RecordCount, totalRecords); - ctx.Request.ProgressValueSetter.Invoke(ctx.RecordCount, totalRecords, msg); } } @@ -275,11 +273,8 @@ private void DetachAllEntitiesAndClear(DataExporterContext ctx) private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, int pageIndex = 0) { var offset = Math.Max(ctx.Request.Profile.Offset, 0) + (pageIndex * PageSize); - var limit = (ctx.IsPreview ? PageSize : Math.Max(ctx.Request.Profile.Limit, 0)); - var recordsPerSegment = (ctx.IsPreview ? 0 : Math.Max(ctx.Request.Profile.BatchSize, 0)); - var totalCount = Math.Max(ctx.Request.Profile.Offset, 0) + ctx.RecordsPerStore.First(x => x.Key == ctx.Store.Id).Value; switch (ctx.Request.Provider.Value.EntityType) @@ -416,7 +411,9 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho if (ctx.IsFileBasedExport && path.HasValue() && ctx.ExecuteContext.DataStream.Length > 0) { if (!ctx.ExecuteContext.DataStream.CanSeek) + { ctx.Log.Warning("Data stream seems to be closed!"); + } ctx.ExecuteContext.DataStream.Seek(0, SeekOrigin.Begin); @@ -424,7 +421,6 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) { ctx.Log.Information("Creating file {0}.".FormatInvariant(path)); - ctx.ExecuteContext.DataStream.CopyTo(fileStream); } } @@ -1052,11 +1048,8 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) } ctx.ExecuteContext.Store = ToDynamic(ctx, ctx.Store); - ctx.ExecuteContext.MaxFileNameLength = dataExchangeSettings.MaxFileNameLength; - ctx.ExecuteContext.HasPublicDeployment = ctx.Request.Profile.Deployments.Any(x => x.IsPublic && x.DeploymentType == ExportDeploymentType.FileSystem); - ctx.ExecuteContext.PublicFolderPath = (ctx.ExecuteContext.HasPublicDeployment ? Path.Combine(HttpRuntime.AppDomainAppPath, PublicFolder) : null); var fileExtension = (ctx.Request.Provider.Value.FileExtension.HasValue() ? ctx.Request.Provider.Value.FileExtension.ToLower().EnsureStartsWith(".") : ""); @@ -1226,7 +1219,6 @@ private void ExportCoreOuter(DataExporterContext ctx) if (ctx.Request.Profile.Deployments.Any(x => x.Enabled)) { SetProgress(ctx, T("Common.Deployment")); - Deploy(ctx, zipPath); } } From 033cd1929c2d55d8949c98ebc5029d402fb3b875 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 18 May 2016 01:25:31 +0200 Subject: [PATCH 165/285] Enhanced help link generator method --- .../SmartStore.Core/SmartStoreVersion.cs | 18 ++++++++++++++---- .../Theming/WebViewPage.cs | 4 ++-- .../Views/ScheduleTask/Edit.cshtml | 1 - .../Administration/Views/Shared/Navbar.cshtml | 5 +++-- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/Libraries/SmartStore.Core/SmartStoreVersion.cs b/src/Libraries/SmartStore.Core/SmartStoreVersion.cs index df3c302220..38750e3069 100644 --- a/src/Libraries/SmartStore.Core/SmartStoreVersion.cs +++ b/src/Libraries/SmartStore.Core/SmartStoreVersion.cs @@ -23,9 +23,10 @@ public static class SmartStoreVersion new Version("2.5") }; - private const string HELP_BASEURL = "http://docs.smartstore.com/display/SMNET25/"; + //private const string HELP_BASEURL = "http://docs.smartstore.com/display/SMNET25/"; + private const string HELP_BASEURL = "http://docs.smartstore.com/display/"; - static SmartStoreVersion() + static SmartStoreVersion() { s_breakingChangesHistory.Reverse(); @@ -67,9 +68,18 @@ public static Version Version } } - public static string GenerateHelpUrl(string path) + public static string GenerateHelpUrl(string languageCode, string path) { - return HELP_BASEURL + path.Trim().TrimStart('/', '\\'); + return String.Concat( + HELP_BASEURL, + GetUserGuideSpaceKey(languageCode), + "/", + path.EmptyNull().Trim().TrimStart('/', '\\')); + } + + public static string GetUserGuideSpaceKey(string languageCode) + { + return "SMNET25"; } /// diff --git a/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPage.cs b/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPage.cs index b4a4268f49..b6fade194d 100644 --- a/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPage.cs +++ b/src/Presentation/SmartStore.Web.Framework/Theming/WebViewPage.cs @@ -322,9 +322,9 @@ public string GetThemeVariable(string varname, string defaultValue = "") return defaultValue; } - public static string GenerateHelpUrl(string path) + public string GenerateHelpUrl(string path) { - return SmartStoreVersion.GenerateHelpUrl(path); + return SmartStoreVersion.GenerateHelpUrl(WorkContext.WorkingLanguage.UniqueSeoCode, path); } private void EnsureThemeContextInitialized() diff --git a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml index 281220e2df..a1018ad9ea 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/ScheduleTask/Edit.cshtml @@ -3,7 +3,6 @@ @using SmartStore.Core @{ ViewBag.Title = T("Admin.System.ScheduleTasks.EditTask") + " - " + Model.Name; - //var cronHelpLink = WorkContext.WorkingLanguage.UniqueSeoCode.ToLower() == "de" ? "https://de.wikipedia.org/wiki/Cron" : "https://en.wikipedia.org/wiki/Cron"; var cronHelpLink = GenerateHelpUrl("Managing+Scheduled+Tasks#ManagingScheduledTasks-Cron"); var returnUrl = ((string)ViewBag.ReturnUrl).NullEmpty(); } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Navbar.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Navbar.cshtml index 5188aa5b37..eae6d73f32 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/Navbar.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/Navbar.cshtml @@ -1,6 +1,7 @@ @using SmartStore.Core.Domain.Stores; @using SmartStore.Services.Customers; @using SmartStore.Services.Stores; +@using SmartStore.Core; @{ var currentCustomer = this.WorkContext.CurrentCustomer; @@ -11,7 +12,7 @@ string communityUrl = (currentLanguageCode == "de" ? "http://community.smartstore.com" : "http://community.smartstore.com/index.php?/&k=880ea6a14ea49e853634fbdc5015a024&setlanguage=1&cal_id=&langid=1"); - string docsUrl = "http://docs.smartstore.com/display/SMNET25/SmartStore.NET+Documentation+Home"; + string docsUrl = GenerateHelpUrl(""); var checkUpdateResult = ViewBag.CheckUpdateResult as CheckUpdateResult; } @@ -33,7 +34,7 @@
    - + From 969ed98cea65a4e9c038b1f2413caea609765f1a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 18 May 2016 12:04:19 +0200 Subject: [PATCH 166/285] #964 Import: make column mapping UI perfectly fit into 1024px screen width (part 2) --- .../Administration/Content/admin.less | 19 +++++++++-- .../Views/Import/_Update.cshtml | 33 +++++++------------ 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index b6bdce0dbf..dfa4145f39 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -1582,6 +1582,23 @@ table.payment-method-features td { /* Import column mapping -------------------------------------------------------------- */ +@media (max-width: 1280px) { + #ImportColumnMappings .right-label, + .column-mapping .mapping-button-label, + .mapping-list-item .right-label { + display: none; + } +} + +.column-mapping .right-label, +.mapping-list-item .right-label { + float: right; + font-size: 0.9em; +} +.mapping-list-item { + .ellipsis(); +} + .column-mapping { .column-one, .column-two { width: 42%; @@ -1630,8 +1647,6 @@ table.payment-method-features td { display: inline-block; } .right-label { - float: right; - font-size: 0.9em; margin-right: 20px; } } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml index c87b1629c2..195b0e8dfe 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml @@ -212,16 +212,7 @@ var mappedProperties = []; jQuery(document).ready(function () { - var isNarrowScreen = ($(window).width() <= 1280), - maxMappingLableLength = @(Model.MaxMappingLabelTextLength), - maxEditTextLength = 40; - - // hide some mapping elements for narrow screen resolution - if (isNarrowScreen) { - maxMappingLableLength -= 10; - maxEditTextLength -= 10; - $('#AddMappingTemplate, #ImportColumnMappings').find('.mapping-button-label, .right-label').hide(); - } + var maxMappingLableLength = @(Model.MaxMappingLabelTextLength); // upload import file $('#ImportFileUploader').fileupload({ @@ -448,7 +439,7 @@ html = ''; if (option.length === 0) { - html += '
    '; + html += '
    '; html += ''; html += ''; html += ''; @@ -469,7 +460,7 @@ var mappingIndex = $.inArray(item.id, mappedProperties); html += '
    '; - html += (mappingIndex === -1 ? '
    ' : '
    '); + html += '
    '; html += ''; html += '' + item.id + ''; - } + html += '' + item.id + ''; html += ' maxEditTextLength) { - html += ' title="' + $('
    ').text(item.text).html() + '">' + item.text.substring(0, maxEditTextLength) + '…'; + if (item.text.length > maxMappingLableLength) { + html += ' title="' + $('
    ').text(item.text).html() + '">' + item.text.substring(0, maxMappingLableLength) + '…'; } else { html += '>' + item.text; @@ -508,12 +497,12 @@ html += '
    '; } - html += '
    '; + html += '
    '; - if (query && item.text !== item.id && !isNarrowScreen) { - html += ' maxEditTextLength) { - html += ' title="' + $('
    ').text(item.text).html() + '">' + item.text.substring(0, maxEditTextLength) + '…'; + if (query && item.text !== item.id) { + html += ' maxMappingLableLength) { + html += ' title="' + $('
    ').text(item.text).html() + '">' + item.text.substring(0, maxMappingLableLength) + '…'; } else { html += '>' + item.text; From 0f839d0541182c451021e9a1d295b506158b601a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 18 May 2016 12:41:00 +0200 Subject: [PATCH 167/285] Resolves #968 Import: column mapping table > clicking outside mapping table should close select2 --- .../Views/Import/_Update.cshtml | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml index 195b0e8dfe..3d5a866db1 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Import/_Update.cshtml @@ -269,7 +269,8 @@ }); // mapping row handling - $('#ImportColumnMappings').on('change', '.select-property, .select-column', function () { + $('#ImportColumnMappings').on('change', '.select-property, .select-column', function (e) { + e.stopPropagation(); var row = $(this).closest('tr'); if (row.hasClass('column-mapping-new')) { var properties = row.find('.select-property'), @@ -281,8 +282,9 @@ row.find('.mapping-add').prop('disabled', !(hasProperty && hasColumn)); } - }).on('click', '.item-inner', function () { + }).on('click', '.item-inner', function (e) { // start editing mapping item + e.stopPropagation(); var item = $(this).closest('.mapping-item'); closeAllMappingEdits(); @@ -303,9 +305,9 @@ select = initSelectBox(select, isProperty); select.select2('focus'); - - }).on('click', '.mapping-apply', function () { + }).on('click', '.mapping-apply', function (e) { // apply edited values + e.stopPropagation(); var item = $(this).closest('.mapping-item'), val = item.find('select').select2('val'); @@ -330,8 +332,9 @@ row.find('.input-default').prop('disabled', !hasColumn || isDefaultDisabled); closeAllMappingEdits(); - }).on('click', '.mapping-add', function () { + }).on('click', '.mapping-add', function (e) { // add new mapping row + e.stopPropagation(); var row = $(this).closest('tr'), table = row.closest('table'), index = parseInt(table.attr('data-maxindex')) + 1; @@ -357,12 +360,22 @@ row.find('.mapping-delete').show(); appendNewMappingRow(); - }).on('click', '.mapping-cancel', function () { + }).on('click', '.mapping-cancel', function (e) { + e.stopPropagation(); closeAllMappingEdits(); - }).on('click', '.mapping-delete', function () { + }).on('click', '.mapping-delete', function (e) { + e.stopPropagation(); $(this).closest('tr').remove(); }); + // close mapping edits + $(document).on('click', function (e) { + var table = $(e.target).closest('table'); + + if (table.length === 0 || !table.hasClass('column-mapping')) { + closeAllMappingEdits(); + } + }); appendNewMappingRow(); From f4bd48c87aaed8355267ee069b3309a6e6706e21 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 18 May 2016 20:39:48 +0200 Subject: [PATCH 168/285] Trace logger should not create new files if the original is locked --- .../SmartStore.Core/Logging/TraceLogger.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs b/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs index 3b8f7a4657..593a39ddb0 100644 --- a/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs +++ b/src/Libraries/SmartStore.Core/Logging/TraceLogger.cs @@ -30,6 +30,8 @@ public TraceLogger(string fileName) console.Filter = new EventTypeFilter(SourceLevels.All); console.Name = "console"; + _traceSource.Listeners.Add(console); + var textListener = new TextWriterTraceListener(fileName); textListener.Filter = new EventTypeFilter(SourceLevels.All); textListener.TraceOutputOptions = TraceOptions.DateTime; @@ -41,17 +43,19 @@ public TraceLogger(string fileName) _streamWriter = new StreamWriter(fileName, append, Encoding.UTF8); textListener.Writer = _streamWriter; + + _traceSource.Listeners.Add(textListener); + } + catch (IOException) + { + // file is locked by another process } - catch (IOException) { } - _traceSource.Listeners.Add(console); - _traceSource.Listeners.Add(textListener); - - // Allow the trace source to send messages to - // listeners for all event types. Currently only + // Allow the trace source to send messages to + // listeners for all event types. Currently only // error messages or higher go to the listeners. - // Messages must get past the source switch to - // get to the listeners, regardless of the settings + // Messages must get past the source switch to + // get to the listeners, regardless of the settings // for the listeners. _traceSource.Switch.Level = SourceLevels.All; } From 6b1e5c9322b3a6b144dc399c54a6420b4dcb9eca Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 19 May 2016 11:48:18 +0200 Subject: [PATCH 169/285] System warnings: Output more information when sitemap is not reachable --- .../201605111140288_ImportExtraData.cs | 4 ++++ .../Controllers/CommonController.cs | 24 +++++++++++++------ 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs index 8b06547f56..b77d237359 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs @@ -59,6 +59,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.System.ScheduleTasks.Cancellation", "The scheduled task \"{0}\" has been canceled", "Die geplante Aufgabe \"{0}\" wurde abgebrochen"); + + builder.AddOrUpdate("Admin.Common.HttpStatus", + "HTTP status {0} ({1}).", + "HTTP-Status {0} ({1})."); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs index a8d2257e4a..ecae61fc64 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs @@ -519,35 +519,45 @@ public ActionResult Warnings() } // sitemap reachability - var sitemapReachable = false; + HttpStatusCode? sitemapStatusCode = null; + string sitemapUrl = null; try { - var sitemapUrl = Url.RouteUrl("SitemapSEO", (object)null, _securitySettings.Value.ForceSslForAllPages ? "https" : "http"); + sitemapUrl = Url.RouteUrl("SitemapSEO", (object)null, _securitySettings.Value.ForceSslForAllPages ? "https" : "http"); var request = (HttpWebRequest)WebRequest.Create(sitemapUrl); request.Method = "HEAD"; request.Timeout = 15000; using (var response = (HttpWebResponse)request.GetResponse()) { - sitemapReachable = (response.StatusCode == HttpStatusCode.OK); + sitemapStatusCode = response.StatusCode; } } - catch (WebException) { } + catch (WebException exception) + { + Logger.Warning(sitemapUrl.IsEmpty() ? "SitemapSEO" : sitemapUrl, exception); + } - if (sitemapReachable) + if (sitemapStatusCode.HasValue && sitemapStatusCode.Value == HttpStatusCode.OK) { model.Add(new SystemWarningModel { Level = SystemWarningLevel.Pass, - Text = _localizationService.GetResource("Admin.System.Warnings.SitemapReachable.OK") + Text = T("Admin.System.Warnings.SitemapReachable.OK") }); } else { + var warningText = T("Admin.System.Warnings.SitemapReachable.Wrong"); + if (sitemapStatusCode.HasValue) + { + warningText = string.Concat(warningText, " ", T("Admin.Common.HttpStatus", (int)sitemapStatusCode.Value, sitemapStatusCode.Value.ToString())); + } + model.Add(new SystemWarningModel { Level = SystemWarningLevel.Warning, - Text = _localizationService.GetResource("Admin.System.Warnings.SitemapReachable.Wrong") + Text = warningText }); } From a18abd6593a3cd90cd0da0104a5153ac2d4d303a Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 19 May 2016 12:48:20 +0200 Subject: [PATCH 170/285] Improvements to last commit --- .../201605111140288_ImportExtraData.cs | 4 ++ .../Controllers/CommonController.cs | 45 ++++++++++--------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs index b77d237359..191ace97ba 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605111140288_ImportExtraData.cs @@ -63,6 +63,10 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Common.HttpStatus", "HTTP status {0} ({1}).", "HTTP-Status {0} ({1})."); + + builder.AddOrUpdate("Admin.System.Warnings.SitemapReachable.MethodNotAllowed", + "The reachability of the sitemap could not be validated.", + "Die Erreichbarkeit der Sitemap konnte nicht überprüft werden."); } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs index ecae61fc64..318391856a 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CommonController.cs @@ -519,7 +519,6 @@ public ActionResult Warnings() } // sitemap reachability - HttpStatusCode? sitemapStatusCode = null; string sitemapUrl = null; try { @@ -530,35 +529,37 @@ public ActionResult Warnings() using (var response = (HttpWebResponse)request.GetResponse()) { - sitemapStatusCode = response.StatusCode; + var status = response.StatusCode; + var warningModel = new SystemWarningModel(); + warningModel.Level = (status == HttpStatusCode.OK ? SystemWarningLevel.Pass : SystemWarningLevel.Warning); + + switch (status) + { + case HttpStatusCode.OK: + warningModel.Text = T("Admin.System.Warnings.SitemapReachable.OK"); + break; + default: + if (status == HttpStatusCode.MethodNotAllowed) + warningModel.Text = T("Admin.System.Warnings.SitemapReachable.MethodNotAllowed"); + else + warningModel.Text = T("Admin.System.Warnings.SitemapReachable.Wrong"); + + warningModel.Text = string.Concat(warningModel.Text, " ", T("Admin.Common.HttpStatus", (int)status, status.ToString())); + break; + } + + model.Add(warningModel); } } catch (WebException exception) { - Logger.Warning(sitemapUrl.IsEmpty() ? "SitemapSEO" : sitemapUrl, exception); - } - - if (sitemapStatusCode.HasValue && sitemapStatusCode.Value == HttpStatusCode.OK) - { - model.Add(new SystemWarningModel - { - Level = SystemWarningLevel.Pass, - Text = T("Admin.System.Warnings.SitemapReachable.OK") - }); - } - else - { - var warningText = T("Admin.System.Warnings.SitemapReachable.Wrong"); - if (sitemapStatusCode.HasValue) - { - warningText = string.Concat(warningText, " ", T("Admin.Common.HttpStatus", (int)sitemapStatusCode.Value, sitemapStatusCode.Value.ToString())); - } - model.Add(new SystemWarningModel { Level = SystemWarningLevel.Warning, - Text = warningText + Text = T("Admin.System.Warnings.SitemapReachable.Wrong") }); + + Logger.Warning(sitemapUrl.IsEmpty() ? "SitemapSEO" : sitemapUrl, exception); } //primary exchange rate currency From a08b83dd7f5f91e3a7d274a0f61f20711ea557b0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 19 May 2016 17:06:38 +0200 Subject: [PATCH 171/285] Resolves #859 Make checkout attributes suitable for multi-stores --- changelog.md | 1 + .../Domain/Orders/CheckoutAttribute.cs | 27 ++- ...16_CheckoutAttributeMultiStore.Designer.cs | 29 +++ ...05191216116_CheckoutAttributeMultiStore.cs | 17 ++ ...191216116_CheckoutAttributeMultiStore.resx | 126 ++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 7 + .../Orders/CheckoutAttributeService.cs | 70 ++++-- .../Orders/ICheckoutAttributeService.cs | 20 +- .../Orders/ShoppingCartService.cs | 2 +- .../CheckoutAttributeController.cs | 96 +++++--- .../Infrastructure/AutoMapperStartupTask.cs | 14 +- .../Orders/CheckoutAttributeListModel.cs | 13 ++ .../Models/Orders/CheckoutAttributeModel.cs | 12 +- .../Administration/SmartStore.Admin.csproj | 1 + .../Views/CheckoutAttribute/List.cshtml | 24 +- .../CheckoutAttribute/_CreateOrUpdate.cshtml | 216 +++++++++++------- .../Controllers/ShoppingCartController.cs | 6 +- ...heckoutAttributeParserAndFormatterTests.cs | 15 +- 18 files changed, 506 insertions(+), 190 deletions(-) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.resx create mode 100644 src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeListModel.cs diff --git a/changelog.md b/changelog.md index 7327aee605..aa1f1df6c0 100644 --- a/changelog.md +++ b/changelog.md @@ -14,6 +14,7 @@ * #886 Add setting to hide manufacturer images on product detail page and to hide default image for manufacturers * Import localized SEO names for product and categories * #477 Implement option to specify the number of exported and imported pictures +* #859 Make checkout attributes suitable for multi-stores ### Improvements * Major improvements in Importer: better field mapping, higher performance, bug fixes etc. diff --git a/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutAttribute.cs b/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutAttribute.cs index a317879baa..63cbf58ea8 100644 --- a/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutAttribute.cs +++ b/src/Libraries/SmartStore.Core/Domain/Orders/CheckoutAttribute.cs @@ -1,14 +1,15 @@ using System.Collections.Generic; using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Localization; +using SmartStore.Core.Domain.Stores; namespace SmartStore.Core.Domain.Orders { - /// - /// Represents a checkout attribute - /// - public partial class CheckoutAttribute : BaseEntity, ILocalizedEntity - { + /// + /// Represents a checkout attribute + /// + public partial class CheckoutAttribute : BaseEntity, ILocalizedEntity, IStoreMappingSupported + { private ICollection _checkoutAttributeValues; public CheckoutAttribute() @@ -60,11 +61,16 @@ public CheckoutAttribute() /// Gets or sets whether the checkout attribute is active ///
    public bool IsActive { get; set; } - - /// - /// Gets the attribute control type - /// - public AttributeControlType AttributeControlType + + /// + /// Gets or sets a value indicating whether the entity is limited/restricted to certain stores + /// + public bool LimitedToStores { get; set; } + + /// + /// Gets the attribute control type + /// + public AttributeControlType AttributeControlType { get { @@ -75,6 +81,7 @@ public AttributeControlType AttributeControlType this.AttributeControlTypeId = (int)value; } } + /// /// Gets the checkout attribute values /// diff --git a/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.Designer.cs new file mode 100644 index 0000000000..d4625babee --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.1.3-40302")] + public sealed partial class CheckoutAttributeMultiStore : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(CheckoutAttributeMultiStore)); + + string IMigrationMetadata.Id + { + get { return "201605191216116_CheckoutAttributeMultiStore"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.cs b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.cs new file mode 100644 index 0000000000..1b6ba5a31f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.cs @@ -0,0 +1,17 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + + public partial class CheckoutAttributeMultiStore : DbMigration + { + public override void Up() + { + AddColumn("dbo.CheckoutAttribute", "LimitedToStores", c => c.Boolean(nullable: false)); + } + + public override void Down() + { + DropColumn("dbo.CheckoutAttribute", "LimitedToStores"); + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.resx b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.resx new file mode 100644 index 0000000000..d743254b0e --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605191216116_CheckoutAttributeMultiStore.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index bdec0c33b4..79a179374f 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -396,6 +396,10 @@ 201605111140288_ImportExtraData.cs + + + 201605191216116_CheckoutAttributeMultiStore.cs + @@ -738,6 +742,9 @@ 201605111140288_ImportExtraData.cs + + 201605191216116_CheckoutAttributeMultiStore.cs + diff --git a/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs index d9a340c451..798636316a 100644 --- a/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs @@ -4,18 +4,19 @@ using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Stores; using SmartStore.Core.Events; namespace SmartStore.Services.Orders { - /// - /// Checkout attribute service - /// - public partial class CheckoutAttributeService : ICheckoutAttributeService + /// + /// Checkout attribute service + /// + public partial class CheckoutAttributeService : ICheckoutAttributeService { #region Constants - private const string CHECKOUTATTRIBUTES_ALL_KEY = "SmartStore.checkoutattribute.all-{0}"; + private const string CHECKOUTATTRIBUTES_ALL_KEY = "SmartStore.checkoutattribute.all-{0}-{1}"; private const string CHECKOUTATTRIBUTEVALUES_ALL_KEY = "SmartStore.checkoutattributevalue.all-{0}"; private const string CHECKOUTATTRIBUTES_PATTERN_KEY = "SmartStore.checkoutattribute."; private const string CHECKOUTATTRIBUTEVALUES_PATTERN_KEY = "SmartStore.checkoutattributevalue."; @@ -28,7 +29,8 @@ public partial class CheckoutAttributeService : ICheckoutAttributeService private readonly IRepository _checkoutAttributeRepository; private readonly IRepository _checkoutAttributeValueRepository; - private readonly IEventPublisher _eventPublisher; + private readonly IRepository _storeMappingRepository; + private readonly IEventPublisher _eventPublisher; private readonly ICacheManager _cacheManager; #endregion @@ -45,25 +47,31 @@ public partial class CheckoutAttributeService : ICheckoutAttributeService public CheckoutAttributeService(ICacheManager cacheManager, IRepository checkoutAttributeRepository, IRepository checkoutAttributeValueRepository, - IEventPublisher eventPublisher) + IRepository storeMappingRepository, + IEventPublisher eventPublisher) { _cacheManager = cacheManager; _checkoutAttributeRepository = checkoutAttributeRepository; _checkoutAttributeValueRepository = checkoutAttributeValueRepository; + _storeMappingRepository = storeMappingRepository; _eventPublisher = eventPublisher; - } - #endregion + this.QuerySettings = DbQuerySettings.Default; + } - #region Methods + #endregion - #region Checkout attributes + public DbQuerySettings QuerySettings { get; set; } - /// - /// Deletes a checkout attribute - /// - /// Checkout attribute - public virtual void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute) + #region Methods + + #region Checkout attributes + + /// + /// Deletes a checkout attribute + /// + /// Checkout attribute + public virtual void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute) { if (checkoutAttribute == null) throw new ArgumentNullException("checkoutAttribute"); @@ -77,13 +85,15 @@ public virtual void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute) _eventPublisher.EntityDeleted(checkoutAttribute); } - /// - /// Gets all checkout attributes - /// - /// Checkout attribute collection - public virtual IList GetAllCheckoutAttributes(bool showHidden = false) + /// + /// Gets all checkout attributes + /// + /// Whether to filter result by store identifier + /// A value indicating whether to show hidden records + /// Checkout attribute collection + public virtual IList GetAllCheckoutAttributes(int storeId = 0, bool showHidden = false) { - string key = CHECKOUTATTRIBUTES_ALL_KEY.FormatInvariant(showHidden); + string key = CHECKOUTATTRIBUTES_ALL_KEY.FormatInvariant(storeId, showHidden); return _cacheManager.Get(key, () => { @@ -92,6 +102,22 @@ public virtual IList GetAllCheckoutAttributes(bool showHidden if (!showHidden) query = query.Where(x => x.IsActive); + if (storeId > 0 && !QuerySettings.IgnoreMultiStore) + { + query = + from x in query + join sm in _storeMappingRepository.Table on new { c1 = x.Id, c2 = "CheckoutAttribute" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into x_sm + from sm in x_sm.DefaultIfEmpty() + where !x.LimitedToStores || storeId == sm.StoreId + select x; + + query = + from x in query + group x by x.Id into grp + orderby grp.Key + select grp.FirstOrDefault(); + } + query = query.OrderBy(x => x.DisplayOrder); var checkoutAttributes = query.ToList(); diff --git a/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs b/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs index 2ceb9c3396..57ee71c45f 100644 --- a/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs @@ -3,10 +3,10 @@ namespace SmartStore.Services.Orders { - /// - /// Checkout attribute service - /// - public partial interface ICheckoutAttributeService + /// + /// Checkout attribute service + /// + public partial interface ICheckoutAttributeService { #region Checkout attributes @@ -16,11 +16,13 @@ public partial interface ICheckoutAttributeService /// Checkout attribute void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute); - /// - /// Gets all checkout attributes - /// - /// Checkout attribute collection - IList GetAllCheckoutAttributes(bool showHidden = false); + /// + /// Gets all checkout attributes + /// + /// Whether to filter result by store identifier + /// A value indicating whether to show hidden records + /// Checkout attribute collection + IList GetAllCheckoutAttributes(int storeId = 0, bool showHidden = false); /// /// Gets a checkout attribute diff --git a/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs b/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs index 259b323910..27f0331be4 100644 --- a/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs +++ b/src/Libraries/SmartStore.Services/Orders/ShoppingCartService.cs @@ -879,7 +879,7 @@ public virtual IList GetShoppingCartWarnings(IList s.ToModel()) + .ToList(); + + if (!excludeProperties) + { + model.SelectedStoreIds = _storeMappingService.GetStoresIdsWithAccess(checkoutAttribute); + } + } #endregion @@ -114,7 +141,14 @@ public ActionResult List() if (!_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) return AccessDeniedView(); - return View(); + var model = new CheckoutAttributeListModel + { + GridPageSize = _adminAreaSettings.GridPageSize + }; + + model.AvailableStores = _services.StoreService.GetAllStores().ToSelectListItems(); + + return View(model); } [HttpPost, GridAction(EnableCustomBinding = true)] @@ -124,7 +158,7 @@ public ActionResult List(GridCommand command) if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) { - var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(true); + var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(0, true); model.Data = checkoutAttributes.Select(x => { @@ -173,10 +207,13 @@ public ActionResult Create(CheckoutAttributeModel model, bool continueEditing) { var checkoutAttribute = model.ToEntity(); _checkoutAttributeService.InsertCheckoutAttribute(checkoutAttribute); + UpdateAttributeLocales(checkoutAttribute, model); - //activity log - _customerActivityService.InsertActivity("AddNewCheckoutAttribute", _services.Localization.GetResource("ActivityLog.AddNewCheckoutAttribute"), checkoutAttribute.Name); + _storeMappingService.SaveStoreMappings(checkoutAttribute, model.SelectedStoreIds); + + //activity log + _customerActivityService.InsertActivity("AddNewCheckoutAttribute", _services.Localization.GetResource("ActivityLog.AddNewCheckoutAttribute"), checkoutAttribute.Name); NotifySuccess(_services.Localization.GetResource("Admin.Catalog.Attributes.CheckoutAttributes.Added")); return continueEditing ? RedirectToAction("Edit", new { id = checkoutAttribute.Id }) : RedirectToAction("List"); @@ -195,7 +232,6 @@ public ActionResult Edit(int id) var checkoutAttribute = _checkoutAttributeService.GetCheckoutAttributeById(id); if (checkoutAttribute == null) - //No checkout attribute found with the specified id return RedirectToAction("List"); var model = checkoutAttribute.ToModel(); @@ -218,7 +254,6 @@ public ActionResult Edit(CheckoutAttributeModel model, bool continueEditing) var checkoutAttribute = _checkoutAttributeService.GetCheckoutAttributeById(model.Id); if (checkoutAttribute == null) - //No checkout attribute found with the specified id return RedirectToAction("List"); if (ModelState.IsValid) @@ -228,8 +263,10 @@ public ActionResult Edit(CheckoutAttributeModel model, bool continueEditing) UpdateAttributeLocales(checkoutAttribute, model); - //activity log - _customerActivityService.InsertActivity("EditCheckoutAttribute", _services.Localization.GetResource("ActivityLog.EditCheckoutAttribute"), checkoutAttribute.Name); + _storeMappingService.SaveStoreMappings(checkoutAttribute, model.SelectedStoreIds); + + //activity log + _customerActivityService.InsertActivity("EditCheckoutAttribute", _services.Localization.GetResource("ActivityLog.EditCheckoutAttribute"), checkoutAttribute.Name); NotifySuccess(_services.Localization.GetResource("Admin.Catalog.Attributes.CheckoutAttributes.Updated")); return continueEditing ? RedirectToAction("Edit", checkoutAttribute.Id) : RedirectToAction("List"); @@ -311,7 +348,6 @@ public ActionResult ValueCreatePopup(string btnId, string formId, CheckoutAttrib var checkoutAttribute = _checkoutAttributeService.GetCheckoutAttributeById(model.CheckoutAttributeId); if (checkoutAttribute == null) - //No checkout attribute found with the specified id return RedirectToAction("List"); model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; @@ -342,7 +378,6 @@ public ActionResult ValueEditPopup(int id) var cav = _checkoutAttributeService.GetCheckoutAttributeValueById(id); if (cav == null) - //No checkout attribute value found with the specified id return RedirectToAction("List"); var model = cav.ToModel(); @@ -366,7 +401,6 @@ public ActionResult ValueEditPopup(string btnId, string formId, CheckoutAttribut var cav = _checkoutAttributeService.GetCheckoutAttributeValueById(model.Id); if (cav == null) - //No checkout attribute value found with the specified id return RedirectToAction("List"); model.PrimaryStoreCurrencyCode = _services.StoreContext.CurrentStore.PrimaryStoreCurrency.CurrencyCode; diff --git a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs index 2f7c0f3339..bee55cf932 100644 --- a/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs +++ b/src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs @@ -520,12 +520,14 @@ public void Execute() Mapper.CreateMap() .ForMember(dest => dest.SpecificationAttribute, mo => mo.Ignore()) .ForMember(dest => dest.ProductSpecificationAttributes, mo => mo.Ignore()); - //checkout attributes - Mapper.CreateMap() - .ForMember(dest => dest.AvailableTaxCategories, mo => mo.Ignore()) - .ForMember(dest => dest.AttributeControlTypeName, mo => mo.Ignore()) - .ForMember(dest => dest.Locales, mo => mo.Ignore()); - Mapper.CreateMap() + //checkout attributes + Mapper.CreateMap() + .ForMember(dest => dest.AvailableTaxCategories, mo => mo.Ignore()) + .ForMember(dest => dest.AttributeControlTypeName, mo => mo.Ignore()) + .ForMember(dest => dest.Locales, mo => mo.Ignore()) + .ForMember(dest => dest.SelectedStoreIds, mo => mo.Ignore()) + .ForMember(dest => dest.AvailableStores, mo => mo.Ignore()); + Mapper.CreateMap() .ForMember(dest => dest.AttributeControlType, mo => mo.Ignore()) .ForMember(dest => dest.CheckoutAttributeValues, mo => mo.Ignore()); Mapper.CreateMap() diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeListModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeListModel.cs new file mode 100644 index 0000000000..6084bd6210 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeListModel.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using SmartStore.Web.Framework.Modelling; + +namespace SmartStore.Admin.Models.Orders +{ + public class CheckoutAttributeListModel : ModelBase + { + public int GridPageSize { get; set; } + + public IList AvailableStores { get; set; } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeModel.cs index 5e26016af9..6f6a404473 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Orders/CheckoutAttributeModel.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Web.Mvc; using FluentValidation.Attributes; +using SmartStore.Admin.Models.Stores; using SmartStore.Admin.Validators.Orders; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Localization; @@ -51,9 +52,16 @@ public CheckoutAttributeModel() public int DisplayOrder { get; set; } public IList Locales { get; set; } - } - public class CheckoutAttributeLocalizedModel : ILocalizedModelLocal + [SmartResourceDisplayName("Admin.Common.Store.LimitedTo")] + public bool LimitedToStores { get; set; } + + [SmartResourceDisplayName("Admin.Common.Store.AvailableFor")] + public List AvailableStores { get; set; } + public int[] SelectedStoreIds { get; set; } + } + + public class CheckoutAttributeLocalizedModel : ILocalizedModelLocal { public int LanguageId { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index 7d95256bb5..2dad6d6fe4 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -265,6 +265,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml index d80b3f8bfb..33ab6112a0 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml @@ -1,5 +1,5 @@ @using Telerik.Web.Mvc.UI - +@model CheckoutAttributeListModel @{ ViewBag.Title = T("Admin.Catalog.Attributes.CheckoutAttributes").Text; } @@ -26,30 +26,22 @@ .Columns(columns => { columns.Bound(x => x.Name) - .Width(300) - .Template(x => Html.ActionLink(x.Name, "Edit", new { id = x.Id })) + .Width(500) .ClientTemplate("<#= Name #>"); - columns.Bound(x => x.AttributeControlTypeName) - .Width(150); + columns.Bound(x => x.TextPrompt); + columns.Bound(x => x.AttributeControlTypeName); columns.Bound(x => x.IsActive) - .Width(100) - .Template(item => @Html.SymbolForBool(item.IsActive)) .ClientTemplate(@Html.SymbolForBool("IsActive")) .Centered(); columns.Bound(x => x.IsRequired) - .Width(100) - .Template(item => @Html.SymbolForBool(item.IsRequired)) .ClientTemplate(@Html.SymbolForBool("IsRequired")) .Centered(); columns.Bound(x => x.DisplayOrder) - .Width(100) .Centered(); - columns.Bound(x => x.Id) - .Width(50) - .Centered() - .Template(x => Html.ActionLink(T("Admin.Common.Edit").Text, "Edit", new { id = x.Id })) - .ClientTemplate("\">" + T("Admin.Common.Edit").Text + "") - .Title(T("Admin.Common.Edit").Text); + columns.Bound(x => x.LimitedToStores) + .ClientTemplate(@Html.SymbolForBool("LimitedToStores")) + .Hidden(Model.AvailableStores.Count <= 1) + .Centered(); }) .DataBinding(dataBinding => dataBinding.Ajax().Select("List", "CheckoutAttribute")) .ClientEvents(events => events.OnRowDataBound("checkoutAttributesGrid_onRowDataBound")) diff --git a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/_CreateOrUpdate.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/_CreateOrUpdate.cshtml index 33499f2b73..be84f43826 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/_CreateOrUpdate.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/_CreateOrUpdate.cshtml @@ -1,17 +1,17 @@ -@model CheckoutAttributeModel - -@using SmartStore.Core.Domain.Catalog; +@using SmartStore.Core.Domain.Catalog; @using Telerik.Web.Mvc.UI; +@model CheckoutAttributeModel @Html.ValidationSummary(false) @Html.HiddenFor(model => model.Id) @Html.SmartStore().TabStrip().Name("checkoutattribute-edit").Items(x => { - x.Add().Text(T("Admin.Catalog.Attributes.CheckoutAttributes.Info").Text).Content(TabInfo()).Selected(true); - x.Add().Text(T("Admin.Catalog.Attributes.CheckoutAttributes.Values").Text).Content(TabValues()); - - //generate an event - EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "checkoutattribute-edit", this.Html, this.Model)); + x.Add().Text(T("Admin.Catalog.Attributes.CheckoutAttributes.Info").Text).Content(TabInfo()).Selected(true); + x.Add().Text(T("Admin.Catalog.Attributes.CheckoutAttributes.Values").Text).Content(TabValues()); + x.Add().Text(T("Admin.Common.Stores").Text).Content(TabStores()); + + //generate an event + EngineContext.Current.Resolve().Publish(new TabStripCreated(x, "checkoutattribute-edit", this.Html, this.Model)); }) @helper TabInfo() @@ -151,85 +151,131 @@ @helper TabValues() { - if (Model.Id > 0) - { - /*TODO display the following warning if values are not supported by selected attribute control type - 'Values are not required for this attribute control type' - */ - - - + +
    - @(Html.Telerik().Grid() - .Name("checkoutattributevalues-grid") - .DataKeys(x => - { - x.Add(y => y.Id).RouteKey("valueId"); - x.Add(y => y.CheckoutAttributeId).RouteKey("checkoutAttributeId"); - }) - .Columns(columns => - { - columns.Bound(x => x.Name); - columns.Bound(x => x.PriceAdjustment) - .Format("{0:0.00}") - .RightAlign() - .Width(200); - columns.Bound(x => x.WeightAdjustment) - .Format("{0:0.00}") - .Centered() - .Width(200); - columns.Bound(x => x.IsPreSelected) - .Width(140) - .Template(item => @Html.SymbolForBool(item.IsPreSelected)) - .ClientTemplate(@Html.SymbolForBool("IsPreSelected")) - .Centered(); - columns.Bound(x => x.DisplayOrder) - .Width(140) - .Centered(); - columns.Bound(x => x.Id) - .Width(120) - .Centered() - .ClientTemplate("?btnId=btnRefresh&formId=checkoutattribute-form', 800, 300, true); return false;\" class='t-button' />") - .Title(T("Admin.Common.Edit").Text); - columns.Command(commands => - { - commands.Delete().Localize(T); - }) + if (Model.Id > 0) + { + /*TODO display the following warning if values are not supported by selected attribute control type + 'Values are not required for this attribute control type' + */ + + + - - - + + + - -
    + @(Html.Telerik().Grid() + .Name("checkoutattributevalues-grid") + .DataKeys(x => + { + x.Add(y => y.Id).RouteKey("valueId"); + x.Add(y => y.CheckoutAttributeId).RouteKey("checkoutAttributeId"); + }) + .Columns(columns => + { + columns.Bound(x => x.Name); + columns.Bound(x => x.PriceAdjustment) + .Format("{0:0.00}") + .RightAlign() + .Width(200); + columns.Bound(x => x.WeightAdjustment) + .Format("{0:0.00}") + .Centered() + .Width(200); + columns.Bound(x => x.IsPreSelected) + .Width(140) + .Template(item => @Html.SymbolForBool(item.IsPreSelected)) + .ClientTemplate(@Html.SymbolForBool("IsPreSelected")) + .Centered(); + columns.Bound(x => x.DisplayOrder) + .Width(140) + .Centered(); + columns.Bound(x => x.Id) .Width(120) - .Title(T("Admin.Common.Delete").Text); + .Centered() + .ClientTemplate("?btnId=btnRefresh&formId=checkoutattribute-form', 800, 300, true); return false;\" class='t-button' />") + .Title(T("Admin.Common.Edit").Text); + columns.Command(commands => + { + commands.Delete().Localize(T); }) - .DataBinding(dataBinding => - dataBinding.Ajax().Select("ValueList", "CheckoutAttribute", new { checkoutAttributeId = Model.Id }) - .Delete("ValueDelete", "CheckoutAttribute")) - .EnableCustomBinding(true)) -
    - + .Width(120) + .Title(T("Admin.Common.Delete").Text); + }) + .DataBinding(dataBinding => + dataBinding.Ajax().Select("ValueList", "CheckoutAttribute", new { checkoutAttributeId = Model.Id }) + .Delete("ValueDelete", "CheckoutAttribute")) + .EnableCustomBinding(true)) +
    + - + - -
    - } - else - { - @T("Admin.Catalog.Attributes.CheckoutAttributes.Values.SaveBeforeEdit") - } - } \ No newline at end of file + //return false to don't reload a page + return false; + }); + }); + +
    + } + else + { + @T("Admin.Catalog.Attributes.CheckoutAttributes.Values.SaveBeforeEdit") + } +} + +@helper TabStores() +{ + + + + + + + + + + + +
    + @Html.SmartLabelFor(model => model.LimitedToStores) + + @Html.EditorFor(model => model.LimitedToStores) + @Html.ValidationMessageFor(model => model.LimitedToStores) +
    + @Html.SmartLabelFor(model => model.AvailableStores) + + @if (Model.AvailableStores != null && Model.AvailableStores.Count > 0) + { + foreach (var store in Model.AvailableStores) + { + + } + } + else + { +
    @T("Admin.Configuration.Stores.NoStoresDefined")
    + } +
    +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index a61c57d5ad..e6ce38af48 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -700,7 +700,7 @@ protected void PrepareShoppingCartModel(ShoppingCartModel model, #region Checkout attributes - var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(); + var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(_storeContext.CurrentStore.Id); if (!cart.RequiresShipping()) { //remove attributes which require shippable products @@ -985,7 +985,7 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() //1. "terms of services" are enabled (OBSOLETE now) //2. we have at least one checkout attribute //3. min order sub-total is OK - var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(); + var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(_storeContext.CurrentStore.Id); if (!cart.RequiresShipping()) { //remove attributes which require shippable products @@ -1076,7 +1076,7 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() protected void ParseAndSaveCheckoutAttributes(List cart, FormCollection form) { string selectedAttributes = ""; - var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(); + var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(_storeContext.CurrentStore.Id); if (!cart.RequiresShipping()) { diff --git a/src/Tests/SmartStore.Services.Tests/Orders/CheckoutAttributeParserAndFormatterTests.cs b/src/Tests/SmartStore.Services.Tests/Orders/CheckoutAttributeParserAndFormatterTests.cs index 11a8780744..c744c9874a 100644 --- a/src/Tests/SmartStore.Services.Tests/Orders/CheckoutAttributeParserAndFormatterTests.cs +++ b/src/Tests/SmartStore.Services.Tests/Orders/CheckoutAttributeParserAndFormatterTests.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; using System.Linq; +using NUnit.Framework; +using Rhino.Mocks; using SmartStore.Core; using SmartStore.Core.Caching; using SmartStore.Core.Data; @@ -7,24 +9,24 @@ using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Domain.Stores; +using SmartStore.Core.Events; using SmartStore.Services.Catalog; using SmartStore.Services.Directory; -using SmartStore.Core.Events; using SmartStore.Services.Media; using SmartStore.Services.Orders; using SmartStore.Services.Tax; using SmartStore.Tests; -using NUnit.Framework; -using Rhino.Mocks; namespace SmartStore.Services.Tests.Orders { - [TestFixture] + [TestFixture] public class CheckoutAttributeParserAndFormatterTests : ServiceTest { IRepository _checkoutAttributeRepo; IRepository _checkoutAttributeValueRepo; - IEventPublisher _eventPublisher; + IRepository _storeMappingRepo; + IEventPublisher _eventPublisher; ICheckoutAttributeService _checkoutAttributeService; ICheckoutAttributeParser _checkoutAttributeParser; @@ -129,6 +131,8 @@ public class CheckoutAttributeParserAndFormatterTests : ServiceTest _checkoutAttributeValueRepo.Expect(x => x.GetById(cav2_1.Id)).Return(cav2_1); _checkoutAttributeValueRepo.Expect(x => x.GetById(cav2_2.Id)).Return(cav2_2); + _storeMappingRepo = MockRepository.GenerateMock>(); + var cacheManager = new NullCache(); _eventPublisher = MockRepository.GenerateMock(); @@ -137,6 +141,7 @@ public class CheckoutAttributeParserAndFormatterTests : ServiceTest _checkoutAttributeService = new CheckoutAttributeService(cacheManager, _checkoutAttributeRepo, _checkoutAttributeValueRepo, + _storeMappingRepo, _eventPublisher); _checkoutAttributeParser = new CheckoutAttributeParser(_checkoutAttributeService); From 6acca50f6e2c0398f9b28ca03fcef7e43f8f51fa Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 18 May 2016 21:35:11 +0200 Subject: [PATCH 172/285] Moved some misplaced project files --- .../{ => Export}/Internal/CategoryExportContext.cs | 0 .../{ => Export}/Internal/CustomerExportContext.cs | 0 .../{ => Export}/Internal/DataExporterContext.cs | 0 .../{ => Export}/Internal/DynamicEntity.cs | 0 .../Internal/ManufacturerExportContext.cs | 0 .../{ => Export}/Internal/OrderExportContext.cs | 0 .../{ => Export}/Internal/ProductExportContext.cs | 0 .../SmartStore.Services/SmartStore.Services.csproj | 14 +++++++------- 8 files changed, 7 insertions(+), 7 deletions(-) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Internal/CategoryExportContext.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Internal/CustomerExportContext.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Internal/DataExporterContext.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Internal/DynamicEntity.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Internal/ManufacturerExportContext.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Internal/OrderExportContext.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Internal/ProductExportContext.cs (100%) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/CategoryExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/CategoryExportContext.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/CategoryExportContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Internal/CategoryExportContext.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/CustomerExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/CustomerExportContext.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/CustomerExportContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Internal/CustomerExportContext.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/DataExporterContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DynamicEntity.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/DynamicEntity.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DynamicEntity.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/ManufacturerExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ManufacturerExportContext.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/ManufacturerExportContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ManufacturerExportContext.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/OrderExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/OrderExportContext.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/OrderExportContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Internal/OrderExportContext.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Internal/ProductExportContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ProductExportContext.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Internal/ProductExportContext.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Internal/ProductExportContext.cs diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 262454bf5c..529b51a6f4 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -205,19 +205,19 @@ - + - - - + + + - + - - + + From a80ca88c54a0bf0d1fedd8f92eefddfc98c97c43 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 19 May 2016 21:43:59 +0200 Subject: [PATCH 173/285] Added paging to backend checkout attribute list --- changelog.md | 1 + .../Orders/CheckoutAttributeService.cs | 64 +++++++++++-------- .../Orders/ICheckoutAttributeService.cs | 9 +++ .../CheckoutAttributeController.cs | 8 ++- .../Views/CheckoutAttribute/List.cshtml | 5 +- 5 files changed, 57 insertions(+), 30 deletions(-) diff --git a/changelog.md b/changelog.md index aa1f1df6c0..9502bd2eb2 100644 --- a/changelog.md +++ b/changelog.md @@ -25,6 +25,7 @@ * Export email attachments needs to be stored in database because the temp file may not exist anymore when sending the email * #913 Use HTML5 Input types (tel, email) * Added paging to frontend order list +* Added paging to backend checkout attribute list ### Bugfixes * Fixed ajax cache issue when saving payment or shipping restrictions. Internet Explorer showed the old data state (before storage). diff --git a/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs index 798636316a..7e266a98b1 100644 --- a/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Orders/CheckoutAttributeService.cs @@ -85,6 +85,40 @@ public virtual void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute) _eventPublisher.EntityDeleted(checkoutAttribute); } + /// + /// Gets checkout attributes + /// + /// Whether to filter result by store identifier + /// A value indicating whether to show hidden records + /// Checkout attributes query + public virtual IQueryable GetCheckoutAttributes(int storeId = 0, bool showHidden = false) + { + var query = _checkoutAttributeRepository.Table; + + if (!showHidden) + query = query.Where(x => x.IsActive); + + if (storeId > 0 && !QuerySettings.IgnoreMultiStore) + { + query = + from x in query + join sm in _storeMappingRepository.Table on new { c1 = x.Id, c2 = "CheckoutAttribute" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into x_sm + from sm in x_sm.DefaultIfEmpty() + where !x.LimitedToStores || storeId == sm.StoreId + select x; + + query = + from x in query + group x by x.Id into grp + orderby grp.Key + select grp.FirstOrDefault(); + } + + query = query.OrderBy(x => x.DisplayOrder); + + return query; + } + /// /// Gets all checkout attributes /// @@ -97,32 +131,10 @@ public virtual IList GetAllCheckoutAttributes(int storeId = 0 return _cacheManager.Get(key, () => { - var query = _checkoutAttributeRepository.Table; - - if (!showHidden) - query = query.Where(x => x.IsActive); - - if (storeId > 0 && !QuerySettings.IgnoreMultiStore) - { - query = - from x in query - join sm in _storeMappingRepository.Table on new { c1 = x.Id, c2 = "CheckoutAttribute" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into x_sm - from sm in x_sm.DefaultIfEmpty() - where !x.LimitedToStores || storeId == sm.StoreId - select x; - - query = - from x in query - group x by x.Id into grp - orderby grp.Key - select grp.FirstOrDefault(); - } - - query = query.OrderBy(x => x.DisplayOrder); - - var checkoutAttributes = query.ToList(); - return checkoutAttributes; - }); + var query = GetCheckoutAttributes(storeId, showHidden); + + return query.ToList(); + }); } /// diff --git a/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs b/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs index 57ee71c45f..9a520ca28b 100644 --- a/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs +++ b/src/Libraries/SmartStore.Services/Orders/ICheckoutAttributeService.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using SmartStore.Core.Domain.Orders; namespace SmartStore.Services.Orders @@ -16,6 +17,14 @@ public partial interface ICheckoutAttributeService /// Checkout attribute void DeleteCheckoutAttribute(CheckoutAttribute checkoutAttribute); + /// + /// Gets checkout attributes + /// + /// Whether to filter result by store identifier + /// A value indicating whether to show hidden records + /// Checkout attributes query + IQueryable GetCheckoutAttributes(int storeId = 0, bool showHidden = false); + /// /// Gets all checkout attributes /// diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/CheckoutAttributeController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/CheckoutAttributeController.cs index da0b4aaf19..51db07dad9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/CheckoutAttributeController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/CheckoutAttributeController.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Web.Mvc; using SmartStore.Admin.Models.Orders; +using SmartStore.Core; using SmartStore.Core.Domain.Common; using SmartStore.Core.Domain.Directory; using SmartStore.Core.Domain.Orders; @@ -158,16 +159,17 @@ public ActionResult List(GridCommand command) if (_services.Permissions.Authorize(StandardPermissionProvider.ManageCatalog)) { - var checkoutAttributes = _checkoutAttributeService.GetAllCheckoutAttributes(0, true); + var query = _checkoutAttributeService.GetCheckoutAttributes(0, true); + var pagedList = new PagedList(query, command.Page - 1, command.PageSize); - model.Data = checkoutAttributes.Select(x => + model.Data = pagedList.Select(x => { var caModel = x.ToModel(); caModel.AttributeControlTypeName = x.AttributeControlType.GetLocalizedEnum(_services.Localization, _services.WorkContext); return caModel; }); - model.Total = checkoutAttributes.Count(); + model.Total = pagedList.TotalCount; } else { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml index 33ab6112a0..b3707ed7fe 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/CheckoutAttribute/List.cshtml @@ -8,7 +8,9 @@ @T("Admin.Catalog.Attributes.CheckoutAttributes")
    \ No newline at end of file From 82020c6c5f835e107d9ec1e3515c792f10bf89b0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 20 May 2016 18:23:04 +0200 Subject: [PATCH 179/285] Removed confusing export deployment ZIP option. It is now using the profile ZIP option. --- .../Domain/DataExchange/ExportDeployment.cs | 6 - .../201605201230154_ExportRevision.resx | 126 ------------------ ...01605201342001_ExportRevision.Designer.cs} | 2 +- ...n.cs => 201605201342001_ExportRevision.cs} | 11 +- .../201605201342001_ExportRevision.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 10 +- .../Deployment/FileSystemFilePublisher.cs | 8 +- .../DataExchange/Export/DataExporter.cs | 26 ++-- .../DataExchange/Export/ExportExtensions.cs | 2 +- .../Administration/Content/admin.less | 3 + .../Controllers/ExportController.cs | 13 +- .../DataExchange/ExportDeploymentModel.cs | 6 +- .../Views/Export/InfoDeployment.cshtml | 8 +- .../Export/_CreateOrUpdate.Deployment.cshtml | 23 ++-- 14 files changed, 190 insertions(+), 180 deletions(-) delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.resx rename src/Libraries/SmartStore.Data/Migrations/{201605201230154_ExportRevision.Designer.cs => 201605201342001_ExportRevision.Designer.cs} (92%) rename src/Libraries/SmartStore.Data/Migrations/{201605201230154_ExportRevision.cs => 201605201342001_ExportRevision.cs} (82%) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.resx diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs index 0cb68384a3..2b89d0925a 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs @@ -20,11 +20,6 @@ public class ExportDeployment : BaseEntity, ICloneable ///
    public bool Enabled { get; set; } - /// - /// Whether to create a zip archive with the content of the export - /// - public bool CreateZip { get; set; } - /// /// The deployment type identifier /// @@ -117,7 +112,6 @@ public ExportDeployment Clone() { Name = this.Name, Enabled = this.Enabled, - CreateZip = this.CreateZip, DeploymentTypeId = this.DeploymentTypeId, Username = this.Username, Password = this.Password, diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.resx b/src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.resx deleted file mode 100644 index c1a2a076b4..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.resx +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 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 - - - dbo - - \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.Designer.cs similarity index 92% rename from src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.Designer.cs rename to src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.Designer.cs index a9a2bb71ff..256bd186e0 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.Designer.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.Designer.cs @@ -13,7 +13,7 @@ public sealed partial class ExportRevision : IMigrationMetadata string IMigrationMetadata.Id { - get { return "201605201230154_ExportRevision"; } + get { return "201605201342001_ExportRevision"; } } string IMigrationMetadata.Source diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs similarity index 82% rename from src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.cs rename to src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs index ee0d9c7eaa..d55e05d285 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201230154_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs @@ -12,10 +12,12 @@ public override void Up() { AddColumn("dbo.ExportDeployment", "SubFolder", c => c.String(maxLength: 400)); AlterColumn("dbo.ExportProfile", "FolderName", c => c.String(nullable: false, maxLength: 400)); + DropColumn("dbo.ExportDeployment", "CreateZip"); } public override void Down() { + AddColumn("dbo.ExportDeployment", "CreateZip", c => c.Boolean(nullable: false)); AlterColumn("dbo.ExportProfile", "FolderName", c => c.String(nullable: false, maxLength: 100)); DropColumn("dbo.ExportDeployment", "SubFolder"); } @@ -77,13 +79,18 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.SubFolder", "Name of subfolder", - "Name des Unterordner", + "Name des Unterordners", "Specifies the name of a subfolder where to deploy the data.", "Legt den Namen eines Unterordners fest, in den die Daten bereitgestellt werden sollen."); + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.ZipUsageNote", + "If there are a large number of export files, it is recommended to use the option Create ZIP archive. This saves time and avoids problems, such as a full email mailbox.", + "Bei einer großen Anzahl an Exportdateien wird empfohlen die Option ZIP-Archiv erstellen zu benutzen. Das spart Zeit und vermeidet Probleme, wie z.B. ein volles E-Mail Postfach."); + builder.Delete( "Admin.DataExchange.Export.FolderAndFileName.Validate", - "Admin.DataExchange.Export.Deployment.IsPublic"); + "Admin.DataExchange.Export.Deployment.IsPublic", + "Admin.DataExchange.Export.Deployment.CreateZip"); } } } diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.resx b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.resx new file mode 100644 index 0000000000..fa683624cb --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index efee428a5b..fa7a0999ef 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -400,9 +400,9 @@ 201605191216116_CheckoutAttributeMultiStore.cs - - - 201605201230154_ExportRevision.cs + + + 201605201342001_ExportRevision.cs @@ -749,8 +749,8 @@ 201605191216116_CheckoutAttributeMultiStore.cs - - 201605201230154_ExportRevision.cs + + 201605201342001_ExportRevision.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs index d4f89e79d8..11a1433afe 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs @@ -39,13 +39,9 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de System.IO.Directory.CreateDirectory(destinationFolder); } - if (deployment.CreateZip) + if (deployment.Profile.CreateZipArchive) { - var name = (new DirectoryInfo(deployment.Profile.FolderName)).Name; - if (name.IsEmpty()) - name = "ExportData"; - - var path = Path.Combine(destinationFolder, name.ToValidFileName() + ".zip"); + var path = Path.Combine(destinationFolder, Path.GetFileName(context.ZipPath)); if (FileSystemHelper.Copy(context.ZipPath, path)) context.Log.Information("Copied ZIP archive " + path); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 572f46cbfb..a581b96ba4 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -451,7 +451,7 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho private void Deploy(DataExporterContext ctx, string zipPath) { - var allFiles = System.IO.Directory.GetFiles(ctx.FolderContent, "*.*", SearchOption.AllDirectories); + string[] allFiles = null; var context = new ExportDeploymentContext { @@ -462,15 +462,25 @@ private void Deploy(DataExporterContext ctx, string zipPath) foreach (var deployment in ctx.Request.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) { - if (deployment.CreateZip) - context.DeploymentFiles = new string[] { zipPath }; - else - context.DeploymentFiles = allFiles; - try { IFilePublisher publisher = null; + if (!ctx.Request.Profile.CreateZipArchive || deployment.DeploymentType == ExportDeploymentType.FileSystem) + { + if (allFiles == null) + allFiles = System.IO.Directory.EnumerateFiles(ctx.FolderContent, "*", SearchOption.AllDirectories).ToArray(); + + context.DeploymentFiles = allFiles; + } + else + { + if (File.Exists(zipPath)) + context.DeploymentFiles = new string[] { zipPath }; + else + context.DeploymentFiles = new string[0]; + } + if (deployment.DeploymentType == ExportDeploymentType.Email) { publisher = new EmailFilePublisher(_emailAccountService.Value, _queuedEmailService.Value); @@ -1220,9 +1230,9 @@ private void ExportCoreOuter(DataExporterContext ctx) { if (ctx.IsFileBasedExport) { - if (ctx.Request.Profile.CreateZipArchive || ctx.Request.Profile.Deployments.Any(x => x.Enabled && x.CreateZip)) + if (ctx.Request.Profile.CreateZipArchive) { - ZipFile.CreateFromDirectory(ctx.FolderContent, zipPath, CompressionLevel.Fastest, true); + ZipFile.CreateFromDirectory(ctx.FolderContent, zipPath, CompressionLevel.Fastest, false); } if (ctx.Request.Profile.Deployments.Any(x => x.Enabled)) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs index 4dec9a2bbe..29a45613ad 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs @@ -91,7 +91,7 @@ public static List GetExportFiles(this ExportProfile profile) if (System.IO.Directory.Exists(exportFolder)) { - return System.IO.Directory.EnumerateFiles(exportFolder, "*", SearchOption.TopDirectoryOnly) + return System.IO.Directory.EnumerateFiles(exportFolder, "*", SearchOption.AllDirectories) .OrderBy(x => x) .ToList(); } diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index dfa4145f39..6da44acf8d 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -911,6 +911,9 @@ td.adminSeparator hr { .disabled { .muted(); } + .centered { + text-align: center; + } .progress-info { min-width: 260px; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 7fa0b7ff35..5de25bbd28 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -318,7 +318,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile model.Deployments = profile.Deployments .Select(x => { - var deploymentModel = PrepareDeploymentModel(x, null, false); + var deploymentModel = PrepareDeploymentModel(profile, x, null, false); deploymentModel.ProfileDetails = PrepareProfileDetailsModel(profile, true); @@ -417,7 +417,7 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile } } - private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment, Provider provider, bool forEdit) + private ExportDeploymentModel PrepareDeploymentModel(ExportProfile profile, ExportDeployment deployment, Provider provider, bool forEdit) { var model = new ExportDeploymentModel { @@ -425,7 +425,6 @@ private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment ProfileId = deployment.ProfileId, Name = deployment.Name, Enabled = deployment.Enabled, - CreateZip = deployment.CreateZip, DeploymentType = deployment.DeploymentType, DeploymentTypeName = deployment.DeploymentType.GetLocalizedEnum(_services.Localization, _services.WorkContext), Username = deployment.Username, @@ -445,6 +444,7 @@ private ExportDeploymentModel PrepareDeploymentModel(ExportDeployment deployment { var allEmailAccounts = _emailAccountService.GetAllEmailAccounts(); + model.CreateZip = profile.CreateZipArchive; model.AvailableDeploymentTypes = ExportDeploymentType.FileSystem.ToSelectList(false).ToList(); model.AvailableHttpTransmissionTypes = ExportHttpTransmissionType.SimplePost.ToSelectList(false).ToList(); @@ -469,7 +469,6 @@ private void ModelToEntity(ExportDeploymentModel model, ExportDeployment deploym deployment.Name = model.Name; deployment.Enabled = model.Enabled; deployment.DeploymentType = model.DeploymentType; - deployment.CreateZip = model.CreateZip; deployment.Username = model.Username; deployment.Password = model.Password; deployment.Url = model.Url; @@ -1196,7 +1195,7 @@ public ActionResult CreateDeployment(int id) //var fileSystemName = ExportDeploymentType.FileSystem.GetLocalizedEnum(_services.Localization, _services.WorkContext); - var model = PrepareDeploymentModel(new ExportDeployment + var model = PrepareDeploymentModel(profile, new ExportDeployment { ProfileId = id, Enabled = true, @@ -1251,7 +1250,7 @@ public ActionResult EditDeployment(int id) if (provider.Metadata.IsHidden) return RedirectToAction("List"); - var model = PrepareDeploymentModel(deployment, provider, true); + var model = PrepareDeploymentModel(deployment.Profile, deployment, provider, true); return View(model); } @@ -1282,7 +1281,7 @@ public ActionResult EditDeployment(ExportDeploymentModel model, bool continueEdi return SmartRedirect(continueEditing, deployment.ProfileId, deployment.Id); } - model = PrepareDeploymentModel(deployment, _exportService.LoadProvider(deployment.Profile.ProviderSystemName), true); + model = PrepareDeploymentModel(deployment.Profile, deployment, _exportService.LoadProvider(deployment.Profile.ProviderSystemName), true); return View(model); } diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs index b9b5c4f40b..7d06e197a2 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs @@ -13,6 +13,7 @@ namespace SmartStore.Admin.Models.DataExchange public class ExportDeploymentModel : EntityModelBase { public int ProfileId { get; set; } + public bool CreateZip { get; set; } [SmartResourceDisplayName("Common.Image")] public string ThumbnailUrl { get; set; } @@ -44,15 +45,14 @@ public string DeploymentTypeIconClass return "fa-globe"; case ExportDeploymentType.Ftp: return "fa-files-o"; + case ExportDeploymentType.PublicFolder: + return "fa-unlock"; default: return "fa-question"; } } } - [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.CreateZip")] - public bool CreateZip { get; set; } - [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.Username")] public string Username { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml index d48d8eeaa0..f111f30257 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml @@ -13,10 +13,6 @@ }
     @Model.DeploymentTypeName - @if (Model.DeploymentType == ExportDeploymentType.FileSystem && Model.IsPublic) - { - (@T("Common.Public")) - }
    @if (Model.DeploymentType == ExportDeploymentType.FileSystem) @@ -51,4 +47,8 @@ {
    @Model.Url
    } + else if (Model.DeploymentType == ExportDeploymentType.PublicFolder) + { +
    TODO
    + }
    \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml index 30b6318cdc..05765e96e4 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_CreateOrUpdate.Deployment.cshtml @@ -7,6 +7,13 @@ @Html.HiddenFor(model => model.Id) @Html.HiddenFor(model => model.ProfileId) +@if (!Model.CreateZip) +{ +
    + @Html.Raw(T("Admin.DataExchange.Export.Deployment.ZipUsageNote")) +
    +} + - - - - - + + @foreach (var deployment in Model.Deployments) { + var result = deployment.LastResult; - + - @@ -151,10 +158,15 @@ \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml index 529ff0124f..e1b19be258 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_ProfileList.cshtml @@ -20,7 +20,7 @@ @if (Model.Any()) { -
    @@ -36,15 +43,6 @@ @Html.ValidationMessageFor(x => x.Enabled)
    - @Html.SmartLabelFor(x => x.CreateZip) - - @Html.EditorFor(x => x.CreateZip) - @Html.ValidationMessageFor(x => x.CreateZip) -
    @Html.SmartLabelFor(x => x.FileSystemPath) @@ -157,10 +155,13 @@ // deployment type changed $('#@(Html.FieldIdFor(x => x.DeploymentType))').change(function () { - var table = $('#DeploymentTable'); + var table = $('#DeploymentTable'), + val = $(this).val(); table.find('tr:not(.deployment-all)').hide(); - table.find('.deployment-' + $(this).val()).show(); + table.find('.deployment-' + val).show(); + + $('#ZipUsageNote').toggle(val === '@((int)ExportDeploymentType.Email)' || val === '@((int)ExportDeploymentType.Http)' || val === '@((int)ExportDeploymentType.Ftp)'); }).trigger('change'); }); From 836f47550887c006f1fe596a391626ff689af6be Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 20 May 2016 20:32:22 +0200 Subject: [PATCH 180/285] Improved export publisher code --- .../Deployment/EmailFilePublisher.cs | 2 +- .../Deployment/FileSystemFilePublisher.cs | 14 ++--------- .../Deployment/FtpFilePublisher.cs | 4 ++-- .../Deployment/HttpFilePublisher.cs | 4 ++-- .../DataExchange/Deployment/IFilePublisher.cs | 24 ++++++++++++++++--- .../Deployment/PublicFolderPublisher.cs | 18 +++++++++++--- .../DataExchange/Export/DataExporter.cs | 20 ++-------------- 7 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs index 8a9beeeea0..298d476c41 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs @@ -42,7 +42,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de EmailAccountId = deployment.EmailAccountId }; - foreach (var path in context.DeploymentFiles) + foreach (var path in context.GetDeploymentFiles()) { var name = Path.GetFileName(path); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs index 11a1433afe..c8bea99259 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs @@ -39,19 +39,9 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de System.IO.Directory.CreateDirectory(destinationFolder); } - if (deployment.Profile.CreateZipArchive) - { - var path = Path.Combine(destinationFolder, Path.GetFileName(context.ZipPath)); - - if (FileSystemHelper.Copy(context.ZipPath, path)) - context.Log.Information("Copied ZIP archive " + path); - } - else - { - FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); + FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); - context.Log.Information("Copied export data files to " + destinationFolder); - } + context.Log.Information("Copied export data files to " + destinationFolder); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FtpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/FtpFilePublisher.cs index 2352d7250f..99262471d5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FtpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/FtpFilePublisher.cs @@ -16,7 +16,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de var url = deployment.Url; var buffLength = 32768; byte[] buff = new byte[buffLength]; - var deploymentFiles = context.DeploymentFiles.ToList(); + var deploymentFiles = context.GetDeploymentFiles().ToList(); var lastIndex = (deploymentFiles.Count - 1); if (!url.StartsWith("ftp://", StringComparison.InvariantCultureIgnoreCase)) @@ -26,7 +26,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de { var fileUrl = url.EnsureEndsWith("/") + Path.GetFileName(path); - var request = (FtpWebRequest)System.Net.WebRequest.Create(fileUrl); + var request = (FtpWebRequest)WebRequest.Create(fileUrl); request.Method = WebRequestMethods.Ftp.UploadFile; request.KeepAlive = (deploymentFiles.IndexOf(path) != lastIndex); request.UseBinary = true; diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/HttpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/HttpFilePublisher.cs index 48303bd200..6afb1260be 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/HttpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/HttpFilePublisher.cs @@ -30,7 +30,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de using (var client = new HttpClient(handler)) using (var formData = new MultipartFormDataContent()) { - foreach (var path in context.DeploymentFiles) + foreach (var path in context.GetDeploymentFiles()) { byte[] fileData = File.ReadAllBytes(path); formData.Add(new ByteArrayContent(fileData), "file {0}".FormatInvariant(++count), Path.GetFileName(path)); @@ -60,7 +60,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de if (deployment.Username.HasValue()) webClient.Credentials = new NetworkCredential(deployment.Username, deployment.Password); - foreach (var path in context.DeploymentFiles) + foreach (var path in context.GetDeploymentFiles()) { webClient.UploadFile(url, path); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs index 25ffddcaa7..68f54ce5f6 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs @@ -1,4 +1,7 @@ -using SmartStore.Core.Domain; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using SmartStore.Core.Domain; using SmartStore.Core.Logging; namespace SmartStore.Services.DataExchange.Export.Deployment @@ -13,10 +16,25 @@ public class ExportDeploymentContext { public ILogger Log { get; set; } - public string[] DeploymentFiles { get; set; } - public string FolderContent { get; set; } public string ZipPath { get; set; } + + public bool CreateZipArchive { get; set; } + + public IEnumerable GetDeploymentFiles() + { + if (!CreateZipArchive) + { + return System.IO.Directory.EnumerateFiles(FolderContent, "*", SearchOption.AllDirectories).ToArray(); + } + + if (File.Exists(ZipPath)) + { + return new string[] { ZipPath }; + } + + return new string[0]; + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/PublicFolderPublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Deployment/PublicFolderPublisher.cs index 890509ab85..5fdeee0ffd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Deployment/PublicFolderPublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Deployment/PublicFolderPublisher.cs @@ -13,11 +13,23 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de if (destinationFolder.HasValue()) { - // TODO: zip + if (context.CreateZipArchive) + { + if (File.Exists(context.ZipPath)) + { + var destinationFile = Path.Combine(destinationFolder, Path.GetFileName(context.ZipPath)); - FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); + File.Copy(context.ZipPath, destinationFile, true); - context.Log.Information("Copied export data files to " + destinationFolder); + context.Log.Information("Copied zipped export data to " + destinationFile); + } + } + else + { + FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); + + context.Log.Information("Copied export data files to " + destinationFolder); + } } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index a581b96ba4..a0b9cc6d6f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -451,13 +451,12 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho private void Deploy(DataExporterContext ctx, string zipPath) { - string[] allFiles = null; - var context = new ExportDeploymentContext { Log = ctx.Log, FolderContent = ctx.FolderContent, - ZipPath = zipPath + ZipPath = zipPath, + CreateZipArchive = ctx.Request.Profile.CreateZipArchive }; foreach (var deployment in ctx.Request.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) @@ -466,21 +465,6 @@ private void Deploy(DataExporterContext ctx, string zipPath) { IFilePublisher publisher = null; - if (!ctx.Request.Profile.CreateZipArchive || deployment.DeploymentType == ExportDeploymentType.FileSystem) - { - if (allFiles == null) - allFiles = System.IO.Directory.EnumerateFiles(ctx.FolderContent, "*", SearchOption.AllDirectories).ToArray(); - - context.DeploymentFiles = allFiles; - } - else - { - if (File.Exists(zipPath)) - context.DeploymentFiles = new string[] { zipPath }; - else - context.DeploymentFiles = new string[0]; - } - if (deployment.DeploymentType == ExportDeploymentType.Email) { publisher = new EmailFilePublisher(_emailAccountService.Value, _queuedEmailService.Value); From 12133ed5acd9f0b2ab0e04b536af58be423391e6 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 20 May 2016 20:36:03 +0200 Subject: [PATCH 181/285] Moving deployment publisher into export folder --- .../DataExchange/Excel/ExcelDataReader.cs | 2 -- .../{ => Export}/Deployment/EmailFilePublisher.cs | 0 .../Deployment/FileSystemFilePublisher.cs | 0 .../{ => Export}/Deployment/FtpFilePublisher.cs | 0 .../{ => Export}/Deployment/HttpFilePublisher.cs | 0 .../{ => Export}/Deployment/IFilePublisher.cs | 0 .../Deployment/PublicFolderPublisher.cs | 0 .../{ => Export}/Events/RowExportingEvent.cs | 0 .../SmartStore.Services/SmartStore.Services.csproj | 14 +++++++------- 9 files changed, 7 insertions(+), 9 deletions(-) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Deployment/EmailFilePublisher.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Deployment/FileSystemFilePublisher.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Deployment/FtpFilePublisher.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Deployment/HttpFilePublisher.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Deployment/IFilePublisher.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Deployment/PublicFolderPublisher.cs (100%) rename src/Libraries/SmartStore.Services/DataExchange/{ => Export}/Events/RowExportingEvent.cs (100%) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Excel/ExcelDataReader.cs b/src/Libraries/SmartStore.Services/DataExchange/Excel/ExcelDataReader.cs index d5eb02d5a9..d46879193c 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Excel/ExcelDataReader.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Excel/ExcelDataReader.cs @@ -6,8 +6,6 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; using OfficeOpenXml; namespace SmartStore.Services.DataExchange.Excel diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Deployment/EmailFilePublisher.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Deployment/FileSystemFilePublisher.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/FtpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Deployment/FtpFilePublisher.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/HttpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Deployment/HttpFilePublisher.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Deployment/IFilePublisher.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Deployment/PublicFolderPublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Deployment/PublicFolderPublisher.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Events/RowExportingEvent.cs similarity index 100% rename from src/Libraries/SmartStore.Services/DataExchange/Events/RowExportingEvent.cs rename to src/Libraries/SmartStore.Services/DataExchange/Export/Events/RowExportingEvent.cs diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index f14da22d07..5bd7ee173b 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -174,20 +174,20 @@ - + - - - - - + + + + + - + From e9c7baf209f7ca19f1fdb57e5d03138dfcc19bd8 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 20 May 2016 20:59:39 +0200 Subject: [PATCH 182/285] Clearing up export folder only when all deployments succeeded --- .../Utilities/FileSystemHelper.cs | 8 ++++- .../201605201342001_ExportRevision.cs | 8 +++++ .../DataExchange/Export/DataExporter.cs | 18 ++++++++--- .../Export/Deployment/EmailFilePublisher.cs | 4 ++- .../Deployment/FileSystemFilePublisher.cs | 6 ++-- .../Export/Deployment/FtpFilePublisher.cs | 7 +++- .../Export/Deployment/HttpFilePublisher.cs | 6 +++- .../Export/Deployment/IFilePublisher.cs | 2 +- .../Deployment/PublicFolderPublisher.cs | 32 +++++++++++-------- 9 files changed, 66 insertions(+), 25 deletions(-) diff --git a/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs b/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs index 95672ae9a5..cf778b71ff 100644 --- a/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs +++ b/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs @@ -114,8 +114,10 @@ public static bool Copy(string sourcePath, string destinationPath, bool overwrit /// Source directory /// Target directory /// Whether to override existing files - public static void CopyDirectory(DirectoryInfo source, DirectoryInfo target, bool overwrite = true) + public static bool CopyDirectory(DirectoryInfo source, DirectoryInfo target, bool overwrite = true) { + var result = true; + foreach (FileInfo fi in source.GetFiles()) { try @@ -124,6 +126,7 @@ public static void CopyDirectory(DirectoryInfo source, DirectoryInfo target, boo } catch (Exception exc) { + result = false; exc.Dump(); } } @@ -137,9 +140,12 @@ public static void CopyDirectory(DirectoryInfo source, DirectoryInfo target, boo } catch (Exception exc) { + result = false; exc.Dump(); } } + + return result; } /// diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs index d55e05d285..23e687029d 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs @@ -87,6 +87,14 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "If there are a large number of export files, it is recommended to use the option Create ZIP archive. This saves time and avoids problems, such as a full email mailbox.", "Bei einer großen Anzahl an Exportdateien wird empfohlen die Option ZIP-Archiv erstellen zu benutzen. Das spart Zeit und vermeidet Probleme, wie z.B. ein volles E-Mail Postfach."); + builder.AddOrUpdate("Admin.DataExchange.Export.Cleanup", + "Clean up after successful deployment", + "Nach erfolgreicher Veröffentlichung aufräumen", + "Specifies whether to delete unneeded files after all deployments succeeded.", + "Legt fest, ob nicht mehr benötigte Dateien gelöscht werden sollen, nachdem alle Veröffentlichungen erfolgreich waren."); + + + builder.Delete( "Admin.DataExchange.Export.FolderAndFileName.Validate", "Admin.DataExchange.Export.Deployment.IsPublic", diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index a0b9cc6d6f..665babf9c1 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -449,8 +449,9 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho return (ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard); } - private void Deploy(DataExporterContext ctx, string zipPath) + private bool Deploy(DataExporterContext ctx, string zipPath) { + var result = true; var context = new ExportDeploymentContext { Log = ctx.Log, @@ -488,15 +489,20 @@ private void Deploy(DataExporterContext ctx, string zipPath) if (publisher != null) { - publisher.Publish(context, deployment); + if (!publisher.Publish(context, deployment)) + result = false; } } catch (Exception exception) { + result = false; + ctx.Log.Error("Deployment \"{0}\" of type {1} failed: {2}".FormatInvariant( deployment.Name, deployment.DeploymentType.ToString(), exception.Message), exception); } } + + return result; } private void SendCompletionEmail(DataExporterContext ctx, string zipPath) @@ -1136,6 +1142,7 @@ private void ExportCoreOuter(DataExporterContext ctx) if (ctx.Request.Profile == null || !ctx.Request.Profile.Enabled) return; + var allDeploymentsSucceeded = false; var logPath = ctx.Request.Profile.GetExportLogPath(); var zipPath = ctx.Request.Profile.GetExportZipPath(); @@ -1222,7 +1229,8 @@ private void ExportCoreOuter(DataExporterContext ctx) if (ctx.Request.Profile.Deployments.Any(x => x.Enabled)) { SetProgress(ctx, T("Common.Deployment")); - Deploy(ctx, zipPath); + + allDeploymentsSucceeded = Deploy(ctx, zipPath); } } @@ -1259,9 +1267,11 @@ private void ExportCoreOuter(DataExporterContext ctx) try { - if (ctx.IsFileBasedExport && ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard && ctx.Request.Profile.Cleanup) + if (ctx.IsFileBasedExport && ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard && ctx.Request.Profile.Cleanup && allDeploymentsSucceeded) { FileSystemHelper.ClearDirectory(ctx.FolderContent, false); + + logger.Information("Cleared up export folder"); } } catch (Exception exception) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs index 298d476c41..814d34498a 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs @@ -23,7 +23,7 @@ public EmailFilePublisher( _queuedEmailService = queuedEmailService; } - public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) { var emailAccount = _emailAccountService.GetEmailAccountById(deployment.EmailAccountId); var smtpContext = new SmtpContext(emailAccount); @@ -60,6 +60,8 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de } context.Log.Information("{0} email(s) created and queued for deployment.".FormatInvariant(count)); + + return true; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs index c8bea99259..774cf7dee1 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs @@ -7,13 +7,13 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class FileSystemFilePublisher : IFilePublisher { - public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) { string destinationFolder = null; if (deployment.FileSystemPath.IsEmpty()) { - return; + return false; } else if (Path.IsPathRooted(deployment.FileSystemPath)) { @@ -42,6 +42,8 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); context.Log.Information("Copied export data files to " + destinationFolder); + + return true; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs index 99262471d5..29a6d086b5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs @@ -9,8 +9,9 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class FtpFilePublisher : IFilePublisher { - public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) { + var result = true; var bytesRead = 0; var succeededFiles = 0; var url = deployment.Url; @@ -61,6 +62,8 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de } else { + result = false; + var msg = "The FTP transfer might fail. {0} ({1}), {2}. File {3}".FormatInvariant( response.StatusCode.ToString(), statusCode, response.StatusDescription.NaIfEmpty(), path); @@ -70,6 +73,8 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de } context.Log.Information("{0} file(s) successfully uploaded via FTP.".FormatInvariant(succeededFiles)); + + return result; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs index 6afb1260be..9952c275ab 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs @@ -10,8 +10,9 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class HttpFilePublisher : IFilePublisher { - public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) { + var result = true; var succeeded = 0; var url = deployment.Url; @@ -44,6 +45,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de } else if (response.Content != null) { + result = false; var content = response.Content.ReadAsStringAsync().Result; var msg = "Multipart form data upload failed. {0} ({1}). Response: {2}".FormatInvariant( @@ -70,6 +72,8 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de } context.Log.Information("{0} file(s) successfully uploaded via HTTP ({1}).".FormatInvariant(succeeded, deployment.HttpTransmissionType.ToString())); + + return result; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs index 68f54ce5f6..a6875e85ec 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs @@ -8,7 +8,7 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public interface IFilePublisher { - void Publish(ExportDeploymentContext context, ExportDeployment deployment); + bool Publish(ExportDeploymentContext context, ExportDeployment deployment); } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs index 5fdeee0ffd..179d9dbf66 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs @@ -7,30 +7,34 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class PublicFolderPublisher : IFilePublisher { - public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) { var destinationFolder = deployment.GetPublicFolder(true); + var result = false; - if (destinationFolder.HasValue()) + if (destinationFolder.IsEmpty()) + return false; + + if (context.CreateZipArchive) { - if (context.CreateZipArchive) + if (File.Exists(context.ZipPath)) { - if (File.Exists(context.ZipPath)) - { - var destinationFile = Path.Combine(destinationFolder, Path.GetFileName(context.ZipPath)); + var destinationFile = Path.Combine(destinationFolder, Path.GetFileName(context.ZipPath)); - File.Copy(context.ZipPath, destinationFile, true); + File.Copy(context.ZipPath, destinationFile, true); + result = true; - context.Log.Information("Copied zipped export data to " + destinationFile); - } + context.Log.Information("Copied zipped export data to " + destinationFile); } - else - { - FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); + } + else + { + result = FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); - context.Log.Information("Copied export data files to " + destinationFolder); - } + context.Log.Information("Copied export data files to " + destinationFolder); } + + return result; } } } From fb9ed21563ab471bbbec23176979b9c316575caf Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 21 May 2016 11:57:20 +0200 Subject: [PATCH 183/285] Display result info about last execution of an export deployment --- .../Domain/DataExchange/ExportDeployment.cs | 5 + .../DataExchange/ExportDeploymentMap.cs | 1 + .../201605201342001_ExportRevision.resx | 126 ------------------ ...01605201911421_ExportRevision.Designer.cs} | 2 +- ...n.cs => 201605201911421_ExportRevision.cs} | 12 ++ .../201605201911421_ExportRevision.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 10 +- .../DataExchange/Export/DataExporter.cs | 80 ++++++----- .../Export/Deployment/DataDeploymentResult.cs | 23 ++++ .../Export/Deployment/EmailFilePublisher.cs | 4 +- .../Deployment/FileSystemFilePublisher.cs | 11 +- .../Export/Deployment/FtpFilePublisher.cs | 12 +- .../Export/Deployment/HttpFilePublisher.cs | 12 +- .../Export/Deployment/IFilePublisher.cs | 7 +- .../Deployment/PublicFolderPublisher.cs | 18 ++- .../Export/ExportProfileService.cs | 10 +- .../Export/IExportProfileService.cs | 8 +- .../SmartStore.Services.csproj | 1 + .../Administration/Content/admin.less | 3 - .../Controllers/ExportController.cs | 12 ++ .../DataExchange/ExportDeploymentModel.cs | 18 ++- .../Views/Export/_Tab.Deployment.cshtml | 26 +++- 22 files changed, 321 insertions(+), 206 deletions(-) delete mode 100644 src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.resx rename src/Libraries/SmartStore.Data/Migrations/{201605201342001_ExportRevision.Designer.cs => 201605201911421_ExportRevision.Designer.cs} (92%) rename src/Libraries/SmartStore.Data/Migrations/{201605201342001_ExportRevision.cs => 201605201911421_ExportRevision.cs} (88%) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.resx create mode 100644 src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/DataDeploymentResult.cs diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs index 2b89d0925a..b9e323c670 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportDeployment.cs @@ -20,6 +20,11 @@ public class ExportDeployment : BaseEntity, ICloneable /// public bool Enabled { get; set; } + /// + /// XML with information about the last deployment result + /// + public string ResultInfo { get; set; } + /// /// The deployment type identifier /// diff --git a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportDeploymentMap.cs b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportDeploymentMap.cs index dc38acf13d..2c49aa7d7a 100644 --- a/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportDeploymentMap.cs +++ b/src/Libraries/SmartStore.Data/Mapping/DataExchange/ExportDeploymentMap.cs @@ -11,6 +11,7 @@ public ExportDeploymentMap() this.HasKey(x => x.Id); this.Property(x => x.Name).IsRequired().HasMaxLength(100); + this.Property(x => x.ResultInfo).IsMaxLength(); this.Property(x => x.Username).HasMaxLength(400); this.Property(x => x.Password).HasMaxLength(400); this.Property(x => x.Url).HasMaxLength(4000); diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.resx b/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.resx deleted file mode 100644 index fa683624cb..0000000000 --- a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.resx +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - H4sIAAAAAAAEAOy923LcuLIo+D4R8w8OP50zsY/ddq+OWLuje05IsmQrti9akmzvvV4UFAuSuMwiq3nRpSfmy+ZhPun8wgHAGwgk7iBZpa4Xu0QkEkAikUgkMhP/6//7/3/7n4/r9MU9Ksokz35/+ebVTy9foCzOV0l2+/vLurr5H39/+T//7//z//jteLV+fPGtg/uZwOGaWfn7y7uq2vz6+nUZ36F1VL5aJ3GRl/lN9SrO16+jVf767U8//fvrN29eI4ziJcb14sVv53VWJWtE/8B/HuVZjDZVHaWf8hVKy/Y7LrmgWF98jtao3EQx+v3lxToqqosqL9Crd1EVvXxxkCYR7sYFSm9evoiyLK+iCnfy168luqiKPLu92OAPUXr5tEEY7iZKS9R2/tcB3HQcP70l43g9VOxQxXVZ5WtLhG9+bgnzmq/uRN6XPeEw6Y4xiasnMmpKvt9fXuabJH75gm/p16O0IFAj0h5R+mKwJHtF65XNf//2ggP6t54p3r765dVPr376txdHdVrVBfo9Q3VVROm/vTirr9Mk/g/0dJn/QNnvWZ2mbE9xX3HZ6AP+dFbkG1RUT+fopu3/6erli9fjeq/5in01pk4zuNOs+vntyxefcePRdYp6RmAIQUf1HmWoiCq0OouqChUZwYEoKYXWubYunsoKrcnvrk3Mf3gdvXzxKXr8iLLb6u73l/jnyxcnySNadV/afnzNErzscKWqqJGuqdMsTusVOs0uEtxktOkaPMzzFEUZMEwNvvIsKsuHvFjhggrFePi+KDuEk9PiMqnS6Sl+mK+eJm/kE6oizNGEbOUsjb1DZVwkm0bgzNDePHP1MVnjZbG6zOmCLn05+RxlK1QclN+T1S2qfLE1WP6ZZ9PToWnqexFt8AZbYSEm9N2k/sVd/jCaN7+RH2LmRkUA+VIkeUGlslq+GwiPy+g28Fz89nrYfdV7cvR4hDeb27x4ctmZo8dXDIb95ixvS7Mt/+2nn4wm2ZK93iXlJo2evhCWt2FUY/65QFVFx2LNO1gk3CS3dUGhX7V49hzkzEFvp+Ggb1Fah9gpLJultNJT14lnP+ZxlCZ/olXXpgP3tjga5hUQ7tlY3lYzHXZTC6hYUXZbR7eWLALgIVOHMH3eF3m9mV9A9+0v1fRc6/tzdJ/cUgaSzOTLF+copQDlXbJp7CniyroawE+KfH2ep9CC7qGuLvK6iMn4ci3oZVRQ9dpNpvTd8hQlLZ69BJG3pdkI30y0XNqZaamu3ImnaB/X/qNGFyg/wjhUrQc4uJ2k0e3pGg/2JEmRhty/mI1Wc8StUt/zWOBDN11L5Z3/OdFVBVfKTCq6m8k4RyWVcaVcgHKQchkqA+xl40iMSqE7oeuunXGogyhoHM69hNXLOl/tqqP1MkeXrvWFjjCnJVldZ2l9m2SCENFVvczrGBI+4bQqf6EA6lZaEeIkFM5QsU5KsjjPUUyN+tYC4QLFNbHXveJx7QWBvK1Al0m2h3+Ti6y3v/wyRduDNXTylqWL94jyNirIsoK3dZ6Hr7gqwxJWQwprWAPutYhZVC4Gw7Z6+YpFtF+9zqt3ohV0UiB0gVl1Q9vzU54vo8fjR7TeeN96YUStIk7wcFOgrnoQV8m99+XTadlItYb5/XAFlY+mQomXDBMLJv7EYSjH3LSLHK9/e4FEqpX0370QkrcV6iyxpCrS+kRMfmEezOhA7sy/ZB/w+jijKr0ftoM0zR/e16is8LnkW155I/SwiQj3RHjdvcOM+bWKO0zkz8tkra17nK0ca/ramtyObUTSgMe0UYGo0o1KIQ1OKfZx7YOsfMDiTNqppvyqEaPjbjFFokjnyr1leIPMS5I3KPbyXCGjMJV2U5Z/rtfXqPhyQyRY6TeAKYy6zfLxW2LQ2oeWoE2fMLmoQUeh9HFQV+xiHHdWAgbKBhmst5xgEXtJCxZRMJnx4jAqUdsBQt1O0e186LSrsyGT9Ro12AKmmn2IbXWc4mWCCGJ+2O8S8rY6Gr2vk77V5rfttWeJ29XdQIa4gjzGs5xO3oqBV3rYhk7yYh1Vvht2h+0iSqvJu36wWifZUb5eMx7D00UylMFsTAc3N0ma4OXiS+0wFqd3KEUB4ig6w9VBHOc14MI9he0qDB99jMrqdHOwWuEjmiqcwdRhRCPxCkQk5ZcMPE9aO5uU1cf8NslcD6i4PuUiLKqlKJwVgpakkruJTvRfMWCDGiCWCrs/AGKrtx4maYqnuZ97VTd5WKCvYxB5hzk4217zmp6q2y3M1aDRiP3mYQQtWwoIqdh+N1Y9aqlBGIaQE1t/M6Xq8fEjUWii9KCu7ohKE1Mg1SlHVQOcBqMKwpyY1bKdIKwG1OuzvKzgsfXF4EDEUqHXAIhTF5tYT3kfabm8k+NiuJccjG036Zkf7iEtAjs3LhH6xRXbdukc4TMGZpE/qIkW7NoIBOwiDCF0VQJm3+WHqFid5UlWlR8SjITcuIP9FuAkvZfDAWNQANuOpLvqNNprBGBA/HEwcgHIA9qKwIu7nNY/wofYU6yUwX3noUDyS4EE2sshvcw6PUEd4pDW6zx71SLYn+nlbZ0kRVkFMt3q1ddZGtId+sO0gllsE2XTh28fkQNaIcbWaK/QKoS/3SdZLB5dNS0yAbCTDatdmm/maujt5A39M9kQXSlKNb78gW6V7/IMNZcfk7d1Ej3O1JLfeVt+kmnWELgVdhttDzNsgFyRsGnz5dZ7NbtIlZ3jIMUujgCkHR1D+RndO3JZ783vkgLFRD171eLYb8/ytjQb5kRhUdQ3pGwNH0EcTcpgzoMP+UdEiHhazhE0dXlXIGTa4M8BGsSCFhVJzDXm6EdTX/8LL7XL/FvkbeLdhtCpuRx2zvEpGM8Bxt1x7SdU3eUSE9IY5mqo3Ii3ZGwQ00MDpyttFevzLbsRSIY12iuA3REEEDsPQnkdC8f0cIkaaRFwU7ffhxRiSb0PTZXpZMZMUuHk0ulthml9dEdWwiRSiREsc8gjXpu0EGFu63us7HoomCNM++Utb0tmZAjrAjiVtnp9XaD7JNKIiTA3wtugBzmdcx33c2HxK3d9L/+t/oY0gBtXi2u/5hVrviWV76I38cYwDQbXea0cbDaY9fwXX1C/jq+b1SRGq/5KJrTHgOzySepa4LSsD9P8tnf4sl7SpHb5isGxHa64bWcu0eP0Xmxk8MSiHM7pt8MIshRD66sBcGAnqFxgJRDIm42arnjwEEGw3wvkbYWK5wuVitey2UB5lF0Mmy2ze6cm6na2Ft8R65bpqIznVXikl9Ht9Emkl4n/M8yTbWrq0De2bXmyw4wscMquiW4EmX0KtqI47Ya88VO5ZQYJH+2xQiGkQiG4XY8h/DL6FPmqjqtzfBpHDy7nuKiKcI9ejfBsh+LXdmlbNkh1Kw3hZlFSz6OKudVzI8oHlG5u6vS/UHmJOSUNguxz7oJLHi7XTD8cK8dy61UPyUTJQQBifBwIZR3cyWJpaZHhw/mxwmIsq3PFrWfJiKQ1xABQs2p+saAd/X2lz/68oJWGpDn/KLQCt0kzNdMEviGk7LekTDD0abZK7pNVHaXpk68esswF2MVdjjXhGfXEE9y/OdubNSyw41q03qQBIvrC5mPp8IS7h9wfaMJErtJjfheXRHeoYKf9Rnu6qNfBjvqBMHboqBLFDdq7j+GQ9h5OB/G25Zq++FFPv+iirL6JYqKCFHgfrbReumGafV8lqgUeppHT8n1yUx1FhfdlT4cnhLZCYqSSAn2p7jDFm+0kwFNgFOeg/GjimoMItZroxiQQEetGB6sV1wfvMZ2W7/KHLM0j/3vyFo/vzH3N0maBdwi9x/ipc4v/ciPgdMxA1KI5ftwkzRtK76InHqcZChooTlGEYPsPUXkRYa0JhZrVMTbLABXcG5JB5OC2QIhVHF07M0I2i9XktDwnmauLAA7RPaKjpzhFTad8ZRyL8QwVSe69+nqcdO+niD3Xyil1KT/OSJUA+TBCJrjF8jQhKy9KO4yNM2Bvv0Zxsia2qbMC/2ofSf77yxcXJNU63j8duh8s38lpeVx6k5N5PtCXcbCKQ+4ls3u8NDG6xufQ++RW5fGPf9RRa+vwEtnNcY1iPLiPElw3SRmsnu5hYE+dN6wkCzjyj/lDM+o2JYm382BeJTdP1CBwkhddHw8RPn35IT6M4h/0fVDyDrh3Gh9yGiQYTxta4hNIf+j11ijooR/PUrKu12EmqcEYPYbDSAmAVi2yBM2grOK1QDp4WD8d1lU1GBc81haB/p6Ud2lSVmGQtos/RZh5sVwf2W+cLz+xdk7RJbG3fWmEJPgO9CVdTdtAezA5otewE7VxscF4otRyIOY4e78Gcnnt4KHA4mr9HBwxdeas46xCRRmEv1qxNcKMJmaKVrDN2iY+fFwmqFmT3gIf76GorA4qLDqv6wod5evrJGuv9QIyIe4zlnk01RtxoU0Tf435O0pu76ZbiuNzTHD035PVhNg/TEubfqfxlSc9Ij9hEu7GIlxwRZg0iVvkJi4OMLlHxROpbGk96dRArIKJF606TbpE7e4T5Njd4/uEorIuEOmSQp0M8ipl3+bBmnWH9BWxPVryY4zajLR1tkoRvdHSWKDCmNeb9s5QQfI1hTJ8jJASaoTGySaaskYsT33ZbIn9Fgk6yXSlVy30ZT7YygfHGDmU4AyjALV1EWSlgcrD52oMKPjzjMplDkpjIEf/pGZW5QnKBLCrlg+k3lUyUJkTkhTelvjdyaQxeGl8rDqzmMJNjAeRDUCAc+w4e4mo7DsLqOg/BCYbAwjrOI6zhOJRDqGFUfSeg5B1nAdz7HN/CR7OrVDpc+fbX3oCvWnTl/bHFmX/4SqK8agryManqeU43ibcQSFQR3CiPGWKpeKUhXGUpt+iIsFKHXSOVHZfUU8xQQa1ZLNkUtVxqnjUVgO3GK3tEJ3HxerpSh4cA4pMyJZLuXAEZJ3IjLsbVnSWBxW7O4aQdpgDs+0ya8MButsXQ5whFAq8IEJ4+TR3qqJTMoxWqe1/7f2a5W1pvHwnetO8m5kQnhpfS3JWivFwA/ikdh0TMQY3anVN2Z7KrW9+lwgtbL2ryqO83uQZm9jN2TIlYJos40k3M9Q/kq4yXx4dMBHi2fqB6I7xl7nmKCYevNkKqrP8ACdsS0pg280JMDFMYZHQj0EwXhhbJFpULefL4yz7NuEawDggQPlQQGi/dDX9Y/LOEUYdiv1WLG9LsxVPFHBD4l8Ctawz4M8XK3GYV5hPZw3rSZNohteZ2mUUKqBnHzAT6JU6EtEXygGwtbRZ3lSRSKqL5E/k1/Yo9Ke8zPGOheKKx+3skdPi+TJ2HpssfRY9kJ5H2a3SZysMDwSOjQvvv7DFkUPbdgse7n5/m+/TbyKsaH1L0MOnSZ5vCH356HBk0V9AAucbYytsI6bhZHX9TVUHxCSp48rE5HQ8gF+Gkq4H1rr7J7RKoldt/b3irhBfDYkOkywqBpf19i+ThaTzM1zTJC7KLWySWHyUnyQpytSng58DBT9+Rg++cvu0vCyirExCREhNk+LS5eZWFCEwhPbmlpM3buJkhNM/58hevJimHpnqWDGfbuS0lTqvBsk1mmzRWK7gsD4Xur4qUiNZX6kxdsEgt2sMvv0qVq4bSq4A648n+3mdIqMHyQMlq9ygyW+t2peYAmXp74NSw6Ab3n/ABwjm+WZLi82Appfw02caZRqNngjz9A/9zNZwNxt6K1Dollt7heU8HT9iQYV1v2jGM/Lg/zDdDZL0dKy8b3Lbe0ZeuB7vm7CI9ruNvC3NRmAYV2B9RZ/mxQf0+C1K6/lbbxXTjznZc6aOqXDVgm3PWYPPtv9Ra8C1XznytgKdtkYe9r7IgiVuiEMGAMl9pRZOOam9qJk5w2SSYhZk3XQ974aSFbq8q9fXGfPqtyuyNnHo9tzgPMerF/lVSMcUDY8YRgZxta7YTUIRJSSvpo8YUtS1vTfhwpqmDYWSGFa0oVMBrUFMW0YDMO96ELOQbqpD6h0c6r0aIm9rIJp3/p6O6MEQUf3e+xG1EqsdjXuJw66hkS6NLFxGlmrXrYEc9lnJAbw1OUz7dSpvK5CGH8p37LQ8wVpPPeQ1XVAfk7/z13OoQdjwACyPG+5/SNaeCDjBPu8S6Kzrb8gdno089pcNLLa9fJhcPrDk/kvIiDG3Gobojyup4/RHf0jWobzCBPLDJ9mASf+DyJJAQmQvPbY5JGRWJ32GE/ZRDdsV1bCPQ9jBOIS9a/4zsg+rHOPGm7voFgeVC4oCCBTitAFnp/E/d8B49zqESvgEOYHAhG8kaJAU4401LED+vCYSqx12iGCsSVxe9QeC0HmkJIcEw+xTpgNT8YnDaNXoTEmgwmJJFyUqL8mpHqqz4FSh3ctNW4Hn/eZDGA/d7ZFg4RLkzbDU7VLpGUkNP7kIkkndrgTPQB+H6oIMdMExgfALLvb2Au+vIJpU3AtLpgWWHC+LfJatz0ntMrr1P5ZhJPul5by0QtlxdXt1sMyukn0UzP7qw5uK7Kn+PKtAvudleVtzPPM7y3u7C70lHPSdG9tXY9TYLB+J0bgpW74Jo/GH285HLA7KMrnN8Crq7krmeBxukZdQTkv67KH/3UsYc+Rw0vzPdRpAa9XIvHCPSFJb55e6+nJDkVK91/6WxvthDFVecs2bGaZVZYY14/oTOFlMl//dYbCuJlbTPOmqtjUp1E2rugxbm3jdWEscD8IjrJRFtNf95G1pzjETBXYaeVeEDOvc7hhSaG9us7nZ7SO+p7HwR7D92pO3FUhxatEEu9Agb7fhT8xD7JNpYadlm5HA24uE2ZeyqshTgm0rEyPZazQuD7sYbuLOCgvPcy7jAe2upnVsR6i+1DAYKo+YhkxZPdBDa1xJJa+WBsr6pk/5qJGEsPAF3Ez2u4jBLjJPnvKFrpImc8P11ZTajCih1SWKds/t8rYkdPP3V9uhRaTxgiU5ey7+qKMClee315MPqrHhrv6FV8IaTZhArnmtfYaG/OKYJzsSUtEQQq39mGQ/mAx1iySysdW2tkfPMtQ7zZQ1px1peHjQMQF4h2C/zyjWbUuj93XSt9r8to0jKPsHHr8W3sl9AFSTbSRdW1NlQScnddS8GDj5WAyynodp6PgRj6kMr69C29Szya9uLPr6N0k9NO8ex178ydsKpB3AyVltw7rcE/KGueC19O9wz6LBDFTzou8YFHrWl4UQlBYJWMjrWcvXh+U9VJkobU/xzQvyH1C6uanTDJWl/wleQBlMrLwgHixtV+gTNO1UtfvmS5MF3PTOdwV+j8p2gOHcGkYdVKn3AoGvuKqCQq+pIVPhddW8GBDv1eVRvnbM0U9qv2JQbAePtZ3Rxa6HMms0jaHH6a+lCKWdErVJub3DCDI6M7FXA+DA1lC5wMQgkDfLOuYp7vl1n5tYY4uJsts6uvW/sw20Bu0Dc4sQy9Gy2RMquuZuNVjihGUecG8ScDQSwjuJxMFmU+T3aNXiOwLcbG3DKPMqPNLAqTOCJpjY57RxG5l0j+1kqXSPpVviADXeYEeF4O46hrA9KzH7M+w14KQF8Lf+SlXBSQsgBq0ii9KDuroje1oTsHaOYsy3LqenLgPQKxXivcqgEEItBX1VhuM1k2x+UnsomeVmdDeJMhwnbJPt1duMLX8hvEy5b56mDuIYH1PnaRD/eZ+sUDHls2RayxgoOFWC5GqoOUhSowrCFmBWy+vEdZIX9fosL11MBLRu+apHsReh8rYu800ShzJLh3C3nf8wc3p2sFoV1AI6sX/ILmRlU8qXfkmBwkQsFSQHAGKrPVIUlG01XWQBoU4O5YpuMkD+8qztjJdAozj2Ek3eFqXS1kg0MlshPJUu6ut/oVglHf82TYDS52YhlH7d/5bgE5inASMqK9ITb5+vFk+oKe7wNXLXzhD1PDaERjxKd4RxMSxrORinPUHXwRYI7B39qepaA+C/BXhK/73gl7dFCfS+yOvNxHnsTPMmb42HPyjVKV97yuQwmwORoEHOIc9xi/hr5YQe1rBcml+xQJw0Z8pgac4C+EvzthNeIp3i2Mt1hZB59tL4Wa7xwPePapEB3yNZiQv+/kgqT5zERTu31pKiSaHT/LcXEvK2KIG0Sc4CXTiQtryjIYI6BHu/6J2kKSZWawj1NlbgJbhRoDMg7wVmizpYR8JgO4ueyG1yUGSfUHWXr6a8SZJwzFFdFCiLn45wzRkabRo7j6pBAVZ7j//deS1cRo/thhrC8PYt0udODChWLurrCovE9DSL00uCNpSrvaqx48d5GrskjeG5iYk701wjHDU6z0hbqTPPCNvGZh0ZbshiKds3NhKOeBdJiIoQpScITU1TectTE1je8tTUbvGr7j8DSrjJmbRb69O2QsXKZE1gxbvOVmhlmxDWupmHqFid5UlWld9RgTDb+fv0Ht2h+EdeDzHecx6lhcZnSaHaqR6hfNgPbm6SNAnwWmR/PthMTgPqVE0ONRj9UYGw/DrCvDXWnZxZCmMiGKafSNLlWdRwgTYTvkJa/kAr2ZRMOsKj+/u3szR0/LhJCmpI+JRnQ5rvmdr8LxRNT092fTXJXd+h6yTA+9Y9qoOY7psf8nQ1A3+IDc/EmEzDh1H2Y5YDMNfmLCKGbfP0aM7maLjKkCdjjiZPr6MZlIt2N6UKYB/COvW6r/GBoEj+pJKGpqiIYvJzUA1mb3qWJSNr/ByVTDLgCSX8hhjS5yW42OhMo72or3sdfd4hn9VFfBeVaE4D/lmUuIYOdhaQ5pg+/by0zZHzORY4m7q5LZndahzogevtvZ5kT8LniFy9MY/1Gl1bfIhIFqLWivM5r/oXzXyJRkJbNtXlXYL7F+HPNFrpQ5Stvtw7nKyk96jjKyDwPpWu0SsecLhThcoFNwwQyNbhT+mU2LQA+SOOSyRdc/ZC7AxPX8voFn1ISvL6AthBCPCqvSEeOiuHEu6qFaC2T3m+T27oKVE7CAjw6muJVt+T6k4YjB5aGJRBFdvB0VokEFTB3zROVOg/VyR0li936hmWGpI82n2xpGdDEdwzpty2Z+dohdAarVgJedyo90BHWSgtU2iBhcHoa1g/XYv7Lo9E7kpFso9LxAcvx8UuvdoYSWMBkpd2HIBE6PFQfmk5QUHo8IRNg6YEBebeG0beVn/H6mk+bqSarw3aS+nT7rbmO+xQQ7PJdh+FtaKGttUXWpkyhYJg1PGx5HJa6bC24OrlBmHbL3N5Wx29fFfoaAcNgax9EmCiW8uJBEpHTXNtd6ihUXS7j8K6VEPbChRO0ZpDaTcaEKzTeQkcfyGzFyzytjqjHCMVoByAZtIphBfexH4Qp2XXWfoObBTA1NUhPMrrzUwW83NMjA3JXz2LSbBvbZ7sORcoI+fYOUbWNDXPsD7hwxZNwTVxO+R5uY47qG1yaWPuRAYo+y3ZyOwkbOCmo4ElqXI8V7I64ogkoNJdWQbvtSUzPfQKb9knSDU5jGMieYeeBNHzzxZ9NYfXfLOkojnSp/YP7hua3B14jtHMMpKp3Zc720ajI05NsnFrU9NuER/aeX1nu7d1AuS4PS07ZMHU+I94gWTDu0WWRyAirZvH2sw5RBOoV2erFGE1K5reS6IR8Ec0CV8o/pYqSgdlmcfE5XnVKSvwrUdALUmm+em0Kn/7rsVFI3hdA1xEGiukiodKBtxn4kMlQqGia2chHirpLy499UqCYq9XytsKog028+Qtkew3YxrnmQ/OHlt5ptVIArOLfXC5ARf/TosNuFj3SmkN4NuvQQXfBYrjbwgeAsdhlEbZ8NaX+13QxKbbuQxoE0kGpSMb5O0CubWp4ASxoQQOf+MU2CHIZDQBr5s6Nx4HWdh51PQ+RHv5J28riA5yWUTxD0zxmbzIaQRw2NMd5Rnk6pv+DqXJPSqeHKvPrvsYO9fxi17ie2fr8id3BGUhrgYZIHZwBCB1AhxDeeWRYlGGEEp7o7t+RVI6BfGkc3lIL4y9XLsYQq4D2XqFV4vjeQWzY3aO/qiR0xsRrYFghGa/DhTrIES2smCLINRxKczl0zmKyjw7yYuGm+Y3g7T8i6jZO8gFgV8H1JFZZlPLP883nbdHFd3ctJEXU1+srNZJpo8b/ttPQV4bGcm2MEnrtih+z/VEzdBEcpaGIIBzJwjmedjMqXJ2hDchP38KHtN+Z1Ot/wA721lUtPqN5Q1hc5/nUJGd4hAelsEskGHcQpbJ34SXJCowDxG3iMkslmGUjl0QxQNzS04XYyl1xcKzZwwpGHDSkMMGjTEWGoL2EimQvt/OFlnV/a7QCnDNK4PRdznIpa+Qv819ExRQ7XdBeVsax2rTB4ptLbboscKf1pvpM5wQB+g/6qQI8GY5saER+JbhQ+E9LS+jx+NHxFDDFRVGdITZ4DYvnoJtxEd5VhV5GkLXCPfewWlJPb2QL8Eme51AEEI0NA62OsOwV4BIHMS1aR3BNm1c0ctcDbcSUKZTfHvBLm9LoNjEj2NNtFNQlfxg9S/MN6z1JLhy3tznzdDQaYkx4WWP4gBeqh4C1VxyzS+zeJXTWtg5Xi3EdUH4us3l5B3WLkG4l1rytniSPedEFvxYJYZLkIeuxMqsKdOsDmDcNKwYdJ2FW2D7laVg4qc4Rc3W7LkaCKIzVCS5d/4J6klD8Xm6Ll5UeNql7i8LnS0C5U48zZIqidJtlmRsF42k2NW4hlx0jQC18moMbWtBk+7/c4tlMfmZpTx3EsuU6fGnj/mtg0TGtW6JfxGDZS+N5W0xZNqmS5xwua23QzBxZAaXMgNzJcAPq1cBJsglFWzQ2wi2IegiAipX9jbMhbZAxhDihMDvRYq8rSYrNO7RQ16oElS/mcZQozEPTfRa73FGgC11LGM+9tsK91ug2vSe335E9yj1f+szL6pw0UCWrZ9g8Nly+Wz6ZK/e68xRn9B5w9yir8X0bhuYyVFRoGKOtmb0t9BwWkGczrLY138kzypyhohK1WtJf3PiIjclRqa8KJWWcMoKfembfQfDQeQ3b5G/ElDt5b+8LZZO3pmNQh2A6Az6m7U2SbylphrlShT4F1yXcihhlSpAvdbsWUGzvPQbr+uCHePZr1bFag3hLUt4KNRKHXI/eCuQ9fW/UKx0zP9lMi+o+TVW4hAVBXBaas3bh0/Ng10BEfYZK31xTiRDWTYG5ehYrFyN4QdBqgATJKkK1takxKZN0feehZb2fQDS9ZyB9LMxdU/lOkj/vm45/NwLf3lb7bHT2/0vzIWY4+2c3EiseGulZ48r4J0VoVA0rQoQfoFC8R1a1Sm6jMofDmxPqpWvWCR7ppe3pTFo/jKNQfMAs4vKvmParG5jzLPjxw3hSHXc6JswIYnNLYC0lb9vkXEYVH03X7LjovBXcj5jje+8drH+fIxoLGdROdY9zlaurdZxjPnEtV2WbNMx2Gn5IVnhhe07QfjPW7IqzhCW40JaUrO6enNwoEGf5w+ttO6HnWQR8Vg4yjPiGkCshZ8oOtrWeM3BHXAQqM37pGjlqsjFKVb4c6enPS6IBwRG96pHst/R5G01pPfV4hosy3iDd0cHol35juMU67NpQDWS7Zvksr7l0asxKHtdD0EAF/YgmJdq+bXwWYX5q77+fgE+5wV4kda387caLNorym5rvDXbzYD5G1WEMZLYJ4SVOGDl2Sse035RTb2o8IjeF3m9mZ+5ccvzNzp6qG8+27PDLYLx6ru8Q2v0LSoSgsrFOkLql69GaPbrTt4WJVQAzp0l6tBvNbwNc16bkvspbhfdjcY9N//tuX1yPrT1F1I6OE2l45VpKMsZOZ5rPMLCrOAPealMGRfI4vIxv83PkpisgO3JhfChWqeH+YpRgaZzhmucx7qEw59R9ZAXPyaf3bMiwYLpiS7ho9au5Z+viuI8fozv8KkAkZeqnFErkvJIG4ET9ZARXilrMRl7dMBi6h5tDfusQ+LM6EfGgUuGNIJSj2UM6peIqO+W9V76LilQTCKzXnVI9juqvC3t/do0BsRmYjTv8v4ySbJU88fw/u66nXzMCQJ/spqYYU/yYo0nnbYwbXu+WXp4oUUWWHm3aPqN5bzMbcdIZdtxtsIzG1zDsjvefIpo4jnPU06LZS+a5W09Azv1hEftwyj+cZrhFuIfnmEDR1EVpfntKwnGPYsGnWDwxj5Eztpg0dcLuPxLWA90/NfBCjq6tkLIJKeyxs7EXKcaUONxnIXIfPopyuqbiNoUiku0xgqF281RK0sgdHtBIm9rmf3pW4IecFfVIXiTtOyqs5ofoNsEpAF4mUe15+M9H8/Gx61wD8DGHKY9F++5eDYupooSedmgVYKcmXiMaM/D8rb6U8WbQKeTt8vcNJnv+EVellgHT/25jEe157Nt5TPzfbRJTvcJVXe5i3dpW798NUK0ZwzFhLKEatJC6ffVSbY3kh3nHWJMZP49cOLBf9SoRqtjzE/pQVVF8Z1j3tk2tKN8BSLc86S8LYZg3vH6uCd4EshV14ipiBGImxEB1FY0qpXCQF4lJwkQzaDZ0/F+yQf9mHh7eGnfE+Vw+5SsEWlzhpblIf2JJGgDXOhXDfhgUJRDCbZEBaitNZRBZdH3US3dEJivhiNha3jZRUf9DCKp9/JZIemKJC+8H6cj7DR/zDRpVetNEiSMOZ9/cJf5LEM7R5v0Kcj4jNqZZUxHR5M3cRjHk7ehz08USA8hTqTTu5CG9HS5wMLtski8nw/AaJxyMjabXxzntf9TJbgTK3J5F6VpPwsBkpv0OzOc4CSwjsCnrjfXKkwHxJJcN6KrMTA4kBGMSscZA3qpNuNuues2LJ69cqNZpqrjxS+TOHy2hlfdqcqwcX0AxPwjPMsLPkeGra9iiWcyDIGsz/ZlqUmVPlHLTXTNRZlai3mReO/QTYRXNd5V6VrAteyQWlzrrzdRcuvi5tbLqw7HXlbJ29JIi6kCc7U65kQNB9I5l3S39vJON3cTaxaRhztCvww5VPvV6LwaJ7KI4lNlo9o1SQPR9An/n9cZM1S+kJDHu8AxLF6nRekZ8Y3DVcRQ+61X7Z+1tY1l5Wf0UH5EZKl7Ouj3IhPGuJec8rZginkn+l/oKBdGnoS1fU0Y4vIJRSVm2uZRbK9I2BGm/XpRrBe1pjHRq10LPxp2Tkg3dUzsdI5vLXO/w8s3K912GGGl9Mj2i2W/WJ7TYjldb/Kiwq3eJE75uUb194tj2xbHSZ5izlmobcwRpM0wEcthMIVJoPQj2fh15DL6gfwwNNkEvmT+50y8SE4SlK7IXzMka+q44ijPbpLbuhh7C05lezh+xMKGdc+bMBlTWq+zPinCxK2doxLL09PsRuUjEqap9oUKjJy8UREuG9P4+Qzg6ni0xVyNwYfLYzmUcH2sAPV7COQpi92zYRDu7LI/vWJQ7TdVxbIOkhKj4Q91xtc302Q+MsrGMdH+TNPGPVYLae2U5pO8d8oPky7EU8tnI5paXZ+m3jNoY2QDFzZz3dkni90sV+ZeMY9EWL5DmzR/cozP4FHsJZq8rXZX8hVpyyzqQPr1wCkhlH4DX5ZA+TD1rithGtKmbg2Su7WqNpdFlJXrhD5HFWIqIJzjQCEqKWAwh9NnYxoKFEOuvaptztqTt2R8Bx2IEWh7Bq+xBhxcmLtdsh6Te/SJSSrp4dLl4himyph0I4tt4nfLqx54OOzIYISjjhTQz1P20c9oOKq/1wbkbW2x0XCi1BtEbJNfLamml9uGgdhB8mXn9wkm7KKx36dluyl2i9fTFyeQRdXPtiTyEOadOQ5u+E+yLc5hwey4p7GbzmLKnNG4+OXmpkSeTuvUl8sPxWFUxXcXyZ+exvkzvMiblz+2x9ONvAdJH3q20R+DRZb9M9kcFPFdCG8dUmt4EcpfFxuUIzgWy0sf4yOvtIpbMKv5SMeSWs3lUBJVMoDVvF6zqqQmNcRpeZJGt2U/q75hWbKWgmmheHVgKZ0+kb2MWa7jaf2E1teo6IROml+/fEFfO/r95U8CC4xgiXqE1VdE0uWjvtIbdaXmANzCAt6bzYSoJklxLPeeoNG9h7ylBSfoIiGi84zGcplN0yfckWSDu0uy8ZMBjiq/MZmCg7LM44SSsNvf6CsCjaDGWzNdnlfd44lc/4+z1Ytm2SprDYt8sMFAFfC+QEeEiYmX9O8v/y9h+KYN9gKRabAbAtfIm/GYcCNfsneI7GEvDqjORcKfyjhaiWITU3Q1/tIuGhKLhTWoErNHklXiATHJYjxvqc1QOCSG50zSyb45vgTvDygjR0SbOTTpB/vUptifvlmOmDra/faaYVYDHsb61arrmyEDg1Wk3MtCW7Mu3NTu8a1yHHMxrXLedoJjsS7f7kLA686llGvV1SDO5WvYMK6mNYB5x49dK1uyIVaepvoVPYICSZGTfdpi+COEO7VUwa7PsDrBOdiNBYl7fpCVD6i4onyiYgoGTsZnDYgtt7GIAX6DGHg7eA3o+EzcBsyFScsEfjFeu8DnaeKD1STtvMK6F9bB4gqtjohJhqa4kXGJvirEkeNaNlxp0B60DdBCnZJkQzF86kPUVokPqFcdemmnIWiQLiygFVnAFswpsfyqVY5ghrWrnCOT9tsqiy3i1tapZUYODmLDFsSGAXms5qz306tXomHHiYUkfZiBeSQ03SW2GYse3TSPV0tYFhrjBhhJKSXDsxPYnxmZCqS1SfujiosxWP/e1nAZI+MAERRireF1MHPeAjADjGXGtC5jP0xScu3bNaDt5hg+OBU49OakEFeXAzXah7yHZ950/eUrqOjRwrqQRWhGYVDYPgVKN4oZJJZuvoz2Q+Y9w0Xk1WGa35J7DL2BR4CE+LIDsmFIEfFOGXuk3Z+BBaVzshNGH9L7o3xNr8x7xlFxCQ8s48AWzpYJBfQAH8oYfDv4UDaCmVhRNj8mzXd1ljNBNm/znCX0Qc+r9n+5JRIEBw2SI0groyTcBmSbhJEvz5XqIcxhpVTOk5GxsqmyNYzZvvxlyjT8Q7tTMCb3OK/YxvYz5ngICzDmeJ6MGHN4U3uZs277ZqxWVvKA4ImmhbE6yvB4zSVjOMOJrBNznEAkdN0FqfYuKZv07gcbPCkkW2M7GtX9i6oSxFQdvA1TKduAzshmjOtFmnal2xCmqzIdWfoWzMV+AJp0P87RH3VSoMbFV9tpqJYNZYLuV6b9A+gKwOkn0UmsGZFuBhlnRCKTfnT1l9bjrjDfJPeoeKIB/RoNawSs0OEclLcxaojX2H5Otn2qejOfFgbS2YirmHpLc9Zhna1SdFqh9UFVFcl1XaEmaupqKNExnAkOBR9KqzswqFFX5HsPM+ZtPXzYjHC+tWDDAkaWnb7W9iyQdiiGZ2lZPaOF4MX5XHs7eMDWjWUJvoZn0ZyXlz54iwOyZ+T5WNiFeUOqFvKuLMJ7O2joadvvzQ39kVfDBEIFBbe52H+kzVgc07dGUEpHMR+XSufLpAtdna3hUkOZyMNPzKM7vJXLxrAAg+6gECVPyNxE1OhaaC3mEDDEmCycDVeC+Jewnqs6MgNfqei8C1b0lqlHwxjxhEYsSSsqpKArz2mbBPhP1dbWyEXtiOYTkNr5NOkKW2+rONtwR4fqzMTPO7y7q8axEAPv4C7ftn+xQXFykzRpSHozmikDq2srWBmu6MDUmh7sIHubjWg+Rjeb411geXgkX+gjdFcSjpSxnwMuMLJQgcYq0NChO1AIj9GyXH6peAx3hoXjwRsmvYMxbOlGomRwN9muJu9iW46yW8ZrDV76y684/7EvvmOZ8I37+mvwLL0Ku235MrpVZIcRYQN7bLCY5SoYLg6Y/KXB+S0qkiir+mk5ytfXSUYBrfxZTPEoCKdA4UBT4w4t7SBj29H55ILtnJr0bJvcalTjMzzQGaDYCo7f4fOdxbC2Y2ns4EnPYFT/qCOaIfZrlnitChbPViyNUYeA9TEa+JKbAdTR7eB4aE5NesbW2zbed90BHMR+AIZ+TgJ+e6T6MxLl3VD0ljpTBMswuNIcJ4xx11h+Ceub7XxbLIKtMbbxg6Opva9k3GrJmkpkFquE4gmwVNT9kS8b3ard2tVjNODlVpIRf1isKh7F0ovL6mhgqv+7WNG2SpNfWF1/Fjr51UVE3nJ4lz9kaR6ttLw1Bg/MXRxyyGzY9XNy3oL7MiN3wZQ2ug0Y1VyMwy4TVOBRkxypbG51GRPA4BCH9ZA2PCZBb5kJnvLZ0nu0eigzsKh6qkw6wNbbAgbVGSIEyAnYchfNC9Lez8qEu2tCOEf3CXowtYONoRV7bwPosANzLewSKypHMN+2Dc/RzrHkB5Rubuo0Izlfx0xlxEHS6lqmZWo686+8dTlDw0tmy9haO7C5+Vw7zxaM31RcjP0/o4eSxphqk7kKkBBTd0A2TCwi3qlkrtLuz8CV0jkxaXvxZK6k913+z55xVFzCA8s40CGZK4ge4EMZg28HH8pGMBMryubHpPmuzvJp+M0edoPBgyeiX+gBt+PHChVZlB7U1R0hb+NiyT0qJ6WNUW2IVKqKNuQz68BOZa+3GtIM691qjm1sI4sJgJO8qNc0B7WWwUVQiJt7KBvWBVBb8WkQa7C8EzNwlpy4u8NGl/kmiQ35aAwrZSQKZs1JHPKFWAnuxVy8BBN4d5jpiv77vsjrjZqTGEApG1lzEIsUYB+mb1u3Z8r6PxfjAfNh0vRQaxuEWMM1BkKmGfIU4qvBLGO+LeU7oOvzCrzRfBjz3RaoXwy/6LUkZsDhVTAGuYz7QL7eEhaUjGFWHU6cH5PmaYXFWPFLsTJ/kg4ChliRwtmwIYjY/CW6QPqbqhczMJKKuibNj2suzFHa88AYLCAX7abNA+77bFy3kyeGLmf91zK6RR8S3JviqU+EL2U8ZS3VqwdsBZe3IeAGFc8YbB+XGg1lBqY1mkOTfiz+7AE4kkbyWfFTs4zn4t6mNYB1QZm9pXw7GsRSTDuaN5NO0ArLbu70vkvNoxycdHu3vUDn8e4OC0p6PtcOL87FLjGbzn1OgJyA4XbRZU7a+1nZbgdd5d4nN9VRVKyuznCX76ISrb4n1d3AQTJ20dSD2LKrYsOVumZkYhHi/nCBFYa9moH3DKfBiBNBDIsz5kiJ6FlIxy9gLRVTumqN6gYB9pStguVlqNFQZuRp5Rya9KOrs108/JVdYnaMPKo6GzePW90dRdR8MEsxNTifJp0ZVVxWbf2cV8jkjDTASVVWAmKtsjJ4d4c1JT2fS1kV52L7z0jn6AEvn7McIyi79aO1vasqQWwIwNswpLK5nbLSm4xkBm41mb+dsOBDAzFTBLQ1jcUeeP6xachtwYjN2iQVv0s21P1cTaQxGJgKvIWwSvs9xro72wvc8RnWKzwP27+5dP1uzswdq+j4YgStYjpbWxzcAJTlWsLV28OC4BBm5ERwjkza7xEs615AurEx9ljhoAM6G/CYzX1WwpndlD2ZS3eW0diUpzZb4Llyjqq6yM7RHzUyiYyAwWF1gIG005zBJnZMZ1aNYRZtWTVPO6EnD502lHuyCsGD9kIKQCvNJKfNHkVFxbxZrbybUdSBtZQxuJ2mIm9Kfn8of1I63GZh0LNZ1BDtVJj0Yqi1oIrMjUS7bUhrTM6Eu7l/aIexBL/u5C4ijELnViGrMDmn7qKvhW4QS7DpDnpenOVp+i2v8CjaAGvy4SArH1TP9crrgOmIOHCrNESKpiBuHTq/dQxrMJQZeNZg7ozYtq+1nJJ+h+Ifec3nLBY+y5V2QwSgEg/WtVLpTVuHtAdhjFvH7bbDm4H1befbSMngKy9oTYnrApPi9ix6olbG0ywheHX3OopasG1lXMHOvKJqzPxiI8jJzKgzs5hLDGbAqB9Mva3hwu4KT2AbUx6RITDhTae7c8PmAW7VLY3lhbLt6BZgf918m3SJr7vYaiDTeo/n/mN+e8X8JjwjXQCKOhDPMyA2fK5qBbIpcp3fOs42GM8MzGwwdya94KpuBftq7WwQ8EQMu5uGNdUIZubNnTSnGXGhjvssuc6V27biVYOFGG1nGYzmE7mor8u4SJoHw82yrIFVpCljWGjr1DFwUwulXlN2ZgZG0xN/J9gOj/c+qtAnVBKn/KuTIl9r+U5RB04Iz4LbpYGXNzQ/2xn0Zg4Tqp74Jr1g620L813mtqw31JiU8ZhmFmc7sS/zM51IdpM+DLWWO1Pc3CQp/oKudD41AiR4muiArM4SAubZc19JuzDHSUBGWKOz6cJOgwdxymWCJoNSnEohcPhcmtpfT0rQWyZTX/6ooB7HLKdT1TzZ6HGk3nIuH1VekOezknVUPB0/xndRdovO8VI7qgvcRPwk9/3Q1QSdQEglK88PbSsg67Z9n0YUGvdpBjY0ngWTvijQbAeD0j/sOHNUJTxLjtEvzItgZ+ZmQpDgFtw3qr8Y2x1G8Y/TDPcl/mFnYNFVhFhQUseGKbXN7pTl2XQ0M/C26XzuhOVGNhidn6em3sw8vYvOn4ZjWZChd9AV9B81qtHqeB0l6UFVRfEdvXc/SRTHK3kViIlBaBsWVjRn+7T50hysH8oMzKufPiNDZrLgiYsZwlUzkFidj1hWQcOwjmw6bgJg0lGft07O6kYyL4+C82XSBbbeNnAqs9hYFrMTeyxh5hO2bKsAOytWzFZxs3pEi4leYE5N+sJUW4y9T9ebvKhw327o9hHfoVWdosuo/CHla3kViKFH0DaMrGgGSmbB9nwaK4O+QzMwoJ74Jp1o6yXZLam5GPMdP1ozn7wK/HyoI/MpmlmG+fQdmoH59MTfOebDDaV546rcsYmaJ8QKcsYbYO15D2gH0kNVDL781q0bymw8K581wyP+zXynpWNcp3rCdSpcAxXdqllHRfXl+l8orkgResSMEFNTWpRleUWx/Pq1REdpQfij/P1lVdQiMxPUF6hiX9UqX75ovjN81T5iJvAoVz16PIoqdJsXCQKx9OVPWlz4FwlvhNC0RVoUH/M4SpM/0aqdQbhTPJS+ax+j7LaObmFsbZlZ59BFVdAYzpIyn7R7HJwW+Rkq1klZJt17yxBiHkaLdPzsuYhwfDeu62GepmCv8Hejyk3YqgxFFz1sOCTVcLRIWl8KkCa9+4muI+RMK1k1TZnBimmTynxC1V0OTvkYQo8QSxGEV8U9Fq1gz0YAxsSm4iqrVDRvQbQoD9P8ljwiCOHqyvTc1BhsQVbqLOcaFN0jNRCO4V0qHX1UotNYbp4lcVUXII62yJQgKkwjCGPykGxSSYHWktkHwPSoUZrco+LpMlmDPWXLTQc+JMhRjJ1NO2SLtg8zPknSSiLENHVMG1Uy1RjGgLca+E9RVt9EdOpVfWfBtKh1OK2QtR242KA4uUliqgv19FP0GK6gl5NgtS/0BgkUmwp4x8bMmzEl3mUE6l5DqSmib1GRRNkQWn+Ur6+TLJIRR19L2/A/6oh++ZoloJxhy11HYdF10yZMcFsj5TtDkyVYdL5NiKGTwu0dHSiB+/s73ekhQQU+xsGqRl+oRfMZPZQy2d2VaZEcP2IZm0XpQV3dkTNbs4jkyrQKXttY/xYyhJl5oNoEjfQEx75FrUcUyTCY9YK+TS/tBS3VIqIZECAcbToJQ52DeSMD3gThpx412IEXOGDs8CMqhthVCM3oJ1sIzLtWJmjIGw9SNM1LGxo0Ym53mF5gDniDg49MpxySWhsikVFsnH5bO1omeSo8zFFuW23fxhnK4P7xmeR0xw0+5w147BBTFNmile44suRLWtpCqSZk3CRJJWLZhgluvZ1gCN0GTQVsRL05KmrcU6Nrcj1o7VFgr0x6I0RwSiX/2OlNq8GwsVKwzjKOS9NRrYuYAek1hCDpViNzzwGuxNHtj3YmU7lWwQSzaNB8LRRo+kL9xoMyhJUspUjgYfR63R0+ylO98ho2HI4ADKxSOWyUaH3WtVYo6kQtsbL0fukmnfgUUYkr7Utbrrdiwc6HoFFL5kBqcby+ROtNKlkFEJyp4epJhZiHMT6GKnByIAZSnYCtFPa+MYR+4EVelrhiqkDJw+gH3mwncivuCMDgMAx4qMCnYtDTyBy9Bqn+zMW4d4FnrJGPnZYn15souQUXUVemXzXNxqJcMGMQo9PpR1Th06JuqcOQBn2OSrx6v6Pk9g4k4wjAFN27BHNDKekpD6NFOnJagTByTkI6YfyUxSpZPBQbnPvHt8PwWZ+/yzdCqhgud2vPoWOuhdXXh1fD5SNTR3GPOFTgb7HZ3DmKer2rQz9k6cWmcFFu2kTn5cA2MVzA8n4KY2KZEpK9/tVTEYbWjA+sJKUff2utox6MfWLS8TfIV9wNsUg+TQ35INUVITICV+AKImrwA4TkxupPzDxNlaw3BlAMhYUDKdNcvauoMUIxNRcNmauby3946CyIuucMpGz4vfOAhggsKoAMICUdSDC+l7/CMhDLwhgrooxbgEgVg1ry0ekrQ7QTXAwU9DNoAVpWw4j9ycq6J1z1bhQAJUFAxdAgeJBenAeFilwgzokp1GUOUdCGB5GPgIOE6MG4yihIwSOaiQicN4ucFGNA/TjGU+tNljE6gDhqrnOgUB/Wy3RUJA8AJR+MCAwRhvHPUhAGwAVQRUpkH4IcJmnKPCymogoHajCccY0A9OEQzkSk1plryBKgoJIAqx8VX0VFp8H1zIBcAmKFFhiCXp3DmlILFIHkAxFgIdIwLnQKmoioJtYKSYNH+Zp6aA+efDA9BDj1OHhwb4YBkQL0kZLaRWseeeBd9Z54gPIMQyoUX7ACqErzjoIqdRrGCmnVEnQBqNSZYPVUgnI/KMfDJX0IRCUuqYOINYigbm3zKi4SYBTykwMF1xfjlqgSxTyqadmlcwW5Oths0gStLnO2nyJRlPDyUamqQcRivIsVtFJihbYu6RQEodzAnyZ066FtxtdVCkmzHue0S65vGHaNVpAMrGAwQqheAMKBaAHaweMMJeCvxm7eUvE+htOK4RG4QrQbyfQxMohEY1f2ULSRupCzr//KSWZUXTt4EywKAqtc5/WUN2pcvt5H4QDhZ6XthF43kVaxIMC4phHFLUnMtTCtGBVbtyKjCwGnJN2sROt1qyH8Q0ozEVY7LKGKgmKG2qAU89TaDd+wnskEUPNR6RnMkVzzcBfrQaQ6UIBw8sFA4BCJuDgiBXlAjLMcR0ctjwObpAwlr6NlAGlVBYuZk1HbCEBSJfZAdNWvURDcbqD6tepHyFk3BDhezICQmoraIavrK4grjYXTk1nT5rQEVwUAXsnC9oBrSwc0igtIe2zglac6GFJ1A+rQAej2y4wpgq8TdVSn7eJRYnPlbjWFZ1xmyo4Yz6mEqTzMCJ1HMY19lVsRWDD9uZ+B9rYhsLjkMqrpfyiqKEJxTe0uxii0FDDFpKC0OiBZPwnGXZjPxqPqkn7/NqntRRX9Tj7lnMyqRKk6Mo47d5qOEQovqrCYFpqYUReA2eHi+KeaIocV4r4sHNaCE52X5fqudaX6alzXefBKRXUKaisVUx5mQvrTSNorSaHFXKjxWJNJic5ijrqQYvuJUvdAPmlaLvFQOg23BUvZbyrgzfTOhUT11UW03qRoSCEipw4HqR/SuII3hTh0kO43JEnxpk+fDmX8fh5AHwmkfEBwBYg+bMIWBYUkCGeIqRhaVmzxIpDJUBTbuDVZZt2qz9F9gh4MdB4OULsCxvDeTnUw1hlJ9AGlm5s6zYin7KhASzN5TcPhShGEpaq8GcXalDXjQO4uG5PS4VUEko9OgIXoxeSHUhBKRDWxwytpsHMiHdJUwfQQ4NTj4MG9uQhECtBHSmofl3ttkKEEUuFlCFYI4G8/exChKg2Z0v/erKJ8yEb1IYpqEq0pqGzW5MQ++312NiV1ASj5uERgiG5szjgFkQBkc1CE5pnTk4QD0wxjDC0lSpcBT0cVDt0cZLli099JaMLCaEbAgEqoEenJwCIBaDBK5xeSOdpsgkrOaGCM5rEZSxieaHDBxBAo6iU02KSMKrHBwJmsdWYsIUQHg07GITCBHWhDMwoahNWBcPLBQOAQbbqkkwq6gKgmDqVr2lTJUw5C132VFDWmwVyyE8rQeTWkVZc7y8MV5MNS1lM5y/NpQA085+E2FJ7zk1GyzadqSMYG2nJ8Db9MSMCmAYB6MDO7LkF60JHSiwfRrJ8BUroK9edUHtM8JFBYj0Qgk84rrEbWhJjHVtTl4b06wz2+i0q0+p5Ud0xeXZE0uirywWlqQmRjcggrqKZDLGOnUKd6KD3y1ZDiWE5DuIJ+oGA9Ff0sJJO6DYCU0jkKRcmv7IQak3Ncy3K8o8pTEnbc0ORyj2S51oh+BkQjpwZIqcRrU27rJB6DaUoSAGm5leqoEl4+JFU1iFKSrOIKoilbmFiPhdrWrlB9JbvBOi4bS7yuc+WYPItaoqUk5CDk4xoDyhJggbGrKjxTLkw2Lf3VkOxeToQxoH4MI3gVSfQqGowScveVUdn56NwlI9NaFHhA3RmYg/c6T/O4JjYqjN4g0EhzEFIlIKAKsFQYv5SglDog0omF9tB5PQtJYQ3uigwYyfL2aWZ24h+g0ISXq8BVIkRaC5ZNwsMZSvkkRz5XeLjQB9W6lANbDFK1Oj3pN9caFRpW2CeksBbjUlgrPCk2k59Lnqbf8oqmW6bXpeyTqYBziwJc4Woir+XvxqLALcnTKkn56rIjgO/WXAFP5QA7hGldhXw3RAESWfY4j2o/MW0PWuTAG0MB9JbxwztXp1lCXtVWHKFUFVQKh6IerMwIzwYp9RkV+mlP8+CzSFfik0Z6Ykrrmg9chsKExKanfsMWAaprJ9Ul5ezwONOV8FCTSHMVuHzQilpgEtrR81QKYqrwQjqm8FZVUPKp9CMQzmxgKq3IiVJz6UI6mhjSQkcD7djnHLPwtpje9QiG1nhDgJWkLhbcAy86VwsY+cSUG7+gdnVS5GsV6VTgCm1NXgsOu+DefVP6M8tRz0u6y9yCcAyw8diGOoGJxiCemGT9G3tXCiOKCKQQsDwsKK6ZZ/9UwlrANbHBpH/dTxvDI4FUbTxQBXgvS02cbSUIZ3BpviAv6eFzcbKOiqfjx/guym7ROZ6m4a0+4JCvraQ4k+vqwg895LrUvnq8IDWH1wrDkpL+YUzDMbThIEeVQlBtjHBqckneWlTqFto68pHqqoLJ26UvRypIqm1o6jz3kvYVRjtdFfvBKkx4QYk6j0EPfLvx6iSBtxMFtHyA8koQDWWvTiooqGhg4mBapuWr8SOTStqNYY0GNqqioZsxtcZIAVpxD2tOxG2jpz5NmY6tZMsaLAmmZEG2HTD0XT5lDsQdPX15NX5LWqSqAlo+THkliI78Y50K+ikQQ7f843e0AwS/2VBOAS0foLwSHNtmTDkF4tkoN7xoenXWPUUqoxsAqxucWEVOs9EDrFqyAZghKaicDQXVfnvdYCE3U1GSoaIv++01mYl11H747TUGidGmqqP0U75CadkVdK/TDjXbLy8uNlFMLln+x8XLF4/rNCt/f3lXVZtfX78uKery1TqJi7zMb6pXcb5+Ha3y129/+unfX79583rd4Hgdj47Iv3G97VvCyjM+7nOl5Cm1FTpJirJ6F1XRdVTi+TharQWwC6yAV1+u/4Xiil7RPXJb3m89qbsG2xwMTWSQOIcEmhiEO3Dyuz2nkKaorv+K9OkVGPg00PAED4tMPh0hYmZcUg/XvIijNCrO2gdTO8m7wiPP03qdDX/zvCevffFUVmhNfo+xsN/NsZ1mcVqvENYoE1w72nA9E0otMJdnUVk+4KM8LqgQeZCRQw4BmOPvKo+RDl/NMV0mVcoRs/1kjuMwXz2NUTRfzDF8QlX0H+jpoTGRsJjGJXYY3yHmRW8e6ajQDi9AM+azOa6PyRqz1uoy747oLEah0BzvOcpWqDgovycrKp9ZtHyZOdamxj/zjBs6+90W2/ci2rS+CBDSUbEt7ou7/AGYKaHQFu9hTm6I+QXNl1ms5SLJCyxNubXcf7Vcy5fRLbCc6VcR02+vOfHO7yCvhS2E29D5Dclsu4oelW84WexaPSbBYGayd6lqT7ODiXuX7a71Lik3afTUOmKwmMYlWzPb+ANxIvKb6BaJwyRLa27rBFPXnzGK9pOFokSIwA+k/7g1rPExx51P/kSrtvu+4oDH5yIUDHBMwzlNH3gcw1cLxaJNmcTjYr9bYCMEQVgJa3NqjDByZQ5YJQjtcQHrZlSwPVzfZ7Ty4nVJqi4TFpdW3VaZ2PX4qE6bt24gtu4LzfF+zZI/anSBcnJAH2PlisxxnqTR7eka94eY1cWhA8UWqn2Vcvo8+bD8keOsvk6T8o7XipnPz1jBaaTMRVVQV+mS2tsC7GMcRtetTItmmjUfdg/qei8up3GJPUZg1+CKbMw+xDfqLK1vE87iMC6xwXiZ17GwrpjPW7MKzlCxTsoyGRLL+awAHpsD9+tRbOtuF9bMOTw9yOIavm4NB+lSSZpzj8qDy4Bz1NW3lWtOCoS6cERO5RiVWBiUosfjR7TecMY55rMVrnb7bhzwOYSjMnOs1Pmbw9Z9s5GyzcpqXAPHgpYtmXoFLyW58zT1lNYYg4uEBqvtgj4SSsa3VxsQk/RFy2jhxEL+JfuAxeAZTRo96iBXZrFe0zR/eE/D0C/zb3nFL12xeI5zg9yKhtkcMzj6WsW8KY0tsbHxrEB87Pf5TnMLypsuUtRX6sARsoayR1Z5GglEWuQxdN/mlDyf6/U1Kr7cfGuSH41QjYue8ZldiIT2ZUQ2UtqRHdUopmPKZhlArDmUhJ84DY3xERf//nLz32S6fTtz/91Dv+/ulmeiddcsj4X9bqG0bvr4nlGXhs82CvDBZlPk96KdYfhuMc4C4b1s9SUTtrlxiYWZdrOSYByXzM6lPHMepvlt+2iDA18qa0/Ek01zl8TVbDxVbIGFLxAeAslnzfeK/b74LCnfizER1ur6E0nqplFBTA+f5/X6akYvMg773QJbVAlmi+6bOZb2sZ3/Qvj4UEXcVYlQaI33cy5H25dtF3czzw/5MroS1aQ837Qv4fyh0MKTKyrb0XBeXMz3xeeReQDIYeqUtafdS0ThMi5Zbnfq3krix8l+37ojShhTuIeaPLd+/L5OJBpyU2KhN5bk9SL+wDx8tTDcNCFXI5tN82kJr+2uzklerCNRJRBK7TFfRGkFY21KLEx+q3WSdZJobO0blVhdisIXE6MCix52SQl4Qo4K5r6UeIdSJMQN9B/tLzf6eEvofqMvXOqS8mOEzwbwiZYrWvIcSrryMb9NMtCIK5baYe4yGUmRCwBbs1cNKTt89ipJLhKDrUpac5qdioZyiazPfLabeRHV8HXePQqL402U8b4C3UcbPFicFII3K/PZ6hqmQvjbfZLFgFMzV2jRRyHq4sgy4qJluzf8vtZ9tcb0FsT01gbTP5MNMbREqejSyBVZaAV3eYaaywFOKWALLNZP9AhhYz7PIeWX0uvpGvB1dW9XkotaL6s5jawUZZutXKO3wmX7GBxwYTwU2eKE3WX4Mgu72kP+EVUVKk5LwKNYLLXAfFcgpMINlFtdCaIiiUHMfJmF3K5pJPNl/i3i1M5xya65GT+z6/iO0T+h6i73dNsc43KJ1tIg2FYZJY01dowzDs+dp7cZSex1R5I28HeG46Lt4UxWt/NkTBaVC1+q609lUA2nRAfYhq+vC3TfJO/gNsxRya6J84WYu7sR9ePrDovjbTNcdRpuDuuBuR2JK6iq2JowS0CLHIoscLYuF23dI9FqB0NYyIK80jciBbLx+b7liNJ8eT4emPvEJSGjCHfuKH/WZZcMcF/vfj0/8208GYnkLr4rsrlvKvDIaPQ4jbgHXVwkMOatfEvK5DpFp9kquU9WdZSmnNwHAWYNB7ijOdYk614stbC11WkqRSwULnmv1zERWmN9TbyNA4qXDmfo6siVTxhiv8FoeIvqTl0UHnXwAbUrHsJeyWp8zC7qNaxhMcVO6pUEPQxh33vqcgbTB4RwGoO8ESmQg43wIOaursYlyysnFz9qroPkg8X6iLL6JopJhooC72gVdAEigzFv5X3Fh543X2z8BYan4ceuAsN3i/60dSClgS+z8T39o04K9KW6Q0Wvg3FeqBCEdQuDugHjH5VbrN8ayy288GOiaBysVhw2fi1roW1mt0vqzc/u8N3C6NLW4WeW/W7hrZWlzfJk8o6P/LaAcpv199iFN0nwwxD21Dh+3CQFNYa9i55KmDI8jH0r1BmEYoDWlhzKQruJyosIK1sIZhmg2EKajmoKl/pCqVWviYPfwW2BkKibiqV23oR9RdEnFSi2WZf9E3j8wmQKbORXW+noKU7RR5TdVne8BIMgXFs4Q0WSC/Mog3FohSoYFI0giSEIK/+4u2RznEX4/CcIxVGRDU55Mga+zMpHJCErOUq72s2VjeAzIoFazsPytDwuBdrSTzbGxD5ZJ89mXJGVTkYMz9k9XrG4cnPpyGOXAtmYMfP4xz/qiJpueDvmqMj6woPWP7iPkjS6TlIBvRzKrSV4EDCExTwkmQK7WGpxGsgfmrG3rpPC5QNQbnVKSm6eqL3jJC+6/h0ifDYVTkpyQIsLiyj+QVMMk8z3QogdX2h53pY/ySMcvM1f75G3SU0heGqTdb2G5x2GsG0hetS1wENY0g2t2qoJvzUBxVarjuw7h/XTYV1VgluBUGqN+XtS3qVJWSnQ8yAWlGlkTYowu5/hc5hoGYMhLC4L8GmIVk1iPtpoVGJjfxRQWeP4kq4ANMNXa2voEbmhheygTYHFHrRBcRKlQO/GJW4Y++u4y2QNXNYpId1abC/stO3xcBYc1poXj7MKFSXEaBCA1a5HBM8IC4LYRwlodQI2bE8FaHUSu0xQsw45ycgVWe3nqByeuj/K19dJRs+3wDi0wFZjwTKxed3rYLNJE/6sAAKY4/+Okts7/hGE9psFdYBznv3J7nuy4pG0nyzoBYzng/V4+j1CLV4UYA5tqQSLFGhJz76gblSBos62KQeIYqzJPSqeyCQK9jOuzBxrp0d+zRLhJpsvs9H8S9RuZ4ChQix1wPwJRWVdINI3CfYRhEMLB2vRzUgodMBLfihxswAW+OtslSJ6eSla+4RCW7xnqCAh57CRSALi2AahgbqJHsJ5FHljasLyVzkSFmxr3Hj6jdvLj6fD4uDII686jScP+dfT373tMXj3wJVZWdgxp8SYRIKTBldk31MZYqjcHjskfviy5+P22N5Alkd5vckzMWYIKrdyewexumHrZoFe/VPlX8avYwhLj4Pmjg1rC5DHAVu4NVIv0BNcHu9v7d7jW8RfTsQyfLVRPsMGDB3mFT6sS7ECxTZmziQSTJv0k425rplrmXcfVL53k1PjahxjZbdiYqkF5oQ6IQkoh882vbxFF8mfvGW1/+roFlhe5hf4XBpXMH4drH3/v0DXIUKhpbn5PMqEcLxRweIurhMbvnbHAXAbzSzhjUm7Ybi5ieq0+pagh0+CligUbo3C1UpPz2iRBolLtIis5jTKVtvcYZJF/NszXJHNjckaic+HDF8txA7KyftrmaDLjQpsPFw+I+7Ov/1k5XVTRFmZCE5ro4I5Ft6y4VRh1skIl3tw1dyrJmCW35BK3M6FV3c6SWuKaKJwQ5j3GIQelj4llmlYq2taZv5ww8YM5LxOkSwroAG4lRsAbHEbFVicnZv8QZJ0C2KpjU7eumDCqIFiGyNcWeGTAtVm2RfLRCdcOZxLa2fSkAgIwqmF6IkwSJeQRtYKB+XSUjcB4NlOAeY2S+Cr3CCAhWH3sSoiIqA4u+7weXtEMnOn6imLGUwuQlhZfVttj7huXnxAj8CLpVyR9bbePBQL7utd0TNWFVpx1dxUkmvKIJrngM5d+VTh2H79s736hTEKhfYeFrBvhYsSA+sq23PL6C88JswvkKQVKnonO24PFUstTI/JCl3e1evrTMhryxWZ42wTPYyx9R8XMgY+ayPetgj1ngcblgws4znsIUS+FuU0O8DQAVhky9/JkF7bdAMR8uuzBQ74qLojRdqX2ljgzgrU3BSJsYCjom3j80CX+WNsLnf6Wgzbr7fILlTdrlJPyxMscushXp/nK6H4+avabJ6LIAzLInRnWjWW7Wdctv9AFOuobM/ATgwcjnM9WXYJXvU/hoT2emKpIPMsksHsvYvUuPY+QLvnA7T3fdkfm8flbgoaDaC9SWLquMxYbwKoajBqd6XNFN/2q2/wSBqpItwcamAtBWpjYhBihPgyW2fEzodC4o84Kn7GSqNqsjyfNVBgdnnlwArdNCsK7oMZ/y8QBfUcOHEKHgzGfTt0uNk1bmjlL03KH2Bnx3jct3Gw8tIzvey8fIuKJMrA/BVB5kuB330erZBOtIN4Zur1z6E7T67fEPmKnlvGkF3NNXBQlslthla9dYWPtwPKrY7cO5Pp4rSE3s8evi5zNht0pf9cc74IXJGFnJogKyQ9sX2pqy83FAXVOKCsdCLI1ux+LOv47XMsJocdTV19ad1kLhP7drkIyqVGG/fFi43+89bwt0RRmkSlC6fH7aJBra0kNSJA5ebYSdof/EnIkcx+t2HgLq0/z8HDd4ft6ijPqiJPoTQtMpjnf+ANu+YCLLbZV1mIjA7LbVXbJbipn9kk0rtx5w8mwiXoJpXjfB8kIhgC2zVupkEYF3/UUYHK89trIEKDLbQ85h+s/lWXlfiehVBocWinh2sZYrF0HjfK+VRGuhygjXFUYHP3nv2QvmEoFE4Z7rBUfFf/aI6PLOxfk7GXe/Kq08i4rr33dcIbVUYlFv4CZf/qzdeCszPwZfb9FFB64oNyO/BlNvIzq1CTNZCXnUyBVWAKkN/BJbnD8SNuvxS0FOazjWzcJ4oQyy0lDZM73EfU9GgcZI2i7vYfjMFAZIfgY1XYuXuoebj4PpsbkoVYGS/90j/mtMPiwMjyqtPw8ccou63Jg0q8hsR8tzDCiM6u1o6uNPoRCIi02mzqlNtbmy/LuFhue/7YxjG3eSEafNi3L7LA2b5E29Y9Eq/oYAiLI+Lw3K28ESnQ8k684d1bn7dj/EI7AlEriyxKD+rqDjfa+kOdo5jS0meXUGF22Dns0E2zm3Rahkz7sEymshYizdtPdocFQpXTFaHJTcKbK6Bye+yt0UPXCABm3tYXMrGX+Q/ELUP2uyW2gxjr7KUM56jUSjO+T1aokOWegsq3ZrWf5EW9PstLz6vuHo3DOlbUnWbRXuabJOZR9B+XWvziGx/Wj/ieHaxWeFPm9kLm8z4WxXZpULYIsDYoHtfFIak8zeqgLfIo+o+LrQ5CAshaPiqwOKI02Z2500n30UIp76TnWAvvv1rcByT4JMzdBDSfbA63ZUUaFg+3w3d7bLKZhMrtsdMsaCDepmQvs+xkVuQvrFzl1Kwi6n2R1xtQTvUlu+BMoBAs7d7DS5bu8xJCiixNUH0aFewFliHP7EOIJ1LbqAgIoLZRPK7iUFJ5Gpm4fRLseXP3VNbThVZN64bvs2Ca6bRfK5J60ywT2hgUDzUqsMQnOmAwn5e7Fg1z8mrzj7c2BCEpn1Bqc/HVpLWWoAaKLeflooqqWsDLFdn3F0YrllqYEJtc4jBiodAab5OjXGqflAHZc9xRXRQoi5+AxxNBCJsWmnrnWCjzmNkS+z5fRo/tfgSZF+RQNp5+YAQm89mWr+vrKq+i9DSLU9wxiL15CMcWjh91LfQQ9i1ckvr98w+qscCQni0qxwZD2rbYigTF2HgIxxYUY+EhHFvAdcW1B0M4yics5xOiY0bpCUIgyQzAQ7QNEtMAPETbIJkNwC0sqU0VTjMdvlryB8x1LpwGZ0Pnimx7R5Yx1L/muzm2c3RTZyu0gmKv+TIbrA9RsTrLk6wqv6MC4bnlHXAkIBb73B2Kf+T1ENwgPeepIT1aFIOWJSD2u7fMnQsqt3DmublJ0gRI5jgqcNDINxKNfGPtvkTOBVjo4oMvFgdHmEUgFUMNadF/XAtwjO6/2mESVdDhqyUmYMxuI/wUlT/QSk1NGYxdn4/u79+KPW6+2mE6ftwkBXV6+ZRnfAIMEMAV/3+hCKAyX+7Gwe+SAsXVO3Sd8P5yMiAbE1Rf7SCmO8iHPAXMUTIon5YgDpJDObV0GGU/xKMWCOCMX1ysIIAb/tMjOWpS5oS1fc1Mirkvd8J+eh3x5lG+0H5foDpJ668J7xBjCIuVVmOdsUj+pMuURmdEMZTQUQXn35rIpGpI/xbPUSlkY9DB2kjHDUmDo6AnDOHTAjQiOZSVJ0Sv5SkGpACzcTIv4ruoRFIrLAhgc65KYD/xUYG9xa85ecH2vq7MHis5cOElvamrZo9WWumMK9lc+ITJhLwL1zLsMeocraMkE7LNSkDM2/gQkUi69rD+Oa/6ZJLjdhRgFnIvjtGmurxLcI8j/Jn6CX+IstWXe+EQoAbdmiuo7pD/tcTntQ9JWfm/JwOgdHlUxgzNNBdWYZ/NpXIVvBCx3J6cl/xCzPU+uaFntoDMBaB0YS4zNNMwV9c2j4X9biG1S7T6nlR3IJMJhXZ4gVdHmc9/AcYNw6se/DlboHGrAjLcAjwGJ4ey537o3o8vs9iZAQuxvWX4tOx6QBNPRkAmEwDAfuz4MLyBzmdQuY22FScbkgZA1GO5IgecQJQUX2ahi6OMnDREdZv5bosN6OCowMIqicpSeLyg/2jDTQPZqcIJpXkTAJ6xVO0lRgCXJ8fAd0XdCV2fSIMSd6WhaH598ixgdolw+RtI5lWasAG8jRZLHTCDd81iqQ0lZf117au8n659BC+5Ha6yuzNQs72Cg5aAuLYBkkECYqEyaK9mfa9kp8gf3aVaArIQcEU2G1VXVar2AAA23rkxyoZEVmKaNKHYou9Yfn4Hssiz3y3cLulrx2SX4Rwume/W8vWIRNlCErYp2K7tGSsinif0Ho3r9gzXnXJ79t9KaZ/HgQC5tViljo55d/kEeu+zxc9YU2StwmHsRiJGF7OREZZpODWsf3kzBE4qtd9ssRzikkxIuTUusrM7Qad59vv8Z7mdW0Hk5qpJp+SzajosLg97SatutyS/LKL4Bx4YdFHKl1lgJa6PkJ4yKrC8zUTwtStfZnU3SZ+FAdEKhX+B1eNvrGAxeayiOU0WfZvA3Xv33cEAAq9Na/vxzmRGPkdVXWTkIQfkm+hnhMpJbVHWn4iNAgXWhWagsIpUOLvXOYrKPDvJi2a2eFs7V2iDl047omd33sQhFDrjlXuPKAHt5w1OISmW2nBqdHPTHHw5Zh2+W1h9VuskA90KxyU2lGaWLxyuKAHZTXeixTb9nP55hIVviI1/jM1p89eh2G7JfRYVreoiRteyJbb2OQjjuMRG2RloDF1JQ+WLHdq3/G3DblSYhVFBHr4QEnHDEEvstnt5B4SDeQk8AZ2DxDPAMY3II/9y9tvIzgthm1+GoxaB6DpFraiAccuhbPp9GT0ePyKBDKMCq0vLI7x2bvPiSchBOC5yEH1b+k6enLahHsnd0XQ5gnQI8AobjDOE6Jr15TWhdUHjgADmlI/7F838pMJiVqy4LkgwaBs6Eeo2DsLqdiNniGmadcc3L57LxfJ94IA114XltgBsNptcf4pT9BFlt0KkOVtgie8MFUkuuOFwRZZ3W7Q2n96ELbAyxwV+yCac5hQqeu80S6okSsEFzpc943VOJwAXfMxv/ZY4g8hhdStrT7OwmSbB84dYvJTVCY6Qt7di7zJzEszBGJRmb/FiUhjDRMZgGuXcPuvEieNx0ZynieOMGCq4/vQft4aFvOWamzybUY7hpj6ie5QKTr3MdytrfFGBPlvjEnOM5EU+EOGowGLj3sDPvWxcnnsJezuARyI89tt/tDnS3KCiQIWAa1Swm5Z2KZcU5OY0i7mLBeazzX5JXy7+EJW8ys4WbI2IoknT2TwjARK4s+hc87ircUy01zFtivFaYulSSz3Ua0HB3uTaNdXurKBRLK389+P4MS4Hdtch2O5L/pMiX8u4my+z4UwZznGJ1doO8vZUgBfjynMUAfd6keVNW2t2OHxqUmwJIVt8sRPuPkJZip6BeMYSo89B6nkM7NC4HADldSeyUcBPBDg9DRDKcAYZ8uRmvKU8yeI7tKpTdBmVPzy9yBhMLh5kyurTcI3/4f4Ac7nwQjj9ZCNg8uz4cUMYVUy5zJVZCH8hHa9tKl5bw4Vi+958yY6Lghf8owKLWcOb2HktimP2u4VVIKJeqEUl4BuX2GE8zlYgvu67Zf9q+vIw3EOmzLKP4owwn2224A/JasW/mDx8tfIavCWsfoaKGLh45wrt8YLWFaHQwv6QP3xDhbhq2e9bI+kP4jTEI+09Gie7sLTuNAK+aZvHMXy1xSRuGOx3+zP2eZ5KM9N3ZTYL8XSVCjeHzbetYcOvRRA27NE4sKGi7l+LDS/Smstx2nxZxNFP8lCD+oGGpZLuoQwVSRzIHZnH5pKET4ti2zn7P9BT8x7mCNPw1QqTgMSmPpBK0jqNpK3JaiE+vrxDa/QtKhKi1Psx8QiVAwdr6k/DvrRR7pTUfJrzEPkXYrjWUdvLzkB+uhgY4HrbalkQ7jQt7zIvyhQ8wLPfLbARp0Lxzpb5bI7rg/jU+Afrd8bz2/wsicnjBsB1Plu0ZBjEh2qdHuYrYX9kv1vf1nbJLT6j6iEvfoAXtwKMlfM7XnBPdLV0T2eKYXwwjHUrx4/xHdbvEH20QN2YDHRrRFvbKV9/825sLnEd0qrbKuRU78G6vQMr5o10SBpJFvvHHBcKLxuNimzP+yd5sY6qKuEflhBL54uPki7Q+jpNyjt+92A+LylYd8FR511Ongg5zlZ4Rrl54Yq2RoRRNmk/BVDSWjBXXU1afduPsoGNNLuh2R9G8Y/TDPcr/hHORUuC1IGjjDFNw1zB8lQEzKsQ2Pd+19wkPkVZfRPRc0JxidZ4H/K140EYHVjVDM22qnTfEvJ2FR8DNnydQ21Y6gTQxviH4SYem8uBQItiz0Vbx0WtLA/DRBwyJ5dPDYY9C20dC50jMlWrdup8I5JZXE7xyGoE0/BPrxG9kWhKb5ywvZVgs3qbe+f46ajIy/ICpWkQjuKxuWxsWhTPl6uW2paabALNe7KemxKLymVLUtefaOrZRmWP8EqB7KIApc+8CIVbwx7/qFGNVvQBs4OqiuI7/1wYIEoHdjHEMw3bMI3ziLgiO2NCdIuIGVpkEaHQhsV5bclWUzpJRH+27pvF/ig8amP7nI2/9vgpWSPRhXr4uo0LL9hy81tk823GSV4kfIbN4atdYJgYDmaLQWS64atNOBkfRGZXW+xF983iEg1t0ie+I/1Hazxil0YFFjbGI86yeGRT+zDmDJP0w/xheeTmn+sI/bLk9dcFXtOXNFca59/Rf7bDBXSN+WxxlUJ37Rh84Zwvs+rhilhZozR9EjrJlGyNkGeH6iflWUwOYl5dfaKLOfG5XOuHctuDtSiJRgV2PkyiC5PV7pUXfIAL/WJxZV2iIhMGNHy1UfzKUsyZM3y1jRm7KPn5Gj5bje8duonqtMJSbYW5MYnSUhgsBLI16/YoWm+i5NbzFrTD4nQDIKu6rWbb57zL7mgy4zYyLtAl6RiZy/2oDsO2sjbWN5v9s4lK5xkAKN7FZRMuJGga5W8qjzl3pVKGcbBWvYEMK0CxC+63atxvfXD/rMb9sxz3QqLuM3ooP6KqQkU4JyYYp4PgM0U0kfwDWxdzT6ng5lX67RJwzHjY3g2Pvk8oKusCNXnpfTd9BpXTlq+sv60b/hQJSc+JWV/wcE+s7HI7dwHdzv+7BDdZegtkHps7QypQ7HnymfPk6XqTF+S9pJvEN3Z2hMqBGzX1t5UVT/IUT6mIh/1ud/cI5Sdnv9v68EP4xiW2prEA6ZR+JFyQfPPF4pIo+sFfEdEvttEyXzL+pMN+N8eGJc5JgtIV+Ys7j3FF9txwlGc3yW1dADflEhCLGX2siki8rGY+2wR3EgRdyAsX0zkqsrl+K+u0Os1uhJu84bsF3zUJ2nAfSIo2QX0VSrdGUF88ZXGYoKYBkUtMk6r2RDcnwUKaLvK6iJGQWYP5vFR4FI14fqxEdKMC25GKKbHZ77YxNqfCmxTDZ1tcF1UhCSDtSmwxHuZ5CuFrvttollkMnpNHBVsjFo4fidL0Dm3SPMDDTTw2l7tVLYpppESrNwIxZTfWpj9/pTCUmjTQEVLixNLdvnf1zRbyoao2l0WUleuEZhqFaCaD8WtF34at2tccY0VnRr7M6nKlOZMI1yvdZ9tLDfgGyP36h9YE74DGJUtfvhDeTu7RJyHfw6jAai0Kzgbdty3baYJYCkaonPeYvaWAkheLgQoLbVFKjAptTWtiL90CAfCf9wkenTTWACi3uaVtRWHLCtxlLVe4gJXD+7SpmH08sYIOzXy2miMiVwXTAvvdfsYbg4RoWIDKl7IGfLm5KRG31XTfLO/igRt4K4+FqIrvLpI/OR5mPlvMAF5ONE3cmO7916W3z6N8vaHvEKi0CCmQ7Z3nP5PNQRHfCXeoYqkF5hRFGZ/ls/84/5Z9UJZ5nFDTnxjyhopWDW3SBV+xqZkxPRURbpqaQkQbB8+CA/rCSmRbRXNXjekC0BaM9noBOcSyhMx9r7w7fBkVtwg6Qht1mMVl2dnfXoP8YM4yF3cJ/bOJALzCsrUqEvLy+hFZ7I0nvcLAaFBbMCuO6jSAQgY4YAr0bXnyDddAAK4x6LIn5zTUm5tpOoa9YmS1KkGgCC1mAmxgupce9bQVsXpOf48wwMQDnfOb6BbPYhN9mKREee0f4jSYbb6KbMpt5nqM05OmHLKQ086h9mNM645hdXuVkPl8cVp+rtP095c3UVry6qZu9N7M8y4pqcZ4dbDZpAnxUW3T+2g2FXU9no066C51kAE7qRrwnKsedQBuUnbTc99oiTW3PBGHdNYl6LBiiKGWjB3O4DwbRmTusG81L/Sd9OOEFs3cjNA12/5/Gd2qjycQuCTx0wBjchAREfuePpwIaty5ILONMS2mSRgfSWU1ZHqE5REURr+NOuRzOWqeo4eoWJ3lSVaVHxLcD7z/fC3R6ntS3bW+kqqsXtrKYh4voYoBX2gb8pyBMa4AfKLv8DaqnzoyhBM43SHc5uwi1AlxeOGQevIRjy2kxOFxbyMD6cevZ6HO1ko8r6IkQwUP0htz2y/932X3oc1eRK6B03KoR+571hElSLmJYmqqWaGTpCgrwmnXUYkakJcvukuS7tqttaz9kR6lzcvPHcCnKEtuUFld5j9Q9vvLtz+9efvyBX28lUTspTcvXzyu06z8NabTGGVZXtGh//7yrqo2v75+XdIWy1frJC7yMr+pXsX5+nW0yl9jXD+/fvPmNVqtX/PVW7RGWH769w5LWa5GeXWYS+POqTffJPGYp377DyQwQ8ck5+jmhYyffnvNV/wN4EnS9u8vE0JSupzpE2Q002Nzc0qgEO3lyxeE7cgVYM96r5Xo2SvNppnsPiriu6j4b+vo8b+z+KpCfCdJ6G0Wp/UKnWYXCUYbbTqk1+SiybJrp2XnnoMLKhTTN6Hd0Q2+PgHGeZlUaRiKNTG/ARB9QlXUBpSUwRCOMuAFwhmOdkIIsTt3nKMMC6qD8nuyIlubB6YGwz/zLMwYG3Tfi2jTPjgk6Zs5rou7/GE0B+6jPMyJEuS5LvssYoyYs8RBh0POzfYUZ2811VI/eoQtdTsr+yGp//LFp+jxI8puq7vfX/7tp5+skY5DyEyn1HgWsDZUCW79z3cG3jrMQPugoWYlmGkHXaR08HmkDzslf6JelX4mMzpEfDCN8Irpr6d4s3n8/eX/Qyv9+uL0P68EelwR35K8zlb/9oKupV9fvHnx/1p3h33SN3iH/ubSIfqg1/DWLM/7YXr2M+mZpyDrezpVJ98G66Tzijdfri0fPZNVqpW7b1zmoqXRUZ2SB2U0ct0a/dcs+aNGFyhv3ulTIbfVC0/S6PZ0jbveeeQq0f/yky3+8yr10RADqvnMo3ruSCZWcZo13YSYnaMSsGDt7Lrz2I76mr38/Mlh++noOYni1SEPqICdliSx8Vla3yaZx8HvtLzM61jO9h7HIt6/8JlwqpFFzNPCNma4X36xRt2fR/0QG8+1/KLyWc2z97ycFAh1Vn6fjeYyejx+ROuNlz0KI2k3rCbCD9yvTIRIl8/Lx6LbLAbKPx543JaUj5DL0/SZMPziW3BoydonUQpgbA2iHhL76pfsQ04C2269+PwgTfOH9zUqK7x/f8srL2RuWitkE4oKchWIaI6BBg9JRVMlZF7t6H2crQJhcj4jWMmAg6x84K/Od1YSkAHZS4Gm1pZIgM/1+hoVX27I2ih9mHriI13v6dVe4TwPBmJfL7ZjoqGmFyOdbkauMWZHObPz0sFmU+T3fhvBOGmpXL6ZmX9ovi8nZNZs+sz4s0mI2zRUUytaQlHeJGSx285Dn3xEaSKzZrk25W5YpDKfh1B4T/JiHVU2F0RyXBdRWoXu58FqnWRH+XrN3Jv7+beUQc5kBzc3SZpgLvcjnf+J7B2i4csjFHL57SCh2xNf947NRIc+ZZf9WOhjVFZm+8wbe+RBdwjS04/5bZKFUqkxPsphWPYaoDTqIxC7Zac5CAhkCoTJCCFPTLvuiBjM+2O8LYOhpTu7K1NXUqMjgCmTBkMG7sFOmMhlb5SFcexrQ6UdtOyuotcawegRdeTNYgejEVfdpydHjJOYF0Hb9fQmJLK3QZD9M9mc5WUVpdAdr5tF6y7PUHNMDoLvJHoMiM1j/zM/1EB5GnZWek7iuUDtjGW7tXobLMsgVx8PefOyyWk5gcPD5V2BkDn+n23x4yWCiiTmcDtZW5t0gpf5t8hLe17Q8WEJY+04f8lfZe07+esG9vAPxSOntxmm3tEdCTWbhENYxeSZMMh0muKCe9P1dYHukwhiUN/j9y54gx2m+S3RCZ8Jiy5+/WwWfWRmzDAJ3TLXm1o7pZd/Y3dt0eI6Yo1uLlL4c16FRjlkzPDcabbzIloVhqfarLcuIs+3swG9duc4Jp5BaXd2Vsy2oyEt+N7OFLgLNFiDxjIQrH4YvyVlgqGxgE/uk1X7Uqs7b0yiFF/c0echwq60EwwdGmfwC66OcdoXn/2mOpwnVIcj1OliL6VHmkfn14sVbfQQRAEhjxJmtxf1OpD2EQRfh+wSa5kpN1jP/oVC2dt7DuJtiZm5+FEHXyJRVt9EMQmTKvAOU8EmZfN7XYcuvK8S4Dw5YYOn5fvkpjqKCq/TZofDf2c/R3/USYG+VHeoOBvlMnTNoEDxDUqC9rbcXljVZHKqJCZKw8FqxTXp1f3T8l3+kKV55GcMaHH4Tc3XLG2Wb4fOa2SfuiubLzcCPidfyRbJ8eMmaV5yfBc9yTAamQxbhNTZgCL05+4PUXkRkeT8IWZ1jMnhEpSr73MLigdGXNAObguEWK3PZVwjRJfoMZSP1DmK66LwvAHqkRw9xSlqxIafvGPxnaEiyT2XaY+Rbv4UrdfCOqU3Z/0LLj6yLFQEEhayNJ9ZlHbYGot8fwpHcbKOUpLqC/8qac6uN3/H51kSwoq3SYeuB3GwOy2PSy8SMtle/JgEqzrEaJnd4yWGkTUXUp7HqiqPf/yjjlrTgIckb05TFN/BfZTguknK4PQwh4N9dNq9kizYeD/mD81YW682v2nA2n9y80RP4Cd50fXvEOEDlQ/awyj+QdMqkcx8nv6h5HBH8J02NMSHi/5Y6qVS0O0Lz0yyrtchJqbBFz2GwkcHjlYtqgSFV0UxmxPgw/rpsK6qXBacbbpkCPT3pLxLk7LyR9iu5RRhvsSCeWQtcbK/Yn2bokpiLyvOCEHwreNLupq2gfaYcURu46Zq42KD8USp80CMLk6YNvpLlMtkHeL6g8XdXqoEwtwZqY6zChWlNy+20muEFU3MQK2Em7VNfF64TFCzdr1kPt48UVkdVFWRXNcVOsrX10lGTzyTMivuf/eEQNk+IeAziu8oub2bbvmOjynB0X9PVhNi/zAtbfpdKbTM6RGHFTih7hrCeJlMHKizLbGK4MiTe1Q8kVm1N72Ma/sYXjq99GuW8DegBv0Y1/bpx2FUonZ79TYU9Lg+oaisC0R6p1SV7XN/9U0crFnPkdDbRN8M+TFuysEieVhnqxQ1WWUBs5jvVUCD/gwVpxVahzDTjBASMoTEd3GXN5YfLLUncMTsdvdn4nmh9UdwySzW0cjfLv61JNMa48F53sj3z1UJ2IIrDl1T7hJkhxObtHdc5VFeb/KMDSdwOskLWEL50naTRC+e6S7ox6gDHrJ1W9jVzQOD9tm8tUiJz5ItYiNuCewfc5hXWAML7l1FnigJvVA6tgvjYrV3ZBo5Koa5szlLqGuKQ3KkrqKPgk/83C6SP5HPCEZuXeVlfoGPj3HFY3Z7RqXB8WV8ixAq8w7V4c+j7FZjs3fgkIDeiGFtUlvo87UTNotQBppttn3cRHVafUvQwye3iHBz//dGdD0TZagdzWGSRUN6XEy1a/rBbefCE0jwaMSS/epHOUm7num0rJ9dXA4/owefFXxaXhZRVia8n5HBVtivgysGiddbFuqV5d0lfTCZbSzJM1tSYPCHiVLUVfSK25tOJ9uGKM9OnWhP++vnkySyG5mD6b6v6TtzPGnP6xQFfvjwYoMmN1C1CTrgKG/bXadz5wuB7ByVFdbbqe7J5oiXYjUzfHVIz6Qe5U5TxSCOngg7ND55oZF3FJYclDyxt0q+F4WPH6siIm+2TqlXshdwz0SkaQ1iv9hrgUd5mhcf0CP4dogv8nbzah6WCXy/N/HG2K795kKK3EY9Ex5aWJtqL/hcO8FV9+pKIEfmeMrbZfll1xwhy4bGrQkilpMUs3nv8uVlUfqQrNDlXb2+zpgciy6I2vjx5a1bz8z65C6SewZp+OWZSOhhfK7ysanrJRx70noGAnRoqHrhh+u0xEK1uU+wWUC27PXMbmcX3u8l92ImqcH6mn5PNZQnWMzVQ7Du/KLXlgPZIPU9F4bgQpai9n0Y1/7LcOMzZMNJXFWCeyowdN/7aoT11dj7V2h7t3v+FXv3g7/WAZCG+t0kMSVkf8J4JlvUwpoSTNtGJjjkH1Fi8zugEkHYnPs9Yw4a96zu6tzfO31KnUxF0GeyAOAhhuK9rXlBchFGeSYssjOz0AqVy+j2GVPe8gBlS7xvUZFEGRj//UyIOkGeQzjJ4Dy5Ez2PaFPG9LtHyZtgdw6SN0pH4Bwj/2yDZQ/KMrnN8ArsDucTZNvZx4Bz9kv/pz8XPuEMt8r/uQ7z5l6wNGH0TPOlrr7cUJR0kFM8QcPywzPZQ6dwjVKYNcP4Lm2RXxS01NsgkOmuWnnV7pnwoq+A6/7v6XL1KaIenYMEvGJn2Su4gW/Ludehjtgk7wX+xOTh9E4K2yRr9tmzmNNHVhV5SjD53clIl+kMfPI2eMTJc1vCkwRgBzRbeF24+YrpxlX4eUy0ZIjOUhCklawwpBxfjmPNnpJO8+LijzoqUHl+ex26n83RdfUvPE1savLwafDoIXyGhhx93SbZZoKwtH7LEftOG/LfaT8m2Y9AT0jZH/LMw1W65wKeh1zthvO+TvqG6iz5o0YJRXmTIN2rJ7YX0GX/0MHXwsvRG0Dj9zJtiy9kUDjRQ1GTlyuUxz0YCe6E7PgR960M5Q20DyZfJpi8z977TETS0p4VyrBJM4cdeYiriUP1qHYIa7TPWEImnzZmaSxIymcUy/ec3jSmcWRhUJHccWEwBXEH3M70jn+ll5+340Vg4Eb9r+mXbSyuiR5ZZFF6UFd3REQ23kznKMYEeyYivNuU3bdzTxF+vGbCYr21fjJbp8wBLyDa1qwQGPsXwluX+Q8UZoVQdAdxjMoyHFL85z0+NRdemWOMF91JXtTrM/LUzPNYYZf5Jontl1dbzS/4cenFbfS0otkZ+uxgtcJ7Z/istLsWhEDXB+WOZ7JA6IDsWbSttuMLhMyjv4m5TaSqVoscPFA+N5LY63HPb74vg3+Myor0wtMK32KRTLkjNprcyfMktJsS6DkJn/dFXm8cJVBbN3hUh//TGYEPX5/bXcdrLYcQKGTtQUrVXqxM4oe3m+KJLstnIqN2Qjw8N3YLaMUz5tyGeM+DaelY4BAdN8sGwae+zV/uQmyx80ObGbc9Gzvk6+Lq+90wtolfnXsjIPC5JKQ1L3BbNd8Tx2GFwNUm3g2IqsnhGzipc8edR3VRoCx+gt7NckTcIDzHQsbw8vXvzqvyMnpsdyX/E/a3SBJv6C7M8KG9wmshPc3iFHd1Ml++UWPHj/M0dkka6xOZzzTCUaPzjLSVDfOMsG1s1pHhhiwWq31jIzGGBX9C9oYoPUFoaprKW56awPKWp6Z2iz9MUnbKJ5Mzok+KY/NWqOiYrAmsqddY21lN/E7COXqIitVZjje48jsqEGYtP9eRozsU/8jrwb069KlRaCBYUGynBEh8lGydTG5ukjTxToTXa/2bIGOkPjTkGEPesygQliNHeP7HeorTtGMspHaYiSBdCqaYCuPzTHVY/kArGem8e3p0f/82GLLjx01S0JPTpzwb8jwExPtfKAozdpYv3yVYuFXv0PXwwLpbHEmP5iCm+8GHPF0FmisReUBGYJAfRtmPYEcpDm+wJcbiPT0KjbJ9dic02tPrKNCG1EpoqhS0/n9h1kSNlb0i+bN52Zn4zUfxOKndJOiDsZusgXNUMjH6ntJoQ/KohCeOiDhgr/G5s9eJwnf9rCZ1SxTanHoWJaE8ebuTYnOcCUPTFiU5q+BFuKkbk+Ektq6ps6Vu74UGe1w4R+soyZhMlg75kD5EJFKqPdx+zqs+/5+Xe3cco011eZfgnkb4M/UX/RBlqy/3Nkqu9YOEX0t8aPiQYD54Nk8xLP4iIa1q33pbzc/Ty30VGrPO++SGniKeIet0Q7OfvKGm1/x9LdHqe1LdObIQV927K6PX6oLbceZk1mfCoJ2exMyy0wNGMjw+N5Edqf1voSa2H56WXVdplr3IM/dDhwyf+TYBTyLneLAbEnQcTAXsMYYLRrlAGVHYQ/WwQReue59QWTKZxr0zXHUzQpU+T5vzDMKvX9jPRPr14wnqILOguna2bKx8iABzktySBplPfY/ZNzT5teUco5llJFNfs3bnpWa/nppk49ampt1kd4Hh7wC7xDfeAdynZYcqiGL0EbN4NqQL8nrLnEj979Pm5qZvejo+ly4V7UckwjMMj9pt+1hHeS4ZZRbcoCkVdexgKr2oi17eXb1su/LImk2fl5VncXfmhqheXrEUw2GURtmQ3sdJfSsnd9cJeQgLe8vA2j+uPK1tVzbGE/P3he6SDb2bfR4rb0FJfllE8Y8kuw14uUid6aZVSugNIQp1hdk9lBEI3Ry7ULcAnpEVox+SU+BIUzPA/ZRoN57cemChe1R1kZGM8ujZJEgJEIcVaOIW139C2J3OUVTm2UleNLwSRklvOQ7R86/Bed8Fqalvg1UAE5dgzvOdnejmhhx8wqA7WK2TTOJixic0s05rMxIUIcLNtsWNxWKDzKkP0VFUPKtN0l9ankVFqzh4WZ4au5DbNStb1+dqlZ1k/+vVxeX/wtce00Vh/O/2rq03blxJ/5XBPC4WJ5tZHGCxyFnAdpyJgWTisZ0Mdl8EpZtuC1FLvZI6iQ+w/30p6sZL8SpSt+RlJm4Wi1XFT7wWq/DXiYo6CL6/oLthZs/VDXPCC5qNjHPQ1bllpldgY7rE3E5kK1vTtx+xD5435UP8/fo7ojR1YYOZXOHuPOTF80jHlenyV1lbanxGyfCRQYSvfEs5kATlHOZfkUXYiFwuo8/PLEF+P2aLU5Pduagfo7W+6du6teG1s/96RA7e3bwXeY/Har0ROFw971LEJhp36oGazS0qklzun2C2hqgvAwi3URd7plkMJpv0oXdDtmJkSZXEqePlD1t78a8jiMXxT+/yw0a+NEojYD1r0IMCg3UHMzZ9LrtESNa/bgSWzfvINq+HchH70mERq10ZuwSjvM5qYoux1LiLtzPaYE3eoa8oHRvMLi8qU8cTI451tiOvzwlOJpH+f3OI9B9wfDR7cn1AvlI6YqSiokCFL36zHHsa5YUs6iusbDfuULdJVvk2LoWwKdym3pN3EglBTAcG2MgYRKvk9anJ7GsXKAmF9UZndEzyKRY9twVxYO+G7I0Ac/yt6JsiP7rDkK09MieIuxh03XGxfANlFPGYEai8Q/HIS5J2D3/53ES38cSsfw65dE/2PqrdRkYB58DTfuJfezgRsj2WMveG2T2h/TlFD3H5ZSO9rd2J/t1hJ3qBvwf1zuPvLjnG8uz6+6mGGOA8x2/JrdmTswuBqclsUePj/5j2/8PT7t2g9T/wVHB3zqK2+qis5hhbpw/ZdVGMG8RbkVxTo8s0ctgs1plgyJG3qyw9A/Kv62y0NJiFf1leOlrmTFJcethfEoFo2Hj7Km/Kt8l+j0YFv8J/HupR4xYVO2o94eCs13GSnNSM1vYu//YJFfTwVuTfvna/SIb+JrRbPXL4Os9NN5Wct1Fw3Lam4RHEn6HbatzlqYvLBlN71MLrBq/c0hCrpo/FT0BNCKj79HzwztSLg5dDEHHzSFx1/ya7rTlw+sAaVqFJ5OYbFJixd55MTLhxZxvWx1nGYHt4Qkf0KS6SmtVGkEZ0sulOo3SXlgOcCU8QIfy1ljXXcGAhnDcCkiATFnD9Nn4SLFPr63CAS+3cBl03jsXwWzp9q7ftw7v8kN8muzpK9zLcwt9Wx/Qy31Pz17j71eYGsntn/geqvuXFF999c1skx7h4Jp9Ol2nN5S0RxGXkuybC8vo71jI7IBLWe6x8MDMLMc2d71vuP8pI6HCto0oUyPK2d28OmSqwHS7e5TUDC6OY+5u8yYsj7jIqsron9s6PToDP8/w5TcqnOXz4vV58ARO0z3j5r/M6Rv51tsedOXYKtFuDvY/JK9eNDEBLP4UItp6+jHdfbjLMf/dli85BHl6mz/v+eXYvpCku/d/H2fkxJkvt4gEd8Zi9mSOnIMPBp6TOaSImXBvNOPCjt+7h7s8+3m4ft8Pezy7ebhffoZRYtenpjfRwP12/9LJa+G0Ml9DDcJGX5T1K05896KcHzQdHOsv2VsxO6yRPhKcMH2b8qkcR4N0yPplxn/15Rme0J3lOLqoq3j1t6GE2pZv9BoOpPM6zG6sVH1B95EX3rFsgMc186nIV8CZx8SZpao3xcaMD3GNdPidZXDw7HfZrv0iXJ5Lv8RYQdvS0YuzyJW7k+7stkrwYGR6sfubh3ee4ZmpwKG/vF5x7F/UhDyHoHTqlz3bSWrANIfGVkBh7LMfL3c43S5M3PC4jdH0v6udW1OdR/z0eFh6KZGSUEczEy7vVZgmzc8y3ytYeN92jbF8f+cVp2neaRw9JWtKNzBNglj12mv27/TVmu83Urw1Y3qYuGd7lvc0Lmb+52c1biXvBUlnDRWdZaiNbuDBuvG7uy9T8MwHVfo0e43Na4cGNIBDXCvDdXcXHU5wctnJ3BH0Wjq6T8KTnxsxoppv+Vjt8vMn2mciPdrDssjHDC7dmEmzekIrB4cfGLVn8Ms6Hh71ivTTVx+Fr7STyGY6yXgKnG3bGHnj95pHXv1vyMh5I/kDfyneo/nA36PEAK+c1MIpkMWr0GPVbmQrCNVuLqPXVYN9hjlw/jR8E/O4HAXcU32b7zZ+T6XsUl+cCtVmzNvJ96OZblwB0YcPb3dW4CO1uGuquscXQa/y5ZOWGhtmfMJoURjfHE9784zYfk828EQuCoTd5WicUDsIam75m6cNL1gef8Q+SviSnMSI8xF/QmPqNJ/iHbNxKHqP4TYLSff2X/1dQXadf5dljcjgXMXT17rRFu/5eFbG3FN5XeXo+Zp1Tug+Od6g8p9VN9ihcArllamuiHWHp6nhH9otAvn6IV0X3z9nuB/DrN7H2YIno8rnhMlj8X3+5KT+SbdR//vKA+97lDiQ/FzsEvvm2Fq/hpRaPHgYcHlOp3jaMNedLUV5TVYGqo6c58i7yexVG19/cdQWqjl9xkiZGB6EFjEjsdDMqPlDD474qfI3oDcPLPJddpxiN47iPAj+HuP5eL3Zfo1OabykLSLt6d3pJ86j0pZpvue1hFTr08/hFsdENp8srbpMLTge+BsEA7KMBVNXpoYiz8piQ2IbjrQpxHOWxhcHc7PVtvf8N72aaTaBvxhZXSi79Rtgbxfh1Fd3HVU79JSRf0XvqUbnjnbzVrb7l3PHzoMTXQYnL85v6+67/1Wru/QM39rG3HuaL/GuCrRLSi/+mbIe+FqJjLmg9HAHNtysHcYPx4mu1i/+sh1JfxzUdNpqDIG/nNp5PWT48PpZolJMYuasfw+AyrnZP98k/Ry0RbvFHSCJdLcJ/oQ6KSuKI2y0B3Hxv/yc5XWB2Y+9tUxRnQ5Q+jzMs7pp2HdjE4aSDhrpMuDy/SJyBwXp0u0Ad/awNNmy5MxNZeIl20OtkJw5XPcgRaZ0ZGw/RzXO3q/rbcsvayjIy7vSmQaf+Blq0nOx4BiNT8w2qWHZzXzNMaK0GRF3KL4e+7ViY9mrblFOvMm25fS5je5IW306CoWaInnydlGTy68JOuHRlx8P4A23bcupLpjE7S1JVx32VtPyWcBqqhuzNW/dH67ad2Ta11r6kxbc+cW1rhujJlnv7v4fY6bJR3jcqctyY21ppWlNKBejkd5KhqRxyzhy7FradOEevgRcwey5/zXubp+mnvI4XOWvmAZP84yM/MqzoRVZ+cwm3RtcN0Qm1E3CXzXqr9m/1e0gqMfKpo1dQw9Akw59RGAXcCTcV4rNyGvpwNzVDgOMyzQ8/Cjh89WVts9u8dHgKPdQMuDq6Q18T9O0tSk+P5zRz3IiuomMZhZ0XN131UaL8FZetxUMcHNKCbr0354qX6m/uaPrJ23BTu98PNztObggNMv8blQ/YiqkHVn/klpxkWL8oy3yXkJ7trlFI+PLGeesOlcTPLOpyFnHgv872v9TL1yGpUSfRPUof/zb8+P6cVskpTXZYhH/8+vJX/pP5kDX5Tn+5IPde9WlGuYv3ojmwGnupDIDkrDwgASvbvwhN4s8YFc3j+6s8K6sixuYWv/kk2yWnOOXtwREaDg+1pj1LvuQ1OqGsvpJX6W3SLp2JSmy/b4brAZ09Xr2gQGWAteSfJCIlkW1FQKPFFlHGlm4DYoxOq8CXcMtF77nL6B74VKhu5mszvSwWTgI95R2mSj6WMAggBZNMAEzzO11J+2aXuAsA60NcHBC/SRyAIQWCquN/QJBaA2RugGpOSqcCZ56m65ica0lZkJEfVj8FEzXWMev2B6qRKLRtTwVESSOjIEH3c5g50rQXPaClVcRoFsTks+GF807BWxi8ldlVqHXSSfQrNZYD06N80STogn2N5GL1FEEwx5lgAvQZ+FpJGjZxrloIJnULMqDXJf38Q6DQBgWzwU/upTcV7qq4QsRjO9uhCPZSXBTGaHlZbLElq8cUo44FlmaDUuei5xFE//a3v70Uem7g1Dle0pz639YOANCrdOFdrwDt+G94cWCw/0QnhAQj3GzA6F2f+qcguqV1VwM8U5pokuEduyFRAo8zncIToErpxi5pUu1+NxOyNAtkiwFis7iy6eMZYCV/3jA1qi6TtH6rCT/tcMKUZvqyGvfWiwajtljjz48GEugnqyJYiYVOXq3QoCh92WYmr04jm8lrNlx1boHruODopGVkGH5c/UVHr4pJW/NfdrRPYhKSBDtq/y+/82jLmUuH7rdpbj4YgVlBuKIwNyCQhcIgidPHpMWWdjFwAp/MCZ0J9eKPAifIQsuB0+BTO8+KqXvZ6XFc0i2cuze7zEKn/3HtYwr8JFnW/TOPJv171IsTNnmdx6QV3+BOtavL9OPw4ySDi/AOHJIlMLZ6lScAl/rdu6RN9dvo2WGmuya1GS42DDOrLp8DZor4CLPBrJ1eVzWWQUs2oWwzI5nN8myJA1mPMM0wNv9CfH58TbgUd4GXMu7EpOjq/nGH/vecFKh+HCs/0l7S2EUJDIrDlG9mDKO1shnH5j5EiHDPJ19R8fxQhziX9i1NxHQqU2C1B7QYDX3hQqpqGGxYzWuUbHNj4vKc7VNUxzO4qKoi+XyuUBO8NBpKdJMcRQl0MF065QmUVDO1kAJxyKlRZuOgGJXraiLDUHs50G2xuprDUdcPZn3rMjecM925IJh5BZh2tvyJEnWjC8FHfw4iCU65rKMtTmgIWRs62OI1sjnXWgyuVjOtzQiq6Qcru3ufmYeqOs/8Y0zunooJr/7oZhl2bMHarwAZbYzgsAynAgYVciXGdmjYQUcqElgeBGrW/e9nBLJulq6wKOytZoZbANimn+nsh7dlLMzvT2iXPCY7UtQfdawHbLD8kFwyyo0AUKLeGqAIi/7hVP8ZmehFP8vT48EMCKGe/ip0NZCyIwzzFNAdQmPfCauUNWkfZrDQwdVc29DA+SHH5tFgm33AVmkwN+ap7Av6WIFLWUUMMoO+2FTpRlYLlEoWK4QZI6kB4DJzLpL0KdCZPwLETDt8TpTBSWumBdqnuEjirOrH1qv8+DnJCOHsDiIK2SBoKcm35FaiUtREiiV5nKjwt5q9+eKBOv28Oxajc2/VDeD55zkmQfM/ZokcowwRjQW2YJPDo9xAi4YeLfbS8LfeMdEEkpsd/dY85HWQ0x9JCoRAl099DLlYNE567OgBlos5a+SV+BSn5x6kag3D4GJa5BJ1TcRsCUNi2AlPYaDcaGuBZ57B3LCefTk54XOMmRaGq1v9Rffx8ZSi1/m3LM1jPj8rdQbTETDnL/2PC0cDrN78SGCNPxsWHhJUYP3qeM/yLMyUW7FLpheCiSCzWC89Iwv16ybSuAz6mDRGS7cAWC1/SzkPiCbcNtrhZ+6NYpfzeB0B8TppGRmGH1cfEK9XxaSt2QPi9SFbrXLtLSpSp26CnTBN2SQhrSC1bKa5+SPCrjdX3hJBN9WiajTw6grzge/6e4WKLE4vztVTzbFxYuKSNy56xFNpwMilJlz9CKhUzwaQs2HxTV6cjySGsW/gyQ8S+jYZTtSvq8fFoMt6QPCQn5Ld1CggjYowaH/eBg4aZdYDhIj89/ciP5+kKKBIhM5rf55kIiINiiIEgo7MMAHBY9TQINcShhBAbvseC4mXGQYd876cdsQh5AtYfMjEHtN1ISE0+erFsl8nXb8QsWYD0YdiHyTPj2rtQtpkmLS/rD29T6OGSUOswWfu/VVsmqcFzZTLXHPUzL7C7WJsfizjA3qbYGmK5wgODrrQWKq05KA8LMFmoqkyapm0O3s4VRBrwLdiP0b8ABAzH1PmwheRcN6Jj9y1LhlRvZSiBAEvWSfDzqDImgCzfLeOeWAzoVuHHXDmduv4PXmsruJiH92ei91TXKL9X0n1JNHBvRs1/oedFAyz4cdwI8lUcZd7XYwwAXbF7BBh1jqwQq5dGmisgSQH5Zlg0WOFAE9os173dBWXhbWP9Kew0NXQkqA22RrJGWdMj867bPojr9Dy19m1lKIEza/rxtCgyPLX2XfoG0b7bY4ZlN3gtIrzSUBwRhywfPVnl5BWqzjJhHDmdRLULMeXA5fJhiFXrDDdMp/f4f1TcqqTUS16JuuEZAP69T+uG0C9HsufxjpRyYkRLLdrrwVGjnDgwBaECaho07GeQGR8LNFXmPeSthbj5PeOXjNF/bym/5Uz+oyrlepcZHVGRBTA1zjQepgSmVvaMCUbWAPT+qxi9ds/4Jl+VLHC5vrGFivgcdafc22bE0Gu4qKiUv2FTEupgQkvEbck4Qu3kz5S0M2kzQWkixQgtIpZagkwm3KuckLX7NOVgK3l38EvAVgT3sg74Wrui/mrJ7T7kp/5kGfCz/IRTKBkhjKxdJq3zaBaatFCBjXT2DMMICUaGg13fNUZt327c4E1PtzGz+To8SZLar6eTiBVp9Nsw9z+jS9c97GioI9Rm1RPLAYf3UWGWiNf/Rzs4ABUSila2BsSN4D4BaXNhQlfdzZ81jD4inviXX6IqH/XHSk/aeDomBMHvmwSQFKtyqQJdW6hslkY3NFKmTTHibgIqK1i5zkfqqbcb9rCafatpn/8hIsByUNnG5BZDVTIM9/78+dyVyRNqsmJw3/QbYvPqdnS1cNC1GkVIMH6fY0r9B6VtQNn9KbIj9OhhG2cOw1ji1aPD04hkxbpzlgKQB7yn/BYCDyGrphvVfv4mKT4FxRNFJqhb5BlNPy69vvZQRWj3c3Mfh8Xu5QLbFhroR0ZCNHMETZ70bndTTpJ+ELRTIHg1Otjsxqp6853mVblRR2cPDnGxfP1991TnB3QHf4irs4FbmL3rIBXS8BCq/vRfJQhIrA3Ys0vgTAB6RUGD40eJg0pOmAZ0CB//MTEDJhgLD8bGC7j3ZebDMuy+xJ0kxtk9pEIz4gkpVn9Ylem2Sp2zDLcLd/nY3mgm9ADZAzm5nYE+fOMzmh/fYyT9KKq4t0TuWx/kyiW2vapoYJADpScS2AGUqw+4xSsl9GxTzLj6psSO2qE36mDpzFETHB4pmCSAY4SXoaxQMiSmyo4uoyao+VbAraoYUyqyriO/SEGNFsYzDamUTVng9/N8ZQXFZbtEQ+w0f3uCe3PKXqIyy/yh480EbP3YwrMd5GMDAxHriTMO0apzmHwwupk0mArYZIdahlnzOEyP1QYGbi0K9uDCqvT6qCCG0rzxgcW1GF8vwbL9cOKDwhEF4ZZPFn3vRewUXoZbgIf51yZ3+Zp+imvMN7bK5e6owam5Oq+P/zCv1c1GB/yiK+nPRdr68Lpxroyi1tcvn3mAEQoNHiFN+p8q9NgApipLW/V5hz4qn+4yMpviqNUioTv1e7nSUaxURjzdZYlMdeCsDWIOGsO2av8SGZKwwGMqjL12EU3zaeR7X/f0IglNbVVcxPDCE6E65oJOGA2YkskeRqXrPIET4yfTrb5LnPS/GA5HFFVph6O6KaZSxv69w0NR1JTWzU3MYzqf4vJlbheFDJMDT9OcxVojyRPwxFsnmXgp5NtRmdbctF4h74m6NtblJ4ez2lWR14x3etJ6k++55PJAVx+A0QbGsLMesSq7TlxyBToXB1aKmmfz4Yov94LkDEWCCam8jKQ5TSszTqWmYN5O6PWioaqFThhOcJpfQ5X9hia1s3qGtepnuuPCtdARedemO/Rm6Qoq9dxFX+OS/Eep651j6r+EQ7JTdv8THVm+3t9SXWM//Hr/nOO+zr+nA5VhAGHYxx/v4ordCBhI0T2dCnYCE2gaQr/qz5IBJrpS6Am+kIN+3f5Lk6Tf6J91+NAQwAN1CRApms8zg5n8kRMbLMvApvqS03UQ/dVQc5iy/xc7MDWQDKpkgKlRopbVByTssQY7w65BQlEEqh1kUrTMvt2R2iVLYZaZCl0euZpCulGfgb1ISUGXLsLC5B3VyhroSs3tFW/DJGaq6dQWawnMmxW0Z66IW0L/VM+oYG+BOLfF+oUqP3WwIGwLwHF7wp1A2Ab5/Q9qp5y6NPhCcDhkKPRtVnh4RkPY1/xdAp9N1w52CJLomlwOGES2hqKoGaGUt1X1C2kxE+oKwG/n65Qw37IyCvwH4qgBoZSHczkE656tjWeam+TXXUuoP7uS0ATdYVmPaBohSNQ9EfUEkXvY4Jr4/6pI00nBTrCAxxIpeo1hlAnAkqTr6h4fkiOkPZsMdgoQ2FmbTqGsMzgNI3C5jSZbeN9OMA3SVrBE5m2ipFoQi0zSRUfl0ChgmVHZYzLtuL7ODs/xgTSCuOwVCo5aEpjWTRC6FtnKcw0vz+hXfKY7MgWiIr2KbOBjF5lDbiOsV3g6h/at0fi5KgkB+dKZQ0n6YzlspHItE8fYmi/RhcqeouUm7XzKS6SOBtijV7lx89JFkv6xaSSQi5lPY28f55j8svHLIGmHbYYkoGlcLOOuUk0U2/zf/vviK8oF8hMEmtc8nq2gXtNzdKSW2CkraFbGPQv38RFQV8ELgj6Ut15TYKK2yIB1/JUGXhWMxRrGhmcVoQ2hiKoibpUy/36O57Oszi9OFdP9YlaMzRJzxPU5JAU6hoa6UiALckGhiqD2iXFZWS0iSG0slM9ulDRkNkJHyGWNaLk31KY8P+9yM8nWSNtoaKllkLTUhu/WWik/R3i3xYZbinYjLnSPQVLptpUsJQaKeC8vYIUMBkkBUxpKIWiZXVrZt0oGV6oMml3Gu1bqKy1cCNNmbSRpljTCJidUmgOpIIaBgkNzpQkW+ChSHaOZLTZZRPfSVuRdxpLoTUpkxULMCZTDpuRIdGqxyehAFTkSWA1eSrdMZCYGEE8DxJpwIMhkcy2cdkKSkZoJIbZqkkaxhzofQkljAMJsaU4BnKYCaA/3qaDHotH3HQpeMxNE5g31cQQVzXXUGiabIi0t1iQZlKNTDQBorzCyxCWRrEaYQm1GxI2RCSwBWEJVCtHjlTXh0M8QrH3hjKw34Zi3fjIvCsVx0amGBwXGQotLFPpRoAqg6GYGi7xPxbyRqgyqBGqWLeOQhnCmy3V4C6SgOsqgUq3RcQ8ENm/fgavSrlycKvIkmjvmHLwFqD9Hb5Tyg3uGYYAauK01BfBF4pdqYno/akHrEFfLFXE9NhEGvVIaFhKCd6cyYgtTokf0PGUwkMJTKY7NR4oDW/DFBKIJKrbMeOWu2NKecMCheq007TZO1ST7eXXmDwBvLZgaXRGLvKyxMxTeasiCWhkgUpn5GbtI73z5spBA7Mk2hNaMDAKcFQL0sFntiCpuSDq5rWN6o/UmCBI4hEaUwwemTEU2i/2eIqTAzRuDUXwF9qV6kamZgWkGpR4CnA84okMzj3foapChWZ4lhHKzkQhWq0J4hKPo3+h5PAE9SlXDqvPkJg1+DrB4C5htUUSRbMUlaZlLiaN0CxXDrXJkehm/edsp5j06VJwzqcJtIfdfGwL4ICbJ4EPtXkqo5blVuXK5W2aWpV36dN47KnJTXwGo5rU/O6XcZySu3pJ6PS+WHeoxEt04rNt4GrWaiv3aBMoVJ5zLRHSN9wdTSv8JEQS1Rl3dHE6pQnaP+QtfWIhhXw5IlCYydCS6yXoXJQM7rkNr7ujgc4YlL2LpZl3q7mXKxMp3/xeV4hhIdpEIJE5q7JUBlNv/yIUnG/7Utkk2xMYeCjKm2JKZX6Kpk3JX4rJsAaQKiAHUNtIpBPDoG2hQeoZhNp5PRpc36k6Cjf2oQL/aoPKBcb54WMR5D72bE2Ff33DxcRf/gWrvqlp6LcGervA1MGMAr2pGCyifCZhbw5hjqfHsTK6b80uWsasolxV2csGoqnutYKSEzS2g1yVLxS8m/IhLg6ocjBlW1FuAKnCKgWXaUI8jSm/R5YgxCdIv0RpVIZemrip1jwniRqOsHI0yUghhRrsU5i+nuSRi72K7PI86tbnqF3UJ8rxxLyyXEn4wQdRVP2MQ8GF27gAzGTPUYIYUD6KmFdWjCSAshL1lmIy+rFM1PMFzAMSBjAF9ACoMYHyXY+96u0eVKU0T+JfXW4/TerJnn+5q8iZTq4oS+i9lyZUvd9Y9kcNisFTTqxfNYArBqXikrMUhkFQQ8gHQTmxl35cghkuk7SOjtxzVhiBIw1nAiMMjVC6CxoysJZrLdCG/AS4F78MA9kzXnszdE81lUtjkSjE8ph/bkpqSp+SOiyTmZeMUf+iEVgtw5SKJTD7fLJZ/8IvMNl64OvLpjpX5F397jBWrz4cAURUA5J/Mer3jwAV/S7Q+O9x/qy++axlj4Ht1VQd5StmeZNqcqX4d9ZEKdkbarAmaBaxMKx55HO/STVPfb4w83SXMFbY4SuFRA409ghlIQ1jgxq+kv8hdSazAE/vwfWjukJInAAxBhgmqqABzpNtxEYHkE61LJ1CGSBaQaOFIgqBK67c1Za+/I/ocAVSaxhV16ooBlmglZXHTVDzksRMgFnr4h54MHBrEv1qTlol2LLOtQO8mMXKIBs0Rb+UHVYHUkuItCHWMpKgHrQhPK5keNX0eBBIg6FhIhMw4UYUux6Qzv/OBwpkQiorA5Q49zwXbIVuQgoBeR3PakGokDICy4OYSf+RgOTBPpR5TCIJTKM3jqZiMDOpg83Q3AwjyDjclSmC5Ugi/YA3aQ5sFDcxeruYGcSAKxt4SMFbEkDIN1rVjdtCWMltYlMt9AsQPUeVLk5y6nDHrIJ/LHPSSpUGMYbCSUlOrVVKog2gxjJMoghYZXpYYMxignMEg2BftEVtwnUFMbF+EjepHewTXZs52QhoTjZlWMgNBAVrI6ZQBWFbq4kdYDo9Nk2sFd5EyuWkcV2t8kqtzdaNizQjieoSSZs1NqmazyRGMeHLRMZRMYej3bgvhAwHyslHw7D3ANF9fDylaIiIKFeco1QsXbi4jM2yRRJzcUKV+5CMEeu4LqosodR7BLk74QuhJkl1eRTJMeor5i6RyP9sFV7VLm6m0mVKJJKL7O4yxUf3JDWHH/15Bpo+3VFX0EN8hLuc7iPx/J5EoqmB/+jkT3DmMpEqeKvS39KsYgg0mQS0JdysAtPam64PT6u0E0AVwihCHF1SlfrVj7okBq5eX44smMJMRN9BYzhSr6PKER1UV6IvTaMRmQndO4gMhuQVa4sV/fZsy1LZrQ3NaEHn6U3yJdJsVV8sRedR9Im/WxJf1+DZAEgnF9z1zQATVZrUgoNGuyqqGps4ihDDUjj1oCDX0eBeJ/fjgysodm+j/figkNoMF2WIbE+mae1uaJeGenS/LsIcfdhuuQ14Er+KCyHHh3qeNj2D/IqtrEjkfysbXtUu0Ht0i/dnT3GJ9n8l1RPVgqi4roo3dZi6fCh7UlUapt7dEMx3O/CXmwGu4EkRsKb0izfKEeDJNB/pfje2D1vL77gwl3n6FAOaAZEiCTAg0ukRhnpg5gN7FYFkBsqlkJI+xMJIkbyBMDHJxuDHLNrvQl/JLzrmMU2XJ0L+TXAUfpXmM2ncd2EiPL3uoNNgRANfuZYsoSfBwZrC/KpM2eG6EepCbmi3fDzhmvZ8TPIRzYAHUoYZ6oCkKu2XrMqVMuKA26CzpbT++zt0XAA+94vmyZKKXLsj8PJESZb1pv36NZlsPBhI9WXIiUN8HLObQrFhlNL63zdObQY4V1AEZDICRg7Tugq8yJIvNcDR5VNS8xI9RgwzI7nMN2yOoegmS6okThVrSVUF3+tIOJNSO/VosiONN0a3Vhab0ttFWjeQukpO8CLcLL2VQ5SrIalTJCR4Ei2nIldM5XACqmZKV2eVkvGR8fBsEtXUBdKFmLWmUVunbnA1efX8qCWkA9NfIMPUwe6RofwTw0WcMpGEi+MdnXYselPkR5U9VOQhDAJnWGsXNsqEaaNN8ZBbGIIiXrsZ+vRwkWL/JhL537gJKe6amtLsdS4jewpEOYbHdpBS39fuTlhCOrx2jJcloXOJV5qTaF3JMS6er7/vnuLsgO6waYcUasC2RFtJZRQ2p1trEDhfG7tDodPMNdsSMIvcSCOQP4y1Z6nXpbYkb5xyMtTWCTH0afLmEUamSfD8mUmxb9dV8b99n9dEYIK06E0CD6QKaoXvgrMLvzIh3H3zAMIkv9sos0RshjWlUVhauWJQ1jeijyqbm8w0MoMEQwfThilI6Epe1VoIYpg8ZhGbOle0kYJaMZ8ACX2baUWRqJfhAOZrIyzUadhcnMFtzKGgDmkOMNFa8x0q86e5mmPIARf1rGXGAGi9KwLwEPPdUWwUOezc0ljQiaai+sDoKs/KqoiTelbBu9N+cdKFYn7IIzFBFbC/88VbvyZyiNvMLhskibua9YMuGZcHs9OJOwwsSZGrlbLKETKrSagUZKZAYbKWiVYbyTE86oCkbffdYzJZirRxhh2eqanN1dOphTd+BjeD2lSeOdPeZ1LTAbuScRzD4wnIzNdsXBQp98YZdogUrzZXT6cW3jgS/Qxqt7tIIWWg8awmqa94oO2/rQlmUqma4o5cn37RYzcxBVZGZ2saqi7Xdz3mc4Kb1rRuXCdGrnn3jTayPu4MR+j/9Mu/6q9eNExqw+NeRkVf9upFkxS1/QH/WeVFfEDv8z1KS/Lrqxd3Z1z7iJq/XqMyOQwsXmGeGSJ5ogemHc1N9lgHfCfpNDmJOpKuuM/bXsX7uIoviiqp41ri4h3+lki6X+JlUp8HfUb7m+zDuTqdK6wyOn5OmUPnVy/U7b96Icj8qgm0V/pQAYuZYBXQh+zynKT7Xu43cVpy200Ziyts/d8R/r3pS/xpVujw3HP6I88MGbXmw5tUlO3xJ/eAjqcUMys/ZPfxV+Qi28cSvUOHePd8W6fxIk4zMib6jmDN/up1Eh+K+Fi2PIb6+E+M4f3x+3/9P6zqGUzatAgA - - - dbo - - \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.Designer.cs similarity index 92% rename from src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.Designer.cs rename to src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.Designer.cs index 256bd186e0..e3a2d0014f 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.Designer.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.Designer.cs @@ -13,7 +13,7 @@ public sealed partial class ExportRevision : IMigrationMetadata string IMigrationMetadata.Id { - get { return "201605201342001_ExportRevision"; } + get { return "201605201911421_ExportRevision"; } } string IMigrationMetadata.Source diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs similarity index 88% rename from src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs rename to src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs index 23e687029d..4aa3f18e8d 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201342001_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs @@ -10,6 +10,7 @@ public partial class ExportRevision : DbMigration, ILocaleResourcesProvider, IDa { public override void Up() { + AddColumn("dbo.ExportDeployment", "ResultInfo", c => c.String()); AddColumn("dbo.ExportDeployment", "SubFolder", c => c.String(maxLength: 400)); AlterColumn("dbo.ExportProfile", "FolderName", c => c.String(nullable: false, maxLength: 400)); DropColumn("dbo.ExportDeployment", "CreateZip"); @@ -20,6 +21,7 @@ public override void Down() AddColumn("dbo.ExportDeployment", "CreateZip", c => c.Boolean(nullable: false)); AlterColumn("dbo.ExportProfile", "FolderName", c => c.String(nullable: false, maxLength: 100)); DropColumn("dbo.ExportDeployment", "SubFolder"); + DropColumn("dbo.ExportDeployment", "ResultInfo"); } public bool RollbackOnFailure @@ -93,6 +95,16 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "Specifies whether to delete unneeded files after all deployments succeeded.", "Legt fest, ob nicht mehr benötigte Dateien gelöscht werden sollen, nachdem alle Veröffentlichungen erfolgreich waren."); + builder.AddOrUpdate("Admin.Common.FtpStatus", + "FTP status {0} ({1}).", + "FTP-Status {0} ({1})."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.CopyFileFailed", + "At least one file could not be copied.", + "Mindestens eine Datei konnte nicht kopiert werden."); + + builder.AddOrUpdate("Admin.Common.LastRun", "Last run", "Letzte Ausführung"); + builder.AddOrUpdate("Admin.Common.SuccessfulOn", "Successful on", "Erfolgreich am"); builder.Delete( diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.resx b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.resx new file mode 100644 index 0000000000..a653f94065 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 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 + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index fa7a0999ef..715e87aaf6 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -400,9 +400,9 @@ 201605191216116_CheckoutAttributeMultiStore.cs - - - 201605201342001_ExportRevision.cs + + + 201605201911421_ExportRevision.cs @@ -749,8 +749,8 @@ 201605191216116_CheckoutAttributeMultiStore.cs - - 201605201342001_ExportRevision.cs + + 201605201911421_ExportRevision.cs diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 665babf9c1..eef4bd4712 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -451,58 +451,78 @@ private bool CallProvider(DataExporterContext ctx, string streamId, string metho private bool Deploy(DataExporterContext ctx, string zipPath) { - var result = true; + var allSucceeded = true; + var deployments = ctx.Request.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled); + + if (deployments.Count() == 0) + return false; + var context = new ExportDeploymentContext { + T = T, Log = ctx.Log, FolderContent = ctx.FolderContent, ZipPath = zipPath, CreateZipArchive = ctx.Request.Profile.CreateZipArchive - }; + }; - foreach (var deployment in ctx.Request.Profile.Deployments.OrderBy(x => x.DeploymentTypeId).Where(x => x.Enabled)) + foreach (var deployment in deployments) { - try + IFilePublisher publisher = null; + + context.Result = new DataDeploymentResult { - IFilePublisher publisher = null; + LastExecutionUtc = DateTime.UtcNow + }; - if (deployment.DeploymentType == ExportDeploymentType.Email) - { - publisher = new EmailFilePublisher(_emailAccountService.Value, _queuedEmailService.Value); - } - else if (deployment.DeploymentType == ExportDeploymentType.FileSystem) - { - publisher = new FileSystemFilePublisher(); - } - else if (deployment.DeploymentType == ExportDeploymentType.Ftp) - { - publisher = new FtpFilePublisher(); - } - else if (deployment.DeploymentType == ExportDeploymentType.Http) - { - publisher = new HttpFilePublisher(); - } - else if (deployment.DeploymentType == ExportDeploymentType.PublicFolder) + try + { + switch (deployment.DeploymentType) { - publisher = new PublicFolderPublisher(); + case ExportDeploymentType.Email: + publisher = new EmailFilePublisher(_emailAccountService.Value, _queuedEmailService.Value); + break; + case ExportDeploymentType.FileSystem: + publisher = new FileSystemFilePublisher(); + break; + case ExportDeploymentType.Ftp: + publisher = new FtpFilePublisher(); + break; + case ExportDeploymentType.Http: + publisher = new HttpFilePublisher(); + break; + case ExportDeploymentType.PublicFolder: + publisher = new PublicFolderPublisher(); + break; } if (publisher != null) { - if (!publisher.Publish(context, deployment)) - result = false; + publisher.Publish(context, deployment); + + if (!context.Result.Succeeded) + allSucceeded = false; } } catch (Exception exception) { - result = false; + allSucceeded = false; + + if (context.Result != null) + { + context.Result.LastError = exception.ToAllMessages(); + } ctx.Log.Error("Deployment \"{0}\" of type {1} failed: {2}".FormatInvariant( deployment.Name, deployment.DeploymentType.ToString(), exception.Message), exception); } + + deployment.ResultInfo = XmlHelper.Serialize(context.Result); + + _exportProfileService.Value.UpdateExportDeployment(deployment); } - return result; + return allSucceeded; } private void SendCompletionEmail(DataExporterContext ctx, string zipPath) @@ -1255,7 +1275,7 @@ private void ExportCoreOuter(DataExporterContext ctx) { if (!ctx.IsPreview && ctx.Request.Profile.Id != 0) { - ctx.Request.Profile.ResultInfo = XmlHelper.Serialize(ctx.Result); + ctx.Request.Profile.ResultInfo = XmlHelper.Serialize(ctx.Result); _exportProfileService.Value.UpdateExportProfile(ctx.Request.Profile); } @@ -1269,9 +1289,9 @@ private void ExportCoreOuter(DataExporterContext ctx) { if (ctx.IsFileBasedExport && ctx.ExecuteContext.Abort != DataExchangeAbortion.Hard && ctx.Request.Profile.Cleanup && allDeploymentsSucceeded) { - FileSystemHelper.ClearDirectory(ctx.FolderContent, false); + logger.Information("Cleaning up export folder"); - logger.Information("Cleared up export folder"); + FileSystemHelper.ClearDirectory(ctx.FolderContent, false); } } catch (Exception exception) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/DataDeploymentResult.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/DataDeploymentResult.cs new file mode 100644 index 0000000000..7fff7a46e5 --- /dev/null +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/DataDeploymentResult.cs @@ -0,0 +1,23 @@ +using System; + +namespace SmartStore.Services.DataExchange.Export.Deployment +{ + [Serializable] + public class DataDeploymentResult + { + /// + /// Whether the deployment succeeded + /// + public bool Succeeded + { + get { return LastError.IsEmpty(); } + } + + public string LastError { get; set; } + + /// + /// Last execution date + /// + public DateTime LastExecutionUtc { get; set; } + } +} diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs index 814d34498a..298d476c41 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/EmailFilePublisher.cs @@ -23,7 +23,7 @@ public EmailFilePublisher( _queuedEmailService = queuedEmailService; } - public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) { var emailAccount = _emailAccountService.GetEmailAccountById(deployment.EmailAccountId); var smtpContext = new SmtpContext(emailAccount); @@ -60,8 +60,6 @@ public virtual bool Publish(ExportDeploymentContext context, ExportDeployment de } context.Log.Information("{0} email(s) created and queued for deployment.".FormatInvariant(count)); - - return true; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs index 774cf7dee1..8827ab4aa3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs @@ -7,13 +7,13 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class FileSystemFilePublisher : IFilePublisher { - public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) { string destinationFolder = null; if (deployment.FileSystemPath.IsEmpty()) { - return false; + return; } else if (Path.IsPathRooted(deployment.FileSystemPath)) { @@ -39,11 +39,12 @@ public virtual bool Publish(ExportDeploymentContext context, ExportDeployment de System.IO.Directory.CreateDirectory(destinationFolder); } - FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); + if (!FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder))) + { + context.Result.LastError = context.T("Admin.DataExchange.Export.Deployment.CopyFileFailed"); + } context.Log.Information("Copied export data files to " + destinationFolder); - - return true; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs index 29a6d086b5..7a0954a1c7 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FtpFilePublisher.cs @@ -9,9 +9,8 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class FtpFilePublisher : IFilePublisher { - public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) { - var result = true; var bytesRead = 0; var succeededFiles = 0; var url = deployment.Url; @@ -62,19 +61,14 @@ public virtual bool Publish(ExportDeploymentContext context, ExportDeployment de } else { - result = false; + context.Result.LastError = context.T("Admin.Common.FtpStatus", statusCode, response.StatusCode.ToString()); - var msg = "The FTP transfer might fail. {0} ({1}), {2}. File {3}".FormatInvariant( - response.StatusCode.ToString(), statusCode, response.StatusDescription.NaIfEmpty(), path); - - context.Log.Error(msg); + context.Log.Error("The FTP transfer failed. FTP status {0} ({1}). File {3}".FormatInvariant(statusCode, response.StatusCode.ToString(), path)); } } } context.Log.Information("{0} file(s) successfully uploaded via FTP.".FormatInvariant(succeededFiles)); - - return result; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs index 9952c275ab..bce4f36bbd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/HttpFilePublisher.cs @@ -10,9 +10,8 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class HttpFilePublisher : IFilePublisher { - public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) { - var result = true; var succeeded = 0; var url = deployment.Url; @@ -45,11 +44,12 @@ public virtual bool Publish(ExportDeploymentContext context, ExportDeployment de } else if (response.Content != null) { - result = false; + context.Result.LastError = context.T("Admin.Common.HttpStatus", (int)response.StatusCode, response.StatusCode.ToString()); + var content = response.Content.ReadAsStringAsync().Result; - var msg = "Multipart form data upload failed. {0} ({1}). Response: {2}".FormatInvariant( - response.StatusCode.ToString(), (int)response.StatusCode, content.NaIfEmpty().Truncate(2000, "...")); + var msg = "Multipart form data upload failed. HTTP status {0} ({1}). Response: {2}".FormatInvariant( + (int)response.StatusCode, response.StatusCode.ToString(), content.NaIfEmpty().Truncate(2000, "...")); context.Log.Error(msg); } @@ -72,8 +72,6 @@ public virtual bool Publish(ExportDeploymentContext context, ExportDeployment de } context.Log.Information("{0} file(s) successfully uploaded via HTTP ({1}).".FormatInvariant(succeeded, deployment.HttpTransmissionType.ToString())); - - return result; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs index a6875e85ec..9bbc852ac1 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/IFilePublisher.cs @@ -2,26 +2,29 @@ using System.IO; using System.Linq; using SmartStore.Core.Domain; +using SmartStore.Core.Localization; using SmartStore.Core.Logging; namespace SmartStore.Services.DataExchange.Export.Deployment { public interface IFilePublisher { - bool Publish(ExportDeploymentContext context, ExportDeployment deployment); + void Publish(ExportDeploymentContext context, ExportDeployment deployment); } public class ExportDeploymentContext { + public Localizer T { get; set; } public ILogger Log { get; set; } public string FolderContent { get; set; } public string ZipPath { get; set; } - public bool CreateZipArchive { get; set; } + public DataDeploymentResult Result { get; set; } + public IEnumerable GetDeploymentFiles() { if (!CreateZipArchive) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs index 179d9dbf66..24b874be5f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/PublicFolderPublisher.cs @@ -7,13 +7,17 @@ namespace SmartStore.Services.DataExchange.Export.Deployment { public class PublicFolderPublisher : IFilePublisher { - public virtual bool Publish(ExportDeploymentContext context, ExportDeployment deployment) + public virtual void Publish(ExportDeploymentContext context, ExportDeployment deployment) { var destinationFolder = deployment.GetPublicFolder(true); - var result = false; if (destinationFolder.IsEmpty()) - return false; + return; + + if (!System.IO.Directory.Exists(destinationFolder)) + { + System.IO.Directory.CreateDirectory(destinationFolder); + } if (context.CreateZipArchive) { @@ -22,19 +26,19 @@ public virtual bool Publish(ExportDeploymentContext context, ExportDeployment de var destinationFile = Path.Combine(destinationFolder, Path.GetFileName(context.ZipPath)); File.Copy(context.ZipPath, destinationFile, true); - result = true; context.Log.Information("Copied zipped export data to " + destinationFile); } } else { - result = FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder)); + if (!FileSystemHelper.CopyDirectory(new DirectoryInfo(context.FolderContent), new DirectoryInfo(destinationFolder))) + { + context.Result.LastError = context.T("Admin.DataExchange.Export.Deployment.CopyFileFailed"); + } context.Log.Information("Copied export data files to " + destinationFolder); } - - return result; } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs index 80933dd1a3..f3e8c982b5 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs @@ -346,14 +346,20 @@ public virtual ExportDeployment GetExportDeploymentById(int id) return deployment; } + public virtual void UpdateExportDeployment(ExportDeployment deployment) + { + if (deployment == null) + throw new ArgumentNullException("deployment"); + } + public virtual void DeleteExportDeployment(ExportDeployment deployment) { if (deployment == null) throw new ArgumentNullException("deployment"); - _exportDeploymentRepository.Delete(deployment); + _exportDeploymentRepository.Update(deployment); - _eventPublisher.EntityDeleted(deployment); + _eventPublisher.EntityUpdated(deployment); } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProfileService.cs index 7a61a36def..b0b0c8a6bd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/IExportProfileService.cs @@ -108,7 +108,13 @@ ExportProfile InsertExportProfile( ExportDeployment GetExportDeploymentById(int id); /// - /// Deleted a export deployment + /// Update an export deployment + /// + /// Export deployment + void UpdateExportDeployment(ExportDeployment deployment); + + /// + /// Deleted an export deployment /// /// Export deployment void DeleteExportDeployment(ExportDeployment deployment); diff --git a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj index 5bd7ee173b..1edfe58738 100644 --- a/src/Libraries/SmartStore.Services/SmartStore.Services.csproj +++ b/src/Libraries/SmartStore.Services/SmartStore.Services.csproj @@ -174,6 +174,7 @@ + diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index 6da44acf8d..dfa4145f39 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -911,9 +911,6 @@ td.adminSeparator hr { .disabled { .muted(); } - .centered { - text-align: center; - } .progress-info { min-width: 260px; } diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 5de25bbd28..9d652d6ccf 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -24,6 +24,7 @@ using SmartStore.Services.Catalog; using SmartStore.Services.Customers; using SmartStore.Services.DataExchange.Export; +using SmartStore.Services.DataExchange.Export.Deployment; using SmartStore.Services.Directory; using SmartStore.Services.Helpers; using SmartStore.Services.Localization; @@ -322,6 +323,17 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile deploymentModel.ProfileDetails = PrepareProfileDetailsModel(profile, true); + if (x.ResultInfo.HasValue()) + { + var resultInfo = XmlHelper.Deserialize(x.ResultInfo); + + deploymentModel.LastResult = new ExportDeploymentModel.LastResultInfo + { + Execution = _dateTimeHelper.ConvertToUserTime(resultInfo.LastExecutionUtc, DateTimeKind.Utc), + ExecutionPretty = resultInfo.LastExecutionUtc.RelativeFormat(true, "f"), + Error = resultInfo.LastError + }; + } return deploymentModel; }) .ToList(); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs index 7d06e197a2..6c3c643cdc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs @@ -1,9 +1,11 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using FluentValidation.Attributes; using SmartStore.Admin.Validators.DataExchange; using SmartStore.Core.Domain.DataExchange; +using SmartStore.Services.DataExchange.Export.Deployment; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; @@ -91,5 +93,19 @@ public string DeploymentTypeIconClass public bool UseSsl { get; set; } public ExportProfileDetailsModel ProfileDetails { get; set; } + + public LastResultInfo LastResult { get; set; } + + public class LastResultInfo + { + public DateTime Execution { get; set; } + public string ExecutionPretty { get; set; } + public string Error { get; set; } + + public bool Succeeded + { + get { return Error.IsEmpty(); } + } + } } } \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml index dd67e6dd24..6ad62bfa39 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml @@ -111,23 +111,41 @@
    @T("Admin.DataExchange.Export.Deployment.Name")@T("Common.Enabled") @T("Admin.DataExchange.Export.Deployment.DeploymentType")@T("Common.Enabled")@T("Admin.Common.LastRun") @T("Admin.Common.Actions")
    @deployment.Name + + @Html.Partial("InfoDeployment", deployment) + @Html.SymbolForBool(deployment.Enabled) - @Html.Partial("InfoDeployment", deployment) + + @if (result == null) + { +
    @T("Common.Never")
    + } + else if (result.Succeeded) + { +
    @(T("Admin.Common.SuccessfulOn")): @result.Execution.ToString("g")
    +
    @result.ExecutionPretty
    + } + else + { +
    @result.Execution.ToString("g")
    +
    @T("Common.Error"): @result.Error
    + }
    From 81b653776d9c84d1e6fe4c88b456081bfef273e4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 21 May 2016 12:04:20 +0200 Subject: [PATCH 184/285] Forgot a method --- .../DataExchange/Export/ExportProfileService.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs index f3e8c982b5..d991b2cd42 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs @@ -350,6 +350,10 @@ public virtual void UpdateExportDeployment(ExportDeployment deployment) { if (deployment == null) throw new ArgumentNullException("deployment"); + + _exportDeploymentRepository.Update(deployment); + + _eventPublisher.EntityUpdated(deployment); } public virtual void DeleteExportDeployment(ExportDeployment deployment) @@ -357,9 +361,9 @@ public virtual void DeleteExportDeployment(ExportDeployment deployment) if (deployment == null) throw new ArgumentNullException("deployment"); - _exportDeploymentRepository.Update(deployment); + _exportDeploymentRepository.Delete(deployment); - _eventPublisher.EntityUpdated(deployment); + _eventPublisher.EntityDeleted(deployment); } } } From 76669070cd3b3af1891faf3d88ec77035d40a0a4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 23 May 2016 11:21:12 +0200 Subject: [PATCH 185/285] Improved export deployment --- .../Utilities/FileSystemHelper.cs | 22 +++++ .../201605201911421_ExportRevision.cs | 32 ++++++- .../DataExchange/Export/DataExporter.cs | 3 - .../Deployment/FileSystemFilePublisher.cs | 11 +-- .../Export/ExportExecuteContext.cs | 5 - .../DataExchange/Export/ExportExtensions.cs | 58 +++++++++++ .../Export/ExportProfileService.cs | 8 +- .../Administration/Content/admin.less | 7 ++ .../Controllers/ExportController.cs | 4 +- .../DataExchange/ExportDeploymentModel.cs | 26 +---- .../Administration/SmartStore.Admin.csproj | 1 - .../Views/Export/InfoDeployment.cshtml | 54 ----------- .../Export/_CreateOrUpdate.Deployment.cshtml | 51 ++++++++-- .../Views/Export/_ProfileList.cshtml | 8 +- .../Views/Export/_Tab.Deployment.cshtml | 96 +++++++++++++------ 15 files changed, 235 insertions(+), 151 deletions(-) delete mode 100644 src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml diff --git a/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs b/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs index cf778b71ff..3fbb4f83f8 100644 --- a/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs +++ b/src/Libraries/SmartStore.Core/Utilities/FileSystemHelper.cs @@ -31,6 +31,28 @@ public static string TempDir(string subDirectory = null) return path; } + /// + /// Ensures that path is a valid root path + /// + /// Relative path + /// Valid root path + public static string ValidateRootPath(string path) + { + if (path.HasValue()) + { + path = path.Replace('\\', '/'); + + if (!path.StartsWith("~/")) + { + if (path.StartsWith("~")) + path = path.Substring(1); + + path = (path.StartsWith("/") ? "~" : "~/") + path; + } + } + return path; + } + /// /// Safe way to cleanup the temp directory. Should be called via scheduled task. /// diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs index 4aa3f18e8d..1af4e3b589 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs @@ -82,8 +82,8 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.SubFolder", "Name of subfolder", "Name des Unterordners", - "Specifies the name of a subfolder where to deploy the data.", - "Legt den Namen eines Unterordners fest, in den die Daten bereitgestellt werden sollen."); + "Specifies the name of a subfolder where to publish the data.", + "Legt den Namen eines Unterordners fest, in den die Daten veröffentlicht werden sollen."); builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.ZipUsageNote", "If there are a large number of export files, it is recommended to use the option Create ZIP archive. This saves time and avoids problems, such as a full email mailbox.", @@ -106,6 +106,34 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) builder.AddOrUpdate("Admin.Common.LastRun", "Last run", "Letzte Ausführung"); builder.AddOrUpdate("Admin.Common.SuccessfulOn", "Successful on", "Erfolgreich am"); + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.Name", + "Name of profile", + "Name des Profils", + "Specifies the name of the publishing profile.", + "Legt den Namen des Veröffentlichungsprofils fest."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.ProfilesTitle", + "Publishing profiles", + "Veröffentlichungsprofile"); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.NoProfiles", + "There are no publishing profiles.", + "Es liegen keine Veröffentlichungsprofile vor."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.Note", + "Click New profile to add one or multiple publishing profiles to specify how to further proceed with the export files.", + "Legen Sie über Neues Profil ein oder mehrere Veröffentlichungsprofile an, um festzulegen wie mit den Exportdateien weiter zu verfahren ist."); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.PublishingTarget", + "Publishing target", + "Veröffentlichungsziel"); + + builder.AddOrUpdate("Admin.DataExchange.Export.Deployment.DeploymentType", + "Publishing type", + "Art der Veröffentlichung", + "Specifies the type of publishing.", + "Legt die Art Veröffentlichung fest."); + builder.Delete( "Admin.DataExchange.Export.FolderAndFileName.Validate", diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index eef4bd4712..79cba8794d 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -1109,9 +1109,6 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) ctx.ExecuteContext.FileName = resolvedPattern + fileExtension; path = Path.Combine(ctx.ExecuteContext.Folder, ctx.ExecuteContext.FileName); - - if (ctx.ExecuteContext.HasPublicDeployment) - ctx.ExecuteContext.PublicFileUrl = ctx.Store.Url.EnsureEndsWith("/") + PublicFolder.EnsureEndsWith("/") + ctx.ExecuteContext.FileName; } if (CallProvider(ctx, null, "Execute", path)) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs index 8827ab4aa3..d769ac2a6b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Deployment/FileSystemFilePublisher.cs @@ -21,16 +21,7 @@ public virtual void Publish(ExportDeploymentContext context, ExportDeployment de } else { - destinationFolder = deployment.FileSystemPath; - - if (!destinationFolder.StartsWith("~/")) - { - if (destinationFolder.StartsWith("~")) - destinationFolder = destinationFolder.Substring(1); - - destinationFolder = (destinationFolder.StartsWith("/") ? "~" : "~/") + destinationFolder; - } - + destinationFolder = FileSystemHelper.ValidateRootPath(deployment.FileSystemPath); destinationFolder = CommonHelper.MapPath(destinationFolder); } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs index 6c25d397bd..d5f2860ff8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExecuteContext.cs @@ -120,11 +120,6 @@ public bool IsMaxFailures /// public string PublicFolderPath { get; internal set; } - /// - /// The public URL of the export file (accessible through the internet). null if the profile has no public deployment. - /// - public string PublicFileUrl { get; internal set; } - /// /// Provider specific configuration data /// diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs index 29a45613ad..3ac52e5b6b 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportExtensions.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Web; +using SmartStore.Core; using SmartStore.Core.Domain; using SmartStore.Core.Domain.DataExchange; using SmartStore.Core.Domain.Stores; @@ -157,5 +158,62 @@ public static string GetPublicFolder(this ExportDeployment deployment, bool crea return path; } + + /// + /// Get url of the public folder and take filtering and projection into consideration + /// + /// Export deployment + /// Common services + /// Absolute URL of the public folder + public static string GetPublicFolderUrl(this ExportDeployment deployment, ICommonServices services) + { + if (deployment != null && deployment.DeploymentType == ExportDeploymentType.PublicFolder) + { + var filter = XmlHelper.Deserialize(deployment.Profile.Filtering); + var storeId = filter.StoreId; + + if (storeId == 0) + { + var projection = XmlHelper.Deserialize(deployment.Profile.Projection); + storeId = (projection.StoreId ?? 0); + } + + var store = (storeId == 0 ? services.StoreContext.CurrentStore : services.StoreService.GetStoreById(storeId)); + + var url = string.Concat( + store.Url.EnsureEndsWith("/"), + DataExporter.PublicFolder.EnsureEndsWith("/"), + deployment.SubFolder.HasValue() ? deployment.SubFolder.EnsureEndsWith("/") : "" + ); + + return url; + } + + return null; + } + + /// + /// Get icon class for a deployment type + /// + /// Deployment type + /// Icon class + public static string GetIconClass(this ExportDeploymentType type) + { + switch (type) + { + case ExportDeploymentType.FileSystem: + return "fa-folder-open-o"; + case ExportDeploymentType.Email: + return "fa-envelope-o"; + case ExportDeploymentType.Http: + return "fa-globe"; + case ExportDeploymentType.Ftp: + return "fa-files-o"; + case ExportDeploymentType.PublicFolder: + return "fa-unlock"; + default: + return "fa-question"; + } + } } } diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs index d991b2cd42..2b9b6b5757 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/ExportProfileService.cs @@ -220,13 +220,7 @@ public virtual void UpdateExportProfile(ExportProfile profile) if (profile == null) throw new ArgumentNullException("profile"); - if (!profile.FolderName.StartsWith("~/")) - { - if (profile.FolderName.StartsWith("~")) - profile.FolderName = profile.FolderName.Substring(1); - - profile.FolderName = (profile.FolderName.StartsWith("/") ? "~" : "~/") + profile.FolderName; - } + profile.FolderName = FileSystemHelper.ValidateRootPath(profile.FolderName); _exportProfileRepository.Update(profile); diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index dfa4145f39..c31aff72bc 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -1580,6 +1580,13 @@ table.payment-method-features td { } +/* Data exchange +-------------------------------------------------------------- */ +.profile-list { + margin-bottom: 10px; + border-bottom: 1px solid #ddd; +} + /* Import column mapping -------------------------------------------------------------- */ @media (max-width: 1280px) { diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs index 9d652d6ccf..d109ac91cb 100644 --- a/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/ExportController.cs @@ -321,8 +321,6 @@ private void PrepareProfileModelForEdit(ExportProfileModel model, ExportProfile { var deploymentModel = PrepareDeploymentModel(profile, x, null, false); - deploymentModel.ProfileDetails = PrepareProfileDetailsModel(profile, true); - if (x.ResultInfo.HasValue()) { var resultInfo = XmlHelper.Deserialize(x.ResultInfo); @@ -452,6 +450,8 @@ private ExportDeploymentModel PrepareDeploymentModel(ExportProfile profile, Expo UseSsl = deployment.UseSsl }; + model.PublicFolderUrl = deployment.GetPublicFolderUrl(Services); + if (forEdit) { var allEmailAccounts = _emailAccountService.GetAllEmailAccounts(); diff --git a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs index 6c3c643cdc..5392139e71 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/DataExchange/ExportDeploymentModel.cs @@ -5,7 +5,6 @@ using FluentValidation.Attributes; using SmartStore.Admin.Validators.DataExchange; using SmartStore.Core.Domain.DataExchange; -using SmartStore.Services.DataExchange.Export.Deployment; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; @@ -16,6 +15,7 @@ public class ExportDeploymentModel : EntityModelBase { public int ProfileId { get; set; } public bool CreateZip { get; set; } + public string PublicFolderUrl { get; set; } [SmartResourceDisplayName("Common.Image")] public string ThumbnailUrl { get; set; } @@ -33,28 +33,6 @@ public class ExportDeploymentModel : EntityModelBase [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.DeploymentType")] public string DeploymentTypeName { get; set; } - public string DeploymentTypeIconClass - { - get - { - switch (DeploymentType) - { - case ExportDeploymentType.FileSystem: - return "fa-folder-open-o"; - case ExportDeploymentType.Email: - return "fa-envelope-o"; - case ExportDeploymentType.Http: - return "fa-globe"; - case ExportDeploymentType.Ftp: - return "fa-files-o"; - case ExportDeploymentType.PublicFolder: - return "fa-unlock"; - default: - return "fa-question"; - } - } - } - [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.Username")] public string Username { get; set; } @@ -92,8 +70,6 @@ public string DeploymentTypeIconClass [SmartResourceDisplayName("Admin.DataExchange.Export.Deployment.UseSsl")] public bool UseSsl { get; set; } - public ExportProfileDetailsModel ProfileDetails { get; set; } - public LastResultInfo LastResult { get; set; } public class LastResultInfo diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index 2dad6d6fe4..15852be9a4 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -508,7 +508,6 @@ - diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml deleted file mode 100644 index f111f30257..0000000000 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoDeployment.cshtml +++ /dev/null @@ -1,54 +0,0 @@ -@using SmartStore.Core.Domain.DataExchange -@using SmartStore.Admin.Models.DataExchange -@model ExportDeploymentModel -@{ - Layout = null; -} -@helper FileDownloadLink(string path, string fileName) -{ - -} -
    -  @Model.DeploymentTypeName -
    -
    - @if (Model.DeploymentType == ExportDeploymentType.FileSystem) - { - foreach (var path in Model.ProfileDetails.ExportFiles) - { - @FileDownloadLink(path, Path.GetFileName(path)) - } - - if (Model.ProfileDetails.PublicFiles.Count > 0) - { - foreach (var grp in Model.ProfileDetails.PublicFiles.OrderBy(x => x.StoreId).GroupBy(x => x.StoreId)) - { -

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

     

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

     

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

     

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

     

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

    @T("Reviews.ProductReviewsFor")

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

    {1}

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

    {2}

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

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

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

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

     @warning

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

     @warning

    }
    } diff --git a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs index 8e2a4f8ca7..ebc506887c 100644 --- a/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs +++ b/src/Presentation/SmartStore.Web/Controllers/CatalogHelper.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Diagnostics; using System.Linq; -using System.Linq.Expressions; using System.Web; using System.Web.Mvc; using SmartStore.Collections; @@ -25,14 +25,12 @@ using SmartStore.Services.Security; using SmartStore.Services.Seo; using SmartStore.Services.Tax; +using SmartStore.Services.Topics; using SmartStore.Web.Framework.UI; using SmartStore.Web.Framework.UI.Captcha; using SmartStore.Web.Infrastructure.Cache; using SmartStore.Web.Models.Catalog; using SmartStore.Web.Models.Media; -using SmartStore.Core.Data; -using System.Collections.Specialized; -using SmartStore.Services.Topics; namespace SmartStore.Web.Controllers { @@ -152,7 +150,8 @@ public ProductDetailsModel PrepareProductDetailsPageModel( bool isAssociatedProduct = false, ProductBundleItemData productBundleItem = null, IList productBundleItems = null, - NameValueCollection selectedAttributes = null) + NameValueCollection selectedAttributes = null, + NameValueCollection queryData = null) { if (product == null) throw new ArgumentNullException("product"); @@ -187,6 +186,30 @@ public ProductDetailsModel PrepareProductDetailsPageModel( IsCurrentCustomerRegistered = _services.WorkContext.CurrentCustomer.IsRegistered() }; + // get gift card values from query string + if (queryData != null && queryData.Count > 0) + { + var giftCardItems = queryData.AllKeys + .Where(x => x.EmptyNull().StartsWith("giftcard_")) + .SelectMany(queryData.GetValues, (k, v) => new { key = k, value = v.TrimSafe() }); + + foreach (var item in giftCardItems) + { + var key = item.key.EmptyNull().ToLower(); + + if (key.EndsWith("recipientname")) + model.GiftCard.RecipientName = item.value; + else if (key.EndsWith("recipientemail")) + model.GiftCard.RecipientEmail = item.value; + else if (key.EndsWith("sendername")) + model.GiftCard.SenderName = item.value; + else if (key.EndsWith("senderemail")) + model.GiftCard.SenderEmail = item.value; + else if (key.EndsWith("message")) + model.GiftCard.Message = item.value; + } + } + // Back in stock subscriptions if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStock && product.BackorderMode == BackorderMode.NoBackorders && @@ -285,92 +308,6 @@ public ProductDetailsModel PrepareProductDetailsPageModel( return model; } - public void SelectProductAttributeValues(ProductDetailsModel model, NameValueCollection queryData) - { - Guard.ArgumentNotNull(() => model); - - if (queryData == null || queryData.Count <= 0) - return; - - try - { - var items = queryData.AllKeys - .Where(x => x.EmptyNull().StartsWith("product_attribute_")) - .SelectMany(queryData.GetValues, (k, v) => new { key = k, value = v }); - - foreach (var item in items) - { - var ids = item.key.Replace("product_attribute_", "").SplitSafe("_"); - if (ids.Count() > 3) - { - var productId = ids[0].ToInt(); - var bundleItemId = ids[1].ToInt(); - var productAttributeId = ids[2].ToInt(); - //var attributeId = ids[3].ToInt(); - - var attribute = model.ProductVariantAttributes.FirstOrDefault(x => - x.ProductId == productId && - x.BundleItemId == bundleItemId && - x.ProductAttributeId == productAttributeId); - - if (attribute != null) - { - switch (attribute.AttributeControlType) - { - case AttributeControlType.DropdownList: - case AttributeControlType.RadioList: - case AttributeControlType.Checkboxes: - case AttributeControlType.ColorSquares: - var attributeValue = attribute.Values.FirstOrDefault(x => x.Name.IsCaseInsensitiveEqual(item.value)); - if (attributeValue != null) - { - attribute.Values.Each(x => x.IsPreSelected = false); - attributeValue.IsPreSelected = true; - } - break; - case AttributeControlType.TextBox: - case AttributeControlType.MultilineTextbox: - attribute.TextValue = item.value; - break; - case AttributeControlType.Datepicker: - if (item.value.Length == 8) - { - attribute.SelectedYear = item.value.Substring(0, 4).ToInt(); - attribute.SelectedMonth = item.value.Substring(4, 2).ToInt(); - attribute.SelectedDay = item.value.Substring(6, 2).ToInt(); - } - break; - } - } - } - } - - items = queryData.AllKeys - .Where(x => x.EmptyNull().StartsWith("giftcard_")) - .SelectMany(queryData.GetValues, (k, v) => new { key = k, value = v }); - - foreach (var item in items) - { - var key = item.key.EmptyNull().ToLower(); - - if (key.EndsWith("recipientname")) - model.GiftCard.RecipientName = item.value; - else if (key.EndsWith("recipientemail")) - model.GiftCard.RecipientEmail = item.value; - else if (key.EndsWith("sendername")) - model.GiftCard.SenderName = item.value; - else if (key.EndsWith("senderemail")) - model.GiftCard.SenderEmail = item.value; - else if (key.EndsWith("message")) - model.GiftCard.Message = item.value; - } - } - catch (Exception exception) - { - Logger.Warning(exception.ToAllMessages(), exception); - } - } - public void PrepareProductReviewsModel(ProductReviewsModel model, Product product) { if (product == null) @@ -591,9 +528,31 @@ public ProductDetailsModel PrepareProductDetailModel( pvaModel.BeginYear = match.Groups[1].Value.ToInt(); pvaModel.EndYear = match.Groups[2].Value.ToInt(); } + + if (hasSelectedAttributes) + { + var attributeKey = "product_attribute_{0}_{1}_{2}_{3}".FormatInvariant(product.Id, bundleItemId, attribute.ProductAttributeId, attribute.Id); + var day = selectedAttributes[attributeKey + "_day"].ToInt(); + var month = selectedAttributes[attributeKey + "_month"].ToInt(); + var year = selectedAttributes[attributeKey + "_year"].ToInt(); + if (day > 0 && month > 0 && year > 0) + { + pvaModel.SelectedDay = day; + pvaModel.SelectedMonth = month; + pvaModel.SelectedYear = year; + } + } + } + else if (attribute.AttributeControlType == AttributeControlType.TextBox || attribute.AttributeControlType == AttributeControlType.MultilineTextbox) + { + if (hasSelectedAttributes) + { + var attributeKey = "product_attribute_{0}_{1}_{2}_{3}".FormatInvariant(product.Id, bundleItemId, attribute.ProductAttributeId, attribute.Id); + pvaModel.TextValue = selectedAttributes[attributeKey]; + } } - int preSelectedValueId = 0; + var preSelectedValueId = 0; var pvaValues = (attribute.ShouldHaveValues() ? _productAttributeService.GetProductVariantAttributeValues(attribute.Id) : new List()); foreach (var pvaValue in pvaValues) diff --git a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs index b3f13e7653..79497418a9 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ProductController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ProductController.cs @@ -8,7 +8,6 @@ using SmartStore.Core.Domain.Localization; using SmartStore.Core.Domain.Media; using SmartStore.Core.Domain.Orders; -using SmartStore.Core.Localization; using SmartStore.Services; using SmartStore.Services.Catalog; using SmartStore.Services.Common; @@ -31,7 +30,7 @@ namespace SmartStore.Web.Controllers { - public partial class ProductController : PublicControllerBase + public partial class ProductController : PublicControllerBase { #region Fields @@ -168,16 +167,16 @@ public ActionResult ProductDetails(int productId, string attributes) } } - // prepare the model var selectedAttributes = new NameValueCollection(); - selectedAttributes.ConvertAttributeQueryData( + // get selected attributes from query string + selectedAttributes.GetSelectedAttributes( + Request.QueryString, _productAttributeParser.DeserializeQueryData(attributes), product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing ? 0 : product.Id); - var model = _helper.PrepareProductDetailsPageModel(product, selectedAttributes: selectedAttributes); - - _helper.SelectProductAttributeValues(model, Request.QueryString); + // prepare the view model + var model = _helper.PrepareProductDetailsPageModel(product, selectedAttributes: selectedAttributes, queryData: Request.QueryString); //save as recently viewed _recentlyViewedProductsService.AddProductToRecentlyViewedList(product.Id); From 3b10ce6a44de3fa805074267b4940a68adfd2416 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Sat, 18 Jun 2016 17:51:18 +0200 Subject: [PATCH 242/285] Updated licensing component --- .../SmartStore.Licensing.dll | Bin 280576 -> 282624 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/lib/SmartStore.Licensing/SmartStore.Licensing.dll b/lib/SmartStore.Licensing/SmartStore.Licensing.dll index 51e391ce236bd865fee64fdd1a7c9ad3e42ef34e..8acdf39c24bdd3254678ba27247be601f4ecfe75 100644 GIT binary patch literal 282624 zcmd3P37izg*?w>LOwUZu%(A_+JF^F}1IXI!%q+_;;0hjq;DINKxQimYAPA_mGT_0w zD{4HVpklmYzzdJWJMlJZj2bmYBNFcjF&d9}UBEX|^%>uHPS>nX0Gi zse0>r>#jXy&gF`xC`t&Q?b{XQ5&Y7>$^59W zQe$)vHX4EpftigIv;rd)9R z!lRwo-cwqK$18M2*$+}h1=@N%%vqRMlcbY{Sru{vVEO4+c?#~p>7S-dK3`F)`2W(c zv4w<%9j^aD6h3THVN+Jr`5U+aa+e#D0<5<)#?+!_)$VFBUG)Q4dAz{7OI`T zW`d$rw7@<=)vRVkF)R`tK&maGWP!uKSwz0*hOTlbHhIuh>s*9vpNB8K#Ynfn|1QcA z@C>h}s@ddfk*j78;M>rr4?$pId>Zj-$LBzNdhj_NpG)w$0iXNvc>$mQ@O`$x-uVZ7 zsoDxepxPulOWOIKKzj)7(G>k80smCMsC%kC7oH{j0|8qE|E>goN5DyfrpP9E_gMclq*&2`eW!GHUG_0i|d+_${+o?W;71Sr_0L9F#?Lm?ZJ+0ns zy^XxE-oe*$RF+e8Yd}@>71xVOh%}x=;Uhg!Cw?{{oXRG%CDmpq9WW!^gVwPovm=F^ zL^4!+7y`1I@a4(GR9lSeH1fkN?gBT|X^TAC5I@=u75y5tG;TXu zTSEB-aQdgMtOc5{RJB{laDp`oK3lYvGaZx+E_#H6lEFofbWk$5=sgZf1{b~8LCN5v z_cU`g@;}6L4GJ`VanO~Jc~~jpR4h?8=trE3Bk{F`GEk+=lD4G!G4hnrE@X}s0k-f zO}LzD99ob_O=nIxpJEv`1BRjys-q-zZhzpr5&jX1$jTx4 zlF$l=9&#XUDX|7%XjDS4O3b5s+S6+dDDKsHgo2DDUF7sDai?vtLAa3;qrS#!B?GX^ zrpgu#ZLa}l`seK*U_Oib3i3;O&gpm1h|MA*ZrbI|{8~J*@T9dxDH2aPT57~&2Y`9(>k{&{J?7zYX?yifnYGB$|j!NTa898;!uoblbb*$H+aa zXK&n*>k2C55Q6T&y(q$E5>pl;e^AdLPj0qmcOu|;bwwX#NFq8NN?w5~;IR=(LdkW& zjq)ec5z}o{dPtGgC1y`#dzEfaVnal?_auX6?}eXq+f7_&JbgjJsbg$F@aP}VzJyL@ z1F*`b6#9pRGEImvc>j><*cCOMik*beEeN1H0A$cDNZ{!(sJ}oJR1B&YNCnqL5`rX0 zU1TXpYG1wdv=5AWgq&fY7+5=Eok8|f9nxor^{W)`PF7Sj8a5Ss)mZ;M6) z>HSg4FG-P7JsE5aNP2%HJPg4n3p45JgYmf(pZWNlfzLs3+Z~@@!TeWzFp}kQBlQhr z4*Gl#?w#H6fC4>%N|^QjK2B;nrwJ!ljdL11X=Dw8tHlLcCs5QaF&-4CDo|(%*>g}X zbbgL_5rV7Ph2J*>>S}OnvMb0Bg;OsD!(EM=HL;a|B zj1=hRav5_%4leY z@KWkm(0t9}fryvtG6`*y5ao5bvMB~#GD>-u3VdgE*{Q3;kvOPCkW%=F8~e<>$_*Z?g=*!69qW3r`8C>*U2PK1xlD^{JkPI%m!9mI3qGjqv zqEI*cQ!QjMA-_>4`opc%_5#XoI`xNl<3oL079Z*lsj!d7rw^Yb$iwj&kIye*{}P{G zm`R5mh+i@XeXf9?&ST*LEu~zCu#BDi3n!>Ru|gtHnw5dzY7mjS&ISRaqKFPr!kU2H z#xG&nCAx}5!mmRjgxyPlaL4CnSf|t?$SBb>NybLO=pmRoF$u2`@NNXV(;MN&bEE<} zxSgSNn1iTYohC8TBB+6)my=jKFf=yN8%nHfeWp~--p1tV6Kk9{!3M|*w}n)DvBpM& z&k76ugLjP$WndOj=G<0@WRr*Two=)Y;PEZQ4XjU1iEbW>BO4Xufr56WfBOgCO2J#yQ^1rSaCxIml98!D!2Tdsv z2!=)A9I2jOE4`p90C+eyb*2P<&A|3`4=mHK8 zcm|$4G4s9zPGaEXH;(v$1eP<Em4n&e-F#f&@?}5ylPA-sePf0|TFade%Idn#l}QOvq*=1u6!VpYHja4q5Tu zVc@%$U*TlHEC$vtI&@A`TnO?UBat7xe~gm@A2HH!$RC>Pg%{`|1bh1PSN%c8b29@& z=N)^J1kPnZ-F4LA61a|myR6s$E&&Xg5XSCDEMF;sItC&y&pki_-(cX%8?7}GxRikn zXFWe!0*^BAodXHWm9%+`GvcUk6$E1>0{v2^|L>g zz%vZYdvoUT66j{&*!GF46UanH?q2xi9O>mH2HrkKeLw=}ERf*83_WkI1Qszc<;8DT zN}!&BbGENML;}w4BXZ+ z>{405moczM{oDJ=XxbTA`tiB1$Yyj218Wxl?8YWjgo?2ZBw2LG9(PJk0|P_TO(#mA zm4VM^e?CnDpD=J=-y2ThwK1^ogLB6_vNkd@^zmlz}a`?s2%xGz_p1%YK(V`E%)G8Uxqd`k!w!Rf#-0l#zcP8yY6P%wZt$+gqNJ zKraJ#&K|O=PEatcK&aD?etm}YaTf#I_Wy8iSrp%4;0Fi%b(Zuog@JW9oH#)O`!Uck z>)U^o0J>`g`p(76|KjBHsf-+PTEyw~E@$Acp`7Am>t2jpdGT$I+TWdl_7@g7wR0^4 z$4%d`R>qTM;2#4EhB<-!TyWIfi4HQIk>B37yVFiE%7t+D_+eMdP>y2YkSlr{Byb`F zS9NY`m%u3uynFiW(XuRh7&z@0`G+0ldOjmRDKtB|bS49H?tSRTGLXv{xG}%wk4~ms z%t+UV16NBgCooX+;zv#cwHbK+s<);%$vKdb)@e5!EJZnufnR@i!ZR|EVGLAT(a93n zmjSc&tGEQd&A@@TY#ixG{{u$GrsiKDz1+$`{@DJF5}3`v=Vw1ZSL(_<1IM3o$=yzJ zFe5<5-u=&oD`m|Y%fRy2iz{Rxn6Sae!5Ejg!?q71|Lo%Az7}%12*=fW3GjMxu%0egCUT5UBm;M=(fxOMYK9klvS${YK zw_R29q>T4t2C5r-elCzb8t#4fZ z#4bX49KpaX3txWENgsyzkp9IF_HYIa>lk?MmDP91K;C0u`vYHoAc2D!xb@n_w@FD( zXW*v$&p1{3xQc-*ubb@X@m(1>V|)EL>Ekd4ULN^kL;`P#FpgOK3kj@e;6MMd;eA)&`^x$WX;$Ofi4F2dh5ca(#QP_+#NdD3G^5S1{VM1htkLQ7?}9C zH%^wocNzHYSwARBU=;&joxjgV5}3)rGoK8*Mgj{M7{1{GrzLGBKpb#T6lH{(ww8-2 zGS4+31OF2~wDg#U?4md2W>~h*G100n&Pr%&Yca*{|C~b9`u{*bnf^bDpFfG8KhsaT z{|jOJ5joOI+NDcI862hF z|9c8N)Xy=d`v;4W*2=X|IFq)}Sb5J2>jR;1Wyl3c4vcz~>M}W+gX9QN#!PZuf9p!g z851VgwjfuC9_kK{smf-Yox9UZ@>a*|3w$BKk&Sm8SMlJs4Zi3_Yt>St27{??vrHD^kA_@lOLivrX7y2eBzo7|rCm_EjWDV5l zy82{d4OA868mNKFD^_@{8b-xxQ_(z!wT;l@xf(4L=@P{ys$ZmQ-TFwTdsI9<<~KQ6 zkita^>7pQasFJKhFe&?*IxdQd>QlQ~+aex(;ESNY;5+oDPF5ZuvPxv^HZ=~Md)cJS_Cz$VrsI+P*JgPb?Kqq)Z>g~^ zEfhmLj-eeVxgBfBVUf=)o1>6RWfWHKK^gs5^4FV?_SwRg=|W#Xf&0_f?oXc_0SJFy zj;~&^@KRSKrLKr+>!B<()QzJcJN*mP_oxGhy5+d9V-h1!H1kpwbOhlVN*lS(nY~Ha zqM`B1Uyv$33(f|_3GA;9#s*+?o@e%^ItWDj$p&o!8>^wvXux$GZ7Ul!+jJ~z>BZUP zd^En$-Zeg-n?W7dY|Jf;0b;bOH7yqUaeZ5A;P0?&HU`&5wPIss%_zW8x37n5i_(a3 z59ZVgVl}1TiI2d}p^Qo;EldOq$KfhCWG{YMwu)55jAYiL@GDYbGn{UkxWNqDn5-Ge z*riKS(F|=g8%1bOBNa*;xkF<5b*Y#clN8<5(@ke>Fm!F z+4#7)Nik7OC@^REiZipZO;xDhp*&N!rTYE>w9u-KGB~j=l7ea{g4K(Fx25{Y+t!FK z;&FVnAk8AaYNcr`9cNUOE8O_zgNYdl=3a&x?#BYGp<>Lw5Wm!4p$?D-svqQoe#r~{ zvKK1b6XCC(%64TXj)NL{eje>7qMZ;^=WqK281wsEiy5bobPms6LiiYKDdQBf&f(h# z)5vG&+&99Zm~{TOPll0B8_-w?<;efC*NinE_C#qfgQu?S=%bFhb&e-S+xEbv2<=lm za4A0fRDw}=BYaG!0qLvBp7@%qR}E`>P^U&=6i8`Ig{ni<%C=eq$=BD=ccJD~`ax}$ z_E2+8F1{^IpuHTFn@43tj;6xZ;p&QQwdh0|BWvgzT>8N|N@B;~LlAOS!Y@O1RSG*;rjc{u*(naWnihQk^d7#g%YbrShTFhB=ML z6i3Ynp%RUJvaU_>Kn>uxl`+#Z02x9j*`IaY=g8K7A7 ztCo?|J4@esJG*Rf#Kwt@W%#$MIDR{0;6cSPhf+pr|P(woS>mrkD$>56t0-$SKbjpHz(Ofr@(t^zR?!5}4R zMq;#ZfjXRRob%+SNO!b*(L(&UXyL*|*+XBLdu*gP+T|h(7xfxuC?CV=@3TP4Rn(+V z#q$|Gl3lcH?wMWDUM8~th48Awt4`}A>=m_i)3s=L+%tC!)wAb+@!M-D##mRc(J)XC z>3UYvcdp+I7e?yY!uc$HFUv!L_8ROfBfZT=$1w|mZyHh7-?uhEp8kJKz8<3Y8g<@) zch$3J9(-&Ok(MNlb@v)gRUTghICvP+yH?W=sx$g`RT+!lU68*6l!v1d+{>|9wLd`C zV!RlN7+b4U>vh-+v^&wo=`F}#(e4t;P)oGCMvR+2LN7Or%4@%kc@@h$#~t^>S-tU^ zR47;D36?ZVUNb{21C5Aw;?0_((A2<4jkeCFpN0V(`3+^?tA4yo?~e5LtR|~Dy11Gg zbv?WJ%oQuTA}D1&)>O6NRqGt!G4a!^XYV;@-p_g?UF0gATu;r}MNd6<2ErVio4xt$ z>Cfw30bHc!$p>cj3a*UJ)U9(7xqTiKO!kj|d!BN;+wo$Kf`|0BijE`)(UcVqBD~K1 zS-9Se8SQcsmM^|Zt&xh9%hi4xRy{j#;v|yJZETBs7pI1s!wZ(zQ~zP8Cy{P$X4Fa- zErhq#H3%wavOjaUy_G`Ga7{=xm<{$fD9@X>n$0ku4|DMX0VrlOtr;4thms`pl^?=C zmo6#jH3FqJkVtb=h-RNTVEo^y)O!=wk1;4u?H)CEKe;y%&F*{P9V<`-y=1=OnA4x7 zf`mCW!JLrlNT+tm+WVb23(Q`#V~(Ev)}Kdy5A|*?&YHGmH;pbXh4|=Ggw5C@ndA2#H;v8*0|u7@ZoaR&y7#GP9s7g&z}^E()XG{w0Wo zUv5p9_4x>_7s2{2f-Xd}doF+Gc`7+Ki+dAhA=;IY)nn*Q(|!oK=4EG36Vi%;jIPEl zs}So4J$o3nQQYT+Q)XSN#jL~3YJ@qWDc-e<+0xa!Ih8e= z((BBoI+iJ0KffzAG@YtRr((I;88bGokTucK-O#vr79yLI{o}MBa#evZb0|hwucm6u zn$4+pvu4-=GS`{0b*bIx$80fo>ov3WUA<;|@8rxSJt3vk( zW%Je>8>zPSa?)FOPp6Dro;~ei8uQkrYR%gE`DSh7f}YJVM@)l0>&#jT9;<*U14y|2 z=R%7mx>Ip;d;xw^L(FihI@e}KPzaH`MjFf^)*Dpvh=6=BiA~G|eHVxo$(oG`mu;nAPo{nwG5oxs-J=>a;l|Z4S9~ ziG7J^6Y=cuIZ~6v>*sYfBD>=zE}4BfVLdg=)&o>C+@)t5?l_PYJ)%GHR#D^U^`<(j zJKdg9=XTsnecxv87Mo=cWvvl0jVbJJhOD)05j`>nJ-~;(j=B0G4 zj(uX0pc_jAV}`VZbdZfu6yJ$;>oIZY9+e8G+v_ldYyTi;3g=P`>w)jn&$pr#WvM$K z9|onOHi+FyTn<6vFs3H3Ww}~sy^AbR`v^~CtA>6vR8bbwIO^wWLy)`hS|_)0h=#^a zn>Y={OHl-?Fvbn@mD_5OM{zCFF#6-i9;gMuDBNMrgGELZNG}>mo8~dMK}wSdODVl@Ty?Uz*^D~GM}5O);6QY z^k*S~BVRk>G)X?5$~09OT4p|lnG5Ne`AEr_5H=XT#M&q3ZdgPv3jk#_oPwhDklPRr zM$AYjg-$&Usv4?E-5k`Dwu+W0Gm~jlj(F}wGZeXuMCro~xDc&ZLTB^z#Wy3 z0ItFGi0`vukN#FNTjUi9uWX`q&ymU(P%J3jLGS4BJ&&pIfC(_ae}N?lZiG+b7b{T; z29L_k_+b3XJ3XhspY!<=m}B<;9s_@)@aa4eUpVK#8@|Pj2$HUooPp8`BtL>-U4hoe zf;$)FF_jm$*F8=YAXrIqnu=gNPMGMEQ-qvQ&|atCLrggBK~D4rhKKcE2mI_NzK=mlh)sAp_|h^RZiga0)% zW!=&6e#Py;>7Vb`<6;XLNcWqLgkQdgL)}2PK1xA~!|2WN=Yb zM1hh)qPWR)HDwG7})#1c?>t!DI}2n)fKS_q>ke?r~HA@ZzqlYrf7Ea685+)JWxwR1DJ^LpXrwin5` zP{4DEL2X6A#W2M0@g!Zs{In%9T-Z_b5%+@lwAWVm3gUpQ~yl59gV`Q zTZ?eT8!mcA^Se*~) zdhcfFe?!?9ZlmEA7JI@tX^;+!JxZH`jH9Eh(W9y!T^ETi?XoJMOxANJ8gBnYM5Twq za<`7c+Y8|#T2p1ZA%cwypOJJ}42iIKp4MWaE;SNcr^lRNdEq_4JvRlizHBo6~3JAX5(*r0Ax_!6i`=UC~ z+fuiZ*&?rqsIn=JJljRNkQyGPaj>@nh9FWb;_|)6kubNwyf=QSjgxr}epli{`hx6e zlKe~NXJFQw(vv>NZznx6gC>CLCNBjVkfM{WRst=lf*X+^EMVuKz=9J36dmAF>n7KY zv=ZFhW^0vbKNL=G)AcyP6R}+N;0X}xNaR#Wk%W|(mW3R1I`3>Zuf2!7@W?Iy|rWJ71Mm)5VAFA;?Cjx0h z(3*H?rHaZ`xM9y9zQ@3geA91`Z}!8!Tgi7`6OXts(!pWVcPra>jEgz!wvpI)+T|Af z7Vxyot@x$24$T%Wnsm{mONC5Zx&>!GLly?{)ZkI~ZzmIeze~TQ!-gXtG}OYu->=%2 z_19&1zlz!p-KC~Bn=kjDS$h{Mems)il*Z7H7wo;dvzx+*)YMqFBL{CKFHTq}JzrRB z$ob~43DEWi=)9{%X`wj}rA2IL@>^V_$Mw3<&3b)!4U~AJS1?pdC}){C;v>JAU%$2o z33B*r)}W?vAfAW4pkm9p(2Ij`{WMhY5{>&1=$f76|MTph-VZx71(9ek6`c>sigEZ( zcw1cFic289D0`@N-qXSeL5$(pfN(IbgGLb;8w7@th``t&Fbog{#s)$Q*=K5Q~ucL+HdZNVNqagkG)Xz6k9mP_H)C z@*8Qp!-q{FGq=>`bKF_*QQl?|nZSCU1hXlU^6{RhzZmDFfeqlJ51?tlntQlZP#$Gt z-)}2PK1xZg5aCxab52C4)pU^gw@tm!n7^ zH!q;6@GcY}8v9ZLZE`Qq3u71ZKT6p`{R)-aV0{`|86Mk2pM(!(GGgZY{?It?o&R+( z{|Fy!;_{mzJ@}=u@9*#-bI|7vxH~hx6R31cV_%ZCb3bs=1yqsKTB zN-D9cPay`IzBmXpS=aA9gGdYW(a>OyV&S3&>OKWA$eSh950d_+FoYa+6u43U>?5c#!-gRy}z;h%IKt;5v=L;q%$`dF;mwPLVsL;Dyl*x5(#2K{pk8YXn3 zsjfT_`GfOIDzr|aR;35yphhriz#5>=ugzuQU!^4gop};x#&Tk0g(x$Ui$w z`X?HNE&|{+qt4F(Xc!tRxqQ5MJRFVAw;2MFpx>a&GCH4Q2wgHkAF{9e843xA$Jm`W zGKBU|z92P-YZ=l7bQoHY(YcBtG)wZuimLMjhN1$xhJDRq2u+53P2sRm6cE)A&=(BR zi&&Z!7tpZ`H8PYC&`5?L9ehDQP*_M8g!m8SvSy(H6Eu%we4n8q0=kT$R~Uj8B40y7 zM0}i~z2Tg%;Y^NW$@e@|l8DiHBfFX8eX?4p)U5lE@3bUSk;pY`1K*;OL3t98kEzlj ziI;f3@A``tPoS7l)K)cxGea$m(gv;|W|DubAHZ)i7ZHIcV?|_?Zsf9}Otsc3^a}UG z3k@o_YRy{}Rrt_bWR=KAQ2zZ?t6LO0qo62lYH}}C>w#1Q;;lq2U#YD`AQdYq}kb50_i&o-q{0<*=U^C0O`?6F(0Qal!a2ZdW&(4H5W9RgJh6jD!(;70h}VH zYlflM^O*>oI*CS{0}7X=@R%RK!AGiG4#G z!vVv33SYEEV4-E}Hrl$8$R(pT+O9!+nMMvmBh!q+XIqVEUYF<9kn)4O>%RcrQ-iPEK8iz!Cb7}~y$Z#`1)+aK;YIbUOjt*7B9xz%XS#J7g&ONr#` z^b8!95DB}T)-&vb+J+N!%zhRa&cY_F9vI$@wU}`fn^rLyxrW!S>w6W5PXF#!jE9x* z4AdGnKdyw)++luJseN0~dSF(SbCeT57w2cuaMtnm9PMhU*qWnVF;%PY;w`Thh1}u9 zUf2abyzX?yYc^u-qFF%irxl!8Y?`)Sugh*?OMTP0^?F10k8Ejdn!8>fnnk|D+b-F6=@;#9 zN~Ays#m=OFL)Z!09FH_*QE%gF)N}FxWfaA3=e%{jdW626qX*nP%}B>%Xke62(@_c& zDWA60PDQDy#TOubKRW)9I`C66?ggVJ&QN2=t0})BD$ZP|N#AD|5)IWmdJ1gkyi7mN_)*_HqPFWv$YFsIn*IYT8y@#%)6FKO8jfYp5<9 z1^2z+&d26d)*gHY&0r0q+v^e1lDKFa2|X$s2BLTs4r(8&QYeqZdWx@dzEMFbb_9EETcR+ZI=qNXU8KTIk#XL{nl7%J?4E zPklQLxc~a|f#R7Q)IHVyIxf4GUI(Bi1`5RLhUh3zceF6wyMZ8#z;3 zwpE~GCv9mY6s?EjtW%5;jyb2qCphLkmH7U`?2M(Ue{54w=OB~XhXHt~fPH?9<3p^2 zASHD!c<)!93F#*FKQosx2$r{HylDBog^@9s3)WUP;%zFhI+mcok!we-+DELWZ5`0Q{TzUii=>?z*+(_9|1f;f+{&_1PAu!o&^41eokWR8rmKj6 zhB}wRq4WFr!U<{)ExX-m0sS-K6_+aLXGaaze+0SqQB(!PIu;S_v6O;I_a6zU6Dfr{ z>=T{{xvn2$MoGxyz<`0=a`&5%sEidoF8Gn14q1_$2Z>PdgZ0NfJ+cB{ZqN-72l)A zk0Bi%g`qlK{59Dzy$Gqr_sNJ(6vhw8h^`yP-;nVvGG_WQ#*}p*GtSjy3H1-qk7|v> zIz&IJBDWnpw7cnGL@6dTA0h*5(ql~-z#u_&JuidkP$7D3h_{9O6elC0^tkTwyOlI{vA z$Kw-iGPDEQLRh~rPq-{J`^vIuHN=UC>TH+k|1gW%si?4X@ zK)tljIq)grpD{jGFHNX%+A5CM_&tgL9DX<=WHG9ZPCg>}l;>^Kq#Cm(@=h0(3;J~0 z@7|$YCS42%r)UhATgi?z&FEYQla{acr0kJI=eYnq$g7E5QOghUB=(U6rPM9ElM}fk zw0wAWr{kcQ<)-7dZ|pKT(hPjonj#zYl`AL zR16t`#nrTR9_I+v)gcPTkE{dn>P2n$bV@v8Rx0snz=u{uNlRjM%QwYn209;~W%ytr zdjLys2K_-IqeUI~y~q3qkNJ-t^PfCs=slPFrN{gq9`j#3=D)gTEqgXjrH)N&+0~F^ zOr=T}Z4K=N8w(XA$ya2v|3Mb)=L}H0wPw0$4%66DneMFzRC%n6WIY+De26UAkMfTj zZo}k9;XfqIv`CHBL$Sf0stS25&Yv1Et4y;;{H1{w!D>o(oav#(ad`?6@{*MGpO7*U z-E^d*M_QB7P4_vLj;!!%T?t>Q>LRU&V$QzQtnRVDO;d41z_O!!j8uvjM``97F;hYU z96c6^XzW-aWNGYZ^K9s+YC@%InyTdpXf?&GF>6H>X1J7yv7SVv(q7Rrf{E2MGriQz zNGh~2=UP*GGM1v9ES!8UWv3_OuxeVKAGBQL$&pIfN?Ee2H{LoCFP(|6Gb2l7yfMUE z%9eVOEp)iO-i*k6F=VPJJA_}(;s$SaG>X7+zT9ju8)evWH#;Ujj&$rGdP0a-DqP%& zNO%&{0-Kwd)}dz7<5mfq>sBt-k{|=$GsHON^4C$c1n?kDpKx+G?3QF&Bya-xc<$T2X zVe6kr0ypuf>`H08x1NIDPu;Y^X}*rqNww{+`~f;;hDWE&EH#LZGb%%caoz|$ts@@B z@$<+#37qkcbSGE+X$$!IHR&Avv9Mp(+$)=Jpi^68i?B0wA5F#^MFW}Z4O z;*PsftC|o$Vaf((QOy?VNRY2@Q|3!uH~A+lABKy1V@I6dCi5nsp`%$7apJm?C)Z?x zlWwF=dOOb8A+czCWr?lyBzBYx9qnz06b*3$mkGk9F6pV1psJE5)}MtoAxXjFEi1Yz z<2e%*Dh>L&`Hvi$rmJ^5R_&mma8svD@@kLd&8o;3Tz2$mMNt+ht_PgWq8-r9C6CGx zWl>Y|CDco*El&4bX`RU#cS+r%su4Y7J%U1V+Rz9!V?9Xa2>+y$GK2G=l)le>HQl47 zQ3}TY2zxcKttJ|4+PV=&3DDU(vA2a`YE&^NqH)qXH;rlzA)dVr6$#hwbGzt~jRSCn zn%YivtAMXOu1jxC@Pa57oZ7cw4+X;QJWc7cG&SOHJ3-Sahf-Vx+^e@S7s5KAU4fs1 zx^=JW3LeE+D(p3pBG#XcgkB8crx?Z;Mz9fEhaswFmd5M|nQZK0iemJS1-c5`Vq)%Njo>WB8;12(LKs;e(j4?K$nI&kqz~UhF+m^F zmN4|8{Skg$RR~^0*$uqInTOp36w|%0y0bJjVZaj;s5wy8o1xG=*Oc)E@z^Y*|&5lE1 zq=rM6b}wCm)O75bP7O(?BDs+mm57GK%?SO?MoM^VIFlNZ!*c_;X9$ea|AslFpqnvt zQKpG492)hRNvxxtj=?=Ptt zw>H6oF!xjK8pMTd8Q#`!sbQU9C_UgF;kFfI-Bc7vJdy>xTHZ5Fux+xmB_s5T*go@6 zQ86*Asfk$+qTpK8h;=6wAnieMMTH_%am5e&aL6Zu(|AcgUn^&)i;+#Sl+oTJbcYuHbVEZlp&m_Rn%IsL?|@KTx2b45PLPU zM5r`cYpaF-IOPkE7qC&l{ZdKirJ#?;z|X8w-Z{F78PI0Bbro{Iprvya8D=}0jrNoF z2KOl9qS=qcXjAqamytQ5MmDDLTdYOxIEN8>Zi&_mx9Awbqybwg%$&KtR4H2!7G~5t zmf;#Wc-JFHU|Nqjxbs^uY5A0A_2H2V5}oG(^+1~wxfNQz%CiFT*ahXK2p@x$B7E(z zA_1?yQhcDi6yaf~rAX~%6PiT@FREF)Qw?YAIv10D5A~}ZNXpU36JLKL#^2nDVEz%Y zLI@=v#Y}`(vWj)cS#d|S2K+AOqlSnBeAHj@A`sRBFb@qmT^;(VD!#hbJy;oBgn$Ms zgBhKd!Npew%MzVu1ND@_vPABb@ll6|d;X>Jx#}Lne zIaMVa877uh$B2ejHYYK?o~zk2ULV6f?_=3>X#z5w$DN5CryrPoSUn`VGZvq`7WwQS zj(ca5t+13P9(#)cEi|d8Ldk)CNVs9SV~1gSSjOs1JFu{^!zf)3^FYs?bUeX=iAhHc zlMb)k4IR76NdfvkPo_voB4wVjT!h6M-|T8*yyu&NJ&fvEvFk8LhO(b!|^ic zWCP0|gHJY&5tEI0X|i$J4wDT`*UBavp0yABNmG&kn1t;x*+?S$XlBqNry3Y#>@d}+ zbo_YMF?Q7dbw2%1bB6Duq>e#-Pa`k-F;Iv{Hr?*%qRFed80)Ej$Fz?I9itNYWzp>Y zzmR7#cxYCb6U~0J962DG{Z=_LA(~zAZW(LWXm&+8QXS1sD?{>X1}8jgwpI_&<^&Ic zwM_pSnBRrkG-xiP;&oy5dV1~_OVw(6DTXN@h;czg84e8NTIf$P?k;{4<3X(B@Q~ZS z1eS=a7s}Quqz-P#b{zlm^Rzb*^Uou6?rP)ErlfQ+NMYTJ_-MRO{jO(CRfVs|(QeE9 z422(P<-v7(M68z3SwSb<#=!b~;7ZX|%5)u`|;Jc|L^ zR#P=Y!aFXhldpGMM7bQJbX>|uQaDejFPlf{wJ-w4K2UKeLdAH%dJ>`*v7QYHi@N|z z#_VDE!OGZk(j!r-B-_hysP<{PB z*XbPV*XZ=;Nit_4=?Rpekg~CZb-G9Huh;1w&;QdporB)FP8VMOJ)J)He_5w<=6;P% zXFsk^zeBTY&@>83jsGmVjh$;O0H_05{A%`aeB-G?E6g>2K4u5hLel<;0ZOs4Ew5aI zaZV4~qg-D|iS-4VX61L0D-88wg#phj8H(FFJ-!G}LqSi=w$R22FjJ%3z;&KK*ot$691G>@?IT-nHtjL=zS;Yj*L zSf!)$oFuKL>(=i0O4aCQjnxU0fy1y{?!)*k!mf#^H4?HJ`b12Qu}z!jb1`@yHjVdS z!Ng)fl%D>H@`*vy=$3zcosT}^;abde40~5f9o`N~Rnq+aK%U<(pv=K@bwjYSGK$=X z@YyfC{L>6uei-2M!8McO91)gm;#e-(jb$ldfk3PR(Aq)!y?WtPQ_qferuq%yl;vDt znuFOEg*pQd)b^6lctpz{O|e$dszoz)+@hQ}v@%M!!>|k>7Er>bi8CDT>OvKw+bj;o z+4m#2@+a|0ac2o9RgH_uBhBgdcH; zP4~`_@n)SUmo!(igs5g>tpY`bwThHjtH3^+GUZ@YRQ$t1A)3P~I3lHJ_(gvXlcGF| z@BPVuDn)n?a2R}!%y;tuqYE0u(|L`9oC#nAJ~H^wBz#0}U(Y=65lp}K@J#C-J3o;d z?VrK{^Ul~F{hApY_2oy%zMRZ=P?v<*8{38{7oiTI@tIm7HjqaRqnkbE7^_jmtr#ngO6mP412l2? zHQj$2c{*!3zPHt4UAR&0!z3kSw<2oQ+JIoZC-}EUBI4;;C}kYd57{V9v5%IF4bVZG z*1Y780Y5!<7*@Y%?@v}lS{CLxP%5$;vQgyPHqvbo7remBJxyJ(`)dgWE=Dd$A^0gP|G^6Lvw^Ti-TT5Xo>?$V=WF~ zb57QXT&v*>dlv(>3X7WKsY>%T;$DD?5#O|p3RM{3M=H1eO(C%{@8(N4?%0;bcV9Ce z=9Lm^qf=1k$5Z_*$wU`d=={AISxd|4IH=o6xuiv}qjf?(%Gpv%wSiZX_+l6>vho5z zYYbO1`0i3jPv-E#TS23(!Z2^q;#H&k1UznxKFBB(bg>mOO6N?IXeK++nYP4qix*@M zg6~z4s2LUoc?$_9kLK^Q07Gvn{t#qoVS-aD&|GD~V4}%geZB!FXLkWiYtRwdmp8#~ z#%_id&9&o@K}e*Ji+m_HIE8;qZebRkKNF0bO&p0N&1**5Ux-i}8cZ(^^XWxrFPHq% zdJ-hYoJFiZiqW^&BBA{%XUyG*wJXdVl!fps#}{JqM*A1_a&!?EqYJ#U$5-5qt%u8z z1qKB?uE1CTx(D4o4=dWxFpRDFh!{LHB0KHxA|H~`yzECX{D7AjT~`T-ZbWa%kJ80l zB&jSaitYgOyqS&)br!#9tqtp5$|A@cDvVZ3$3POE^C3x|q8oZNHka3wFd;8_!~Fs*UuRX8$~2}-18*_KA=(JKPmG1|NSJ6J3F_FRu^JV9`gjT#qZCo?SO;)b z6&-u~+fl5qClricD1RK8OMSwDgY^l+b5neM!tUNaA(6BET|#+}fF-@JS;HHw-q!o- zZM#&cV^JwYT~8<%ARYS-mW~n`nKV@sor$>y0(i3>DqvKyEdFwa}zR!>Z*C^&y?MaM{&aHE6{7wCf?+5|%#)2O}ir&D1~9#JL~c1o|!dw%cKs4>O0cof5X5 zMHy4~=yBK5{G3cPv=4EtgAu0cSRR0-pSmgsc#?oq2(a?(QILibOkJh>%5zKcAkpRu z%95*v?%(+yamG3ofjFJP&hsRbDQ_cb<@d;wrN|SxUfVIuv$iSEd0^nFFBH|onaq+m zv=DKeG!=20YhPDj%)Da`(z- z**Hq*tdCGc&Ke*3@Ez9p!or7~4N^XhLE2}P>a(Luid5Nn@5V&N4!U==iNxLVp? z(GrWkx6xI7SfoNO@#Y0$qx92Q^`sr;`q##)=lU8n>^qEAfq5JB{a@3d=`2M9WHRx; zry63TPUzSE37cGB>#q$BQe}s9J zm^V24M>{>U_D7Hd&#a{t^sCCrh!1z3oT2`tFfd0WBKx^-ghrh79L${+a1_l0l^^TY zqOy4(E!rDnA8u|@iy}ui>6W1h<|92#sT91k znc>9iHgQjDN4zH5GPaO7juI5lEjj{2hyOmv|bPLj}Cl=T8TOo9W(=^H65ddC8T zNeppOtQ6n_W+g85+j{}V8aQoY6ef$}DRk-sd&ADri1NYW%@8r-uF(hfp^%2~f?Arc z7UrTl7ISD_4L7`V`_iewQXYx?snrK2gGDZ`Fu9aIfayv(Xktc16Bd&2Az53R^nt1H zr>!`J(y=d$x{%76fH-sA+KRK_kcx-$QIQbrXAew+H#1&iPseYrQXkmQW5?`)&Zh9x z5`T(iV1HORvALu!-8g8oa*vnL1a+=~Tr4SsOu1@Saem_X4lI~ZQ5RrFJXJ#^`&4gv zMP+eunxmzB0FCqHK$3zNFzdXKXDu_M56plkuPc{=K5!6m>2N(yqL6v}EW<1c$B=-F zTy`WLIK=U4hG@S{-iE>S$XG#EC|X(@r3rs#khRfN+c@PEw0axq)$ph8&f6?Et%`>s z1RNnem-J~wGdichmrzI4iZk8QE0IU^!N8oBC6vEFQ_^Q0%s;~C3w#(r^3+r3BKH6M-L4_==aYpGZ2sXLsCWgB{(;;53)(D zui?BCuLGskC@)bZW1>~W#3@_7X@z(y1+@`>gD#E*@=!!PmV%aoej#knM4;ApsWm-@ zI>4u8=}GbSH(F$n^1Ln$_y(KD4&}P%lp3E6g4UAbJ@;(T*OcG45x*>Vy4N&-{8&z9 z)VB;;&prYo7FyK49vZO>hc2X5P5V7)8G0yH?_UY4+IJ4V#XTTN!*G<&>b`}{$~TG? zJlabqJ3pKw_P}#E*IHT$)$KQ^3d%t{$#OKc(ekpPi)pl`fl(AMydx4suIGnf*z6Wf z0NSaeWO^IhYL(5BCH_L8utcjX>{&>5^VUdAtSLn%Vw{Ir^gQyK602Im zrd5aO{T-tjFZQ%=3*jyvo>i!lZZs~jXT#OAI#sC0RfpP80S~zF-~{T1#*PI#^H4A} z>w3xqVJRg5f5VPr)_K1!2cgMsVRyu67H0g>MhfL4X{)ijMYPV}85F6+!!$Vc0rf*^ zE+sQ+Mr1k*V^j@!^Ct*_66KspMZKJeJx2sA|GJZtCo$=Xa!9j}A;FMnnSEQwIth?} z8|@EBOv_vmtRVz>H-aa1e@(RmYPUr@fiyiaL6%CI&=9-GtS5`og)80Z-L0>>m(_&qLW?v{2 z%=oFZ8C6JNG)WlienStn#xO;r;XMkgMQD}60U_2dG~gt!)^SKvzC&0-7P7{OZ;dtc ztxtN2M_m6zdo=Qu5qh}>c}5TM&>t{I9TM+W9IOmEMu{B-WkA|3wo1`@V4kD#;En$D z+yJTqD$#I!w_sjb=h62meyaYo@^*)XBi4usaYrdBS|1C{ca+c`-B z{lshxK6OXK&>S}H&Sz;3Ya;JXr}ihdiqZbWi4>aSlHMc#2T*f_opc~&I?Z{V=D#w9 z^25`Tyn(IhydnvGMf)-ucSB-_qM7(pv&Xw5G^|76ucxE1@9eN?|rW%y#Yf{o_qTl^5E%ZaF~V4pGI9eclx9;&jaF~ z7@|E5j7=5D{jbl#n?Uf+o2MXOX_cxM`DXL^NzWXd7J-81;HuI23naoD)=uZ(9G39i z(D5~Ma2fBQb8ywj9Vfc5Zulmf5xRW62R9x8pv+i+o=XSp7OJy$MFt8lCAExuJYC&5zDO0Bq4>f!j7`1vpYnc=scTV->w z+DLgvvufe13+=HqX4~;xUZbzicl_da9TgtUN&fryaC=28?n$Hn@q4(v zGS57RD$DQT3JLx@_i#lN|D}7llIomi`aipeE92eSJzSYD|NcE(8TS8w_i&|P|MERt znFIgoJzTz%5TuuPdgp5x+LIh9((s4Q$wzq27Aq{c|3;mJq4SF~=pEC#y%<0?BQBwcU+oOzkXYEnu%fH_qW!V4!wMQw~zuX>W z4tUz5b5}xMS>bkrRTA3gm*XF7O?%(g;1xF7Yp#vPc+e*HZ@$FfNBnUMOo~4&y}67;t=+wYgc54d;QmTEP8T)g~N+kfvY*i2`-PRF`j z3y=QjE`JX5i_%#za^HYsUH5oNu0@b=mp?B^@+=s+6A3%jVqt({axH>{vtZbQ@SFuB zcaR1<3+4)0b{0(B2g2HcJ1%!Oh%Q3rI~zm>%n*;a{|=D%flTivVCOk-^#cAEVjeEP zkF{j*`&i!?`tD;PEbopTJnVc69`!h>d=CPiP4$>3;uF`ys_|`y>`i5t81^`q zTvqa8JVx~S(=achb#^QOBCn17BDwHai3OizEYm)_GL*iK7W-Gym9PB%v9}P{L99|500c_S%H2dJok#uq&XqPpm`Sku4cKH zK#H|{V)Rc}@T&C)W?q%(>24v&y|Mj?7!&Vo?FeU3JZBDeIya&|IACAVxi@i}+II@( zYy+4#hYDuRHhnHQ#ifvSH9?G9HO?KtO8&c#s4U+3RB7D~XV>KF(8?rIg_A@Wq)$c8 z#IdHhayLN9SiU;OFUF^0)iLWtDw*6UeI?39r5RX0h4PBgbj+wWEUe?eS)L!pC0q-u zV0fHYEn7frLD+N zl>ge|Ff%MJPp6`Izyp7%6NM<=Be+?ttA^2q{89v?i}pHhEKWzpYHXXz5)BW3FB}WL zHN5ez;)R9tPyt(%r^V_f?vEdUoLcquE1*;!m`jepFa49-cF-5-{S4en57$_z=tc|H z8SPWaK>iI@i^BhMfc!Uf%=Ox|`%#wKA#54n|Wj1C6eXL3S|y7A(~$AA{^*oE01g+HhGMWCvsKU1g#a$PUI~!HoTV z46=jqR50VmJ_gyrcq2F+$N3m!2jkG-IIi+B$PUKV;5e{iR+d*}2jl4AI7a&zWCvqo zFat|gWpR)ljJ{yTO+E(M!T4S<;{+dr>|l%zW*qEekR6O`g7XTBtt=g62jgeK4Cu}> z2HC;5A()ZzF~|XgY01ZHCQSv>z2hq zb}+sb9LG8zgY01ZIGBN_6UyQsI~ex{GcaB(V~`z;GlLoPeGIabRKY2K*~cI|7#~?cxCj>KI^f6@n_+~I;hL1sZ#DOc#LAm=A zAA{^*92m?v%Eur(7_SEBRnf;FI~dOd$AQCIWg3p`V4NMyxWUIDI~YF=mTGq&gY00O z7#znlJ_gyrI3$>HosU6wFuonkc+AHjI~X;=j4OQ%vV(DTaLPaPF~| zj7NeQZ}=Ew2jkITsea{SkR6P>gBh3k7-R zf*A{a46=jq^Wb#6=VOo^j2nXE*yLl79gL@f?~- z!DtC){MN@HI~b{8#sNMC*})ha%$V$BkR6P>gBiE`7-R?I@?gfTJ_gyr_$s(Oa8stN z%*hVMqrq`B`WR#f0^)`j2{FumiZWD2jjS4#dKbX<$V~`z;y@DB8AA{^*ycC>Qm-rZD2jksfhUH_B9gOY4jO9KC*}=fU zi6GtnsE|op;9LHWh2HC;b9302KJ_gyr$OOkR!p9&x7&isS(e7iA z9gM#RGdB7dWC!EAV5#2nF~|nWCvqSuv7&fgY01ZIhb*_k3n`Y9u8*I`50sen*%Fis9;bov-%2V+z)qrt}@I~YF+ zW{mYQ$PUK*V8+ot2HC;5H8`(k`50se)SvV(C(Fyl}kgY01J z63jT+#~?cxj|DRh^fAZ|1|ByE>N%hDF~||i_)9LG66 z2HC;5bdWgct?EVvM*tOc8D@wchL~QX;2T?qGtb*&=9PA6@+-HWN{_V5b{yw;bxe_r1O-XP;tlYG&*LO>B9S3$@X`|xmmjW z7Pe1^gE%}3dD@Y)^wgqee@5|)gVlsc&;a6{2Ky6&M^W^#mf}2$onB+G|3GjrzD%Q~ zh*=5W0M_h}2|kW|$6K=BcJF>d1@Ya_iwAonIid2qBF^1dx@&+51-)yBv+cK{9Oa_` zrMs`V{iEA=Ax!UG0_QD+!QKt=-Vd69XSL5j7W6O2ksq!=#l>(s z!JTRwu6&6?yZ|zk-d<{K8yZPZIuEir$Ln{8`6w!JbP_mEj6Xa}b?Gd8Uc~1Lfb^-8 zzZ2L~deVIScAk%~fVY?MdbVfP4X(Ji4OdhV@#-1gL3GW@XsoR& zDz08eWBiLh4r6p$pva4)l^rAHz3E2P|3_|nkTFK*KOnrvNh|rXgok(FVX)B9$*O#a zTl{HUqynMc&7rwYTImnyTEfE`_7Ed3d?B%}k^hjokz3}*m5`E$GDfaTT``$BN1@JY zC8Oj_^gkZuy?}cq>W=hRt#Z8*;R=lIbpCh#UgQzZacjEy;ac1~Cg{gJcTC9rdR!N| z*GqCO?j4hML6Ub&$Q`7?u8X+wd9I7#eq6?Lr@KbAyjIpC=E1Jpo8E*pB2 z**!fSrtdkeTIm*{VN*yC$!701 z`yJps{5GK3T?2l}uLETl z8+~A4m}OafU{uni823}tU3%`DX&lNhT);5i1EOVR*r-$p7p3<<`DPlO%{LRAMQ8QR zG}`hm5K`pin+d71`DQ}WEO&u+Sl$If0)xUo`<`Vp#|c{kx@`Ri9+;L&X+E%k8P|Zp zhHQC>ejp|(Ko*~X$3U#_>~qXLXqoPxR?^hWRv*ooD~7SEX}sxf8ySZ*;)ZvyMihYE z7VHjlJakryVP`{$HUilbfyA(F&keod@(TvdSUTxZe~meiC|Je6?ox}os(^*TM6uAg0*c&wIerd&4-TL(O4t0+a>Z& zLaW81TcYB2RTj=s)<5?k&JtIGf@b(Vu>7|zt;frwZ?s_cQenA z{*mqU|Kx{mYc@Z0owE6%gCRCEnc(Ytrni(UjY&KS}3w675{Z*CJS*E@gimUsT-%&#A|7h!%G<%eBB zf-*VPXDhsnz^V&y(3~-Nricf{iR)Cxjq3lquVgO z3RXEN`+xHF>;&XBTFm(0_4UlEHaO_PLDI~u2BQ@mnXhNMt_jmbwDi1l^7U-ifq&!c z*(^ECyZ&GH^~?(YCtuGz5#LMxt*_@6{Pg<&(AP5?X2Ds{pM5>ERQysQ**G${w+Hr> zXcDa{{UP9>g#8V1mFSP)P2~i7mIB&QSU_-#Oi654K{21x6utS+il!p1z>xb z>2zkYRAr;9BQu?|Os6p$T?z9o>Lut98(lt`>BQwC6pLMizOTZo{10CTZ^7@tWY+Ih z!1g;fAWgVm>pSph4YHR0!JoBFG`{Ko)t3KXj2%J-l8i^;EQWsA4$p1*w`7T#Eq|Kd zowW8)H}Mm2tQ1W_8_l*RqM$}033v%E>_8&t)&U!%*G5|IpKPP2n_0H(CWFrOX{Q zj31AsroqpX)X~OS;bt3}jA3(xaSI$Ft%%ddtn^~I#vxtUP(%u2gZ5AAA`$#!hjf)O z4|~qA-%QY2s0tpVo5Fa86Y@!zV{L5*piMwUcV{KW(jPus%&2T$kbGGZNFkOk(3&hP z+nBCIpL0|OSi|O+&W;({aKz2(h$gU>3p3K@UZmyh$~5mCcUn>(Gl?TQZ<1j$)i832 zI~^RXVAW>vgi36t2PG zQN^-ogHII{Tl|(_1RRGBM}^Ft8Sl1{2j*d~xMU7nLQ=gmtt;XdxX0tlFPV-2&klz% zawq2EC>uNpZ#Wr&KZXacyaTssWFJZTfP!Rb7T^U3tbuZ709t3w?LMoS)o7hhwis?Y zS`U33_yIGSM&=VB*G~Z)qYnd(w%TY^c?&F9nSVte;1=-L7C+)HF2;|zHS~}83yqBX zp0fyCaI)JV+=YkbO6)k?h-%m(Bp2*xBpU2!cxk7i$^ZvzDc|=eU_7iEf(^j$@7Kzb z&O;jL;AEfaS_)@6Cwna#qw=?wHq=!S(zh_C*Pli61H52P6ZVN|UbWManE51cNI9IO zaGS>sZCYEqG}N(;Q8pMQN-JD7Hq;qLIdD=2{~7k2X$a^m3Rb=-6cQe|VJ~PKwg3U8 zCcmL#?g6!kQe&45;$aC13XMTlxKDSRw|qT%t(}1e!kmX0WF{aJ=F*5*fM@Z9sNUHo z%!e9cqj_dgc$f@OW65?>_j%gc$OL5yR}*Q#xlk zvu!Jv&!c^Vb1GAN8?>KxGPoFT9}$^7Uc(Y<}C;) z^D0BHT3if6aqd!sn>qHa!HZkXtqO1zh-a(0ARy zKcnz=B%*ITcs^juSXub(b`CDgY|YMQ_)jFbR|SNz%2_z1opoTa89m^vH1l@stYS;%H-Io?gw;gATEQt#IEq?OA$**J#k{`Glhatj93AN?RV|!xAa1 znMV)fDPf^m6Fagdnn)j8^D6BGxsju-qn&RAn#d(bW}*V;EH1E_0YqR5Il)Gb_ACda zN(S$ya2O%b|Cr>g?R12v>7F!&0x#YzB)b!3$e=8oD9ggYGgU;stfg(x|ZBc zq;@_KEI?}(FTg=isN(eFkk-PVq_z#JFiXvBv&)LxnUxj@qt6Kt!b<}dK;GaiNKrsIm>Hy!9_*T zz7^pEg|mt)jF^R>mH_&^Yhw{22WEon8aQ|WorYbUxqO5yL%~EFYA2^mS8PvWv6B-r z?MB1l@HWcUAmxDLU8S7ala?DcC^_4pK!*!b4jy2#3^{XzY2)IkG&@n4#W*{_FqH$_ z*^-s0MF}Z~3yu|@7=plp{JaeR_1?KjP@M6t0wJObC~LGPrQp`2oaM3rJ8lZ!XsHS( zT!Ff6jIJ^1N9Ynv6DT)XKprSg2&VIum<`%@ji+^dX(WzPC%Hn~4dB2KX@Jq}Jcin^ zDI*EadPt^Cp;QoYI7$^#F0(;ve1y{BI*@#ZB+@=*YzIU-bXo)D!$mqaL^^Vjj(Xso z^5oqo7`BK$Uu{47tAM)&3{P->5NJyu>e2#YfNC=NT z+;^%y?25rZ56cUc3fUbcW?x$YJ-FrXXz(N45Z{Tm7^$szdgGY{3?)Q=j^AVaG5`m~ z#QqVSz8IN+L0?$}WdZiju!mu-8t4Jj_vYX1tE02*VR+BBhXH23he3#GTt*dh*jFoQ zhGpY#cn7DZ++P4Z3k3KG{x=oe|E8^@qIW-9t+yd1HaPdj=i1x_C*Lyk zI0nLOeFwf}^v;LW5aQ@hqfsV?0Bs`rWk8Gov71V%&p;$+q<|fyq%#^YSPwZL4%srJ zhj44|A>7&q=TN~-f!rVK$l$v(c_Tl5l5DgfT4~SrI0rNUM&x>9|KpBG8D{ZS#};lO z+QgiRvxH|InzKI81b|xxhLvKiH!uXJUCvXhiZl~V^U8}cmuVTqSWKp65ODojrbY5! zs@Gg@#U)mpE$kdzO$4h6qQ%q~COUQoUzuEsGtMoYJHg@;eZx(D-8oPE5iO!ts|{SIE&$5wCz*Ia2a+ z*Q*dBWiM8Tn!P0R!yaa67Le=<;;Ke^a0-q<9jMsymU#JI#ni{CoT$zVi z{naVV%b_zq?*v4sq3uZ4~tHNL)-y%R&}l?sI{k>6g-)5quCcZ^x!H zeJcz`wllqRa7|G=h(wc-&!B5ZtcI$dXoLhL&uz0Gtvqaq7BGr`1{nA=MtDUmMuOu~ zvS=rsGw_49q6oZW2A?d%(|hPO{4t(t{mhi|VzcXvsX|jtsqI311bs9Yvs{qXs6wYB zl^M*8=%d&^rZU{%sljwa$*IB`ro?t3d4vjG%`%ZJNEMoo)o48InDGH9mIhMjV{ZC? z7)NOA!7Y%G7*a_KML^?`EItm~l$Ix7X-w{&CAqOR@z^;G$)WiCC+$LBUUOr4z7}l_ zq@|+7QsMHf7p$$gx@+E6V;Zv&kEdgzmM8xKBoPF%)K;_PrnV+iTY2)+NN$nJm_bOe zmJ%=`Q{ZYOqb^k_xQC|Uwio71?xqR_0}lmOpiE8C6AQgXRcI>~8-rNvT*RgV>!^U7 zA3SHERXL#tge)SSBv7ab%v)6`81rZWo$JHo#uSX_A~clBqlC0{%#=wj+<2%BUOW_B zr8I!LfKQpo2~Mq-v7WAB+41Ry|9Y;>hwlaaRO%y8wJLNxi?%_uE!PBe2Bm@2pi$W) z#8JsK1Mx}S#Kd#aF%R0<*Ip$D3+D=B8Wht4)!2NCqF4bww( zRI`VcEFi0N_U3bgY}j0L(*1Jf{5|8Dk?+CWAJ#JCKFAmnAmS^bp#f#N7+8RXu;PC_s2Wf*cWtz@C*7+LH~v}hJEiaGfv8Gyl1100W+tK zVeC6s+St&3e1l3Ezd^z`$~m|IlT9s0dnJMl8fM{XFy-)=%{`@o>8}#Y*?7tWeaxnV zz8+FG0gxH7QD>H}A;wXMR>xYvDQcMp=mpom34KtcOn(S7D&s=_a7;{C9;A}#_u&s6 zn;6Zv1j8GTAANzodCDZ_+O)U%EN+pD? z&yp5LXY;fQo|gFdawfYpE0>($oiLONqKj}%$7xFi@yPW=I-V%!3pJHgft zvZ23vP^_weV6y`0kYHXp&XQCn7N{8l8>Na78#90ZHlVWzeh4~ZKn-e_L|-g4&TVOw z!e|=@;tA%=7mPIU1?5OVCqdG?^4B#ZR+}xxldV0*4b;^jm~qqQEV&YYV#!$=&K8(^ zM)b^)Pp4w{0Mw+%DO!m6lsH=e7cgfv(hjZGIcV&Ryci|}h5*HIep!ij#TTTF)^Hvuj2D?d))w1f zh#%Ym4O>O+=UWtAAi=>5Hz1@k$aq(wS~yaTx@B;rx*C9i@`SO77ff%<6UN|0{~H;# zB{7Hk6Zn&}S#F{!NoN7a2D004&g(Mx5foH4!SZ0dXdL3Os>z)oXZ5fz792&%c|d01 zegOUGTc$86X*gTM@h{hh=iVH7L3tTVXCsDAkQ+l7V8*(jv9n?HEk0ZVpMaIjjQT?Z zyCPNz`2kylao|!M;j7>c9n_KN*IG7~(19h+ObygCP@F(-`r^n@(BL`G2$96P>2cIo z6=KH;`ckl{fmA%5;Y{Zw#~@Q;?F1TiJ4&p+10p;c#obc)qhI_C z4th4kVMcAK z%!E`n*5tj*sL4rVYh*oJ2-9-Z6SUCeq`d!AJ;7$^ol(m5zpE$6sy2w#aF*m~Y$n%Y zdV(9MfdW~Ylb)bi2mXzopjq<&`+9<`@PE=1cC)vu88Tz)lV~7B3Rmj5`cCq?ErsZ7{HA;ShW^q zzd@&J%XC;?vxejCx^0ErzNq;7F1 z!#UF64U+C8vh$B8!vWxmW4sFELv#$|KZ1Wu{6h9Mgs96HcHv>hFT{xlRv#oX3G%ZXZV3s^lV@oili2W^M9%i4Flx`%O0qoA108n}It~Vbe4c?u z<9CV5>Y>fZ9iqgKLpPlj?tBdK^>Y(8nT^<$k{#8~4vq?OLLvqfo?xWb;K=%6@SPp+ zL=Y3U7~NLRB9L+?A=Nx*Cz41xnUQryW-MturQdSI0g7w%VW*MlX|zch$taO>bhI-` zr2NZ^I2|?9%45A1iIzYkDn&)`1b1GbVZoi{XAcss+HloYHQYYwP_%*bO8Fc_*O0F* z;hW$P$#BjNAB_mZW&vCm1<^Z>H#I^hAtVWKvN$({4gt^tqU#}n$kBABe)7 zLHE~!7tj!8U=5Sc_~RH0d@c?2XGmT%defO-fQPP@bw|#|F;G&J%($@$$}>>Wl64dm z#f_VDEN6+aqq6oVHf6QK#1{{;RhX$Uzs1dAJi!{eHYCh44Wh-h>SL!)D<$voOAgl%A!* zTpGhC5ym~e6sm@?6lBF`kpe%hqj2#kS5$hM%P4C@dSJ z9-u}zu-M469#`aA(ycKrF!TCME6w}_2>zMM(FWRvZJoL1n_2-5EvYIhj3=@XLw&Z& zto13)>%&n3urj<&|%WSRM;SHNMr{0XT56Bm@Wh+oFQeL!h9AFYvD%$L5CKHnI zHCslk)dH!&hTIHP=X?Y(E{X37+Ja#AR@yD}5VMCK$_!XSf$n(Uh9F3JB%lzft*V%8 zJFhrl1ipUIFO(#*0LlYwqyfeP3?EdC8@7Y$(w@Z>=y#wDu{HxiTu{>P#9IutNg^V# z*-pZVKHwIK1MDTs=}AlH5=|ZRPdJJfg7#2ZAr8DCyi$4G1P1xUrv8GLlrYNB9v0wj z9BqKH0AS6G<3sEsu{4obNLb5e<$xUEePsb&&Ugci1sF^VAqnxY5}n!(T($m9akEsb%it{oeqUGrKLqh zvXXRp0a6ku5F3-J2JD=LF%$#Es0w2&7isX^g?i#2A}cv_qXz1!F)}gTfaY%G86S>~ z;bFQtAPp>_Q63WbY#Gf^4bxZFH$lAjdSVWfn4%M9s6^A zBXh~uJC|{ZJN)M|o8{8KKDp$ZoJ)NbbIJGX=%3pXkxM??T;e=)iTgX3xU;#$UCkwK zSuSxCa*3OhOI*y6KaWe@T;iJL5|=-hIGbGJ+;fS$mrLB~T;eY061Oy$xN*6}&B`Ti z$mu_iXYE|#n&c9v$t6yiOPp&iahbWqoy;ZfVlHt9a*4Z|%eoztOTKTp>}OMR$#*H2 zeDP=gJV&c@*(aLkl5c1b1$)S}P$wcX1hm1w$rF<;5Ai~&9#6}9 zWFAs>1<$;~ek?PocQN{e%K{4t`8lVBwbQJPAWqOmvn;Hgx#xj$$A8WPS|o=&x?J0K z&X{L_GYGn#^oM93PJ4dNh&B-ONmD=#WwbHAPNmoN##g#3m;qfnPWdNIXQUI(e_Y+w zPOM)JDr+!h63APsBOEhGbS5;%IGxD?XfI&6*2iHiz+tluFc#o2+sc<#Ipe6i;J~%4x9jfCPoQ313uMcJn5gLPeVWh zpccNI0$MUS=r7^U%?SwJx)IozQrZDXf$oN+bZ}x&3K*dzz-Lej7@;`%a3}=?C|I$( zL5}j`jC67;(aB^Ak?3UV2s4FfNhhoF5*`;6UB!9!PxO0LcXYBE5ch(MgQJ@}EJ#tXj|_*vuqIerxW)9(V}Lz^N24gj6-S3d;})`AzvdlOoLM>$Xw zj^i@c$54L`1&J@#zcxZ$I21fzL0y4nA~cg)CFu>Gj#%$!O#75W!w5=D0$wh)25F7w zehvi(hM=7(Eg1#mqaF&xCa@iWYoTBPOd|ns41pOtaJOMc9c9s4N-|?v0}e@Q0nd zA_2vj5PvZpj`+`Wt7Ocy9pQuqze@OFm>KR&#}mvHQ3igYYzka?Et-KF_If&6KFf2v^a}v_YiK8HBbNy=VL5GM`kdT=7usebD>$*y*tghuLxhL+}HhS zR77*;O%1@WCVtWQ^}~-aJsH29_&J~;^WWDTVCM1n5FctowVRj^i4$rYK|lJCmF z50L|jjcZ;2pW$HRq8GqBIM}#m1@K}HHf{z2cnk*{cLNUA^+tWhy`ZlEYy+Ti%^QHV z0L1>u1#v<`;+4b@jO_x(nX=?{ET`FI9W-05B2jCO2NFnlz={O}M3M-?KuX_C_NMFiM|(#((VPykn+2gwhPA_J6|`fSFOkg9l%zXBM-+T zswzfU)CUt*56=PkrQlZ=@8|IRj$c89k7oG+-$|9W!ah8C%UMgHIvQgb7)bo^QB~pY zZwRThQ=6C)uNarIHY38+)X+pK#jF@JWYD4p^Q4WkB{d?5cE4p(C~jpb)rd6O1C~jD zqMMctFk+1Mpk>nY*^(L&NqfjL>56Pgjp(I4Y>`wq*s^vb8f%YOrfX)I&h+(6vrHFc zna=cWvK)C6Y7(98h4$`a`#Pawd?(Bpb{cc;n@BWF_}&Z#MyL{x;SatX>HKjV58A&n zc^ZjZQ7_GtOTr;Y^V1ND$5um#KT(Che z9NBTfOAGZm0AO{;urg85Jxb*kyx9$VjJ2pQbrAn()bCi1 zy40<>Oe&ewrI?rIq9I1J$`Il+i)d*S?^jU=%`9VNZBz!$J~lBDsnBtpR#e8947^}$ zY(Rq_;!R*}h`ub~P&}V`{duHw4|34-F-EYmC2sl)h=GZ|MwSt1wGv9VYY>Tsmoh|Z zYz!fx;Vgw&+ue=v|E}$K91Cf_Tjue{uE%Y5O%N5>C~^cgS>+YSU!pnd4ORgbh+6G%b^)Sv055o zECB+FEm3O=Pk~v!VQlnjBoiiy$GS%`q@wl_9^$x2a|7Cs3eO>{MHRd)sFo7DM-^1D zmB_JAXeckpNu23QxT{E^zYtiGyskIuI&7?qmj&BVp@vX^+QSfRp|j%48e_t`55|I7 z_hr2MEJ|oiK^v?vYL;BW>dZ1WH1^nF%<5;O3nO$o4c$Uu!^vupM+rm3o9-S-8mUxp z@v9)-cHjel2`|`>>A{wE;$;H(W+q;t=O7%>RJ`{X! z_cJanVb&-ki1~_!%J``BY7!{YEmKN&sbm2@h9tZn6vw7Hks1TFDVEwb1XhDEE)C#(togS>1j^#5C&@Xv#n z^K=m1-VLM<(PvQn<8(Uv^Bp7 zTjw9o1)v2`4fH(q9cu&R?<3J|Pi9`_H#?QbBAR6&cTmXVs!Y?}~fZ7#W z6;T)+6A5<>6V94!-~^aRw(G_yd!7rQRe?!x!x;4uZ9#!1lI*52>N<}ykzlusQ6MxZ zmx<=PZH!vRqY6`9kAVmiupwKSMmBUfj}N4HCD;uUKwz20>pG%b6ZK`B5nVmNO;p|; zLw!0rTbQW2yGA(4QDdkQcIGgZlan0vDB;p%j4$C5A&8y^`;WM^$$(2Nfc^lNh#NA2 zSXqQi670ovIG}%$KAQk#JTVGrN$&tWv4sRA_7??W4Nzc@iKQh#ftND~FIh&@+4dN< znRGCz&D0R4Q()O<6~2d!`dvkQD4{ea1EiA~Pb-e~C$tKWGftoY4&-1X!t$XAb>mx3NjviOR*eP{(YgM^EXh0 zY^-4CfijU)UkCSsn4Lq6MNs?(ES#hrfP)6$c}kh*K^gW>}jfrStZ(sC_WLq5z+@TUlX_qXKu#pPGfi%2`Df58Q`Ipq)RPL8 z3nrXi~b7Y)C#xQ3ZW=NI~Z_hMLhOXl@0Eb zkDymM4y_1}L^ysF2Z(T$7Fz*WcL8#koeC7YPzV<69wVb%I}TAu@dPh)16IYrreFFu+4W_4;%vVEC zZ#IkzY|2)V2xNgnaV2Ilm5z;dD=`>MqY@yFqB@oso;=X{z2L_ zRLiYolgy|&+6wZ`F@6s^t&(}_6Y#W=!+5cN5JEaD7nr72aK9^fu~lZwL2(Q(>C^~w z)%e2{@l2S(UW1q=TI_lHmzWddp8B`;66((YBby^P!y@e|z_e%S&pC?iV;M^@jHhw` zlt4CB3GehTqqjxcjCjk}EcIjDu|~XDQAU5ug8Es$W~ntxgD)5g zXWfZmJ+r7sDgs%_{$=TFmVwFA9X>jiEz|T8zfgDV*r7wm4*1)#LyR`vSUc-34XeKh z=Mc@V?(dIL5|%nD#($bvGq}|=1q1QSQ4&qk^5px*M3FHx?jQm+%3_KEMsSo^5)~6; zoKreCU_f|(vJ6y~`N%SEmR-r(`7EQ#@WV8N##gg;NJQ<0b}*FEOtBe%kM)<2PfUho z>)6C(8RIMy5~De|vg0kG3@U}q0mq#3oK6tC#cFB$z_&!k4$;SrYkzzJG$0KYE*M@vGA!)QR+u)X#+akl(>lN}=5$mgI zOJ=v^anbYuBGLG~#r&N3)A(RhWFI*zf5Vu#YlhJPP`lCK4%FL|)LZ^a01wR)8V`!) zsTN@%&(lB}G9eWo14_bE;ig>|8woa5+BsSOv-dI5M(HSvw9Ro~BGv!cf3RX{7Q~7~ zng#zAgCPl$x&+yaKr_Fciieib{MPpg7MyZi$ecNO24A1b={8 ziL8B=1Ls{bC5aHnkhugZoXHW1X5^jCv`U1ShUsy$Xl6_JZ{9uhPs02)okSNU0q(p2 z6>6b^WVky|A2ONnj)%|&)@~Q#2UCk6cri4L_BpbfYARnLssY$3l+O#tm<+mN6i~1+ z;jP9Z#|PCF%v9Eiv3SM(f=6g5mLO}6ejH$O|G|GONc)w#)dKbZL_nNCt#zBqy_XQ3 zDP=rr&>sKP7SNU8tPqd2mStOI7yNNQ*RuRhY)wiNOmw3v~)_+YrPqKc{+=7+FMhMp|L8O8o?e(V$cwHV-eI43H^RVYsUyruY zRPNdTK{le(IwLQ5B97v`|Fu>~A83lart+sZ4FM1On6)quYoU!i4m+VNE}ES+{{N^G z3s`iT`uU$nSLI}t>59{Y*vU$kcosc&vLcZu(>Jmf=~3>P|Gn{3E41&hz3_9FFU>pZ zw$>n!)P^q=l{FnFY+%MuCXatFq?L+rk|HGsN3q*fF~e4|fJYpKRAS5{pF&~wgozX9 zohtTKvbptD@p<*NVRNb$`>GLV%j4{XIC~anOZW1$?}dG;k=WP447;=+1UTKf$%}JC z2HNE&>=QZcFxp>853SUq`LrL{WOD;SXch^T=u_`)2GU>~CGEzm*C zwr9QtvkfnScHlrq?6$O9PsB!R#WrOZwM-tx5(ir&q_bvW@@GK17cUM@L-gaZL}BVY z+up*YWQKG~F{{Q+{|Sw9mdXR2m9h|f0SG&jpJ`eYfpS^=7lhQf#Z0Yo!>R5o-V4c< zkgLfey5c6Zf~mV;ffHRm9dk(kB!OO}z`s5Z{P>pV} zFC2aFV7;2Z(C1F*Z%!yjpV%@hXdK)*!Vw^JCIQ$8FarP{!UE9K=gU@Tpz^{3B}pc2 z1~@{h)%Aw9je=)pzWQ&=h4(Ss?)T>T@mv9SPwEKf?cY#$t2A|E0#*obZ3gpJM+r{1^aV&`kV`?k2ymK%nh7Kqiqz8P-*{ zy-+3xgGo~S9ML6o-U-L^lF$~pZvhZyNrr)m|Aq|@dc9AuiKt}?-yv<}bh|iOCC?LS zqhOmso4`{zIGOH;W&o>kKlB_Pr~tBR7Q&Mvn64=cpw8BkK0~m^NPyRy0$K@}-MGS9 zUc%Dy98lK~$9XJK9KnF}y<-!`=-%6Vwe+Gq0NkkQOlL z=WrMca2Rx9IE)1t%&031oe3Vt0<1o*1DZk%Wuva_Pt>h-q<)Z`+Z{OPC&CHnk1|5Y zxsjc@(x`{02EWqyea4SICmrwukBiK$iH_n|14UWmc}ED-drrS#!1Lf2S{Z*ZykO-B z62f1G28IUlD5J>lJZpETI|qXZ1{%3*cvH2+B8#`~)zzWDDV*0N%sF zM!6xcaXE(;rlKFCXr`Pla}u3vd{84|qBj089ctXiYrnA5$Tb2A0ugVlD%;?4kcn zVIQ&SMGu@)k)c2p>G**AA!ei(d*LO}njzh9il#8y0>uViFAH!OG#WUJ1vu=c0mcFx zcFO=`0S>!ufUy8zbfzhUC&@BQGBR^91bFq?i3lYceldyIL^`%b=3qtN#&7(Yw7S zz+sRHau^G67-Ui$#sVA$_9=(40EdCu$zd$OVc>Ui7z=RNTLX**I1J1Ro{j}L3`_|Q zV*w7s!JfldfWvU2=P(xFFdV2kj0HFhr)dsj0l@xw?`&$CUkHBLwIowwjVxxI#xc6)Tx`28&8k6A3ImZR%{qwlb z&S^a01BHD{K{@iavH;2heuDzxFc#o2P$C@00vrZfgu_^X!$6vF7z=P1zS|te0vv|D zki%Gj!?1=qj0FIrzWp? z<85UD-hQOwFc#o2*q-As766QB#?2#qB(GX4MPZovrhr-DSI`cAlY(vxe^+s6zA>xf z3NCCkX8+q}^~Y=TRj$(QLjA)>Mu&!;ZoMzQoHlGp;?*6gPbXGS2$eP%ssHY`XxP=_ z;u^#Av`?!1ed^sY=^>*}ei}Y@{pp(_?!U#)#^+yEu;=$O{w-UUk5H739Jo(6{X|gF z&OL^HsQe^u{<5dTzYKbkci-y^A?L02zugAyDSv06&7j+_)h9X+`{kotCUyS3bk(|p zYwCN{Yx-tWp^;vVk9^rV#O|*F&tHG^`MNpt=KIu!+MOLG^#(p_J*n5Pfi3M)9Uk;A zF{E_2{^2tqmiwMKsiP!$$iC_$d$&mMcC)BwMsP>h!x_WNRov?L*Yg>tE{%_xc6j33 z;Y}O2-2S0JXwt+&vwO{4`tp>%Y=QCH7Z-Vap;)2Cp&@P^st#VZ>q2nfYK0nWGD>>S zF0cGGdFz0Q>&xW#4NY#8bZ^7Vqw6wOq>R7t-2Tk^hu^wP&mS`~es)lw{h!Z{`xReA z-D}>d7Kz~lkCflk#(Q%c-(HI+47>Ycv3ox2NUQL6O`m#-x3_wfJjX8LP@-G)f<=Ec zxL0PVebWO|zidd2@GKFZ-uA`R!+pEVU3ccd#jyA?tJW$jI{fHeaX_1;WxfpQFLHQW ztkJxlRST63J=|H;KWgECv;AK z`^b@9Tzk&j^|*Gw4xPr{y;Qw>_ZG#rc^2yXvrp=Kx8ye)#yhtEQl)y+-5XoAnS7%{ zb@!E?Lw$dA=)CM|=-8XrH`iNHv|f3aW}2iAF3$t1Owaq{%ILhAeUoceX)-iwVu@+# z6P|oIQ^{WxbkkbvH23LK?`!H4Zx1DGI@!C-4zHDUOLcIHwF`@|syF!X+zyZ8Z-op_ z9QkaF@}$1!uq%h0j{VgvCSc*J*UFREo;8X{-B)tdw7$vb-wb|e(`Ja*{AZ6cmbBTf zJXq{S@a!dj$3$sWq1Ow2d3a6N>wfF^_m`LX9(eHi4W}V}nl(ApW%`jGPp5}WIPhuY zeYZm2yN@rjW?#!&F&$cMY9CnhYzxWL9fA8gby?jj?o@dzH|f9*YCnlz)j~f*NA;Rl zcf_Q#TVikeG@tG2ywu@tnAO);L)8hh7S}rIn*4Us`4PJ=JgeFucyzyx-#ax;nP<25 zq_W?nYgZ+H>4h#Y3A;XVjI4ih{lb;s&MqT~>*N_f{rAcxfp3aeJsqD?F{td?t($k1 zo%vePd~fCAZQNJv3_j@g`Gj>rzfGN1`@N{Vw0Pw_{pWgm?%ed(QmaWX z>&|U7=0dscao)d9O{{zBT;)s6hR<(x{dL6j2YWm#u3CTb$tk)0;MTQ|T6>>-Fm&v0 zuj`f5?unLO?=|Szk1nm=9J=)~Wt4r%x|c8KOV4+7bJWKt=N<1=ywhWO$7Q1z_9=7a z@j}P^`qzoimlW9j)$iz*YTE+Vr5=|qo_lHT;cZVoziygY<)M4Q?uQ4JbXob~#`6); z&#RqUKWp`|9pmN~sntERZ|C=YHdcSS^-M-WsY8_(%-e7;J~IERlycS+!&@GzlypQi zb>z^pTRuH1-8!sV+i^*+s&990andhjLD=aY`FdN8Dn9l`+M)Umuu76;`G@9s1B})u_iUKA&thW~*O`jKsTj_KaBAG;;fu zm7?l5tI6NJ^C}nl*tYn}Nk>{&Ns+!e*X!xms3Q^MedLjQi&nGI`rEd&S4Q^<@NG7+ z%&vt`hfMqK_3Y-0f`1)66Y{jY%i7MheF9=n)senf71D6$+nuGZICeQ})3!ipA^$BY zvGa>uoAK~|iP|S_-R=GI@QJcFHAOOqEWA0f!Y}K1k4Ec0{;WRXP2OOyHWLH3202@8 zJ}dDEA2}lL>{BZ*P481?Y~AuWajzU%t9a>~cungKsgFRVN{ zV{C(3>x+BzEOVt(s`_?J*Q*oT_LeAiIQBQ6UI%j`kV+&o;)5=wAiuTy6 zy;r@&Q}@avPB+@~X;p*nqN`he*bE-Lufm#LN$tZ=beos(QvbO0v&JPpm79Ea`@ZFy zB8wiLs@pgA(51{4C!Dv|p64^Q?}K59-FtRh=ijP)fM)iTs>9#RY2eV)J@`nuCTnEt zR$VRnK<7ViZCcEjr!PNluD)jLhdqhc^6dE$w#(B!@Va9oC-v>{-rouwJDtCZ;`iwM zOXiPHmiD+%@N3;!wo8A!dS9SQTJ5Wkd*$nOb;!A275g>6RNOJo$iQjU5)0SykL|PN z?)!CLkLs&Qx8JLBsrH7;+kdOei>iG*b9nyIsQ#7n#B7sw-dOVHv4LZ^NmLzul|DzN zOkI1blUK-&DNeml1wDLQY}1rSQ?AuHSh!ZZb7lJN_^xjfk}~UnYk|CFtK|7TXIKaS zmevmkjgPSH-0tg3?Z}OF4sBfdJD>9yug7Cff2zGE*0#iPo!^Uy7R4I(?e=O?l~cZ( zdkyv5`e1#?wK;dsPC1zW{^V`}iUJwazDFM^{kB$LyV~N9Z)}~8c4*&c!v6ksd#{e3 zF5mUq>r2Y?k1x;5t&a}>_$n&A&EOA}qt1R-&1s(AF3PP_+Rl$a_mnQjVWo-F1$8y^v0i ziZ)QDIyp4j7_VN?%DVXZQ049RqW5*0t#}zwP4}sM%|@MHJ)fqV*Sy5~S!+`~8tzDY z679U@Q(v!*<;v%|Rc7@A*H=TAtZZKE`06XEwWAz$Why-jpHOkp;L%dd@Qce{O!ea;J%S0bF&ueLh>@XkAJ z%&yka6=!q}_3hlk)usOTmVH|{DSkR{wCA3|;y&At)^hKbS)l5N`|>Ko&fI;`V{G#- z&i+^5*ZvvMGp6eC{Lzy>guZQeyFgLDDGw40oD1w8+bL|(`tfgl`X24k{q*#kZD(A+ zSaW+--+mXO>Kr-RJMgoc@6nVkHH*(}TI>9_cJ>#Z7Cf)|ou^!tq-80Csy_%SV=wOi z<@8DUnY7A7n!5HIx2t#NJ>8W~gHxApAE}bgTd>h3-fi5>gxPuG?QPD_e)lQgonEEX(w)TW_dh+T*H>+0rD%TDymU2UU zu<-Ylp7$bbErn{56bcldpoUyE+Ne;9K7*Aacmb6XpybebPfufw6KQ)~XZ zyQs*ga$`5#NpB&o;`#ah+CjUImu*s^)#4qkMaPEjX+7p-P~ywogOAkl-{;={?xV*6*tEuo;{#ma3kvC@|#Pq4k_oqwNC6>#UAUDhrh3?x~XonBE^b~ z{#^XQEpa73=QD%SJu-g=efR!Sa;K=ZgZ$>bjC0|&+?>6toC$edu5)rLb@Nd(B6s}W z9G)3-pu(E)BL`1bkZvwnty1ccZbg5ty_qTT@{>BH+lF2J`MLJ!h#}#pi~YLv&2iGj zzeR2f)>ZoLxV-RT$LbZ7exapX7m)Ybw{L^P1J#tCDe7a>oBr}Y|7vjN+GU=tYR)aS z;(2nVY0c-qe^I2siokZAeA@rDdB~V1y=xu{Y+5MEZteXyUcqs<>up}vyJZvACrgY1oK`5w(u@oG`O#{J{5Lx0c+O(WP+cjXaX$hr0~F^L^U9QYGj2 zy8iv?-|+`GMOzI$D?b{X@o|cmDzIPog01UV?|Q6i^W(D3IkVl7U- z2*0dcGrnNI2Im(#Y#V*0TKn~Z0}elN*j&88C|Sj!-s`{4(QPa@vBQSF-<$R6ziV!9 z?}WO~M?02I9^T*kbuZu38=v`AU;pyOq%yka^H+AO(99~&7nI5Jw(w9x@U-{ zRa~?A`scny)BajnWZj|W{Q^CQH|jpubGnT>qskl4f~&qqU2h#bu=(?vwPUtCZqs>Q z_fn(gUQQ|9Lz^~y>+!FLo^75~?#YAK1FU{ebX?Q+TV(ZT_1gG-eXwTh#}S=QP1p>E z<0f{YFN|b+PXr49r_OC3*Wvo1&rls#r@dztQa8_p9ZPH2*!_xJnWm42!lj&^O)?#;#vO;;b?jMjh0 zNZs;ROmx@}-}MQz_FlNU z@;#Rfk>~j@Gs{&h8hih~e9e*JO$UWFULrd4D&LXvI_vpYB30qu+xiBcRLkELxN=r< z;ys{LVcBAGGSddd=LY z)610BH#l^Bx?9(>Nu!VS@9(uNJ$6g+brViR)Y(c{9B7dN-Sto>uCLTV&4* zK95@-Z&YP+^4jk|o2{NwO?zbKr-uFs<35M?N0Nn2&mSvRV9fJ|Pb<5%nm2f9v?jFo zFZ;e@zOa&HT^`Qb9foAf@5cRXeWuD==gsO=n|t=%%LC?cufN;pj~9M*uhhR?LWN$% zI_%W#Y+mX8{++iF`Fz;3buqhA{SKYKvUTn6@%osS;-1<$7dxL#uJH0T;@hjoe_x3}7b*OFHn zof;&)@&3@!6MO2rFK!g%_tU51h^RI-{o)>9t1&S>?W;pU@B8=5PW8W6KXpX@L4cea zFjv&{^}PK4W3O}^g7ry^W^BIX&{@lLC_0*%!Up31P ze^%>vHYou{pvba%Sc)o&zpR{K%q_(($nr- zp0nqh!-L3)Q<_wIc=vwG_MIamSGBG2K4kPjN#oaP^LI(NTB(z#2$>EPn)*ZtPSaNT z&VsTow^Gi$V?nzvmmLQcL8;o3C+{_@Ji1=m`u-h4P;PC#CH^yFe#Y$SS+3!> zsRd8#54xWGEAo5ULPMMulq>E1ZEsNL^B2FzUYxq?A&0IILaNvkuQ`^eRr?|%S zToGOR=Ul)$Ubnf`>&Bqn=V~awRK0$EDnJvP$whHXtZO_@OjP_GcV}14Y1-yai>^++ z@%CVcWY}AhuUvUu`%2`8PW?|TnRiX!B=tq+n`g2ni=6r`996&4 z%n`l$0R4I&K3@nPuK1!jGFOdU6&e=3#9ntbV@>R{^!RIKOeD4p$MQv;|qS!n6xz>5#sbf<%#!W0PSygvOzHVbq^xAM|IudM;zOpX7 zUTOtx=T-aaKd5|6-M-JkDnj{tD=qj-2#&B1-q45ls>=`cau$?z+0mlPE*1o$MYo$Q zB$zqnmwp_ou3I4HWXo1VSGIKBH2wG9BD>39`Z@8N5-|s_&Rub-MMvo}`!C7X?du%d zH9X1o`FH0!D=ytxIt<1_QSCFE3ktu&)9Q=OPwyz!U*Xa?sduT%U4*rX*x_+@K z;?+_YZXeP!ebuk#8%H4NyjC-(xA3WYqV9<%b5`#P4c*ggrXuEjpC<r(#BS zY5ulCKm(V+;xR=LdA8Wwb>rTDEqLg#_!KmI+fb;$TfXOQUO z)vZMoDP_ajB&9cavA@IGOV?tq{A}B?a>YtPe~ETJL<+yW+N884E{hsQp80&;J4y1b z^nm;?--hp5UiMi23vDM4MUtA$Kb|~WKC!7ndZ_4Uk8^!SJ9}0x(|vrnk7s1q`U6N% zaN4~yZIZVlwApd)$z`9c;?7qcadOJ=2_y4Kr1tmDjA~hLOnTaykv>a*PJeegXrOwY zBrfkDfSNq_yEA*`#ptn}nw;y6_wY({U#==q^nma3s(*jeJRd3BU3#Vi;DvTcuea-b z*)MeUttGw3d6g>%C}Qi7$xgc_2^w zaWAee5zTGfKeBZ-Y251Axd1!2@Q7B;h^^eO^JKhj%Qxr7nbD~T?esb2dSp@mjcYF! zTWA;mq5YvH%K>d0>bs;|y-A)8-#i)LWORDk?8H&ebwggg41XL{39rqD#qFptclP7D z<3EKA!F#o{1v-{|88H~aVR3(to;$wOhkRp3&CS$Kw~DUzyYkeenAHd0v@C__hx3ap zJk_g858EM=Hbva8SnpY@F^4L*P4wFOc}&W5+b^;?>(s@%wy*K~m;IulXZmejb-;I6 zo3=r>kn_xriNlUxIEPTk0nbS;W$#T`n2DF}JCb&GmG&I+Wn=Oey!y+Nwp^U-dNWV2 zhRSw#y7+Eu+Tn+GG9n%wO5Hs&D(>ThN6S1`91Qq)spgLw`vI?Tao(2XsCMqrwzf;~ zx=tP<^{;pBSG7^;Y2J+k9Q?YszR}p#GjNpp>7ZJh+O)BqsvYs=V0FYU^h;03bL;cT z2gf=L#Ot!TwSOeED%lRf8(+d#&6?^_YUY&+1DbjR&|;RPyFH$ts+O4c=JkvfF5lMG zyD{7OTRFg11l+z`vX*UBm61(9ZAvNtNTU2?tkdN-<-5hMwN1Qr1&}M3E}S3QzERl+ zt31DyKe_iaKo6!DDea%$t6orLRrht5WeZE)t6$zxGUnLbWm`ubkbjO=H#iwm>4sJL zb%#W4hL5h@ZMUvre2Ej^N_p8WD49Nb5=yu!QVdu!D*a&#>9`Ix-sD?8XK97Y-)bl} z-rmvTU6tD{ea~-bGcrAG(t=-_!bMizxxTjHo4QQ^_*V7%)TgiJSC6}U>eKn=JMudQ zyWU^h7!lv!Pj>oQb;;sOom$`iOSP@-V3&@YlQ&d*zs|pn-jl0;iRPihAmM70`P{gPFld7@?S4X6$b-MRD zr0?2D`|IA{d-feL<$nD@s$vwVr3S1O2{fI7Q>a~>5jc)jaHEVFOvSjSh`@s`dr&hlDtXbcuwLX^Z)S&c} zK?~XzOdVN!+FX5!PGj1h3nf|OPubykq+IJfYPe``E-ojmumeud+JNw7}VLvZp7=}*$b^lAleLF?7&)qzJ`yL=Yc2+3zeN&R+^Vvdq zj!jMKP%V!-6JW22U6;>}PnDHZe}2|^Y`w7dox1E-r_CSbmsIr3OvEc4W<4L?eE8MV zwT7-5altLq*T4DuQ%Q&@;J82fl>ehPJBn@DD?MJmz|MKfDkEJV_&qG={pnZF1AX?r z|Jr7FdfKIlduN~3|Mp&1VdkBWwRVry)M(kyw)v(-)!#4dgy`9-_>JB1?7Cp*ea|9J z2pw?mod3P-fJ5;1eyY<}05}yYAsW~tHKNScgi?4_AG*`5P>;X*H{PUmIrB|5qog;0 z@~`)Fy}uVOveEBivH6wHHS2CYJ>TGFH^tLe?Jm{({G12fch!iixN%Owy?&=Qc6i{q zwnA*TyunQ`#pQj5%-4#w$P+SUHbRoyvD55lEktO1m6X!88jnXPAaz;%;q{vl%IkGo zlG=ATLYXP@C0|2tW}NgXblpMLs`{k(Ik!A-{~p+($hqTB9@Q<}TGTmx;fM5j14bVi zw#R4TjL3=SukDWOJL~H6g0C76Dm6WIey?HaY3@H9mP{*rQggiQhB?h2Ojxw_Km{j& zUH1&CmlzxAH!$|(mw7)4GIo?WU|@P1rPIM#;{)1Osk&+1%!K6??%Pa0)wY-l@$tQu zWxCZGxbXC$*jd-Amw){#_4g{jdl%cUZsVA6Z-KUz@^PN=ACaz7$%S^m`;IyN{?S{! z#TM%Odb?lagbe@dL-a14hDl2*D$fM`eS`ch`d_R*e0Jg0Cwv>d8v{`2!zT_0-nH6; z;Ku!zT+f~ISm_m3+Vjr1L&*R=sdnw$Ykm0N9UnS18k(MV;l}W?H7Bn4c<{*b2k$3V z0pvzd>K@g{h%nvS0&dQMu>!7chl+Tf-1Dtmn!?PUfv^p?(LjV^bx@Ohr|`_ zDf$q$wfn4z#iG(~PX+9l-K;|+-=5lksau!v1v=OBT}9c~&YznR^l(G;!BjU*Dc_?p zT@~x6-ETEA{OFTpMDIPe>s6=uv-^GdcBszq>T?{bC#9#=P~B*A%<*V71P@>IFIhA0 zuW}XNX7s_UYJ1U$rz`(*wGZDr2(Kq~K3^Qv6%P7&E$LXG&)CV0roWH_Txsa?mao39 zl_FR;rdG<08hy%XU5+_EF9pEysbAA&UHi;`9B5U(l+)@A9~d@RhHh{i=Tm zzkuLT&BvRkf**UgJaK#f^HFa8c}n+Ht9B0f8&TV*i08MM{|%wV%UZnMJEBdeX;&Bg zE>m^QrmkDoHCsO(5oO+vv8s3YWLhF>n6u@z96>%z1-UN6s$yI5}iF1&a3 z@XD}Wc>hg=2;hqVTr%Lh(9kHvEg0$_W=6eihHoC+sIc^Nz%%7VFi!)gw*z{ z82-Lq>uOa3CI#kMf7Tk3-{$`YXlhurP5v{BCUqZI zLu%)Bd2HPMdwz=nf1*9|W&5k@iIp?IrQeAj;y1Fc=geoWYwEny&L}nt@u{u%CpKT} zrL;@E)xY8X`paS(RMKuK_p@F3N3l10NA7O9U)8$OqV%+ex8~TbpV+tgy*7vUc8CbJ zc6S~OXr(dv-ImO2pm%9nqS?|F!-oXCK4tT$y`n)`@wtPk%kzHrMVgE`??0MWNX6{DR>{~VIxT%x=DA^@Y^@-KYeA@d}H5zD$( z3X&|mQM$+dA5@ZL;WVcPO-`LIQlj$SHvqhk%p2$PLZdx-H|}>rq2mYV1D5CD_?;mEZq3x~1qVBf0ap{uow!2`2A(WxJW9Y%|Kt@FwWsvU9q3kXkWRy`rx?|`8 z2D<|obQBbRYlA*|p5Hm&=e+Orediz9dw;IyT)Wn~*S+qwkJe^2(7n)TcNG1AfsvE(iIW9up@n@o$4B35!fJrCHJb2UQ@vydiOJSyzQ|~={-jvVCl-abTaq z0^yNS>lkDHWQl6|6&BrYw~AcJSx<&92Om^~?+k{*mfT#-wJY@V2who&TY*p^FOBc@fl@@eL(s8cFwphuZn%z z;+#$Vi+QZmb5dL=4EQWQT}+}N(CCU2ziqzj`9pr;NmoYRP*&gGGVg`z$HPKT+s`*= z!)N91`Z?+F@8IXv^jsgdd7P$J;l>`#mnZi%&1*S3X?6|9q7I$CiQ^C+Mx?6CR&tU~ z?%?|HoxtwYxoMRPzEj8K+Be-*VHst~0X$C_KOT^?Zw09M!-z?DP=2smZ$al?Sb}2oDpMQ*P=%f@HhGH#&uGW%pUb67rG>51)xW=ZcY&Q{`7;#tCu)q zmP5zO6p^b;IlO8^Z^*}a;}?2dwkz>Gp2r@9GFKQ~tdcEJJKUeB!WlR3kz};LG&T>7 zyiCqNaOHN-4;WXi-st`k{(T&zyEEcF{Bi0Yzh*l8OL?rfH5dL(@T|LHl7VGhiid{c zcJB7Gspm)S%b^j%`&m>m`xwrtb=w(edLJqkrX{vKzI=c{sjF_L$<1l>c#@Di5Ivgc z039{jPvVrmJTct=TBLHLKXiOtA-cM0;F(0g4rnZ&UsJl8_vpRd@V7|f7v*=W1q5xC zpz)#DHlFODAeSc&4RNpFi(~smPM@gXu0y|<6}Q`CzYzxdZ|C1#&*WO!HY|F;Ld%D{ z+>A9TZQTe1rSFb4%m2V;vx(oE_m7=ib*SdaQ|MlNK7QTtW}OcQMKq+;UyUm$QYRJ- zdA)#@zzT-J`e?4y0e&x1KV7GK?*PzDRFmSv*x& zj!@oNuW$zkJ(q%%QaAK{l0PWv@s;%5D(TO0?N?BLtcleh_*^?GiXY+(Mpt_pbRx0jmQd4!Id%fi1 z?2k9&MP60x=Th|6|E2?dT<32e>6<*b5#Blrnb>x;V}YOw1G<;7H8~$I;~zb%WD>27Q|=9`3tT<5jP`V9%OnofEhHJ_PN6?t;+% zX3xjPec?Hak8Bh9cwQX(wvf~Zi!P-utG<8oV>wLD(4Bf=)#oELmeUf>pWj-;Kd<@3U8ob(X_+@WT_vQO?k8%x} zbNE&WK=;?GHy`S_T-^=JC-{mBD^`gG&}FN=K3$~fa@N4`75~i?(jhyG#8i*ED^Ihd&iVLt1>xWkcrVKCiObjYv*|-TGMJ z%P=HISLc>FoiEJwmT(E0`Ytg$*6k>k`xZ9pnNe5m;GA)Fc*A}^+YS?G^*+8l(ucEJ zSE(z0*wR~OgdMj7dUa|D@mvBgcEX>pxHj-cNUz`I$}#s>lYtKTso3|=e5nUM&-bM& z2|`DH%aHI<=Pj3&ELTBGS1({wAZho3-lr8Jw_Jmk*G81EJD^`t^4X5Li-^JSrx5Rs zouO}K870e~unFY(o3AaFiTEmuv$WHlpC5hiV6@7Bj@M)s#uzhxay(gl4r$Atk)d_@ zmaB}n=waJvEJ?lWD@i5ow^_VB8*;l3KIV*x>g*zKzMi$kr1*1oM153wt(vp1@O;bD zE3@}?zxhIsW9+vOQIcHOpEvyHrPp>kD97EY0W3GD|Q*nWL zW_76vhLVRTt=_fNy_Y8)I3>AaOR4NanJ8yy zEZs>sD|R%za?&BfVzc1!y8*s&d}=i!7fK`!7tYao@z!;@tnU$#L*|QCzF*SLshJ8n zo#gBd1Ysu0}KO!gnPFEL=O~fSEiRkbAWP2bR z{%4BnOOit1Uw)(A7o*|dHm|l1L-235=!Cg+t?o+tnVRGX@}&P(nd4LQ?bkcyk)#w0 ze2TJpR(>A)q^8R3*Km=S?2WpZU%JzB@YDPc=Y5ySDQ$_*UE7kpuh@0taIw|#ho1s$ zm+twxCSniU>1BHujc0XDL#6)n-FMc%Pg2@CJ@*`b0=qg5E*pEn=vZFFX}-@G(tF*2 zt4SeGOjb?k>cu@EKf4sJS-Y?1)8@q03JVRCdAa6jVw#)l;a6R)@4C;KU}v$(!pF>6 zk>u<_aI?ibd8m@T9GyK)32r`QFB^ii`X++2k;yg_4=aLfAl}EzX^xVrjJNxIDIq~A z8A)S1Cv#t8Ukh*d^ge?D+w=EQIJx!Ruq$$Cy~j) zWLx-I!jJy!b!BrClac>oklC4=#jNk^YUu14N=tju^QfBD*B_ab5tovbl2n*BQaeyp zS;LC#W;HgounLDe&xK~>7G&Qp2)&k^PRVagsjkSZOmA&zg#R9;t*MdC=6w0gnS|f; zL`Pm!-l+r*{Gd63>Xgx2+4WeHZFny$hkZ7Vc6gYuD@@qg%}1696ZAF2`};Y&`Vg%C zAJ4z(bq}kh>3!Ycqn?~eRu`+gp3%{l!e+Mj3{|uG+h4aa+n8yE?^%pTbYDKRH2HLsWw zc_}YDGp8{pG%E|%W9kPl27XurM`wp^yBr8MUPhXzjm2u~>g(&{a5!y4yv4q4CU_Hq z8MF;>v*X%WI4{-J6%`e@<@1E+@JK0nE)y4$l2mZ+Zc;O?0lDgsQ!T zuZ5?Dr;WR}jiVUK z(42HiQb8d#GmnxVl6fyRF$VT~VzTka^fa42#ukJhFZ`x2UXL`iz!MaMJxv6KW`)-rU<;PEbKY zR9dd1Fzp4Srkd5&9SNs9B`H6wFm1eYpsKZj71_aTYs87T6=l-1k|#`XteE)P@M-+R`~J=k-v_>j zc2_<9(HQomxpgeIH}2GrS5>c>J%i01Z+pM7dW+9zM`hnFzTG=;IkO-k?mFD>%zk=7 zdO=2XYD#wJ@P*7%AqCkvjSbzc-K`yMot?KM3&TPqQfo5`65|pI($uB5n!9-uT>S~g z2OV9#oWy+1{k-7<=;@lk@y8jcNQfC58U}1RuJ$H?&8vR+PLzML|*+Ik_JlA@Zhy1=PJX#T&> z2eN;(PtRNH+gLp*W2~lu!ZcQOS6@vpqd%+g4YQ?!IXDy>mJ^i`ml2m%_`PwsuJ#cl zwx7}3*1+scEBq9hQbWC@@l9_Eu1fnkylta-%?mqS4aXU zxkOf7%XYr(g2j3(1&nQYG&ia)^Y#Y!CMvQ@1k7DNm^%iTyY9ZU!Z$sSDwuu!u@M=G z8L=rzX@w)T!*!K4jM#2gYg-GeGcD~|ZN~ts>pQFRQ&$(WBQ!st% z8t>v6z@MJjH`>;yjm1W`f2&)z5r=C&8}98CB$kKTXP}M7N40)`-FqwSg(BQeC zB$sgf9Xg%g@uZ1yDV2IAx13W#Glica{HVk`$+5LnLm$6aPDQ^NX=#Xf|MJI^q^Q@= zI^T{>vA&(V7*`cqQjm1Mh*qC;@mgX@byXAnW<oFv$e4*?Owt?8#5<7$;5s$kH`^sk>CTzg7)qW2oP45-vtiQ1{|Uh zI79+CL`!gp((1dtj10YeJcaob#bkwL#ii9x@T&{(^GR7Ns)>mUsY$!|E##L}SCiQw z#l2QWLP^qCl3!gN?)%XrA5M_K-v>EK2&ixg&76M{DLDAeF)H;k%x}EqWAxbjiOK%D z;i;%or@CIIRo1_64h=0YqyEGPru4qo3~-Q-!9g-|o>w(6x(6rfnwa?^l)Rkqf!@oR zl$f~damlALhi+#_qz(++D2R#AOipQiG*sJP*I(B=a4#_}E;b>vH9s>nEFv?jci>uV zep;sEF}(Fw4#0=yF=IP=LdDblC*R-`U z!7dRM)Zo#V)mpEtWv{h*12@LSZ`I%ZIdiD1FzX4Urk>H&9h#65n;)7VmYX$R+1u9I zzzpqR*48~@4CH3LYVGf3430Bv|J9#QF(%mT*IkW~586sYGfx*?uA`*(X4a%66c$r& zC1h7tPJ#LVY5yk^ucsCy{KR`(9Gu;qZSY>Usyn@nobfJz_hbVdeO!ZVEbzXj1P@P3 zdF5RI4{ZP*8aXQ-2K*~21ZAR!!IO+Ed02!qK?7LdDhv$Vh;3l^yNS4VRSNT z9yc)BSS{@K!M?}COlEG@^GYy~jFj)~k9rEzV0o%xc_I-YtQx8s2Dp<8aHjy^PAtHk zLV!D=0C%zh?py=7li!$8Q9)_VZf$6-ZLR}{**aq{czFT-0sh16+h%WSLcq%gknpw+ zK8DUt-pw-Opty zx2&?1!a6nnl@c;?s`iqGhH}OV_IB3hwl+#8Hr6xeKP~{ye=t!;PGAw28jqB|jDkei z5dgr{y8D1{XeFhYcj)Ek^Fk{pD;QI(me)ODNrm0-CX&J;LY{;%+G8V2OG}sED=)vC zoLD*amHFxY_mTREx~?hOwW_d);(JAH@wK(nQ`2+i!VmmAXYWK`ZQ_mF(UIpfuNF`* z+-!MxJu|od_O(0L%R_FaB;UMors(|Ds~0o;wg-v?ZQgal15XSL62%MckoWVq_cY`Z z-@jXoD6PH^;JyXGeKUaj&H(pq!9S=2JTe1#5D{8OLtmD$03#B9(1 z&S-tY>}_RzV)e9io+?aZfgM!whf_-y;j7A|p~8a|#lZVhYka zhtGjSEC7cX3J#G14zU^>VmdfPI6nn#b+uEYY=f^H&Rm2CzwQ1y4gPPLgs?fkr|;YY zPL?+I9-D29w>WM!-eQd3w}+d%bpr;YiSg3MV)ga)^a1bA+<)x;ZT6N91ia*NYbOb5 zBR_W~UKtr7c_~#FzvXgVbCeVmtxeqELO40N_^ks2$qfcl77U~)7)Tc|kjh{n)xbc? zfPw4?4`+mSg`SEHjZR`FMMd5zqO#~Tn)y9Cy|f(cKf(u=v=2Ye|Hx2(PkSANN7WD> z^+I@5_zT~(bvJdl{TqA(;ZY=nN9hnA-GcBa1;V3h2#+cuJgSHAs7@FF0Koa5cpkTX z6W}*zFU4RthrJGV((2BJjy^`-1UF^n{RAspyrYwt0K$Wc0`kho>|JcE?Eucp0i2fu zIKL9$yqtuNobW;g6)9PBMO#ZXYZY@dV{20rDGL*0wErjFg5mxL26)Pd%LwrSJUU5T zRz{^!@6+i2GT)A$^JVu8Rpx)`uVgYBnEkyd{sD8*!^|1)YhY#N0z7J_e?MjR>xa!NG3!crQDGuOHrpTUp-A)Wg-v9q%ixz8E%K z4K~~XHrxR=+yyqA2R7UsHe3WY+z&Qf7B+kxY`6q$xV@yQshqKlv7NQCrHztbA>HT$gA>Jt~c|Oon!=SJE4))ixMp(7656fr9OYSbC!gv{t z!>V|}7_MZP7o+un;c|%QMk$jhp8uXli921K8H?^WYEMie`;P1(C+vJ;bW%ZP$Ajdw zzps~SpqHZw_%|;rV{g&_;=IFh;bFP(uv~j#x$yt_yayd8-#JVsM#FhOdgR7lm_PaW zY9%!lKDkK1i$SCkf3#kA0N^10!_<`C{=T-ksfU$6(l;=`=xbwibu_1Z`7%_?V$}3h zbv0z=GMWe59}P1*asePT06^#njYIx#s15@DLIz) z5Nhii7{R!j9tOKgM%a4)k*(qu+YhcVQ8DFSueuClV6bqN_8MdRLqziY~g(0s{n z4ikxSCj$c`q1gcwM)zM0j>8esaTrJO-$~g24Kx~^SxPUzR6e`kccG2&?E)M0cHZ8HHEB*MZFR)B1z!r|OtVp2=(bc%6nk=7yGps@ z=>(gzVU2}k-Q?u-hh7eqAeI@bH^rIMtW$5!8K1_SB2{mtG&n@niDLpjP*DS1O$F5yvy&N0zO1g`qbomT9xfP96&kY)rl%Wp$8D zK9Fy9?|@X|tFcl#J@)#LF1N=ahi8{)G{46y0?Tw7Hwnsy+}L=lghtz}(O_44G|W2$ z#Ah^NpCc9fvw!E50OCLIXKXN|sfvL1c0!&!ai_ zuGZz(9wm`d1zP4-rWYk`+8h}8tk;FJ+a(YeOh$YN`2T3q(q-Ip0RZH47{~3G-*PoqrJ~14we>c4#j&g}o zkolk>C#`oNGdeFhA2Jz{<#)%CN}nP-h+-yY3+Vc6)*Mzp%n52of9fQNn@n<2Q zLK{Y-zqGu6-WL(pH9lFzjP6aUZH|d;45Mcjc0Np?LO_G?eAMZvyol6(dTMq-Mto*U z_HgHg%!u%U?2Oum?#7PRj;i6#+o8E(p)m!u8I;(#1ZO{W>7B-I;PCtj=6fAo@xEfd z#@^lt@93GWz~Pnw|8dNOf4@)g7J{ce>Cj%@ATAe+qh{_t{QL$u10yi!LR$g@54kBQ zh)akkK~#S+h+Lq=B^+U$T;0+tkECe+}PUEfI{O?gr`6*dH%OS|{_eBOAXqw>G|S==(fU$x5q; z`TRbb{bKafSYl#f)^lc0L)+`N_7-N!G!Vl5KnM?J<$eJ8(*yA5QGaEBEx?}vfIo2n ze_8?lgaQ2N1^AN>@CW$MbJ+lYYExp<(t!RL`%>|kGx-?e1Iq2X;JrRZB=Z0hC%bLj zzT2Iwef9>332SR>&t0Xjzet7K-rIRM+0^`)h3{sb1ACmr&5v!`>t(k?SXfU_ckVLq z7epUNdwyQcCAPTv5}NY*Qt}#HHU?Z~Qc^1yScwQJESFp;HBV=?gt(Bl>UK08xVkh=ioEjjxHXi6m2< zAkvTk`8!`48XFSDh)PN*4D;H5Z{UH zAACzIG3>`r5Jz0pJV*!085n!vcWQ4gd$)|D34Ytgo%TZH+C= z(4n5%x|*JW+}wAq{hd97)%xR$-Nhnt+_2%OWK?KJjJZ zGNMXO7$pfgRbDkkiC_7Vp~O@9p*fK`aap-v8#|jCAN3>-F)FKSn7vuKpBjfc|HzMY zH+FWy_TMR5RSNisPW{yWzNrQBKefQ;0-l@AXCb^R$hm$VrdyEO@h~~BFf%o0pqCPz zogR~xncoTS;1sxnoLUGNJ0M^LI>Q|33^SlJ2ta2Lz#VJ_cR&DlU<~fS%U8^a7akII z6%!Q(f9)kM0s$jdTdNt}7uf+7->j1GJL+RuKP`m(yMi zKCY{Q{8!?A`V9WNQ)G$w655SHR@-AXt2ydU80CCeW>{kMy);T*dde#>aIhR-zGZwu z`3}ydywgz;1v$yBkRXBf^dJ1!B}jk7LA-au&DYu0VbhNP{rm@eH}Z#MZ2|9ne_<4rHf$Vd%jFhKW}dhExP=mv!prmT1wuf z*0hjwDHLi*&gIhD+KFkg?|}dHFwJn@X{+`1GuGj7O}ZG&{`FWa9uR<$0S*WBKgM$* z7OShT3-#R-vk~3a~=+So&(Ic4)!+}_IvB>c_3o_0|VpY zU*-huBa>UO;Y(SGQrshehi{*4%LqI{rK;I8Duc@C^nfVu_{*26)U)|^50tD~ts5Zv zJ-ma*(;e*JueOwNPp#p38qIRa+)mZ#GaC9Z|LS2#%?cMJet0%LEgiD{X5%{!M`Z2} zMOkRiIbfP=NJ%u}eLM+(U%A=;p9jfWqVlV@{ zE>3EGD6(oX_`)+V*KZ*PGf(i-Vc( zC2K!x$<>5%?IseVUsk9iyz`jeW{UCy*J1pQMWJQHb~4#)=y3@ePkFUE4~>ff;JeCiMvKAA-tc~Mvc+wm9$F8YwhW)onekIL zOKx^*SM|<0v>XcoPTU+ce_@#Igm@?Kj+OfQq8ZmGDsF~*7hp-DzsARb4@2vNLF$5D7NbE4({VgJhL}G+03G*Bz63cATOiyhlk39un!w5s8D$R|B$9w`b3H6U^smn0tuT6&meAA$8KZPPCcZ z^^gPC<=OTh{LiE3OeO%rCIEy(<&@;qoN&1Q-84vdr-WqQ&P`3vOO3fw5Djp!v-5O8 zYDC!iu=JRM?%8}eB|mwf_e?=dbZT-+Ws535xcmPhzl*ot>Eq+;NSyFkJV4+$?1 zPhlQe5m^WirPck-{Va^loQ2&8_6~Lg7wNzAxj^qp0ljAg^qvFId*)ys1CHppA-KB< z*27*(NrhKdiciPER>s(1skTB@=(}%UVq-mWX85l^^Igo>fM1!c{CBK@oKMW&&t1d6 z`A=y7#{Y=UgZSs4@juX?1wen+0R347^k*en4~+*BXg`RM z004{v02l=TFc$z|69B+O0Dx5h0J8u94gmnX2OFFX0I(JSU;zNY8UTP*001ii5KaB? zA4LA}`}tH52qoQ3&ZWfX_M-f6<&0ir>POZ9nxCG(v&m0>*T%<)3Vg^6->+M~2H`y& zS7(!5hwZJb2u?QVkvs<0OB?%B|58DenkUQ)0vNZ2a`H|~rKC5i$jf=V|I~Xx+s|dO z93QWmf~>g%;)k6~{9M*?EAwy*shP_v35$q=ydw?d9YFX2-}$b+3*;RgAnynTc}Faq zPi9zLEaFFh=YxA6vwGk{w84c)dBx~_n3t7#Cn+oyUwB|$Z91b^o!38-;NX$p%e$tQxn2Rab;AK^d5|8^75c{zso z-;GLA(hwkx4EFZS;u+v~AIcU0-l0BdXH-4^!m4j(^)Vv>5F&h3dS8+Pc(D9IMNi%9 z2}X52bCC570C58B?_c`~g030-E6DkY_d7~xl#sCO@XU;?g5H7D_`Kv(g_IO9f2m;p z(o-s%I{r!D1Ln^InZLPny~KQkg~WvC2;c?8#SlL-lMh0CR-o|D{oWuUtu7}a0Qmr3 zw`C$~DspPlNf_{O`(R~aN^W5VrTA9amAmPebK>7&|V4t!ktV;`%S|Z)94#o zw+ZeqYCi{n$0j?2Mj!eTD#Pu1(xE+>N;`2tz^U#Yy}W-cQUQ&3rPJsVABt->9{V_~ z*CtTLqp}sy+`jzZ_^O}J9>pm>kGZI764=X#hh}WcQr()ot()fgvwhxrl#30!)y52 z2;U6rBYY=cQS^qxF=$e+uN|76UcbadaSzCW{6iPnBL3I)$_A^gY?!VHOm{vG=P11T z62gCLB=z+X9#mGwV2aIOhaf!Uu8IT1*E=T@q5;JJt^oXh&-!{b!ZVXjBoe}NOhA8# z|J?!i{kz!d`mW_Cgp8<5ypi5TcQcv%Re1J$<)7cL0sJ?0QPcH(FkK(%U*vPBRB=|g zcoXDz7Lmon5&p3{OQT7KU(gbQ_>YGXBGLwUX&F%@XA&uG-bZTCW2~M;r}x#!0K|{3hXWFn zfBa$&}OSo<}XGHHRb;$?qv+I{QD8`MjIV+y% zT}1cg{fBAHr8H&wVs#uzPao+y5&wm;*ye?|a@ghIXy+vf@?Sj2e?37CGL!$u`+MVE zeVomJ|H6YDWFN?RsCy(jF8pQHKt&RJ{KuPw;l9wavayL_Hm9$v zF}9$j@N#N%MpW#D^P#Qbk)a8V*PEkSBOXRnUaKhwQAkrp>Oj-gfxP_MJ0O_+GS)k5 z4}*B+54kz>9zJ+XXrHg+E^B{yVIIHT#O;Lj<}G%F;G=@_8Pr5+x zQC5;x4)UnP+V554!yli7Hht^wd-FJJDE#@UfvQJuBVqdGv;Iy{aPr*cP=K?kLziO1 zQvV0O=b!!mzvFv;>wV8@N{E?nllBrkY%S*L6qK45`l9ZQ| zTWqK&V{0is&stW}e1o~E{Sw}#ii(yZt1JX05jmk5vHxE0Slcno>;i$vr>=HZM`%Z5VIdpz-JBUcFQj{?1IA9rhKHq6y6>fD z0U;Cz>4A{`;_RI4#F!XLN?gb7y!_I3ni%|u+1!1WL(cKS{Q&aQu z>6y7vsS*F8hb_*I0Qhr*0s?@a`GUXV1#=*HJwX6q1p)wd5CHgr0Kgdp0FEF4Z~}rC z{J*AFo&v%<8v+Op2c0dv5&qNCM|iMx1LA)IK+ZL*=OQm!1^7r#$X8mK+dzC-l@J znD!jvHC9i?GX@BLp&X(D${}K*90KJ-(*CYz{wKW;ltZw;d{*d&<(hn-O^MqV=;LVY z`s92*X9m4if<7AEe8G_XQ1O_vM<2g81L@vU<})h)0pAMxq<(f&6gnj3vwOPO&|d~jf!iVsD3lRJmO*@#sfwI2-Mp+*p(7$3Z8dz>EtbUgw$_EgE{_DTCxjo$3IDsmS z!+n5sW`&yAqk4A|Nyd8fyJjWfnh5V%yjL&B-{1@ZPv87wqP@W+5^5_$Tg72Y+r#GBhS zwfmnIqW0%AK$rqtkg8&{4GR*D_Vr9oI$jxxC|`YcSg%RC`tFm-Ny)8O6tvsqE86Qg z9G@HGr@qVwKnU%k4*&@3)a&tW)6=g3&t7Lo%|&>3t{4tC{x+K8eXJlNU|rByB41iO1%t#59m%^ zpgSeZ_rEFyI`Dum07SsQJ0W3w?`B;-4#I&{9uNosy=f6iqp5r-esp-#$F!pWheiPo z`7Zt7(-nJWC2EUA%&re6t-KuH##JE(bf8J>?0W26^?Sa{N0UD#*M8WE=I_ujIcb?0 zX=OOfW-G;+gC*N?`V$25R{NApE^ zzYXS(;(_b0f5!ukfBRkv_Ve%a7rFI&{%Acq1AgQGT$n#PFVaAm>~;r1#BY7`v!8s= z2nfLbSM#4U@!@R#<7-KNR%YD26h7cTzDC(n$AItjatooBgML;e9ta4t&$LpAi4K zzi9BB?2>ar49Is3eTkHrN8*Y0YU8U$_&>|5ERNe%L;Q0Mhx1~XO;2xqviC#pBz0_b z*EG_9jFqJ(#YH}Z`hZHf?-MimU&#NA3ZncFXC$v6>GTy!0?65Y;d+CAo3&@N`fjY_ zANqHH4^N}ndY~W+8@w;#15K3y{(1rYwE_6+yIz4$>Q_Eb)!xY0_$=KYtJU708GF)PJWzLbM$cqAW;={wF>R7|`)=8F2csLhd4;8T_YMq{~>=v<@+M-KkK(u1p7S~4QrBTq$wK8F<-776^$ zouWv{_s~kpN8vp#@bA>3nS8ee(ErH)o6hhB_p2daI0jOork)H+<|)K){Mujk7~(&s z1|}!F`YIDsGm6iKpN`DP&ZjjF-->#0FYRu4)qTqOb8Yzv*P1e$N{a%x_8i*AD^23z zhZ%x_2e~i8H(32RSdaAG>#X*?+XCTnCxplPxUvfC;NxB~jb^K{`cN?9{z1cWtE_?b zU}N6hsMDKCE}bu@qWEw7so8N9|1FH8(oRPo{EGOI zU-6$;8)x!OC%b&=N8>2+@1w=p8QGL1#Q%)U_(Q~ZBK-q8|Fik;?{_i^DN(m`(EdVs z!>m8wILr5LbaXb^7D#aOuyJpJVT1>?0S+MlrxWDsj(0fbR0HNg7wBOe5d=;EZ$R%w zn$h#c2Lvqrx9u6dKh(n!<>mqXD<$W)Tuf3#PO9fsV-;h%ryua||9<}8hynS~v8T6F z!_I}q6+BExPNk*hXBDPeF_AtjuLjlw_WNi3Yqh>32!K&N++hzd3s*OSn@xbDH{MTm zr@e(Ukznazg?9iv@GCz6f(a;pz{7Dz<&73l{@@L8M+C|r?4kUD2g)DZq5MG=${%!~ z{6PWAAI#yntC>rfo0&s`LCwa*7|i1z_P_g={U2o3_4Tv5+rG39Fgx?n`i%GWx3C)8 zm@R3Av-J|c?Li&Ve?kHT%3HFayoK_AW)G&S5x&<4{`;7#n;jT5FC$w)RPPdl2!AjK zhJtASmmKzRvm;p99Jg~MY_c!{_i$Ys0MpF=qj+YWT_B0;0Ez7AW6Zt4VEsD7H5;`g zmZ|W`&J$HolhM=AuovT&l(5Fmm0M~guuz6)@dBgu(t;AkC&<<)-#^3uem@2L4!zt6 zI?N!y4e9?GeJ_=|@D7dEF%c?(^217y9Xz&2z!z6S2f}cZ;L$L75I}N3;AtaCoS2@@ zxzp(3ap7yidhFF+l>dDAf(-=VGDF0N>HLik(}w&9(mz%Ko}VZ9?AZC~B;I5{e);9| zjvXzB>G>YzPfy>y!RqxN{LtyiFu`AZ7?ul%<6m-BE9cYvWcveWHzK~r!;DCLq#v@d zK%_yiOWE3c`@RjDli>bQyHxGmdGj4twa~bvo^gBoYJa{PZ7D`l&o{2jxHzQo$UmfH|~?IaOYc__B*-G@Abw5hoyw$}`FuJxZSj z$gHfIB=PWWQN7s0W+qk_l{b86nFrwfd;vT|+pnU&kZxZI{5yh=&J^WWntQca}fWEC~ z;1i>V)p2cB|6D>tdT`J`8Dm5nQ-{*J!Ngw}D`5lA@u?K$E`y6$$;S*4o_A+(#uyS|yMD>tR?xOf4j-G^6|QJ0(5yQyuI5H*lipC_QgC&i2O{>WtjTM)C?Gw^CJ2BLUk>tqwo~<+y=#OsB*8hDCnPUIw9*XN5@Bh#pGak{vdGl@RCDi}X z&rHpn{~;(3pwS9oy`+qpjc>ri{k5O403KWRWj_LgS6_H%LAZ?ayHprUcS;$?r4PSJ)ztY$>GKSNgoL1jwYs1=WDBKZLMtW zpk@dqK4AYdb&$P3te_;YY8^ixpOm6FMQ2tI7C@ZQ{{tK(DXrq4i}AA<__Mph@YuF<%dM!QQdoq~90 zq8x_rfIFD=2j$Ox*1tWiVZ38DK(SEgaOW)F{h#W)Kn|V{a_|U{gAaflJPqXFnIH$x z137pO$Uz$asPCGI|1^)H`@O-R;1Edgv^hvHvfu}POdFdWtpCTj4!ZwAzP64o`~q@I z_|#_e`-ty;PeSu08wjpaQ(Pe`oe0l81VH*9%{K_u`^@l#fjock|09DnjGU?Np$2Ai zQ%hqzdfy;ttREO)*7tS@@S4zh_^p2*fd>OX{`0f`DIulyzpH=B0r?N?_tf-X`I3y; z_adO284u;mP~RHh=4nT;wJp{>V27(Sr5Oi=sL9ja%1j|D~uo4smD?oxkky}|A&Hto~F5sg8 zB!5_@BPJxIBq@^e88#TM^Iv*yTWZf^CI~!UgTRA1Q}3KVG4tNb*RJj+W?fSu2%`Sd ze|!i2|2LoA(9zh@2IbJ5P!1gi<2aG4~_u=0Q4@x|F=&dd_S8Hg!gfL{&*k9AH5Gmbj5o)J4w#)A9FlG z04=2E?!5{ipumj%_gpUn9ezLvfO}N6h5~U^4-4)Q-oN1Ig69IH;JE-LcrIY=-}5=c z!>}GDch`MK{@_+0v+n&!ZA}fMAIcxPTA1fKuv|zFbN4hG`HOpf@ZQVCh}>B{4}^15 zwGaSL4RAP6-Vhs~+WN4#=-$KX@I?Cco3zlhn8ez%>BwLH{2tkT9xw@;4tDx*FYQbpLjNnCJgYFHBnyP9(iLa;&&UT=yd-b z;%;kTx~Tnh8U&xNCr!Ly0TBPmfbk&a)@|GXXMYrNIB9YI_(_x0W{bOHY<9(f6*<48 z(h>Q8)!b&*E7{SODY?4?r9F!=9hx1i1f>!t<{o z|7RUA>;GgyAB^zs0+=w;cT!+NNbf48Ioo#9C6ncOzOn+FluWD4Ko4T+@@TS6&9Ket zXdtOh&azH7m`p|dAJRWuh5C2J_%j=apuP|BA4uOY`mLM_1#r6ap#BfFyS|oF2P1DR zMD=fm-|6L`4-wx%XM?;`OTdmgIh~tym@t#yM||MKw}`jEcZ>}}eZmJc!3a;2SV(x^ACjBe|P?eqQij@ z3k5_zu2gqO;G>lrW1=ot%!9!BQoBE3^? zW9M*cd~)8Y!h(!}UU&{88lJ2tbph7KJXmIPIwN(lizdhT!OHV zFdsL(@8m2X&aJ066AxfC1&(2{b_?|31p{t-0h@V}I9?-eS!vycDjVeWH0-b@iu!s2 zdiDmgRt_80EbR1fP#ru^i%-V<_-uX*2x6oc->gR>*{nzUpO@?8Al9!jWJNTCv5*r zbI(Nb!udqbc0aOy&~YOVFaSi7j2zE01JFC7`iGzVIPhhBpqJ)@`Zx5R1LO7g{?M3D z-;-XBM|Zva{QTvko~Vhz$jFD~5!rD6TR{E|@o?gO*za;~=s@u>iht#xjpARQGhY)t z^{@7#`(5;g%|`h4Y{}&0>x!ihpd7Xyjobe*j^g3kp~*>*gUIfI37$@Eda`!8G=_Y7 zGD$Fd`v$vm&JOu8f#JC)7vn$<-pea2iPg^+TwGKD^M`W<06{G8q`fd~-ea%4i4yUt2TR--{s;KOF5&EqEX~(-SeV>MylzR{FG&cT+ z_q`5I4Yr1ssUadl~s!Y34R``-STdR#R;HquoS@;0&t z&VO0?tRC{W9+bUx4D4I#rTlXbs9DzY zze9mkEfh%gLxEHSD4+|WK#($@89`$6Pe9w1C_nf~To0pdv5`ymkOuf_(Hx}~$ zOHD!G8(?|T3-4lWhu`LG7O@QIGZ?I&FDW-ea!FuMD=f$ z_7=iY2Fn)7;1ZB`3HF3!w;VKkca0zEa7<%Zg}296`uD{hUY!p;du|QfASCTyoVt?@8Mt^6hz_z z07MK31^pY+JE8SZ#42y((=eBHz$`P7)D&I3lwVDcAJ!uv5X!|!WZ(m-)bA-^-c%{z z-y*Vs|GG^benq2&0{z?l5$qfFK@pWU&iyvA9O(m@qqFrwM!B=~Le+rwbXHiXmcf2* zD=*1&z4ATg$x2A?g5L4|6U6Uoh&w&xa8@{1Ub_hPe-phlnIM)L$~7`(SBE?`U-xhpVc-*|B{q7F&NY-sL>`s1F*)f538P_+n_E zo~*Jo*fEw~l0l-QdZrt}MB;m_flM*K{VSc;1=b|Z9bb}D90NJU!uGFffet*qkVx!? za+zo_|M$eKpFOCIT#NABK>ln#d(Yl~&F^12MWxDyU(t<#_W_Qz#x6wf0}R80p?tZ- zPRN)4kPZb z48n`T&?`GF0C=O1AI!qZ~r zKFD9hCEmXWxK9k=J}Gkk`)$(4`A_)oE`a+e0q&y?xQ`d$K8W`%Mo0e6LH=v0GaU)z z)Yklf_`;7}LAoo~zXao>Bk5lbHd0fYYhlvWK+m*W50BZpnmxMw ztY-o|Pzvb2pFsC52i>;B`^JOrdk=Kq`qGyVYAfqPtHZ+TBOZkAjA*ZCQgDLe zJ81y;km;Ci8m1da{ciE;{mglfA7K1w8-8#pI(2W%<@i)96ugnoPep!bNq9CK_vUhb z@0^v8Lvr^beh~42mO8rIxm*2JFI(aY{tp1Jpm!L8-a&9CXV5!TLGMrjy~7mr4%2_D z4}so+_&%$(-ak?J4h8=L2%u%DeLQ~igF@__7n7_gGQ zhoMUqjnwqbjdhJoyJ-*5=&Y~P6@WK{_rwQ~;oGns{;K!&M!io>-*j#tWfiP{&whXh zYW^F(;b*=N;DIjz53B<`Fbwd((7*G2Zq#CZMc+aAK9mz55B;~WsFSGw(R{!DaSGwT z^!uT}#H~^3329g3Q=x{l~Ep zNat~!i)S7F9)8l|2-?ABXa`GWe2JH58GJk%0H8m}@f|35-0qF{L3%$R_)h-4WfkE5 z_W<{21MdH|VE7U)=n!u}hxlA1UcliF)48z$f+kCDa07;uzGtex0Q66tkEt3rIv&f) zOp@vUyvZt>_TZOwinDS+2eAPiWEtdtlCwir^cDynTmx5{aY_> zxorm3gTh}T;RXHG;PItfFa7WXv^#!)08a73(Wf9tfF9fxc(xlBay;9H;31P{+#jSW zqWG!E*jDzn?(R_WMP>zz<%Q-fRAq2M7C>{yJ&d#j2Ai=h$?Aoskic)QPKB zvqRbZicm=WU(kQ%nj;`=<3k?;V}7`CH2c@%Yo9#6FUbJ>yA}T}O>B(^GIu6N#NqSK z*|j@v>sII;wCVjM0ssuT>}`LywXmt?MRV{}S$>av zSB?i8eieL(TFKof?{0{myn9vg_W7&kmWq#!kwJC0BBUkvpNpFl4(07hIe#?2EH(VZ zrJ|DXU0c&aPbVEngi^}o#N&m>_9y&Z?;~+{T*@}+A++@i{#(5dT#qAqxc>map^apEDW3g}wIC6w>0&YbVPRE$ z-N(kdhm9riX>TeUuN6HiszpQK3;2z4y~phd5v_WUkOMA+9B>`vfJ-0;oc5F6;~v2M zaNak6?skRZp)aT70+Q1LqfQ@3xm(7EYZ4tljpt(0|0aDnmd#J~eUk+~*3-Q`r*vE4 zVUGp?-+kir$=-H0GpBYpZl{mG4()*MX#YC?TYVdHxJF$efC@QWQ?$v{A%{!-v7vp@ z5D?58V^s9?Vd0&|I#FYBZAs1Tn|H2#EM@ZF&-FF+Qy;hC-$)OV@NZM!;y%a%K@xfy zjgbGjld|Tz?0$os<;G&%Z%r-9TaR5$hzgBJ1ky8D3-ITU;`MoA$;L9Of3*PqD9eX; zARlgx|484>f~j|b-|fJr``y4m)qciePj$_8O6#Zlu2x*=F~wQaW&O%^wyNv>RxP%j zXXCX-YtiKClht(xsr)j*p6+|(x9$1grUMW^8{xLs@8-!>*47xNt*@uAq3$!I75_EF zl=L!N^`5tqzV&~i_gt|xpgO1`pdvK%VMJtOSR=)EpD)EDzWdg6B0M@LWp{FX6ab%C z?sAm(N&otCHST|z-qD$)toYbd1&4CN^Y>qf+ml&ZvQy1=rj|P0e{|nZ7{6MF;sMgP zgW$2ps=14mw|FYqZd7qyFk|^lZ{8FoEd&hck0=5EM!zpde~ZT#^fqAh@9no>h_Byw zy)3^KY#7{w+oOZ7io8EI5Emr+V_i(O`wSaA)~@e!T2C}Ivim^KPIxs}Qki$3@KBdz z^}gq?GBT{ecSHRzdm^4A;wS#=vx1u2jZ8f#9QA(M?~r%L`hKfu%Ll&Ug2(j#!n?l1 zg6}(>7!{S6l9;eNBPuRNi0CK~^$&BI-Uq_pu`Y-JQSC693B6aQ{$Yjo2h~3=b*1;i zo`w3y7FTaq`TJ4_+?VIOW|ToU10!wXKk)zJ`_g&9ybs^?%=|YDHPSZI?V^u%Ab+Dw z55^wqe-z(*iKXyncXCj9<+cw$ZUj~o-2U{s?B?gUJA;4RfBo`pS@lX0_{DqWK*dG^bV;=<+Mob zTgb$_(7O>SeiX@`Q#_34vVJG+XTrD6_?^pI2EFgLF+K=Zhv_V~$c^65A`32ebMPuv z_A#FSOp1Tghz-Z18@wYpKXIYH{p7c#FlfKC%!uLJGYLouWdEg%hh?;7t{nfjrf7I8R) z4;(jx#iDrGci7e1A{(xd^lbdFz>3ZX1|hsplJ!H7;Dkc)Cu!Pc-1a%n(UbaNHxU1K zZDoYMs<#)@|5X@?e(-W-&)Yce*Of-}Cw|Y#3%YzB=<+iB zV^b>Qe=vUu<7e>QN&O&}`;O}?w)pP66XKEh!`W!FcZFj&^}1U7t&S@tKJ@0%Z1%dK z#ai4i_&M?4yMX^s{Pwjm6U*~4o`d#ATfMW#bcXL^3HfuXchYnEeH&lsmSoaPubV2@ zsh-JSEa(3$LORf%|3iAHSKJFaml=Q{wQSXkB(#Hf`~X4fmmkSb_vXJ^KzJ`>6hJ}m z=YAmmKPJ50v)`G&^Z|(fPx;|%@S)94EoO@d4@>;-fY<*s|NZW~Ga?J(|Kz@t^MCxB z?_AsH9bP&a^-$U`#Q!3G(*|QdKgWtUIRQ_u8z(^C^7O|};ali?cLE-Y@}r?fetuK& zgE0IcQ}^U4;{R{Rs4g9v1Ux?kkNhEcbZ^4M(iXk6Rkp%F!vX%34~9QR`ycq=x|-bF zWf$+9C4Hb#m5_&hGl+lQIQmzoOwj$6e$e;*v_t}Wzq6rRvqNewKo5B(D8SgM!niI{r%Qm@<3`;-1bnim9(5>z~l~pV$A- zOHKz$B{Q+Uc5mhDyB6DjX$w0m^SYDo8u#t_j(`J~9SQJxK6>WhB)uo)7IYpI;}19v zAEetqyn^r?!*tl}AswD>K>N1?joa{z1EF|@&p)3<{a%H~vVJe*!dUECN(0Z*{m{>l z%Ud|@X4<3IdW&)I1_ zP=4QG!aHq-$;UVlwd41Q!~jfQ8WUQ+0OQ+her52l0CdarpMT-7*q>~bj#9m63+{jF zhljkD_rt0GjSHb;vl53pK;h*G+W)xW*C}q>FR$+ySVQgu=l^0)$bAfWk-KtsNQq<+nEWoyjBajzv_oRZ2`QU?gKjiITaZh7dE|?E=N0r z>OW^-5VHa05YLR(eh}`z*@)-N_Q3_O@A~jgk4O)tcZ?Wv&ko&mpocvosBi^+A2aq! zq}>m#;yxO)XHH2XDgS=9nQ@`I60<{n+@RZTV*wfILX*5?z5AN6<( zzLu`4_G>pfFS7UTW!BHkYO@Z$}{wL*s_geXlV@g-zfHR-z`!1JrYTJ_Ublx}MyeGaR#eY+=9q7Es zuFcKuB%1Q4a%G;wNz7*{%OQ{SPfjBKU&*#BYp;Jo{0|u(O0I|R(R>JYIgzl-33vZT zzq>BL_sm^Bx5J8gZmXuN@~5hMtXi?s*?-yGi9S{vZ5#zFXRhAtx_MkDk4ZKita=Qy z{Tu#|qibN=O;caTK(2o^)y>|;=|9oGYRJETAMYb4+wBhVAu>~{;CFX}>L2)*p6>Y| zE9(^WvPC<--hAuGkzZgy)H&YZL0w^_dGYnM&sFWb0i z>SFJSinDArR86~4|0CR#_J^Dg!Gi&wO#f@VR<|Bzjx+vSKEy)^yjH>h?+y&`8eo9e zo)2;Lick@PSaAtis1Jmf()@E$?i4+^Q35;!BAkmjlzI zqbuu(-wXPi^hS`Z-qYNOA2iApF;VHV{(3sByqdjD}d5TtNDklvA}zxrO_?0*k&;Beydp_ecR4R6q2c8*J(d{Y|% z0Pg#JY7oFd`8QtSQm=4j9xoE<&K-3>Kk9$yDDimneFSX&CKW$F!P{N}nZC)Ln(eRd zJbw25yEyduJ@JjkJLUNeugijit_VXx2W!kJ#9J>?fzqfHpy?{Q0f@PlUs zx5|QpvxUTWg&_*nds?^qX|%iB;lItULJsj7-mu%r=M#6wmR`DW>2j1XDmzWMH>33I)oq8i2L_+Mai#3cuA|43Z>#ufp`ZCb z=>4?v8_^zCG=v~D+QY_Z4?Fy<7t)^J2zw_zypFZ&t2*F(^YeFs0EQy<1M$yq)23QY zX8xQ>BbA2@8`#}Zd!%vqqECU>n**P}1KbVzZ#~NBJlqREz^ApJ%0Ju+xph6T;9*VG zv%)XmK9+obRrlb>^B2z_eiQ}PJg9nB|K@H@K6u~w-06F7-~T?{+W*cDPT3t@_`m4k z{2RaF|E+&)ZqB~8jP&uu4){QLXaGR|nbvos;MrOMkH3ZH>kBztnZIEeZ*Wh@KPyB2 zxs#F07VGJJP<@cTf2s5`@Nk*=<+&G+gh!U{xxDw{VZ~*B>V66vHvO)(aMqG#)16e* z-KWo(zHsiGrCXeT_gL!YqU5k%dDUj?%^Q5&o!9-gVe?G-9;jYq39TQ@9|G|9s=Agw z+}^)vm~^yI>A*3vW9cc37%-;G;IWz``;8c)W^Qigtf{0vW|W2bpngt-55j%`K!EfC zbJ8+04!2yFRMs|Dmo-8FhN*{N%yoQ@`KjMRKO{XXDuKR-dpQ4WpqKHe00O{}hizyI z?#iF*doppWSln^>wCL^uV-D~_L?Ug?<=x67knjKwX@%F*`J8S^YNSMx`Fc|i!ypmw z8R$=|Bl=8R$bmpC^xdkh*=(I|&fYRTH;g-3vDtm~Sbpv5VHYi0_25^!Pq(%5wsA7t zFxA?@VfrjbU1ei+PfM2dm`+L>8eN7MDXJKC(e1Bd&{@~awDbSH{>`E56fdMFRDm9F zgYH8d|4k@=GW{&qPlbUb=?1wVk!d;DvU2T*S1|2(U#%7@=u{+{o* z3JZ!JRNbg4dYB*kM}B2x{Z06RHQfi}vGVran*T`;;b%Paf5m?WUZz#=!RDXV*VSgW zz2Q1M_N13_dWuGzw>_g zp6vkxIKn?!a*kT#_yhiDU|;&4dE-pA$E!jNsl^2S4H^DJ=I_AZqjvB$Tm9a^e^XU4 zRn~ID2cxag#o$_H!Ts_CX-)CnZ})2N+^Q+M-(yAri0HTp_g^6tHx62#&!65m*x33Pl_<76tC3DuePjXe%Ug|s*>5e+m9do2R^gb-dJK3Qh#2UA5va7qZx4?T2 zCJgx7kZD+d+4D#N&i}OwMyePXjfMW}REHrNiU7QQGo*Zn`ah)qOZrfx59ZeopSuR& zKNi|ub=628>i-`96M^F&3w|LfDiMHxdLI~l#PVbPTVN278cuq#^u8qDNDt$;1B{-H zl?~r)=0fM`b~E@hm&}?wdCP+JYrUr_DUD8fF6z{agd-t)x z{V}`(yuTsvo*A^>7@x;;!iV+7aDRG6MrS-fOXmZ|sP4-AoO7|i*8uO^7v+Db-^k)2 z>i-cQFatvu(0_FBoL&#j|1%JDx7HF&&VJ;OKKE;am-V|4Hk#s8XGe17}!3W2;6s$dCy+ld z)yFjPhn>8#->LrRo2Y%Q1MwZA)JinN6z1=X7|vgYf^SHMf}eo!KkKO;W~k5je{H1q zv&DeVp9j0(BHd|4by!b5$bF_0pTh(bM)}t|o%l`1j^_#oo5gdr8w0c*!GCH#Ght58 z^+HzLIP_TESmHl%BjtLYCR?SVB{S~(6+VmI(yH$%8@JQ@l#KxZ%6kR981NzSeOnr{ z+2c2Z@Cmznmx=C-9uR{E7%$uZWwP(x8t0+sTJ^o8e;U=FgMv_GwG+PoPpZfADkd%Z zY>)e$^j!%Loz3-=m}z{f^#7uYKcL)G_0>%oA|Kp^Is5R(Eb(T^KMF~{r-vinYnucKyHo_ z?%ftC!@J4)ozc7KIRu|}?MqJ#2#AW2;REo&zz%302QR?Fat*{?yqq`NJ36}0wUg@` z-tJB3pZRDH2RiR2=#U;9_5Vl9`v0@ZA-sJ&WBfOKYzrVk81A?IMY*<+@s_YC*Ru6FQuHuRt2=IiC)j0?dG0nbbXJgx|M z$nW=Oyzu|(_Y1)rT?pP-LGZ>If;YBwAFK&(qVuo~FU*{OnHQZb={(E&Qd88FSJl`^ z`2QRxA4WU`Kt%4q7YslCY4g`@+uzl_c~bo2<@c&vIj437CuU@p7PjI&AP0&2Eq)i_ zedzp;jueUR;f37CdS_uA&zyhad>n|d%L}!^0oUg5LH)frIA9R}UmXB<`rgSOe0LV( z2i_uN`2WupnR@wFSIv+D9Qc6$^(Os~Z-?aiA7}^c``Hg94uW^oFOC!Q|MCNA^ZP)% zV-Do4yV4kR$ZfWq++6B+IV0hTvB=8ucgzRBF*%g~X#Cu}bDF_RX>r zbq?9+?|=_R_2Bat*&H+oHq#h_2#|kCF~SD{IQ*P;`Ytj)7vceb1GNDYfPU68CA<&m zKl}|3yv79fcl=FjdYEeT9HepQ$AjyTKcM^$`Tr!k?=q0y!0&e;?3=iG2J1QhD8UXor zc%Y8h4r0mY9XJn81q0sgy%Ahy2Jgb?U%}ppzIV=O;`{LDctaeW!H0toDmxEwAhtf9 zXWm842nML8UAw8^{PP#sjp51gKLNPl1%hRo(8uBGcTl#_&>f&{s-dnq!WaS&ov0q- z*KGy92TV_qiQZ58MZ6G< z_m|;?2%`c*&c~%Df$uF$3`-7A+!~U&H{l=naQjaM2S5O#E;}eGJ~1Ia5eBh8=QF7H zfc|5s_D}Vm_WH1(19-RBv-0;BgaZ$>c72#E-kA+Mh@Ko>g`TjxGB#!F8jwBK=pKsZ z#>Rb=!4L09dbsI7;hQcIzUj&Jng@+d53gbU(=&o8{*mkdSN&c8pZMOip5MMMME^_L zBEArq96-l7-wJ%07!z9)K5VAhX8<7eLG-2%V2CS7XT2!#ZiSz z6}l%aw!hL0`b&C7I_p4!oQ--UI6&bf|hH;e*FR9)>=$~^i{|nZC1jzcmfPXv~ zG{QVX!FZ>T^p5B}>}APf!R|D>78_m*0nrx-gcjfh(Q_KpdG-n~0C4UpOB@9P$U7!% z#r(Sh5PJN9X8$%`KI^FYdP)4+p8X5Wnf#PN~#0@9h?E@Sm3A zxFbE(0cdx&>7i2n=a8j{mkhYTP{`l3_=7O~=2sRg9)OPt0CYTP{J_ni{vP35xFPPE zAv?w}en_?%91h_DMpOJN%m5+mMA!`C{|90E6(T-=$LlgZ6LZ@t;(Lw#lP{)z_f-kv zeYE>ODILqBacJw9^xsz#U%s-ZKtTRZDyOoth~G3Fke_M2I@~Ay_bY^dKWQngV+t>n9=o8&7-yZF*P_4$JRDl*9M-$rMPj{4c3K?*C0S z9qms6+Mlaz(~0lxwNA(-emD6+WoTfzHL=`n@!*DKxg3+Q{Ua>^7aPJ;3nRS9s6}%A zTh2RwgMAiK0qMUE+_GJOmj>sXg*k;;ysZ_bc znTo}@{YHFS%p<*j;`hz!%+7ka@`RX0^Jk;|JM}040%&)41Ra`7V~02LG422J(;2^0 zt#-cYuml=#uPd>-WBZgf2ITV^lvfB3jm?*D*z-uZP4qwmie zp9^`n&UtAflDc@BR(eeU45bDGas&hfPs@u_8+wa)!(7Xc#(oC?1lK>|ALd~?ZGIPR_y@}O9$$1Uu}pXR zmiq?|z$5@dED0Z0`JI*ZI!bGW=Qvq>FaXu z{`S>lE#D7>fJp$zMVh(s_=K0bb5MqtlKEMX{=>nYxq!QAdsbXv{GB7X%z1FhdCDNS zAQsZUN5gm-q>miAQm&6|hUJw2?xOmE{0#`-rX&>c$?v(}58y#wnV%v4N8yma^FPky zh$VXamb04lw+2p(HhW!Yxe41@uM<~D`#<+Q!+)gpH>dSxw_9(*N2&R-v*>%!%;9ot zL4aIxs^4rn|Hdy82-?1fE|dKvfP+K`eqMi{_44&cK$u7UZy3DE^1G5IA&bUlHCeff zO@rPSW(zaCZYy42lwEt0&V%la3?GL0GP3blq0H|?LhC>E5&2ns-`Snyja&Jzv>!O@ z8UM2x4N|H8R;vK=e`_00;X)@i5f~J`(;zmnV|pqdpV=*YO#P^(uSvHSH3udgzEtfjTS z(NN2Q;|-L?aGmsZj5Kr{M@%)JINofGx$Oj9cJEQ_Q3lrjfJY-fTrcB4n<-QLlR^Fe zo4L89=W!769q?dTcV+RgP^f`%bxQ;UQhaupLLHFspn7;tdKq*d7;qUsZ(9G)SJ2(f z8+^_=^}6$H)Vro;bUaD>!MELhn2|`gY*KIxL?OKlJ0A-u2Fqy9VF6K9|vd zEEx#5p9H`mPweohKDemL&0i~-# zQ|8YWuzlCRpG5J_?*@o}`x_|h8(Zjh)6y`~^kgAlLU`c2QPCgI{Wm>+P}p#HkC^(| zDN>=3@&C-cIF0fFVMZDBe?C_>-p{|$Sj^zvNgovL@QV}axiEwDe=Z{Yg`f3~PCszs zA>L{FOz|J&jk0%#M@5BDKKM!IhqSOsDqY$lLcG}gS%LBe>65+gb}I$$lWcq)T$eg~ zJJ?Tyy~%V`M7=#=2%rH5IN~GV-<2VU#Nn_A|L*5HalZR(YwyxAsE1Jgzs7DHCgiu4 z@|l6AW~%zK{D%5lTmGC#{SSZAdm7$V+enXNs;c0|qWL5K-xxsgV8-#Pnu0$hHO04o z)YsfCy;UQw$*a22SaiFxs+#ca*dE}An#ASil0Qn$_xq4TyqG1+|7G=$?;_H(o(uW+ z*!NQDxdxF)=>s#qeU9|);g7jnEKUGAct1V|;{QcQip2|xB@%UvW%VBx${VtTCWOu9 zN>LCYg9Dnc2>{QO|0*Hip?cRP`$s$I{QD|kXZbjsi=%>AEuHs^{rP?kQx<)+M11>6 z^;90|fh=Fk=z%=NdeePJ_X7fgzwQU>k0T&pk)F?Q<8L^oTNe7V=e=X_P*>aVP;~!O z{FhGg-;qqA&=dezF7V=1FPnn=0QOFkaJ_f+T_o`%J(CXD04V=$b5~7`m! z$PZR}s}223_`rc!|9P$&L*J8Lc>3$iI~Sg^v&i3v;ycT&-U_=0p&m+jzqD2@qscQorUJk#YY7yv>uZz1^2EonlEQ9a!=L&@rhjqA5-#gv+_N`;`M=d(Z;Gw5K z%0NJ6OwVoLddTVh+4RWYn(8Hnl@Oo=}*z5U(Gk=R0v#oB7 z&a`h4Zd=Xa(D^o+^8L(oM&BBOPe*CIt#$nTqT`LR?c>GK6#qt}o=f%2Be47206S2s z@6q|c57!}`_ig%Ek3kQKL%2wO2xa>Qd5|B#c)L~{GrakFP8Lid-^ecr*=?tBz&tv9)oxMM)7?!)jJL^ z5Q+MJ-TRX8!8?AJO0~|4-;y3?8on5>CGno4*m6E&_Hd@Y(}sVX3BL={hXvgAW+Cc9 zgom9qLjLUP(&W*>{bosr9@ajUsch>^`zwG>-vmSr+5?t{HAi9r0BY^7LPPiZ_AGins%#pn= zGx}G{btC?q4?nc7ug?7&zF(yI>!dWifIo2oYJDkjnP)+G;2Yq>(fz++I4gtBbJD+< zsrAcAihp&U{14v)`9VD5ak;}xO2&|11o{pt6rS+!z zZznT9$vMy=gqT0=j}yOT_M`g$Kno_GCqBcK3HiQk%`eP+W%1eU4+%iT`Zhc?`GFnzExi|w`!)j}h4KE6_;m~9E;PSqWp$57!cXgM zju(RPE{y!ZXspmWZsW)H>z$F&et&48;Y0PmhbNeNp5gC@yV8EoL_%btDi!2E+OwXX z1O6!i{F5aaxG_*sO7Y?Fz2pbCwQe^@uJ;^|dI0(Ra>?J9MfrdJbQZyX9Yweg`VLv` zzZ>@hjpOh=wB`Q~yYl(bJzr<>@dCE{QNJU&tDm0%f9ZQR) z7Jnsv^RG)7ely{leFw<#&E;=WHqvt+0AvyFv&G1FRV1|kRl$#9yV%eFl*D2w^>jN= z@sH3`9{-RY_8%u<{|7&~fK15!wfZCPJj-H{A9C8!nHse!gmwjHDF6rG;{dabhNQBSf=no+N zsoqO^V6-1d|CY|jw)#%~I1cAnV!^Ba@8Z;#l#F0=P!N7Kn&zM6DxZHo_<*qr1G5)# zA^(E!h2~H9nbr#i?@jR^jYHuF5EAetWxWXx0O6*XxJ!N#$`5pKJtS)1=+Nem7v!G- zLBHd@DgPh0g313^9p3cXVOl>=Q|P}D{@onaZ^FMC$N%b-`y1%ucXD&)5kG|LW4`Jl zo{aAf`@8iVu~cdlX%(;s1uxJ&e#ddpqoS9HhjyRyYZSz2|I-h)Az@Sp17^Ikser|z z{EtKD*-ps&KF57%N%!Hi<;;C(N&KgpFYMe9_l_aMfPV%-eo|jEt{)QOa?7FJ{a9;x zOn-aZHbMU(s<6K@^&DlU-w6N!$eVOFgW-_}439dZ-)#+rkhuT=%mx6UI{*Mi002PV zq!RKbFClMI33-!3$eYwa-Xs9>Cbf_^LA|zSYa|Sh0Ed;udl@+tFMfJhUs8NM6aX+8 zpA7-6q?OOCDLTFna@tXG)lfJIKXl;V@F4%K-V5ZRp$7{@WVc!H2ehL8KkwWd#IuNZ ziU0W+i0@f5?Nx1l(H+)Fb2xiqf3|5qg8_!zUK3clQn*h_4lb+Jt29>&6yz-+za!#*Y&phajrz7+K=I%pog1T`#Hla1f$6Ib@Vqj@D#kkD z#gbe32A!&sDm7NtCV>XuCXUbo(_W`>?v{_#kH<2f3W z|A*V+ms0_NlTiJG?z@8Dnfp#^7guI?8QBiygL%!&eLrush}SlDS4|9!e9B^xo`1aY zi&E~u?rcGuzxRYW zk^fM??;-Lz2jqWx7)uH7gRvUMQT$Vs zcc5XfkOcz7=bN6#IcRvjM8j)qM>_A`WB!!?F85=x2rnQXGx2;B%g>MWzm8)5bpQ3j zn9l!14)a{sT+WBlvVc4m{NWGceG&ihg_w!|y^;RXc~{fSj5l!@zE3Cr_Iw`?7{o#i ze#e3@YBgbM&O(vs>Wj5qmyVbC(RyoRzJ&j0^iTKvN%`=y~s55ao&hyJfC=nh^;e>WrjoyBvvakT^h0O{>m7WutX|J!Xihf91|8hiEz{*^=d zua7p5C%}Hcjqg7T>8vfbH|4j>pl?QcF!o{|Z{d`)qv(5}_;=MNh7V|h-#-H#rV7`6 zm*3>|ytevIn;zKZHa#$G_wj9dU=%OR03k3}xiZY*MUx2jwtB^(0*2qxO#GH6FQk8I zfWLP`I8^#>*!e%-KHI%~UQ7w`LkBK}{gcI!48p(k>f5r4;=L7xoLs_xz8ycO_Z+2x z2dN%-`8`)i`QQYEd)txz((@X~qYz)NCYRAaqx|REN(A`e`)z1Z$`qdh;nx-hz*O%} zJtvT&{c&@XFB2dB2E#An|L_=nQ1LR#|AkEjMU{1Te^iy!G~Q$(9Xc<=yZ40S2jP3C z$m&DccK{Fk_TXksNo7&}gSxHXTfY}s|C9Qe(z7wgBEung)g-G2h(AhhpOZ*wy^nto z!46L>*^YjA=O5S)I1r^hcqgi+^@!^tx8)aoDS*1tuKlvNCt?wbwn~4t@KA*w+t5))OlV+DlsXuaO z;A$0476@W^J{r%dUy_M%<|Nu5g#YL+6bkDg?@xNiXZIn&L%64m>6_Xk0fzq*@t-E+ z1rZ*o+$c9UELxF0@z+U7vmkGJlncWbgfmyb_oaRp-3J;l0HpQq1iCBrhl0GN(&C4X ze#HMJ{kuFegu(ns|IP*NEYi2@)ENyQ(0y-#-${OVwoxor2eiYe-$QzMu(sopW`#6 z`{4gYxJx8u_pJB|tMGc@htrQG_rqyB4;Oh+z3|V~RR5Ie|Cya(^#3$K@J%fAc5>(~ zU;o#sN-O4KKM?e8a%FA zut)oo@jFz6-yi8CHhf~@1KKa04l?*Q%6DY^?zKP!Zbty{7=M_61W4i8a26W{fFkQe z4!5ITU~bugZ9c?@oZ2GSzisokD|s*A3F``9`8YHFUaE}(-Xxz}QsReF{i}D5y#57) zm?{v214?^kWKsXa3jW{N0M6I|Sr9;C5g!;Epk$Y?ygx+#=k&fd>U zQ;6>87C&C2`CTL;ean#6dWZ_#A%?k^AFamoluW?=&<_}c`rrr4BN_C*e$f7R8r+I+ zLwTpfg7|*>1~YnBwyDrRFbfyb{CD3i_jr4Gc{m`QkqN zJHA^3g7AtEgoizzC))p}Iv|8~oy#@qs%zN6bc~*9kFG|Y#g#>sNf7vlo$Vihv)027 z_zvuV8({~W^s4?=Sv>?`wKOP+t1`|BPC@5td=q261} zVzIgFW;s}InL6EdJQqV2o56eFzFUF(hw9*@LnhIAPhwBW$F zeR10p0GC(C`5?venvc%?h3eho{hgh?=6VPL|FmFF(vrnDGW=tTKAX+@edbaJ5JG2~ zGkV}-Iax!wT=nr3?>M#MAHLob2+;2Q;ei0CCfHOE~XV-)D3>0w#DnbyUZA^Nn8z2xd82JFz1L;0kQ5oQF*B?bK_rxa{6!9?IQkwXxx;9?<3oFmyKKm&3;QK=qQu=3oEvzj*=yfcJPI zRNskcS^k$F7WKpCHF0vu4+r*#<*+}L;QbhHd&=-14z2MHj(e4+ z%w9e??kC~6_iMwyY2kvLh70ll3dT>6&`~@$#MJJ$J#_w^Gh)s^@;5|*BY+#roR6j_ znf~yg^bC{(7`?#Cjqf0D&>ivyVjOSzINpr&1kiUtfkQ3GOCl}4Yvl)lbcFwU#qkXi z@;icEoCtJzI~^m$Lnw&I^v>Nr{>5j;=dGSh{B92u4$412Zrew(*Z{<20Pqq+_d(Ld zzwyNrywJ}B;9(-2@9NqAuk(H4{m(qe{ap-Es%QKUAb>ZG#wGLo1PBO4?fjq1TK%8N z52yK7Z7lhpTzl~FVdafawbi#93vNcIwfet2s;rA`zV!h7h`7K2;Jr%ot4pejD{kL? zcrG|CBxrk7X>4jlWO!=y-JkgpdHH){uiwtUmY7z2>tnbN)XWP0Apf6M zzn62y*zfeYxA!{-oujdG&mZ*_DUzasqUV*R<&DKRe>9fC|D?6vaVjxWsjwS^qN5wMwR#&1@ z<@jc~ANsCVKlDR!dm3^AW9lxHhij*fP>T7&El`tJ~Y@^aN#>h5CVZ#7e{2d-+O zXwt#(pY_1UV1W&=z_wHD4GpK+*-zH(KGDiXcajqp*dGf_=i^2+K>Lj8+D)T#7dDYg_mG^U8*MjkPyx?o^~S<>gh@SKrT% zPpT<lgV z?5Js^YhtFpeJJ(+|ETu!BmN`dTdiKve!utta872vUu)_calz60;&HMOeGg{|ANu|- z1hJxwKdQkw1*!0=uQfy7%7NPKWDEt;8s?3;T0VEb;lo@Z&rt%^JtAYu8Fu>7f5 z{!lD`8kRo@%fA=Pe-g`|QhL2GHl?_ryd*WLOhwIIK?(7!p`CJH{2Uve@9>W(bW;-(eU^;#Db>>9|a8KQCZWf*KwGT96`~s~#r@Bp%`33&0XZaL{NR3s%H@~mCQ(qAj7F1Ck z5E&F$^KeIaWPMmDzK`_tBh+ZW9Hjm7Jvk*LaBosl)J|dQj^yyTNXUUV+RExX06-QcPcqJ+k2LuxjxBR%UmG<{onV*+zz5k}~C!g=M*7+{P0kO$yrr{bq%S$t%(W44oBa}v!|{hCfoxP*2jbm4DGGmFku%MB#wYVqLPZP zeK&+holW&-K>oM_^2gsHe_RFmgy}rEctQp9&B<&oYpJfh`M9Pq zzw-KRd-OlA3j6p_yx;t}774Eea1P+VGgCvhMjTGv0YBVh@rfx%QqocZ2;Y&KCX4s* z^QO=EAn@}yqz|!Wj-8i__e^(ZEcjZlY13S1x-RAo-w!%lP0t!&bcr_@lY7 zH8cty!||>b3D4L1;`;XY`~MgJi`!)hg|XLT3k#$3ZzdKe;`46#_`wJ9u~7pD`$ zB9l`B;`U6U^8k#v9|_awe4F4qV+POL%f}1#{5C&u86FyXD96%he=vS;bLMzC+byxT zah>E2HGF&z_#j&Oojd&AZW;jlFOC27KAb+wR(+hNminWT+RvZz8>7p1etr`sdj9cg zQ$)mnlMjXH2yhu|Nd8drTRF{R?__T3E^_9kpijg35>7#12G(mp<{NZ*O;;xOc&Qz8Fc zj_ab#MA=Mlo%L)Rz`aympl4#`>SNY@iM5-D>#WsYwkxL3a-XZ!LDkKrqq0_K@7bE2 za9yb4y091<6i)G=<=igtJ0(5`{t5iI6yCu7&)?iZR~rG3j)swr8tGsB#fNTvzCr%H zi2S`t&!v^mhT~mbk?#9rbl-o!(@<2?P|#FYRrRp4x)y{8Y?oJ$>3NQP{s}+LKNI|h znAqc~nEtLdf5_@nsR@{VvfK}&yc*N5!1PyR`qr4f8>a7w>FZ(ovoU>dOn)Y(uYl=y z!1TQ^eGN=s77uZD(f2=k8t{F;=ym{~nfU$wg2L-Ge_uZMzOwU9UX99bm!ZxT6w)EC}DfTj(@24R6sE2I|Os8Aea*r7SRx! z2TP#1=xTSh;5d1|o$g3?jF9daAl;c{Z)h^b&VH(C_lcA3bSGJ3e{9+~s0j5j-a-Z37vnp2 z?KaRzOWi21=wr>pnrn>=-|4E*O(R2A-(dWoCr-?`y*r}tW8L?vGLTm)9^8K@eN_AO zea(yN{It5z9TD&EW!~Hqa=0)m^=5MH_M0g=-C^)!4Ffn27{J-X0B$-6@C#r7=celBG-JjD4lBQ% z9xCyl%ts!Mp!y#gzz%2t({e-ph7Y>8p8ySD2Hy&N=dc2*2l5rLL0J0A`q&rYkp?lq#nai<|K0Nl7)ukO#P1 zQ;`<^yz<8N#;WSz$UXSqf8uu;o0k_?zPJ2VIr&8w+$?SWEY1i2c^(>qEhFuAVn4IZ z(axTU>yPex=sS`_5ynPldKO)k5&ncC zoX7ft|1_(f&V%&^;6F{!g+HLmSbcp{h7V8u^1tu`dP7w6V1S!|gdgdA?VeXP_16&( zT>SC}xw&Qb=V!Gy| z4H7B7C3)w0i@e+?5g(4Z|KSHB&kr1a$`5{X_nPvXzJjWm@@V}Iu>WkQ_id?YAol~e z^_=WE$<4uI_BeGt4OK;bCqrE&df%uisNzallZPS?mW8QX1aJg+Rjjd-x~yqVTZiZ3klBT>1KNK zVGx7xP*q*0vyK8TjPAHFoN-|&;lfaZ;p=P|z8b>tbrcL=d%*D39tn;K5}c_>a3bMCl4a6yTo)MV)^7-iscU!v3rt=}#Tfp9d+=>ud5vH3jw3 zN?HC}d9Ad)Kk^>jck=uf2~YzPpnuPQuNNjD0V+iTq<{pd0}>$Qzs6f>e{&}y|DEsc zJx$={0LK$-57zlmJV(P9cCfPihixN2ALmX`>EhX2*GPkFq;1O4@1ceOSYJhJ_Uu-@ zkJkKX_Uxgioiz2k=s4NVwep=mYT++XP-&lED(ijp|=Sb^M`R_v;%6Db{ zKL=Ceb_r8=r853M4O33P%C`hUeZ((CuBr=PTcW-3{=*0)f>! z9`6GHkfd+vgE8RH*HP}%mWn>Wkof9SPrd(v#8i-R8p7e_LjW#jmRs zKM?*c8H6a5Kd(2;Wca~7@cq+x_hpHM^gjvD_;?wctqwp-H2^R)_S?&56TX=M!Hhma zE!Lmv{e+KXQ~JC4ou`TzxgOQ#3a-z=yE01oypGXIwU{C?=& z9uUGtfiObvD?^>>ms(jf_{X0148E%wfbfY_{}tIwqyWHaceZBmFhBaeH9tW8ujy8h z%RIH5{XGWL|3mZ7)ouOmmT;#3`EeBEhjt~N8Q($wzpWoi_@6wCssH`?o4o&>2KXb7 z@H|xC--fPj`+7L_2V{IO{6L16#}Cl{NSiin0pou|c;0oW_q6r*+zhfH2dm*W0C*_k z|6sy)7y9N32N>Bb%3ky7*pr^%{}UcAi~L_sjFkJoq`&a(a%_T_PyO!`Pto8l8+?xV zUSn|I>27rlBYuq48%8f%udlrSP4hARiuttm!wC=PGndO9fcHcASHc6N0`L0?^iO&} zgohnRcxXQOerSI_p!qH=W9t7G*YWviZ{8vOWx3=zqZ8 zcf906Ru<%5uZ3)U7ft=`&!>TZG7b9kDf&Nz2hCk0=ey4)fb@v`rD%_Bw7IS)l==A# zm?V~v-t*(Ja=l-|JAWqsKkbQPaa(_E+YF{ZK46jr|G|gt`eWsMrbE)m4up5S9RN50 z@IZw3=@Z7_ecs}GAE?I`(EcCw1@Zt{6F8j9phFP;qvL%(pYZNWvHz+6|62hBZ?PZN z68~!x2(c+TJO&T!TN$LU8ehcl0UD;v|2WW(>2Dj^Hqt+hUb|#LEWAzmgvGy2Unz*NE;4{ZGQbdqEAJ^qohd-%ImT9V^E> z>nu5La4~MGR^Thhr7fNoJ@E7H_wzVgJF27{Y9hRAC!B8s6#iWOE>3I7TNa128+@4478_LyMUHAN4zl&-A+*!*5uWT?NA17Mp6Md&GAjz9a4T95cwHT%>-H-Nb&L z5h_9&(KGpy^Zi+M>;Zi-*e+|yp!K!kY-jE z@u5cn4&8>2d8PmY@UV{e3_)+12ZB=`+I_@#ju<89JNHBTZ{=r^i1^N=4=mmQ!F%nF zt8|Q}iN&A;M<)OPMe`lCi_w3l@A>6u7K`}b^gi^z$lnL?9rqzX0Q_M#@ujH&SRmcX z>l)=R}FP~Xo{}N_DwDrRWqdrdSU3rapeg)6luYcyn7~F3{;qjI`gl}rP zU0GXxyRxEI+}LpMVU0}hG$%J#8v!A%|62(E_TuL!K2)?kz!jd7j>|kEGAefO59u9m zif`>?^{sCd--7N$^)ImmhzKbdvLXrPgBah#7%yONo1fQ}9~>?Sf4htk|D)kqd*~xa z*fu7-uO;VtYy88SfdCQdVYd|?j$XR3{YC&}*2&b!jP$I!^iVW2?Kay3;Z8?{JJxn+ zklCR@hH$4l!W|EHt4NcIA@O1hK(+E zGZX|9=UXXm{#ASJw2j&`=4oy~{U3J7Hn2mU>ZQYT@1k#_0DT8}d}lS+*3(uV-|1t* zN|;0p3i==I`k$4p{m*K5m5Y8%V;ysgiBP@-H z{e#9^5CF`BVc`A7>c8@#atHv{70L72f6j*z%8FADqHO7Ij}XUr*a{+Cm4HrR)9er~BA0SI?BT z_5;RW%|$%N_aC7PInPe$|JjrNMH?R0pYmZ1K99e0roFm}xsGGUE?tzzcN(H%)AN^M zTAF>Pm>XC}K;Q!Y4_VnVKa-z%}fj{~5K`u!>2<^6u*_rC(* zxy9@0Gl~aCLhpw7?{7PCxip^d&WxL2080E9`>TBZN)W(E-<ETlR zYn$%p2f2%J=zsdd>jX|^B^pY*QJkE2l%_YnU8Q`^zZuW>g$<;06_kxiXZ^Y1_7WG z8YDU(0H9qy7wz&s5~YLGG~@a*uU#zqhRkumg*Y zj86OhFa5Cw$UPQ9?y(T~fK4X7?D+;h_BQ`o3$N{yA*|9(0z|GEC>soBw6 zeU7S%67}Z? zO<&OmcBoxphpJ0{Z>{}68#f2?%bno^`LiPE8-+st>^9`jqJQoO!Va|(cBp}{Lyd$T zYB1f07kQiUJ>;gpslQbt+s~x$^i^_cq*S^Pga^_$qwk&k{aTuiVk~Z^dfx*GB0hus zdDh{S?6^IFCGq9EFBS=`J?(8>l-1^_s# zS76MgdLhOh7#CcBx5xVmmUqg*FsV)u zF~-awgOTO`I_JJDpU?mI`2K(2$K&7Q;kcjgbKTdyU+cNfDc-jzegA?TD_8Abk>fqc zecWs_GrKN{R=6Jaf5r2k1F`;zp08bBJnz19%@a@|w59g2b!D9?UKDucpg|#_WdDi) zSk`3k`_sUX(7?{OM}<*)|7r%z{db+%*c{`bWFI-8{f$Y%WdEFzrH>B`{TUqq`1i{> zME<-|e#mEk@b9iZxcT@ld>r3Ab)#(A>q~DQynlD=N$i5d`PGu}0-S{XGpb9jA4*Ge zaQ%sg3jL^mL;bzm7j=$UQO}e3`WBrjK0NbvooW5It0mUARfG9wUATMhbaEt(|Jo10 zbnhsyLHxag?b?&STPhzcP(F;A;bzv`aO50UZ?nj;13jnLHZZbpQ{SMqcV(XzmZo0i zoVEG}bqxDEc>7fFh={xzgztm=L+SDE=SMCjfAOI+W{2W-xL(2i>NDk+OoYEO>3isq z-v1~6AGytQ@@T#9(W6-Z3ja`KAH3^;ZvWOkQ@4k^@GdX^Lfp6P&!;}(9Ym~X zPD^x%$}iaQy6kCD!^7n7hT@~l#`gdRa7F0wRQk6;>FZUCBmDoR`{8ixx=3ya_n&_) zyV$w2?%$E@TWLLHj*YYP>WNNX3n}~&`iFDs>%9Es7oO4LRYJ2vp|3ao(K@wVmE#L4 zj)nWvpAimtj^*Ts&JNaI7w=~ug1xJIbKTza0M>`l``^F+r}aRwyA)V$p6?Z=Y2y-i>hV zaE1AwA;Q6Qd5HzUo!Z;k;q_0Y{tvAWr}ob$e|dG+?P_^;&J6wi$AHGdm0poB?_KML z05@0;bb8G!!~;eH=r-B=y`LN&N%rsld%xJfUs_;=<^Q_BpL;j+^1s=CVJrFjSxevl zk>%*_y@;#%$v&9uf2n`iV^L1djRoJeC41ri@c!!y``&-g>+4yspU~}pmp0bzeMhw} zEv^4PKJ82Vv%U>~515VS@6OTs z`jG4SdjFT=A$K3I;vr+bN3F@XGzs)G`D8Ae8*RjA0;lUo<`(|D#zYt4d}SoGfZo>! z0OjG_Ikv;d{%c1bDUUvT0;uBZyzr1tn#r-qa3vVx2@FU9m1DTlq=Jwx` z+yCAE3x~fCwEzCey-M%%Llp1{X-5})cYf9OJqvfNOb(KKBLTL1PnGEXCU0$8&$4iW#8MXT)7S&l`VZMqWv>lFYhqF@!3Aas+opx zuvLb2t!@JIplZ$Qn3lJIKfuka^!W~$yKp?)J1Z7%T)b}ax@DN)I)w?YA2C622@?dj zFhTG)CI}EN1J+(x*Rn+TT@mw*o4IdtewqeF-G|NHdDR3ARd*t8P!-zyUmqmt7W zFG@;YKYx!ZA4<_)Vt@H%DSKk$m%-j`Zdk1`J-mX3_bj`4G8cW)NgQA z1o-Xl0)MDx2DQppD_^Zr)w&TL!+UzT_cI$D1mp2mFmZ}*O0x`yfg+lhbNJ#(XA<8RlBZeNd6_Kx>%UEI9(!KLFR@DH;95p(~y z_KpQ7l8>D~_v#hLga7b;<40=GbsF1m$na?c=Jp*r-^F{FU(S7c{%HSn&3{bgBN;%A znHc1W@E_zKoZ3GQoo3de@4pfJAGMM_>>aBZR4m`p$g^?X`flYrcWYb4+QY*Lmp4Bo zwJ)`YXU|zZn7;ooXX`38+FI2vN8bm{7jt{=%^GUYr6K(5i)SBSKaYuxzW;Q=lY6Jm z#$J9_7FYZzE>8D!ny;!E}iyI{+a+#|=27w6`mS#~fxDL!TG)(!g#wx23~ zQ`Q>}a6Vc##ru$Z745-)%POJ7BY%-FmjrKEOzTDM?CRor?%{gt>g^r0|KR?vq^Kwp zoQe0pZF}4ez8}ucmAb)QqQ~PXsxNDxzDx`6Y(f5yY5jk_y>NGh@y|S5uVwh`Jv6>$ zvM2JuGn}@X94m*4){mkALOuLVNG^hby1fd-R&5`}a#9nV08u4#R&a@8^xp z!2+S-ORnBR_zSl-eJ_sD*MI8ch3zVtH`2@V-x_}W*gMDYd-4ZI<$VxVf6{u_-(FyR zIP*Y?i5O**j(BiXUlg>}$gwynQJ#=!A}7e{KPF>x0vEVqx*LX;l6)FxbI5{_k9cD zzW;`KDBX_YdP;&l6kY`M!OhyL3I)gCxS=&#TwE zz2gVOd*J?(>FZUh|GVEG0d9Nr=s6;2@Wdcr?;$>)?OVUZzxf~ip?)3z2JVjQeC-dF z-v8+|{u4So0M(-`*Q~Q;l~(QzO^jXJHE7VXm91ADhmVW@J^kXJHh zVjtctx)S$k{Nw2UduShGx|`;|pPadM_Qe(01AS#LgzG(gmEPz3H;PyMl#sD;*ZM{0 zk1o#Jv~BC!<;!<&-@k43xl`;9tBl6GHsW@azJ4E*v~pYare$jrHo&|A@y)aj{JlHt z^SdP_9fOoT_;lPK*aJtRdYXRmJ&0vl+#Gt$xzJG@f9e<pn6T6I$Iics&3`l($T7qMavdu4wl~DpV~X~=hbZ(g6BWa z5bNDLTU%RI`Y(Ghn~7AO9ijH^tGlPKU-*gJ!}P#ayysNJdtOks^5w^sXaBu&Iqsh) zC!ZJpQhfK(xidFQe!744M)Bq?8#fl_9!y!XEcNIQ@r7Uf;jXJs{lA~1us#3=aVudE zm$^PMW$}jPs2^XwesBoy173)}zf*bc=!WI~VSX_19tH!?p8dRVeVspF@4!FldRs;e z>GjPJR}X)e9%FjAqq;mg0TlqMvl+8q)IutTz*!b!_~a}%YWx* zIy^-GAcy>&lfB>E1DTntpdW(ifUWd?;C22PI9jb3Y0~qT&+u(g~ zKC4A!?fP(tmt(rSI^2VhKiD1huwWL`KO<1V-NJBZhA$TMVfgO@nC7={-B;_o(cI7Z zqhrUA4>wNI`rlW7e_8)a>6`C`LlZ22qWQm%TO%XW5f7I9Tcv-m{99o?G|hL9+#VkO z&I}Vm7%uI;OJ6U17t5t6{%;Rl&!_qBF4HnIseN1hQ~P#jW~P1N1VQ6NOGm*$0Oo^c zCFtp?z3kBT%X|-A&$n$#ORX^6OY5B@Q6Wt5uhjTaAQniEY`W_@mOq`@j|MZ1fBxB| zjKU?vQ-8+$iSExx{^6WY{(U#0KSblbeS@5xF+CE4<)5dwKI+ni#z#ZEbbslWdM@=O ze@IakzDbK~88Y#AtoilzWe>iaO^gXS2^C|dtitpb!=``Q}W^5iU_cu9&{^F!>=RP6-Z-?L? zuMHfU(e*t3y{v5V@ayn~+CA!5H2B}bAqvKSP~MlO!#r{!%tH^xty;AyadYy2!~?53 z*1x-3P{3g0O4Vriv(o=I9#}8$88bpNtC2syTHm3-4+*ocZU6(v@|M+1t?KWj_Wys_ zLsnPzkR9q&8VK|6O0e&GaH;6TjjJVh*KXG5KmMbAhj3sEH>`bGc>VmjleZRJy!GGy zV*YzQi{8FNeSjOFUw?(tHOfA8|N3PYvHmA}tri+y8x8vp$d`$lwk zx}&>O1KpiL=h9v>LcZ?M_G3KfQ!2bz^LM|Y<>x;xyT*}jPeKqJW>zA>8jmT2DB z|4#q@XU}PzPVK+BwsoyVl`rKPDUvfb6qLHau&UPmwoWfI(+c?+fo-4#y-1u z=iY<&4{km9eZ!ku9}140Tzu@og}7L<|JT2N4ePnPXCa^?x<7H-mo1AubSyh+&F=FF zr_$mVZ;FaKyzoRu@}AQxqR;Nm*|2O^(ShvPLrKdLir;+n%0T-sqB7dELnosGbaKA& z2bTN$b%eulM0u&VkB9fbh#9VLC<-`y-{H>m{P9QQ zJ!Ic#489KThxL%dp$A5`fd%AayKa_b4?+IlN5Y}ecNhS3Iya^Qy8rv=dpJ8PCx;lz zyS)y;0fzImK{c^>selHLCCj+@F{yZ2P#(5DpGw$lp=S-@3n}8Qt`Fu=B2V z>@v>6YVwe~&i|br;XYoiF4`AbcA~oHiS`)pn0d5~GYp{4(0a&9TfVG^B!9;=KA2P* z5()=6O=!I2gCJ&(EazCU4;n7X?5J`2&7?Kws}k`WwS^e{f_E zQ#_dCqeOnP_nBI&?Bk!;UuTw<(s*a?&9bt0WZxdW=f@X%`@anJ-5nYaE!qzU0Td7Y zXoJ0%Kh5`3dDkB09o)NyICS9lQ0Go_{CWrV<9Hx;82<0#ysf>n-HvW{9rtubeb}W7 zhD)78oY23CY*-W1e^}2qKN3jwpDT(tAM41-yvlg~p=&YTNB5)oSNG$S|4SHn!M$t$ zC2ep$A=S)UhwaFOd)E~f6yLbU?fgtE2>ylqV;-XMp2@c}Gpj}I?DPcvC9?k*Wp!%n zQZ@cb_8)`Lzwe$Wwzi(PwBlQ;|I8CgOPz^gEeS{1CK-d4v zfQ}tEnl?Tdh4-8M9alj_^iTe-;SOwcA>MZyAH(w33LD@L?rZ-DE(1CT zxux^|&Rf$ZtOm?s&>uPpdxz{EYhA`C>*M1z{!9EC;~!)nau=_E)^>xzlMm?rZ0Y)H z;QC~l3oiI1E*R!+lP~D;j;Eo%r+9xf9@O!wK3+un8o0jh2>(O=9^X>^rs0QYopTB}oa1 zg=cMyZM6Sse9IpNejpY2zJmtg_2|}a$`vaAKHE!Ec^5i+cC*y*@DaE^o7ycqyV2>y_U%{kK5gn@I=D*pfsU3{t6H%= ztl?nWD%I+gt76^P4$I}Lx?;JEA(qS3z;c<->wj0$eCPko`d>_lVY$o#ESFh=J$&O80JE+ zULUT4@$a(t7~L+rMg5%z81Ke%mdbtz@J90;RK9iV`fFR<9=rB4hxQxd<<9jX$d2RT z6%`dZdra}r{oU29U87FTO0ajSFwDNXO%G;q$LfpZoOod3)D@a#i*E~x*H9?7yQMfvv@<9$ch#3f;>7YiVd+tBz9uJ8TX zYBb*Y4htZ+<*rz|e0}0FQ!s89?w7+e>K{4S`1K5Mr~XbS?(a~%?=SbmJ|b{f&wfFJ z-6DcqDgIkG=MRVo@kQS;0LvRXV|`0Jk7pg2N%jAXS?L)64-XIPR=c(YPY*p55gH!F z_5b|o-PcinDAK{QVx4wYCgok>@GJ*^SkA-inS6XQPpS2-Z!TQMa3|SA6|H-A_v+n~ z*KQ}4-YPh8IX3mh&2!i8p1cc(DgR&o{r<=GaT`lY`WgEfOJj4xNGq}QW!XocH6X~cf>Q@-|Ow# zr7&-FSw-z3OkD2HR$L>-Y0KIgA4_2XSIw;MYq-CiZ=G8HW9HeJ*UHVn_#Y~~3XQHg z+db$S*p%$Qj`i%4@AzU=_+O1bOrrV$6GG0I5V{$G@xdqc-;RTU$n9g-oI^$pTN-gI zu8Mg!{9VJ~)umckNa*HPk8&c(-}4(-gOfkt?RXz(K6uWWkdOi^^Bx~X?5F8){zkB_<%3`l2r2n<19{XnsE2p}HTS_R$r;y!=}6-*)xF`%(T@jKP~9^76_5(JS(Q6=|*ezk2#-DFWQx z3m^1T6SI#L-=+Y=z3d;9>_eaa`SByd<3%Do-gM$Ws($>)?L8jvC3_y{kL@kYU3&~0 z73?~{!Z*Ra-OEiGH)m`mlQGl!Pntb9XwI}ry$n2N1ofFPFVwWxsL8%_O?&imZeb)k znb_HMb*$Reqg`WrGv`3~zwGGgS>CLibvrYyZsl6F-5pz)n|HLe8|v-Rut&{?9ZZ_E zXx+xKrbiXC1$RFDuhzT%-}paz^-pO{D*xW10h}65<2_U!zAaPz!3wWKUMYK#!wJKO z`}_4k|JSoxL{Q%$z5V@cLWc~%4+DpIxel*rIkDex5C53~pVog?t&8xtfe3#)5W}NE zy?v0}wK?GO#|jTl)^<1H*&ddPH0z`;blNQQU{bqd=cK zClJG3^XF6h#;&mqVq;6Ms=gK-dO2&g%#ADe=!FRpOm`q2s{MM52ZuT(VZbbWJxaSW zS6qK~J{EMKyL;~9Pk+3PDt&(Z+4H}bmEQg9>9uEt@9z}E#lie5-;dfy7!WMJ_4d*G zrN1wDQT()YZS;wv)vH#2|HJBC(WiHu+_gCC`;A!#3R8dBnY(rWlDzZh5b#tze_HQ# z>1}E0ZN!H^dhWofdvP1i{NNSt?cSsJ%<**%X8X*aHeld%!|++tM~(E2oHS7^Tu!}SLyLOw-`MYK~bwv2bWmkJ*{BPxwzt(I`2@QwCBNTU$nBL#N?D=AS zeS`msNVxxfwYq$4XgCZ6oud9KsZH%=7yx>XND7Z!^Govq6|W@DF3aKZ+_jDLUehq6ieLzx?4Gb?ClzuA5I zc=a3R-D`*o0v=-e&mGf$H8B0x8`FQoF#U&kXw}MN`p+BFf88m){oZdh3P-UyPkfO{6Ec|?Nq0HxAKUOTiMv4isAaDU(bKd zU5xteQ1;VXr$3xH@#C$lg~u)-+(lmgk^DHkAVKbEAADNx>w^ZuH8c>;-@Ufx)w9HZ z*KbZvTZjqHn|<4#C~~!5?T+ z3BB)ZAB1=q2S!l)z%kt4$G}=t$8Zj{cd)+gkF`|4Pw!Ss;&#ydAJ_M@X}rfD^`l>) ztJc)fs#5EAhF!hPJev%y?r`T_;lo=$#=d{|^4QPUKK!}jm$hqeTzdHa-qW-3B@2$` zw;am-fg$c#9mVBAv!j`rdAJ{!&>kqn_wZV^=cv4+_#Xb?(|F<0ye&hAAN_rOz7O`# zhBLtQgFp29boIw`Xy20kYnR$xx-^4BL+TH&hlgM4|C*ZX_2DG6e~6RL>hS^mi#l?= z#6sWk-rtPz*=a(e!kB0hS+R)1( z7%vI=YQ6u*Zt#E5bMUxe-x=bYnO@z?jhHlRYRsFNk|K6*ww_jay!Qwom|^BcQkem@bfk9?<%eB+ji)KS#y`>7RGRg+oes1 zwvAd<^(=4S*}>YeqI1&*KD8X%I&^No0tNuLt{p%27y<2XC6-=8c*lafH!pm)?_LqT zF?L<-m-Vjyy2W!HZ$(hxpTLZSOJ6je}34IX;UJCJ?HiuFh6v%K@aztVbd%5 z7#qX>vDXV#wL(y5zU8<#$|w!O@IcPnq(wrTaAuJsL@)oEScwzsj5 zm8DhpIt{#Oee~S$>Ier&<-y~*^u5gMW@c8WY6WWlXXms+^A6AdDA{{#g85U*>ey3v z@14E<`|=AnFFg7w=0nuOf1jT&`|H;Y_x^bFOYFVEmka)hTX^MB>G{-!o30-^_0MZT z@3SGj-mgnv;Rf`5Sypxx-@_*S^3UskQ2*0-ct_9_+>ex3Wo5t6=YjE0Tn~?THbwu= z5A)k;nD2Jx{vSqzz_i}y(T`vJA(MR={AiE9jjw^$|17sp@=A!-=d=HvrrSSW8n639 zemP&ae_U$2$vqXVeOeDQd4fLvGiioC{^N-8A6oz9l#!oL@!xvC?6>rYiyq&uR(yI_ z6SNPaHW=B_d^hQbp9y}l9(r0YbZI}ros4qXO#Uq4xAyyg(7kK(Yv)}w{~huY;hhbbOJFn#|A(I2GoPOGW9 zKTtG)O^Fvxjl^(XdZ+Oy-_hVTu}6Q0*87n?pcCu?DgGPT18(~+C$DYoRd!S!SfIZ| z@ednK&vPdK$9}M9#)L;++1pR``(xDah0Ag>Po0b{IS{otCF$GPe^IPL6Cck0;jExiAC?$Qb9QdY??=auWnhH6Sxxp8kOE z0|C#`oaspOSv0?8bi>Y$`a4H39zxIeQ#{~Gxc;>8*@1NZnP2sfDBc@A|K17u^`NG2JEs{S0U@96XDOMmeGgVNH`gYJd@ zez`R7EBl9zZ*LUGEnBlI*L$|%*f96ra?I4}-}Ob69= zap+pn-mXXchV@N-jrtflHEh+jQRn_0?K^wGJyew{a0m-`XjL#iMDbv&-?_4R70j86 zV9pe~JTWeD!N!G~G2QhOrn~N9y6Y6eBiuuHghGTzxPkBpe;_={G8VIUiN*miXAu2$2-+Li1fXNOu*a|r%WB-%egDDDFW zc$n2~?(1dJ#G`&)L;L!*UA)_NwDPqws`dKrlUIMQeEa!wX}_NC z(uMfPuxk|`Ku`9eE#Us*<)ei+E1|w1`_Sa#A>J?G&7bUhD`Pz4q}`I1w7&IanC{;` z74|^s)2&C(JlewfR$LwPcfq=S5Y-n8mg(yQZ~CNnE%$#Y-}iZcD&O_>pTnQF2+hZL z^P6`c1PDVoq)ar2mUKjoLE|6C5dfFgyOY1u2`CRf_3!uLdq=oW=NGN#Evoo|=0{dx zeeBHGW>_HJ$s~O~-2cw%e%yilSr5hMPV@OMZ4m%x%J#B$5=zQA8@==TJK$Tn%*94+#(d= zp<1VSjNASXjb}~A)1>*W;fU`@@o+QdJ}IU64m98AwIwqL4gosALWbLeBT)XwZoAr* z)`tfl%`ZiOpXn&@OfUid5fk8%m=NDZ{!OhhzkveK0tG}Uwf~;|TZZtDy`#t;{2AH< z7!ZobfYAAR_3b)tU0XM&c~!ee=WazP?&~1_S*x1H-b2j2r}Q1&Yi^Lg-@x$^22+FP z2Yxfo&D^{l%!SZCVEcHdkR>4@p)2QwhGz$p|DSm%{%O9WBVPa7XztMTC)}fR{{XAO zA}tX9_3L;5x3=o@J0bX-tGnJh<(T`QJOlkb@&}&=hsWh!d;US|U&$VV`~^&03WGQ- zXDyH6Uh?Pfk8ti`Z?FKmO;4LasNX(un+KhDkv+tpmb!g#f4u$_A0PtN1^NFcdk=}> zE%Ikh{{CO1db^A2fa5>mevo|#t%q;c+Sz&cs@nOq9=>r^EEwo|-oY9D|1h|FY+T2x zZbu7iOB(;29%_L5v2+g4_s*dEGX(MRU?6SYu2Z{q4a#+QY*@cmkD8vX>U4AJc)YM^ z1wMDS_kww2er8UlE6jZ<{$Y=U`S~+&KMv#^MEii|LtaCV72`SkFh9B#?*slyzeoL# zfMzRb{B!V`2lMcTzs3MytU7<$J#TXon7u zVFd?=E(mZJnp8N%Bg@gQBNo8zYk$FT1v*5*U-$RhVm_Gs<7T)%j;HZbEC}TO|H_S_ z;gbeN&qM`}&p#v-0TA9(`)1xjzFQDH7dg&7Hl@xRf0??PM; z`TMQndW-Ghd-=2&+@o(^{@z6QTm<~X@Muonr|;pr0DZm0ZnTe(K6d7t(%1Ol=b?f( zIBEG2)1QW0>3u(s_n+dw())hml^!33=EqSXzD9)@O6wtTzm7z<*W=yi*;KgIXy z()pB=vvU;;chmgO$XTPy_ZT~ET%|xmkKkbwB4^AWJz>UFP3x-rJ7RmUut+%c3|TfO zgva~mhLb;#C`|wL!0V5AAmJup_zS`N5E^HaUs-Ln;%JF^k) zkmftD9@f``ef@nL!}~$=p^mV3B73IBXUobc-k(V=H~_`;=jZoZ#rsQoqQ|@Gv8kZf zWW4`%FdR(dqm%!K_glF3_jZ*+(y*bZfPb^?+t{|Tv$bp2iGDE>Qy{M_5~AUJAzWgG zXaxJphW%&Gm^gKO2&EXaND2%5->~OEFE{)@6MVQH*mcUZQDEEcQrtEI59|*TLK8v` z+Pu}dTqVF;X(y`V2W;lz-*Wt`hy<(9im-*x(f ztyp$o>B6!n%fTkoO^n4tmhmjVV_C%V9?N$uYnf8YCM=y<`m>zQGK}RSmMJWEm}Z+A zi?b|mvb@LgDa%rpWt`He9OYbxr7g=YEInBEWf{P79Lq44N#*FewzAAAccPrJxKZv6 zWKA=Yb}T(u_GLMZ<$RXOX1mPrcCoz9@}AiX{8Fhr$@(liu=Hf<&vF9GIV=;(?<{XD zj+Li#xW@7c%Xcg_7;)oXv20g?(m1koX6egvc!lvu6T&irWgN@3EWhV8M=G2|np==& z;%DA|&GG|ljVn^ltk1F;%eE{#vh2@t7|XGc)y1TWLm{Ift;E`jlelczT9N+a z@>E)gJ)lNXa+zl#ZiAwwhLS4^x(en*rYPfxoIaBPMS*;Tsjxv-79s=GNR|@~Ipuv$ zX(n2-&H}|+BUxUwVx0>pT2>JDtP2MDpiD=Uf$=r7-c0$jfK$$6Dv-rYClr-1nKq>T3gjs!7e!|b=uIA{=p5@-D!RyY zK+$EUONy@YU25D&;IB~Lz?~3cAShZE%A2fP$GRf<6W^titSgdt@uc-9c2+wpKd|m! z)}2?>yfNi+QJNak(;N(HB(KOyOiNjJRn{=1KEhcPES}RqOtc&20ulRslL0DTj0V;i8H7(5{zk zBCOhzbYR(!B=&Q zy*&9Lw!23t8S}`Icq%&a_v1mcA?}v5aN8h2>F}w^+VnS)~i@ z)rO@v%W*6hu-wG*5X+6$l=2#HzhG(ZM8C9P>A`Xo%lRzVv&?3BiRE7`&AQTFO<20J zJk*kY3F7U!ER$ItV0n(^6PCu^C}jhd-B=D`Ih$n?%e^d5v3$f*bEdtbtx4A5?anOy zSzhJeXYlq4mb+LMv;3Lm2bNYYl!F7yek`Z5jAOZ-Wg*MEEZ?%M-ktVp&(fFWB$lx( zx3E0Q@)pZiEUUQEUO(`;wc%}Vmg871V7ZCqA(q!zzF=wIgZ6E~(u3tFmh)MzXPM3N z63gd&l)re}%#HSK!qSyx5X-qNlUW{Md5+~1md5V1R|A&aSPo%1n`IKqy(~|$e8f`o zpuOs_?99@i+`9Q! zDZ6Q3Que@hMH`aQ)+C>^{DCF%
    %)hw08mVl{RWpOEBIJULHB-;=6!JvVW>w$li?)&htp9|L*aLKo2goT zNV=A**0h(q%?rp2V#{I|FTccLUw0eI z!QDo$-I94*KbHPnj^liueRC4I)tid^h2PXb$S9T>{L6jbwwOlRDbwk!QX!4S8J6#+ z)94P-%PF5DXoasK` z$>JC9F0@y)HT_c9ezYN;Z07(2ypvt%mo1$q8n(lG@(og6a+(FLFSH1r(~0g_6Nhla z`gn7q==bjQ``eyzNIAn}7Uk@kfZu00QhTGY(`fv%(VFhLYggKLc1M!tPASO2-DV4p z;_9#$X+C%y!Y?g49L08aj};oef}SPVZsBwmzh`&2gl#LAeh#1BWO^c0N*E(Il6ooOZu1Z4DNVn; zrPMLom1muvcWIvU!m*7Pnlw;7M^id;fQTTj9EbCy4F8TF++q^Ix?#sIIP%%OK+FXy0N z%lXgS&26q=+uf!Vvid!Gg6&z3VwuFU@E$#v7x&7LronwmGmd5QeIqlytY{~Kei@8= zvV!H&U+Dh)#j?&rDu;a^(l3)9w!uhX?5}xBO0n+|<#w9oGnN&8qhFe_?9Os9%Nf7X zIi&q|7Dvqa?GohW-`?PPz2@x(kLkLc9@D;K9^ZymKj+vd^h-9&M=Y!VLHP{%gYvO| zO4=}%J6PU$N?PkbN&2vi_>~_(3KNAn3Bca78@`&nx)9RB~gK*7EEUp4QaUP>te;GR&CLd)^l^y5;lm0GM7B>{p(JPCGis)*qh{rnd)m9M}P3d_0 znN<}|Ov&O&s}c;Kj!|UQx#mODcaTg4~%WsY)(4; zjjSc==_GEoZYyeuW{S+(*n#X7b#5c2m2grtsSQGF3Qs2e8Pyg(I`LW87O9Hp8Ci=f z`hNV3ti^4e#FjQm&^=UisZBEInZ6%CBMeBjpz|y?rMqM!Qgz~IR7aF39o@4!;*8SK zGpZx9T79{`y5g`-0)C=oT~VZ{PTQKG3rzY`sVD9$qT|&Q6I%1p`FQokYMuCBP^WnW)fCKOWteX5xk-y7SG&ZJqe&%|(gbmpjoyT+m5OZP!?~5H}RX zwQB)-$fV!-mg1=*IzmhFPA9(FR-&2xm#b|hTIs~cYb8>cV))H!Ez(p|QCjmTos?@JqD+2hMuw-NO|Q!-L6`Ah?3Te12x4Ux9u-Desp+lvZrpK_Tf zI|%E~G(&b0k988ho>8Kkc&2DR(;G#5nS?uEt-R~m04Xh)9?1q?L^YX;WDBntkQ-C9 zi1mt)&LWj5R%9cki%4TCmXExmrHd$FS}0t-iAod&dB=iYDq7*4BwU512OWK(*u}I> z(Jk*4(p9`6?OAT zmF{ARm#&-YlO#OF4Mm%Lwnt@4ZL%5fhm7C+#PKbrPCylJFB@irV`ogJKlHzLYu#~=qJwTI^o*qBvMxBL+6qxF7>G`28d@&3-Put2jP(TTPQ~P z{Uis95lnQR&p}IcGA-=;UJe!siq7=aw80`p(NBF%Kxv8|^{oKf!Bi|>^sNl4*O!h^ zEUNWO5<|oiMfLkF1*P^Qoqk`2h;2HF7OdN&$fMtKq|9Z~uYIUE%%orYP;rGRSK#~|O;uXypkPJ#uv<12`B2CdH=*EaW zO#0Q16&bpY%a^etSEbYjdT3)sfui;UeL!dQ{iN@}Br#50QM7AdGU&caY3$!0mL!iA zIrt9(y;L*~x(VW)qGaeM2-AUl2SqV-6NQDMSI|uq){2@8>aR@_jTE^K8U(Uc6a?L5 z;izZ@bd!afPJBgEgpbl49W+v#A_ge>YtVSm2t{TAOT{;0f}$n?%Rw_0`3FRasUkvA zY(NZX36p*Yr-^uwscL&q6Hon#=v#@>rU|D(tP=}{L}|goXEB*+ALwIs7~NIki8<`z$7tCOkj!@^8*`zf)(uzOa@(0Db0s95VOTQ zMcsyN)Mkr-kph4E_Yf+AKU1;@6&X5-HbF@uOyn{}i=dzepedtxKd~Zcrxqqk6x|Bi ztIZJsqrc=CE=K4iVu$Bw;bMZK?BRK!nM`^sFG8g0#I=p<^4L@P^#DV@#8RdR_~qNo{DilPo9*Fv|0DO$LW zyr(S{MY>MR7&%fd6PA)4|sDD8_nDo1wEUxIpPdZsVR7B4qS-ez4PkOC* zqxPdazgEOgp`#~@uu&hh6cOsYn$Girz7$FeQtcqs>=tQ?3dRmI*dul*x;=I@C`-|su~8yj99Gn9 zTnwm0QNXw;u~%GBlnUKmaa$)Y>-LF$#j?@(WRU(xPjjk4hIsSU zei?$yV{msx)cASOnJP*hzYt`p=*;*WZNIQ*(!Z+x!buT5&Hchh5k1WV!cQk|#U2m| zim2_KDGoE~_bgKs=_KBbUv7{o&M0a%ArW*#-;dv(Eb&+o9WM)Gwsh9|dv;Ki=)_;~ zL5$?;I(nB5irf$)y$zi$3YcO=;DliY*%-YgU95v-92J&|iY6w5>gg#( zy-86bUo=zXG${sTuM_7|Ae@+z#gs|w3<`t`Q=(WhX_LW^VuI4;PTFozDDH=TI=Y=G z5~x1#7b_l5YAcQlQ$;l=+kqM}>F-jpXr+jrZLx4vMAv>oIO)XKegdQ7l&AjOOT-8e zYG2_wIm4htOi>iZbb(Xq?bZ_USm}05J}FCtZ8+tUC@iLA8JrS+iqb%5M6jX@Q}PVX zi7OyFg7_xS;5_YwdLgl%?Kg!67eq<~F}=yl#bxny9+L>3cFy36h?%dq1P)KTY;aX< z1Hr;rSO?!VxF$>^b&3dnWN=*+DY_l}%;1*riz1!=b=($ceh>+tkfq|bSgk06>4Hu; zdNOp+m>$TCSsxAV2*+sbCLYMN+2suH2p2^UXGg&r!Fr*t6QPx$YozE|Xane4DY6eE zvQ;!DtS!iqNpBh75k5?Go&)3^Q8R|}j20<#>Kfh^o=mZ#*<4q7UlcGUi-5U}4etxF zh*IibyaY0c;xVz!+!XlPV zhpzp;;U8kjXZqdnPm#u?-=)7q&Bc^5T9kyRi@!vXqKBZrMaB|cXBiPAo{5yDI=Mxp z!#=!-DN)Rf_}lP>*b%4e5}Qh^sHx}-=$&Y#=UNJOWL8ZuXrEvTk^$&@U7qPrT^k}m7?{UV}cL~Yq>y*dI? zT1yv2B_JC)K+zje9l2Ujvjyp*uFO&t0IDa86fId0BkIdN8}vMLp=%&>6+H$ulqHI4 zF7z>KBv)_JQ+h5;7fofZqOgU7jhe|Nn|0leg=39c%GHW)AZ2S=q{uWTU9^!m6glFt zww3o4O@OYgtdOSXk^-`owu(wX?PP$WH=y=%jG|_XzA>_s^}f~jb6XT*WG_c3nz<+) z_L&Kawt*aEs-i24e2hBE3EODDMDc7;j{%GVOc)2m#P_ zlSPV_fShH?4qcZEa*=^Mb$SfyE+;6exj0?8%9)Bh7cVvHA#GvaOYi&`=z2;QMXN!c za)P3w#p{f`WbPh4*HZ?AdP@sMX-jr$zH*PE3()nE z1x%=cmTocflg;+(``Io{7yab`MI%51WW1t;rTdHq%G-+eKsQhZ@6-3Y4f2<(6;+6H z6oce8MNV<)Ajb?n5v08N&;Oo`%Y{3)X;^5sGO2-eH57)_NKKj`GMJY59KGm0WW)1~KOUAG4` zLk26l4GNKAiYlx~7c=EHMNXhuGDFc6&}?~I(GpOoe4{8A6ei=3=tp=Anj`Ix>SVYw zUCfn^it2&FWuPKIP=pLt6a|_mQx&DIY#`>#X8C$9XP}!e;}yMIdDkdXUieYhwMzKi zXrYWKBtq*cAUo}lZnKP6^bV9JbD0uF&DE8Szm>mRq8+PW8*Y;i&**s; zLAPDLRP++`y>vUP>l&>|7dvFIq5*408|;*+iejMKB{LLdt*K+YTV7D~5W4;H$~iq3 z%j6hwKpsA?lUs6o<4ig8f=)A&yBX)mJ&Lv^r;A*9L(vt`57OeIp3-n_k~k!5Dyp}( z0mw~}-`aH0V?|MGV?-XN%Juy+pgSxr72O9Nkr|3CQqsjy`B0Hdil=eDw7jD47o5`1 zxKR2iN=q4Rd`wn&r|S-=5kJWi16@}$HC^16w-wog?#O3~#(?fhLqk1fRO%Ard(u)- z26XqOqoVtu2hvB8#m02;vkX?`0{TT38R@wMZ$ukf8XD^qzcEJqDxWGkjFgY03#?)2 zYM+9BlYxq?H>HcmGM*_>_-ryT{#{zYu7XldfbLIettbWbmrPTX1^Qc_QS=b>OxA>b z2<;~}8yG#8mKAie-Hf)dtjB~SY+h;nN{&&w20dxl+6vqzcQXFQDkl2V)UEgX~Qsf5uASWoA3Hm6fDB6~`&RA%N6WYU?xs zRA0+glmcp?6*0w%l5N?>4Yeyu_XfHq8XDF77Bu^=foQ75C<^!v?On~@M%T>*HPbv5 zZTqgkxViSYuCBWRT}v&Zo=%3_(?u&SK~cT!CC06_3H5cIA9QWBRHj4`wY{=&TkWvY zWk6@E6)3t7YNuH?(D$?W9<5)^UXjc9=Z)>O_(r-e5ITD;OHn+igZ7LmRviBRhOvY8 zPU&ty*GaQztnX(Eqm|B@wIatI=^$H013+Cg3_$UHi2*rjX-s&_cbqcns#&zqbq}F) z*6bBo?nGNyb5!I8>aL|K8Ub?EZYW9s_0aA!C5wWc>kQnqhfIm$_RgP;-8I9O`VrnB zrH7WHsM#*Gnzb}V0U%E;OVN^DzZ-jLQ(Ei$i%|3!hugWNb^)w zVNW@e0PU$Fr#vEwRu6ZhYyvM?1gf_E-uB(|| z&19rz>!6coI$F4z8&jePOFv~aMoaCe>vp8qH5sS9Q*A zk>lPNF-7z1qU$C=_l*{>C+{AQkwUeHU?LM@3wPsz3 z62*vp9ZW*BET(7?0o_dPouWOUSz66*`hF##*;+kCZ$P1%ts=_|w0E@$irheRv=l`% zL36b%McY8(+6_flGF}@-XgCpJz#}fsCi4{)%V$U#^_gC0xSK?OCWHNbO=2`p7dkT< zsTpaqNDE+kBrEQpViK#R>N-(v|2%^w+BQY*`xk;T6wTTnC6;QrioV+)11e(DXPx7; zr;2DsI!?3fPRDyBU-XSKS*AHM>0^2Enu|{2>i+&(yymIM=)fS*07V@Rv=z&>Kt*E@ z*nuYKglmX5S)oOJCfMF+OH|4=2bO{o6dgaX9F(f)p98}T618oL8fT6MeSJTZKHsxv z8ObKAKhx`sjV8&T$zcCZll5ALo(qqkZP0R6o&z(NiVa$UqD7g@L1&os@!eGIiXs}> zOVu7KqLICg+GCw~e0QVf=Sp`_AK%@iMKF;?K?AW#i(y(NWY!^*O~ff(XUKz z6jj6U(PqukT|c@zldYmzOg@UfW167oDpQOiqaP@jG({bl4l5eVbVJb^rk9G2Gg)}( zNB@VZm7>OnC{IsC1DVDsTErBk=pa+7qFda9`L5z5olQ$PAZrbdbuF}Wx@$P}pP zSEevU)s9jws};F3Whk1(bVkv4Opg^^Wis^Ak8YGtxztnCfyqhHSf&6)YnWy#I?j}! z=pUv%iW(PCo+XL~GCfqZhzSE@{QVtdvQ~7F$&rcXZ_k?GomXV@<7Ja?wKPQ&f4ph3 zRlBd~$d6macFnRE<(VvQg1*ggmF6-J5OS~Eol3u8d`iY`L8M{`mnkHvsI z6}3AyQcKtT6pcJK9u%l3@z_$aR~w_~$79Pu!HS+AYpdRZ%S%h2K# zeFI&FmZE55(MWB-mZs=z(Rk1vCf$zZfR>@_xTHOx<*JnLi~4H^v;sw~j}HQ!QTx%H zO{R8R5tZzjT3BzoOM2;@rESxRXJxar9ZE+tI9XbJpHKHQSsc{%Fg=i7$Df*HYcCbe zIsR13*4`=Fc|1yFYZiTJzX#H&I0;l!QHSCNpn8hNGBr}PrnoJ1_DuTo%hsH968nqa z;COC|9u&U=`7!A=SB^G95nW%77OWFnZsuyKN=Nlru9l{BbbYy+Q~yubXKea|Hb5s) z`9ww2Lt3Ds?k9{v!A$z`^0Y8Tbi6!mhax({VQr62`Yf<^Wgs0vzt=~!cTD>A9ntFf ze>!@yIHHaCOjS*fYKDVIr@!U-nkCbta??*FiG0nP>46MCfjMEVo}%j~+JfAeVuf8v z15mJ{J|#A$`PywoktGdH3$zgdbc6?TUr9^TLd|6`pNlAevb|}M=E?Lxb~)(;3Q)B6 zWI4kkZGuW!da{8i(n=IHJoR1{X>SzuXBsm^&vW4^cjWR=QRb;$rpL8bLrF(%h9RIp zCf&mH|KaUT;N7Usv+pu1CL7yaaX zU|LBUtT62EpIi@?CslUfCpQ6`x?D~{d4~K)+C>Ue?qq8tDwFSF z-mjGqQ+(d9-L5cfM1?u-X7KsE_FBZ0oIS6-9kH9RM;`G>R-#FR37U7Gpz~MDlDn$M!z{;W4)87jTVJ zdk2aq=bDFj-#f$l^WW57&C5Qo!suW8+5D5*YgKkL#5<|ogBU!s@QaH$V!BuHxm=U#$dougch89Y61s zc7v$s_WbHX@NzR^*I^I*>S{>+Uc_#|PX6lp`5N5QQ!GFD8M);7HNYPyKK0 zBK&T|c3?Zrkv46{0@a-_wC;Wy1P~O-2UCb_NnYN=r-Z^ zt8Cq~yMaBXGV8Ouu+8{WDyxBRGybB=ZUWsFeCwYn_5KaeZNVcdd+6Ei*n9Ad%3gjJ zp7X%BschbJ3l^M@(<(dvxmCbKmHo+cyRZxJD^%8gZa1*2RrXh))8W^u>^q>-;d@l} z^XJ~P;Jx^5DtrC8_W`>TF{P$2#1A0016%U^`PhZ{%IzrEs8%k-Hz;gs_4C_7r&F2j z`JKS@h$%C|`|yCmu)jF)kp=I=C6)d7fOEk`_#G;H;XnYt7(bvg?XSOzy&qq@Lr(8H zY}2np3qF8fc7_!ed>Fq?Wgq!9v%riWR9WNK(gFv*vls3K-S#nE0KZycQ=fZr z7Z$*;RoP81?gq9;Wq%F2Aby+5{snYF{7#iU4!RKjEtMSrT?oHdWp98kj6a|{69AUpllPiF;Hwdg&yvgvxfkG_^2=7gV9;SUfvCCugcsnuUeSH zZ&z9I8`D*G7d%J{V^i-2w$ezVGopxcJurn1_r+c5&aLuET(h3`V) z-%{CEKv%){sq9YBRqzK?_I=P*@yAs5An2<2ew951>5=%0Dmx76k@#ViE&9zajKWW< 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
    Rx-nNx50{K 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 z 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 Date: Sat, 18 Jun 2016 21:49:21 +0200 Subject: [PATCH 243/285] Revert "Minor enhancements in data layer" This reverts commit 1a810e2c1ca95cdad0e62b6235963a813d26bf41. --- .../SmartStore.Data/ObjectContextBase.cs | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/src/Libraries/SmartStore.Data/ObjectContextBase.cs b/src/Libraries/SmartStore.Data/ObjectContextBase.cs index 26b4e52171..579994d654 100644 --- a/src/Libraries/SmartStore.Data/ObjectContextBase.cs +++ b/src/Libraries/SmartStore.Data/ObjectContextBase.cs @@ -16,7 +16,6 @@ using SmartStore.Core.Data; using SmartStore.Core.Data.Hooks; using SmartStore.Core.Events; -using System.Data.Entity.Core.Objects; namespace SmartStore.Data { @@ -139,7 +138,7 @@ public bool HooksEnabled public virtual string CreateDatabaseScript() { - return ObjectContext.CreateDatabaseScript(); + return ((IObjectContextAdapter)this).ObjectContext.CreateDatabaseScript(); } public new DbSet Set() where TEntity : BaseEntity @@ -220,7 +219,7 @@ private IEnumerable ToParameters(params object[] parameters) // database call var reader = cmd.ExecuteReader(); - var result = ObjectContext.Translate(reader).ToList(); + var result = ((IObjectContextAdapter)(this)).ObjectContext.Translate(reader).ToList(); if (!ForceNoTracking) { for (int i = 0; i < result.Count; i++) @@ -264,8 +263,8 @@ public int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, in if (timeout.HasValue) { //store previous timeout - previousTimeout = ObjectContext.CommandTimeout; - ObjectContext.CommandTimeout = timeout; + previousTimeout = ((IObjectContextAdapter)this).ObjectContext.CommandTimeout; + ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = timeout; } var transactionalBehavior = doNotEnsureTransaction @@ -276,7 +275,7 @@ public int ExecuteSqlCommand(string sql, bool doNotEnsureTransaction = false, in if (timeout.HasValue) { //Set previous timeout back - ObjectContext.CommandTimeout = previousTimeout; + ((IObjectContextAdapter)this).ObjectContext.CommandTimeout = previousTimeout; } return result; @@ -499,29 +498,27 @@ protected internal bool IsSqlServer2012OrHigher() public TEntity Attach(TEntity entity) where TEntity : BaseEntity { - ObjectStateEntry entry; - if (ObjectContext.ObjectStateManager.TryGetObjectStateEntry(entity, out entry)) + var dbSet = Set(); + var alreadyAttached = dbSet.Local.FirstOrDefault(x => x.Id == entity.Id); + + if (alreadyAttached == null) { - if (entry.State != System.Data.Entity.EntityState.Detached) - { - return (TEntity)entry.Entity; - } + dbSet.Attach(entity); + return entity; } - Set().Attach(entity); - return entity; + return alreadyAttached; } public bool IsAttached(TEntity entity) where TEntity : BaseEntity { - ObjectStateEntry entry; - if (ObjectContext.ObjectStateManager.TryGetObjectStateEntry(entity, out entry)) + if (entity != null) { - return (entry.State != System.Data.Entity.EntityState.Detached); + return Set().Local.Any(x => x.Id == entity.Id); } - + return false; - } + } public void DetachEntity(TEntity entity) where TEntity : BaseEntity { @@ -553,6 +550,7 @@ public int DetachEntities(bool unchangedEntitiesOnly = true) where TEnt public void ChangeState(TEntity entity, System.Data.Entity.EntityState newState) where TEntity : BaseEntity { + Console.WriteLine("ChangeState ORIGINAL"); this.Entry(entity).State = newState; } @@ -561,11 +559,6 @@ public void ReloadEntity(TEntity entity) where TEntity : BaseEntity this.Entry(entity).Reload(); } - internal ObjectContext ObjectContext - { - get { return ((IObjectContextAdapter)this).ObjectContext; } - } - private string FormatValidationExceptionMessage(IEnumerable results) { var sb = new StringBuilder(); From 7158f24d8d752e43317e61bda0ef063ef7e9fe5f Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 20 Jun 2016 11:06:23 +0200 Subject: [PATCH 244/285] Export: Some improvements --- changelog.md | 1 + .../Domain/DataExchange/ExportEnums.cs | 7 ++- .../Catalog/IProductAttributeParser.cs | 4 +- .../Catalog/ProductAttributeParser.cs | 9 ++-- .../DataExchange/Csv/CsvWriter.cs | 4 +- .../DataExchange/Export/DataExporter.cs | 8 +++- .../Export/DynamicEntityHelper.cs | 46 +++++++++++-------- .../Views/Export/_Tab.Deployment.cshtml | 2 +- 8 files changed, 49 insertions(+), 32 deletions(-) diff --git a/changelog.md b/changelog.md index ddeda6310a..480d9476d6 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,7 @@ * #637 Integrated PayPal PLUS payment provider * #932 Implement paydirekt payment plugin * New Viveum payment plugin +* #979 Implement BeezUP product feed ### New Features * #637 Integrated PayPal PLUS payment Provider diff --git a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs index a5b5a48763..6ea68ec73c 100644 --- a/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs +++ b/src/Libraries/SmartStore.Core/Domain/DataExchange/ExportEnums.cs @@ -126,7 +126,12 @@ public enum ExportFeatures /// /// Whether to not automatically send a completion email /// - CanOmitCompletionMail = 1 << 12 + CanOmitCompletionMail = 1 << 12, + + /// + /// Whether to provide additional data of attribute combinations + /// + UsesAttributeCombination = 1 << 13 } /// diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs index 64a412d339..a02648a747 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs @@ -35,11 +35,11 @@ public partial interface IProductAttributeParser /// /// Get list of localized product variant attribute values /// - /// XML formatted attributes + /// Map of combined attributes /// Product variant attributes /// Language identifier /// List of localized product variant attribute values - IList ParseProductVariantAttributeValues(string attributesXml, IEnumerable attributes, int languageId = 0); + IList ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes, int languageId = 0); /// /// Gets selected product variant attribute value diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs index 9c87960614..0afe47049b 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs @@ -194,21 +194,20 @@ where id.HasValue() return values; } - public IList ParseProductVariantAttributeValues(string attributesXml, IEnumerable attributes, int languageId = 0) + public virtual IList ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes, int languageId = 0) { var values = new List(); - if (attributesXml.IsEmpty()) + if (attributeCombination == null || !attributeCombination.Any()) return values; var allValueIds = new List(); - var combinedAttributes = DeserializeProductVariantAttributes(attributesXml); foreach (var pva in attributes.Where(x => x.ShouldHaveValues()).OrderBy(x => x.DisplayOrder)) { - if (combinedAttributes.ContainsKey(pva.Id)) + if (attributeCombination.ContainsKey(pva.Id)) { - var pvaValuesStr = combinedAttributes[pva.Id]; + var pvaValuesStr = attributeCombination[pva.Id]; var ids = pvaValuesStr.Where(x => x.HasValue()).Select(x => x.ToInt()); allValueIds.AddRange(ids); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvWriter.cs b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvWriter.cs index 83eee20228..8bb9942052 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvWriter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Csv/CsvWriter.cs @@ -51,7 +51,7 @@ public CsvConfiguration Configuration /// to complete writing of the current row. /// /// The fields to write. - public virtual void WriteFields(string[] fields) + public virtual void WriteFields(IEnumerable fields) { Guard.ArgumentNotNull(() => fields); fields.Each(x => WriteField(x)); @@ -69,7 +69,7 @@ public virtual void WriteFields(string[] fields) /// /// The fields to write. /// True to quote the fields, otherwise false. - public virtual void WriteFields(string[] fields, bool shouldQuote) + public virtual void WriteFields(IEnumerable fields, bool shouldQuote) { Guard.ArgumentNotNull(() => fields); fields.Each(x => WriteField(x, shouldQuote)); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 173b61b475..41cb53a69f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -1065,8 +1065,12 @@ private void ExportCoreInner(DataExporterContext ctx, Store store) logHead.AppendLine("Entity:\t\t\t" + ctx.Request.Provider.Value.EntityType.ToString()); - var storeInfo = (ctx.Request.Profile.PerStore ? "{0} (Id {1})".FormatInvariant(ctx.Store.Name, ctx.Store.Id) : "All stores"); - logHead.AppendLine("Store:\t\t\t" + storeInfo); + try + { + var uri = new Uri(store.Url); + logHead.AppendLine("Store:\t\t\t{0} (Id {1})".FormatInvariant(uri.DnsSafeHost.NaIfEmpty(), ctx.Store.Id)); + } + catch { } var customer = _services.WorkContext.CurrentCustomer; logHead.Append("Executed by:\t\t" + (customer.Email.HasValue() ? customer.Email : customer.SystemName)); diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs index 96362b5999..e925553e9f 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs @@ -578,14 +578,14 @@ private dynamic ToDynamic( #region gerneral data - dynObject.Price = CalculatePrice(ctx, product, combination != null); - - if (combination != null && ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) - { - var values = _productAttributeParser.Value.ParseProductVariantAttributeValues(combination.AttributesXml, productAttributes, ctx.Projection.LanguageId ?? 0); + dynObject._CategoryName = null; + dynObject._CategoryPath = null; + dynObject._AttributeCombination = null; + dynObject._AttributeCombinationValues = null; + dynObject._AttributeCombinationId = (combination == null ? 0 : combination.Id); + dynObject._DetailUrl = ctx.Store.Url.EnsureEndsWith("/") + (string)dynObject.SeName; - dynObject.Name = ((string)dynObject.Name).Grow(string.Join(", ", values), " "); - } + dynObject.Price = CalculatePrice(ctx, product, combination != null); dynObject._BasePriceInfo = product.GetBasePriceInfo(_services.Localization, _priceFormatter.Value, _currencyService.Value, _taxService.Value, _priceCalculationService.Value, ctx.ContextCurrency, decimal.Zero, true); @@ -595,21 +595,34 @@ private dynamic ToDynamic( else dynObject._ProductTemplateViewPath = ""; - var detailUrl = ctx.Store.Url.EnsureEndsWith("/") + (string)dynObject.SeName; - if (combination != null) { + if (ctx.Supports(ExportFeatures.UsesAttributeCombination) || + ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) + { + var variantAttributes = _productAttributeParser.Value.DeserializeProductVariantAttributes(combination.AttributesXml); + var variantAttributeValues = _productAttributeParser.Value.ParseProductVariantAttributeValues(variantAttributes, productAttributes, languageId); + + if (ctx.Supports(ExportFeatures.UsesAttributeCombination)) + { + dynObject._AttributeCombination = variantAttributes; + dynObject._AttributeCombinationValues = variantAttributeValues; + } + + if (ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) + { + dynObject.Name = ((string)dynObject.Name).Grow(string.Join(", ", variantAttributeValues), " "); + } + } + var attributeQueryString = _productAttributeParser.Value.SerializeQueryData(combination.AttributesXml, product.Id); if (attributeQueryString.HasValue()) { - detailUrl = string.Concat(detailUrl, detailUrl.Contains("?") ? "&" : "?", "attributes=", attributeQueryString); + var url = (string)dynObject._DetailUrl; + dynObject._DetailUrl = string.Concat(url, url.Contains("?") ? "&" : "?", "attributes=", attributeQueryString); } } - dynObject._IsChild = (combination != null); - dynObject._DetailUrl = detailUrl; - dynObject._CategoryName = null; - if (ctx.Categories.Count > 0) { dynObject._CategoryPath = _categoryService.Value.GetCategoryPath( @@ -621,13 +634,8 @@ private dynamic ToDynamic( productCategories.OrderBy(x => x.DisplayOrder).FirstOrDefault() ); } - else - { - dynObject._CategoryPath = null; - } ToDeliveryTime(ctx, dynObject, product.DeliveryTimeId); - ToQuantityUnit(ctx, dynObject, product.QuantityUnitId); dynObject.ProductPictures = productPictures diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml index 1db883b2d3..d77bc3cf54 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/_Tab.Deployment.cshtml @@ -104,7 +104,7 @@ {
    - @T("Admin.DataExchange.Export.NoProfiles") + @T("Admin.DataExchange.Export.Deployment.NoProfiles")
    } From 59186e1f3520cb43887e048cb7b4068a8d1fc254 Mon Sep 17 00:00:00 2001 From: Michael-Herzog Date: Tue, 21 Jun 2016 11:20:14 +0200 Subject: [PATCH 245/285] Update README.md Updated link to new website --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d3402dc825..a7a83aee3a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ A comprehensive set of tools for CRM & CMS, sales, marketing, payment & shipping The state-of-the-art architecture of SmartStore.NET - with `ASP.NET 4.5` + `MVC 5`, `Entity Framework 6` and Domain Driven Design approach - makes it easy to extend, extremely flexible and essentially fun to work with ;-) -* **Website:** [http://www.smartstore.com/net](http://www.smartstore.com/net) +* **Website:** [http://www.smartstore.com/en/net](http://www.smartstore.com/en/net) * **Forum:** [http://community.smartstore.com](http://community.smartstore.com) * **Marketplace:** [http://community.smartstore.com/marketplace](http://community.smartstore.com/marketplace) * **Documentation:** [SmartStore.NET Documentation in English](http://docs.smartstore.com/display/SMNET/SmartStore.NET+Documentation+Home) From d604fd1b244e07593bfa81dc2f837fc69b6d8682 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 21 Jun 2016 11:35:24 +0200 Subject: [PATCH 246/285] Duplicate entry in changelog --- changelog.md | 1 - 1 file changed, 1 deletion(-) diff --git a/changelog.md b/changelog.md index 480d9476d6..db0febbc6f 100644 --- a/changelog.md +++ b/changelog.md @@ -10,7 +10,6 @@ * #979 Implement BeezUP product feed ### New Features -* #637 Integrated PayPal PLUS payment Provider * #961 Fix "Open Redirection Vulnerability" * #571 Option to display another checkbox on confirm page to let the customer accept that his email address can be handed over to a third party * #870 Implement address import for customers (both billing & shipping address) From 30713d24ba65b31babf271cb688b09e933dedc3c Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 21 Jun 2016 15:13:31 +0200 Subject: [PATCH 247/285] Enabled import context to accept strings as progress messages --- .../Import/ImportExecuteContext.cs | 14 ++++++++++++++ .../Administration/Content/admin.less | 6 ++++++ .../Views/Export/InfoProfile.cshtml | 18 +++++++++--------- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs index 67e3c090a1..70170c0fd3 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Import/ImportExecuteContext.cs @@ -209,5 +209,19 @@ public void SetProgress(int value, int maximum) } catch { } } + + /// + /// Allows to set a message + /// + /// Message to display + public void SetProgress(string message) + { + try + { + if (_progressValueSetter != null && message.HasValue()) + _progressValueSetter.Invoke(0, 0, message); + } + catch { } + } } } diff --git a/src/Presentation/SmartStore.Web/Administration/Content/admin.less b/src/Presentation/SmartStore.Web/Administration/Content/admin.less index c31aff72bc..5483f5971f 100644 --- a/src/Presentation/SmartStore.Web/Administration/Content/admin.less +++ b/src/Presentation/SmartStore.Web/Administration/Content/admin.less @@ -1580,6 +1580,8 @@ table.payment-method-features td { } + + /* Data exchange -------------------------------------------------------------- */ .profile-list { @@ -1587,6 +1589,10 @@ table.payment-method-features td { border-bottom: 1px solid #ddd; } +.info-profile td { + padding-top: 20px; +} + /* Import column mapping -------------------------------------------------------------- */ @media (max-width: 1280px) { diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml index 54799e977b..912809feb9 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Export/InfoProfile.cshtml @@ -16,15 +16,15 @@ } @foreach (var profile in Model.Profiles) { - - - @Html.SmartLabelFor(m => m.Profiles) - - -
    - @profile.Name - - + + + @Html.SmartLabelFor(m => m.Profiles) + + + + @profile.Name + + if (profile.ScheduleTaskId.HasValue) { From e10d4f8c6cebc36bac0626f10a7a54dd7590d74c Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Tue, 21 Jun 2016 15:33:32 +0200 Subject: [PATCH 248/285] Removed promotion feeds node from admin menu --- src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs | 6 +----- .../SmartStore.Web/Administration/sitemap.config | 1 - 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs index b6c3684093..2dbc226f57 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/AdminMenu.cs @@ -7,17 +7,13 @@ public class AdminMenu : AdminMenuProvider { protected override void BuildMenuCore(TreeNode pluginsNode) { - var root = pluginsNode.SelectNode(x => x.Value.Id == "promotion-feeds"); - if (root == null) - return; - var menuItem = new MenuItem().ToBuilder() .Text("Google Merchant Center") .ResKey("Plugins.FriendlyName.SmartStore.GoogleMerchantCenter") .Action("ConfigurePlugin", "Plugin", new { systemName = GoogleMerchantCenterFeedPlugin.SystemName, area = "Admin" }) .ToItem(); - root.Append(menuItem); + pluginsNode.Prepend(menuItem); } } diff --git a/src/Presentation/SmartStore.Web/Administration/sitemap.config b/src/Presentation/SmartStore.Web/Administration/sitemap.config index 179ac3a5c4..a1501d7653 100644 --- a/src/Presentation/SmartStore.Web/Administration/sitemap.config +++ b/src/Presentation/SmartStore.Web/Administration/sitemap.config @@ -147,7 +147,6 @@ - From 3b58ca04740eeaeb10239063d2e80f9fafeb2db4 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 21 Jun 2016 20:32:20 +0200 Subject: [PATCH 249/285] Cart and mini cart should not have different item order --- .../Customers/CustomerExtentions.cs | 11 ++++++----- .../Controllers/ShoppingCartController.cs | 3 +-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs b/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs index 719f144ed5..a3525e9cac 100644 --- a/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs +++ b/src/Libraries/SmartStore.Services/Customers/CustomerExtentions.cs @@ -287,14 +287,15 @@ public static int CountProductsInCart(this Customer customer, ShoppingCartType c return count; } - public static List GetCartItems(this Customer customer, ShoppingCartType cartType, int? storeId = null, bool orderById = false) + + public static List GetCartItems(this Customer customer, ShoppingCartType cartType, int? storeId = null) { var rawItems = customer.ShoppingCartItems.Filter(cartType, storeId); - if (orderById) - rawItems = rawItems.OrderByDescending(x => x.Id); - - var organizedItems = rawItems.ToList().Organize(); + var organizedItems = rawItems + .OrderByDescending(x => x.Id) + .ToList() + .Organize(); return organizedItems.ToList(); } diff --git a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs index f194738cb8..e4ff2cb6a7 100644 --- a/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/ShoppingCartController.cs @@ -996,7 +996,6 @@ protected MiniShoppingCartModel PrepareMiniShoppingCartModel() //products. sort descending (recently added products) foreach (var sci in cart - .OrderByDescending(x => x.Item.Id) .Take(_shoppingCartSettings.MiniShoppingCartProductNumber) .ToList()) { @@ -2633,7 +2632,7 @@ public ActionResult FlyoutWishlist() { Customer customer = _workContext.CurrentCustomer; - var cart = customer.GetCartItems(ShoppingCartType.Wishlist, _storeContext.CurrentStore.Id, true); + var cart = customer.GetCartItems(ShoppingCartType.Wishlist, _storeContext.CurrentStore.Id); var model = new WishlistModel(); PrepareWishlistModel(model, cart, true); From ee9a7c0ba9da185fc6fa98032102f5483e697a01 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Tue, 21 Jun 2016 21:41:02 +0200 Subject: [PATCH 250/285] Minor perf improvement --- .../Discounts/DiscountService.cs | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs b/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs index 9b75b86e8b..b973e84153 100644 --- a/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs +++ b/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs @@ -12,6 +12,7 @@ using SmartStore.Services.Customers; using SmartStore.Services.Common; using SmartStore.Services.Configuration; +using SmartStore.Services.Orders; namespace SmartStore.Services.Discounts { @@ -317,18 +318,18 @@ public virtual bool IsDiscountValid(Discount discount, Customer customer, string } //check date range - DateTime now = DateTime.UtcNow; - int storeId = _storeContext.CurrentStore.Id; + var now = DateTime.UtcNow; + var store = _storeContext.CurrentStore; if (discount.StartDateUtc.HasValue) { - DateTime startDate = DateTime.SpecifyKind(discount.StartDateUtc.Value, DateTimeKind.Utc); + var startDate = DateTime.SpecifyKind(discount.StartDateUtc.Value, DateTimeKind.Utc); if (startDate.CompareTo(now) > 0) return false; } if (discount.EndDateUtc.HasValue) { - DateTime endDate = DateTime.SpecifyKind(discount.EndDateUtc.Value, DateTimeKind.Utc); + var endDate = DateTime.SpecifyKind(discount.EndDateUtc.Value, DateTimeKind.Utc); if (endDate.CompareTo(now) < 0) return false; } @@ -336,33 +337,36 @@ public virtual bool IsDiscountValid(Discount discount, Customer customer, string if (!CheckDiscountLimitations(discount, customer)) return false; - // discount requirements - var requirements = discount.DiscountRequirements; + // better not to apply discounts if there are gift cards in the cart cause the customer could "earn" money through that. + if (discount.DiscountType == DiscountType.AssignedToOrderTotal || discount.DiscountType == DiscountType.AssignedToOrderSubTotal) + { + var cart = customer.ShoppingCartItems + .Filter(ShoppingCartType.ShoppingCart, store.Id) + .ToList(); + + if (cart.Any(x => x.Product.IsGiftCard)) + return false; + } + + // discount requirements + var requirements = discount.DiscountRequirements; foreach (var req in requirements) { - var requirementRule = LoadDiscountRequirementRuleBySystemName(req.DiscountRequirementRuleSystemName, storeId); + var requirementRule = LoadDiscountRequirementRuleBySystemName(req.DiscountRequirementRuleSystemName, store.Id); if (requirementRule == null) continue; - var request = new CheckDiscountRequirementRequest() + var request = new CheckDiscountRequirementRequest { DiscountRequirement = req, Customer = customer, - Store = _storeContext.CurrentStore + Store = store }; + if (!requirementRule.Value.CheckRequirement(request)) return false; } - // better not to apply discounts if there are gift cards in the cart cause the customer could "earn" money through that. - if (discount.DiscountType == DiscountType.AssignedToOrderTotal || discount.DiscountType == DiscountType.AssignedToOrderSubTotal) - { - var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, storeId); - - if (cart.Any(x => x.Item.Product.IsGiftCard)) - return false; - } - return true; } From 2a8f648f10e106ac5a0e367827fb260b965ec1f9 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 22 Jun 2016 11:38:15 +0200 Subject: [PATCH 251/285] Discount rule has spent amount including sub total option can cause wrong discount calculation if the cart contains a product several times --- changelog.md | 1 + .../Discounts/DiscountService.cs | 3 +- .../SmartStore.DiscountRules/Description.txt | 2 +- .../Providers/HadSpentAmountRule.cs | 54 +++++++++++++------ 4 files changed, 43 insertions(+), 17 deletions(-) diff --git a/changelog.md b/changelog.md index db0febbc6f..097390ed9d 100644 --- a/changelog.md +++ b/changelog.md @@ -60,6 +60,7 @@ * Private messages: Fixes "No route in the route table matches the supplied values" * Payone: Hash string incorrect for frontend API payments where the order has more than 9 products * Export mail notification: Download link not working if SSL is enabled +* Discount rule has spent amount including sub total option can cause wrong discount calculation if the cart contains a product several times ## SmartStore.NET 2.5 diff --git a/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs b/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs index b973e84153..ffa0fa2615 100644 --- a/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs +++ b/src/Libraries/SmartStore.Services/Discounts/DiscountService.cs @@ -363,7 +363,8 @@ public virtual bool IsDiscountValid(Discount discount, Customer customer, string Store = store }; - if (!requirementRule.Value.CheckRequirement(request)) + // TODO: cache result... CheckRequirement is very often called + if (!requirementRule.Value.CheckRequirement(request)) return false; } diff --git a/src/Plugins/SmartStore.DiscountRules/Description.txt b/src/Plugins/SmartStore.DiscountRules/Description.txt index 2a17b4fcd9..a2336d8b8e 100644 --- a/src/Plugins/SmartStore.DiscountRules/Description.txt +++ b/src/Plugins/SmartStore.DiscountRules/Description.txt @@ -2,7 +2,7 @@ Description: Contains common discount requirement rule providers like "Billing country is", "Customer role is", "Had spent amount" etc. Group: Marketing SystemName: SmartStore.DiscountRules -Version: 2.5.0 +Version: 2.5.0.1 MinAppVersion: 2.5.0 DisplayOrder: 0 FileName: SmartStore.DiscountRules.dll diff --git a/src/Plugins/SmartStore.DiscountRules/Providers/HadSpentAmountRule.cs b/src/Plugins/SmartStore.DiscountRules/Providers/HadSpentAmountRule.cs index b67d2a7efd..69d7f2614b 100644 --- a/src/Plugins/SmartStore.DiscountRules/Providers/HadSpentAmountRule.cs +++ b/src/Plugins/SmartStore.DiscountRules/Providers/HadSpentAmountRule.cs @@ -1,15 +1,16 @@ using System; +using System.Collections.Generic; using System.Linq; +using Newtonsoft.Json; using SmartStore.Core.Domain.Orders; +using SmartStore.Core.Logging; using SmartStore.Core.Plugins; -using SmartStore.Services.Discounts; +using SmartStore.DiscountRules.Settings; +using SmartStore.Services.Catalog; using SmartStore.Services.Customers; +using SmartStore.Services.Discounts; using SmartStore.Services.Orders; -using SmartStore.Services.Catalog; using SmartStore.Services.Tax; -using SmartStore.Core.Localization; -using SmartStore.DiscountRules.Settings; -using Newtonsoft.Json; namespace SmartStore.DiscountRules { @@ -21,15 +22,18 @@ public partial class HadSpentAmountRule : DiscountRequirementRuleBase private readonly IOrderService _orderService; private readonly IPriceCalculationService _priceCalculationService; private readonly ITaxService _taxService; + private readonly ILogger _logger; public HadSpentAmountRule( IOrderService orderService, IPriceCalculationService priceCalculationService, - ITaxService taxService) + ITaxService taxService, + ILogger logger) { - this._orderService = orderService; - this._priceCalculationService = priceCalculationService; - this._taxService = taxService; + _orderService = orderService; + _priceCalculationService = priceCalculationService; + _taxService = taxService; + _logger = logger; } public override bool CheckRequirement(CheckDiscountRequirementRequest request) @@ -88,14 +92,34 @@ private bool CheckTotalHistoryRequirement(CheckDiscountRequirementRequest reques private bool CheckCurrentSubTotalRequirement(CheckDiscountRequirementRequest request) { - var cartItems = request.Customer.GetCartItems(ShoppingCartType.ShoppingCart, request.Store.Id); + var spentAmount = decimal.Zero; - decimal spentAmount = decimal.Zero; - decimal taxRate = decimal.Zero; - foreach (var sci in cartItems) + try { - // includeDiscounts == true produces a stack overflow! - spentAmount += sci.Item.Quantity * _taxService.GetProductPrice(sci.Item.Product, _priceCalculationService.GetUnitPrice(sci, false), out taxRate); + var taxRate = decimal.Zero; + var cartItems = request.Customer.GetCartItems(ShoppingCartType.ShoppingCart, request.Store.Id); + + foreach (var cartItem in cartItems) + { + var product = cartItem.Item.Product; + Dictionary mergedValuesClone = null; + + // we must reapply merged values because CheckCurrentSubTotalRequirement uses price calculation and is called by it itself. + // this can cause wrong discount calculation if the cart contains a product several times. + if (product.MergedDataValues != null) + mergedValuesClone = new Dictionary(product.MergedDataValues); + + // includeDiscounts == true produces a stack overflow! + spentAmount += cartItem.Item.Quantity * _taxService.GetProductPrice(product, _priceCalculationService.GetUnitPrice(cartItem, false), out taxRate); + + if (mergedValuesClone != null) + product.MergedDataValues = new Dictionary(mergedValuesClone); + } + } + catch (Exception exception) + { + _logger.Error(exception); + return false; } return spentAmount >= request.DiscountRequirement.SpentAmount; From c3753153b07b91e2cf0fbf0122f1eaf0a4509959 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 22 Jun 2016 18:11:46 +0200 Subject: [PATCH 252/285] CronExpression.GetFriendlyDescription() should not throw exception when expression is empty --- .../SmartStore.Services/Tasks/CronExpression.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Tasks/CronExpression.cs b/src/Libraries/SmartStore.Services/Tasks/CronExpression.cs index 25243b0ad5..c35b4b7128 100644 --- a/src/Libraries/SmartStore.Services/Tasks/CronExpression.cs +++ b/src/Libraries/SmartStore.Services/Tasks/CronExpression.cs @@ -59,14 +59,16 @@ public static string GetFriendlyDescription(string expression) Use24HourTimeFormat = Thread.CurrentThread.CurrentUICulture.DateTimeFormat.AMDesignator.IsEmpty() }; - try - { - return ExpressionDescriptor.GetDescription(expression, options); - } - catch + if (expression.HasValue()) { - return null; + try + { + return ExpressionDescriptor.GetDescription(expression, options); + } + catch { } } + + return "?"; } } From ecc787a1ad4ec3e02b369ac1b8651852c5c39831 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 22 Jun 2016 18:33:35 +0200 Subject: [PATCH 253/285] Updated changelog --- changelog.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/changelog.md b/changelog.md index 097390ed9d..8efe9bbc11 100644 --- a/changelog.md +++ b/changelog.md @@ -1,13 +1,14 @@ # Release Notes -## SmartStore.NET 2.5.1 +## SmartStore.NET 2.6 ### Highlights -* Major improvements in Importer: better field mapping, higher performance, bug fixes etc. -* #637 Integrated PayPal PLUS payment provider -* #932 Implement paydirekt payment plugin -* New Viveum payment plugin -* #979 Implement BeezUP product feed +* Major improvements in Importer & Exporter: better field mapping, higher performance, bug fixes etc. +* 'PayPal PLUS' payment plugin +* 'paydirekt' payment plugin +* 'Viveum' payment plugin +* 'BeezUP' export provider +* (Dev) Publishing SmartStore.Web from within Visual Studio now deploys the project correctly. No need to execute ClickToBuild.cmd anymore. Just publish directly to any target, including Azure. ### New Features * #961 Fix "Open Redirection Vulnerability" From c2ef0419896c0cc383dc327dca8afe94266f3cf5 Mon Sep 17 00:00:00 2001 From: Murat Cakir Date: Wed, 22 Jun 2016 18:58:07 +0200 Subject: [PATCH 254/285] Updated ua-parser-regexes.yaml --- .../App_Data/ua-parser.regexes.yaml | 53 +++++++++++++++---- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/src/Presentation/SmartStore.Web/App_Data/ua-parser.regexes.yaml b/src/Presentation/SmartStore.Web/App_Data/ua-parser.regexes.yaml index 6ef74a0d89..c160ade711 100644 --- a/src/Presentation/SmartStore.Web/App_Data/ua-parser.regexes.yaml +++ b/src/Presentation/SmartStore.Web/App_Data/ua-parser.regexes.yaml @@ -17,6 +17,10 @@ user_agent_parsers: - regex: 'Google.*/\+/web/snippet' family_replacement: 'GooglePlusBot' + # Twitter + - regex: '(Twitterbot)/(\d+)\.(\d+)' + family_replacement: 'TwitterBot' + # Bots Pattern '/name-0.0' - regex: '/((?:Ant-)?Nutch|[A-z]+[Bb]ot|[A-z]+[Ss]pider|Axtaris|fetchurl|Isara|ShopSalad|Tailsweep)[ \-](\d+)(?:\.(\d+)(?:\.(\d+))?)?' # Bots Pattern 'name/0.0' @@ -106,7 +110,7 @@ user_agent_parsers: # Opera will stop at 9.80 and hide the real version in the Version string. # see: http://dev.opera.com/articles/view/opera-ua-string-changes/ - regex: '(Opera Tablet).*Version/(\d+)\.(\d+)(?:\.(\d+))?' - - regex: '(Opera Mini)(?:/att)?/(\d+)?(?:\.(\d+))?(?:\.(\d+))?' + - regex: '(Opera Mini)(?:/att)?/?(\d+)?(?:\.(\d+))?(?:\.(\d+))?' - regex: '(Opera)/.+Opera Mobi.+Version/(\d+)\.(\d+)' family_replacement: 'Opera Mobile' - regex: '(Opera)/(\d+)\.(\d+).+Opera Mobi' @@ -205,6 +209,10 @@ user_agent_parsers: - regex: 'Windows Phone .*(Edge)/(\d+)\.(\d+)' family_replacement: 'Edge Mobile' + # Samsung Internet (based on Chrome, but lacking some features) + - regex: '(SamsungBrowser)/(\d+)\.(\d+)' + family_replacement: 'Samsung Internet' + # Chrome Mobile - regex: '(CrMo)/(\d+)\.(\d+)\.(\d+)\.(\d+)' family_replacement: 'Chrome Mobile' @@ -272,12 +280,12 @@ user_agent_parsers: - regex: '(AdobeAIR|FireWeb|Jasmine|ANTGalio|Midori|Fresco|Lobo|PaleMoon|Maxthon|Lynx|OmniWeb|Dillo|Camino|Demeter|Fluid|Fennec|Epiphany|Shiira|Sunrise|Spotify|Flock|Netscape|Lunascape|WebPilot|NetFront|Netfront|Konqueror|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|Opera Mini|iCab|NetNewsWire|ThunderBrowse|Iris|UP\.Browser|Bunjalloo|Google Earth|Raven for Mac|Openwave)/(\d+)\.(\d+)\.(\d+)' # Outlook 2007 - - regex: 'MSOffice 12' + - regex: 'Microsoft Office Outlook 12\.\d+\.\d+|MSOffice 12' family_replacement: 'Outlook' v1_replacement: '2007' # Outlook 2010 - - regex: 'MSOffice 14' + - regex: 'Microsoft Outlook 14\.\d+\.\d+|MSOffice 14' family_replacement: 'Outlook' v1_replacement: '2010' @@ -286,6 +294,15 @@ user_agent_parsers: family_replacement: 'Outlook' v1_replacement: '2013' + # Outlook 2016 + - regex: 'Microsoft Outlook (?:Mail )?16\.\d+\.\d+' + family_replacement: 'Outlook' + v1_replacement: '2016' + + # Windows Live Mail + - regex: 'Outlook-Express\/7\.0.*' + family_replacement: 'Windows Live Mail' + # Apple Air Mail - regex: '(Airmail) (\d+)\.(\d+)(?:\.(\d+))?' @@ -299,6 +316,10 @@ user_agent_parsers: # Edge/major_version.minor_version - regex: '(Edge)/(\d+)\.(\d+)' + # Brave Browser https://brave.com/ + - regex: '(brave)/(\d+)\.(\d+)\.(\d+) Chrome' + family_replacement: 'Brave' + # Chrome/Chromium/major_version.minor_version.beta_version - regex: '(Chromium|Chrome)/(\d+)\.(\d+)\.(\d+)' @@ -388,15 +409,15 @@ user_agent_parsers: - regex: '(iPod|iPhone|iPad).+Version/(\d+)\.(\d+)(?:\.(\d+))?.* Safari' family_replacement: 'Mobile Safari' - regex: '(iPod|iPhone|iPad).+Version/(\d+)\.(\d+)(?:\.(\d+))?' - family_replacement: 'Mobile Safari UIWebView' + family_replacement: 'Mobile Safari UI/WKWebView' - regex: '(iPod|iPhone|iPad);.*CPU.*OS (\d+)_(\d+)(?:_(\d+))?.*Mobile.* Safari' family_replacement: 'Mobile Safari' - regex: '(iPod|iPhone|iPad);.*CPU.*OS (\d+)_(\d+)(?:_(\d+))?.*Mobile' - family_replacement: 'Mobile Safari UIWebView' + family_replacement: 'Mobile Safari UI/WKWebView' - regex: '(iPod|iPhone|iPad).* Safari' family_replacement: 'Mobile Safari' - regex: '(iPod|iPhone|iPad)' - family_replacement: 'Mobile Safari UIWebView' + family_replacement: 'Mobile Safari UI/WKWebView' - regex: '(AvantGo) (\d+).(\d+)' @@ -498,7 +519,7 @@ user_agent_parsers: # apple mail - not directly detectable, have it after Safari stuff - regex: '(AppleWebKit)/(\d+)\.(\d+)\.(\d+)' - family_replacement: 'AppleMail' + family_replacement: 'Apple Mail' # AFTER THE EDGE CASES ABOVE! # AFTER IE11 @@ -514,6 +535,9 @@ user_agent_parsers: - regex: '(Java)[/ ]{0,1}\d+\.(\d+)\.(\d+)[_-]*([a-zA-Z0-9]+)*' + # Roku Digital-Video-Players https://www.roku.com/ + - regex: '^(Roku)/DVP-(\d+)\.(\d+)' + os_parsers: ########## # HbbTV vendors @@ -981,6 +1005,9 @@ os_parsers: - regex: 'SunOS' os_replacement: 'Solaris' + # Roku Digital-Video-Players https://www.roku.com/ + - regex: '^(Roku)/DVP-(\d+)\.(\d+)' + device_parsers: ######### @@ -2049,7 +2076,7 @@ device_parsers: device_replacement: 'Huawei Ideos$1' brand_replacement: 'Huawei' model_replacement: 'Ideos$1' - - regex: '; *(Orange Daytona|Pulse|Pulse Mini|Vodafone 858|C8500|C8600|C8650|C8660) Build' + - regex: '; *(Orange Daytona|Pulse|Pulse Mini|Vodafone 858|C8500|C8600|C8650|C8660|Nexus 6P) Build' device_replacement: 'Huawei $1' brand_replacement: 'Huawei' model_replacement: '$1' @@ -2571,7 +2598,7 @@ device_parsers: # LG # @ref: http://www.lg.com/uk/mobile ######### - - regex: '; *(E[34][0-9]{2}|LS[6-8][0-9]{2}|VS[6-9][0-9]+[^;/]+|Nexus [45]|GT540f?|Optimus (?:2X|G|4X HD)|OptimusX4HD) *(?:Build|;)' + - regex: '; *(E[34][0-9]{2}|LS[6-8][0-9]{2}|VS[6-9][0-9]+[^;/]+|Nexus 4|Nexus 5X?|GT540f?|Optimus (?:2X|G|4X HD)|OptimusX4HD) *(?:Build|;)' device_replacement: '$1' brand_replacement: 'LG' model_replacement: '$1' @@ -3841,7 +3868,7 @@ device_parsers: # XiaoMi # @ref: http://www.xiaomi.com/event/buyphone ######### - - regex: '; *((MI|HM|MI-ONE|Redmi)[ -](NOTE |Note )?[^;/]*) Build/' + - regex: '; *((MI|HM|MI-ONE|Redmi)[ -](NOTE |Note )?[^;/]*) (Build|MIUI)/' device_replacement: 'XiaoMi $1' brand_replacement: 'XiaoMi' model_replacement: '$1' @@ -4700,6 +4727,10 @@ device_parsers: - regex: '(WebTV)/\d+.\d+' brand_replacement: 'Generic_Inettv' model_replacement: '$1' + # Roku Digital-Video-Players https://www.roku.com/ + - regex: '^(Roku)/DVP-\d+\.\d+' + brand_replacement: 'Generic_Inettv' + model_replacement: '$1' ########## # Generic Tablet @@ -4764,4 +4795,4 @@ device_parsers: regex_flag: 'i' device_replacement: 'Generic Feature Phone' brand_replacement: 'Generic' - model_replacement: 'Feature Phone' + model_replacement: 'Feature Phone' \ No newline at end of file From 97918a561971ee21cbf3b888f50458f108efaec0 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 22 Jun 2016 22:24:04 +0200 Subject: [PATCH 255/285] Resolves #986 File uploads possible through /content/filemanager/index.html --- changelog.md | 1 + .../Content/filemanager/LICENSE.txt | 0 .../Content/filemanager/conf.json | 33 + .../css/images/animated-overlay.gif | Bin .../css/images/ui-bg_flat_0_aaaaaa_40x100.png | Bin .../images/ui-bg_flat_75_ffffff_40x100.png | Bin .../images/ui-bg_glass_55_fbf9ee_1x400.png | Bin .../images/ui-bg_glass_65_ffffff_1x400.png | Bin .../images/ui-bg_glass_75_dadada_1x400.png | Bin .../images/ui-bg_glass_75_e6e6e6_1x400.png | Bin .../images/ui-bg_glass_95_fef1ec_1x400.png | Bin .../ui-bg_highlight-soft_75_cccccc_1x100.png | Bin .../css/images/ui-icons_222222_256x240.png | Bin .../css/images/ui-icons_2e83ff_256x240.png | Bin .../css/images/ui-icons_454545_256x240.png | Bin .../css/images/ui-icons_888888_256x240.png | Bin .../css/images/ui-icons_cd0a0a_256x240.png | Bin .../css/jquery-ui-1.10.4.custom.css | 0 .../css/jquery-ui-1.10.4.custom.min.css | 0 .../Content/filemanager/css/main.css | 0 .../Content/filemanager/css/main.min.css | 0 .../images/action-folder-paste.png | Bin .../images/action-folder-rename.png | Bin .../Content/filemanager/images/arrow_down.png | Bin .../Content/filemanager/images/arrow_up.png | Bin .../Content/filemanager/images/blank.gif | Bin .../Content/filemanager/images/copy.png | Bin .../Content/filemanager/images/cut.png | Bin .../Content/filemanager/images/dir-minus.png | Bin .../Content/filemanager/images/dir-plus.png | Bin .../Content/filemanager/images/file-add.png | Bin .../filemanager/images/file-delete.png | Bin .../filemanager/images/file-download.png | Bin .../filemanager/images/file-duplicate.png | Bin .../filemanager/images/file-upload.png | Bin .../filetypes/big/file_extension_3gp.png | Bin .../filetypes/big/file_extension_7z.png | Bin .../filetypes/big/file_extension_ace.png | Bin .../filetypes/big/file_extension_ai.png | Bin .../filetypes/big/file_extension_aif.png | Bin .../filetypes/big/file_extension_aiff.png | Bin .../filetypes/big/file_extension_amr.png | Bin .../filetypes/big/file_extension_asf.png | Bin .../filetypes/big/file_extension_asx.png | Bin .../filetypes/big/file_extension_bat.png | Bin .../filetypes/big/file_extension_bin.png | Bin .../filetypes/big/file_extension_bmp.png | Bin .../filetypes/big/file_extension_bup.png | Bin .../filetypes/big/file_extension_cab.png | Bin .../filetypes/big/file_extension_cbr.png | Bin .../filetypes/big/file_extension_cda.png | Bin .../filetypes/big/file_extension_cdl.png | Bin .../filetypes/big/file_extension_cdr.png | Bin .../filetypes/big/file_extension_chm.png | Bin .../filetypes/big/file_extension_dat.png | Bin .../filetypes/big/file_extension_divx.png | Bin .../filetypes/big/file_extension_dll.png | Bin .../filetypes/big/file_extension_dmg.png | Bin .../filetypes/big/file_extension_doc.png | Bin .../filetypes/big/file_extension_dss.png | Bin .../filetypes/big/file_extension_dvf.png | Bin .../filetypes/big/file_extension_dwg.png | Bin .../filetypes/big/file_extension_eml.png | Bin .../filetypes/big/file_extension_eps.png | Bin .../filetypes/big/file_extension_exe.png | Bin .../filetypes/big/file_extension_fla.png | Bin .../filetypes/big/file_extension_flv.png | Bin .../filetypes/big/file_extension_gif.png | Bin .../filetypes/big/file_extension_gz.png | Bin .../filetypes/big/file_extension_hqx.png | Bin .../filetypes/big/file_extension_htm.png | Bin .../filetypes/big/file_extension_html.png | Bin .../filetypes/big/file_extension_ifo.png | Bin .../filetypes/big/file_extension_indd.png | Bin .../filetypes/big/file_extension_iso.png | Bin .../filetypes/big/file_extension_jar.png | Bin .../filetypes/big/file_extension_jpeg.png | Bin .../filetypes/big/file_extension_jpg.png | Bin .../filetypes/big/file_extension_lnk.png | Bin .../filetypes/big/file_extension_log.png | Bin .../filetypes/big/file_extension_m4a.png | Bin .../filetypes/big/file_extension_m4b.png | Bin .../filetypes/big/file_extension_m4p.png | Bin .../filetypes/big/file_extension_m4v.png | Bin .../filetypes/big/file_extension_mcd.png | Bin .../filetypes/big/file_extension_mdb.png | Bin .../filetypes/big/file_extension_mid.png | Bin .../filetypes/big/file_extension_mov.png | Bin .../filetypes/big/file_extension_mp2.png | Bin .../filetypes/big/file_extension_mp4.png | Bin .../filetypes/big/file_extension_mpeg.png | Bin .../filetypes/big/file_extension_mpg.png | Bin .../filetypes/big/file_extension_msi.png | Bin .../filetypes/big/file_extension_mswmm.png | Bin .../filetypes/big/file_extension_ogg.png | Bin .../filetypes/big/file_extension_pdf.png | Bin .../filetypes/big/file_extension_png.png | Bin .../filetypes/big/file_extension_pps.png | Bin .../filetypes/big/file_extension_ps.png | Bin .../filetypes/big/file_extension_psd.png | Bin .../filetypes/big/file_extension_pst.png | Bin .../filetypes/big/file_extension_ptb.png | Bin .../filetypes/big/file_extension_pub.png | Bin .../filetypes/big/file_extension_qbb.png | Bin .../filetypes/big/file_extension_qbw.png | Bin .../filetypes/big/file_extension_qxd.png | Bin .../filetypes/big/file_extension_ram.png | Bin .../filetypes/big/file_extension_rar.png | Bin .../filetypes/big/file_extension_rm.png | Bin .../filetypes/big/file_extension_rmvb.png | Bin .../filetypes/big/file_extension_rtf.png | Bin .../filetypes/big/file_extension_sea.png | Bin .../filetypes/big/file_extension_ses.png | Bin .../filetypes/big/file_extension_sit.png | Bin .../filetypes/big/file_extension_sitx.png | Bin .../filetypes/big/file_extension_ss.png | Bin .../filetypes/big/file_extension_swf.png | Bin .../filetypes/big/file_extension_tgz.png | Bin .../filetypes/big/file_extension_thm.png | Bin .../filetypes/big/file_extension_tif.png | Bin .../filetypes/big/file_extension_tmp.png | Bin .../filetypes/big/file_extension_torrent.png | Bin .../filetypes/big/file_extension_ttf.png | Bin .../filetypes/big/file_extension_txt.png | Bin .../filetypes/big/file_extension_vcd.png | Bin .../filetypes/big/file_extension_vob.png | Bin .../filetypes/big/file_extension_wav.png | Bin .../filetypes/big/file_extension_wma.png | Bin .../filetypes/big/file_extension_wmv.png | Bin .../filetypes/big/file_extension_wps.png | Bin .../filetypes/big/file_extension_xls.png | Bin .../filetypes/big/file_extension_xpi.png | Bin .../filetypes/big/file_extension_zip.png | Bin .../images/filetypes/big/unknown.png | Bin .../images/filetypes/file_extension_3gp.png | Bin .../images/filetypes/file_extension_7z.png | Bin .../images/filetypes/file_extension_ace.png | Bin .../images/filetypes/file_extension_ai.png | Bin .../images/filetypes/file_extension_aif.png | Bin .../images/filetypes/file_extension_aiff.png | Bin .../images/filetypes/file_extension_amr.png | Bin .../images/filetypes/file_extension_asf.png | Bin .../images/filetypes/file_extension_asx.png | Bin .../images/filetypes/file_extension_bat.png | Bin .../images/filetypes/file_extension_bin.png | Bin .../images/filetypes/file_extension_bmp.png | Bin .../images/filetypes/file_extension_bup.png | Bin .../images/filetypes/file_extension_cab.png | Bin .../images/filetypes/file_extension_cbr.png | Bin .../images/filetypes/file_extension_cda.png | Bin .../images/filetypes/file_extension_cdl.png | Bin .../images/filetypes/file_extension_cdr.png | Bin .../images/filetypes/file_extension_chm.png | Bin .../images/filetypes/file_extension_dat.png | Bin .../images/filetypes/file_extension_divx.png | Bin .../images/filetypes/file_extension_dll.png | Bin .../images/filetypes/file_extension_dmg.png | Bin .../images/filetypes/file_extension_doc.png | Bin .../images/filetypes/file_extension_dss.png | Bin .../images/filetypes/file_extension_dvf.png | Bin .../images/filetypes/file_extension_dwg.png | Bin .../images/filetypes/file_extension_eml.png | Bin .../images/filetypes/file_extension_eps.png | Bin .../images/filetypes/file_extension_exe.png | Bin .../images/filetypes/file_extension_fla.png | Bin .../images/filetypes/file_extension_flv.png | Bin .../images/filetypes/file_extension_gif.png | Bin .../images/filetypes/file_extension_gz.png | Bin .../images/filetypes/file_extension_hqx.png | Bin .../images/filetypes/file_extension_htm.png | Bin .../images/filetypes/file_extension_html.png | Bin .../images/filetypes/file_extension_ifo.png | Bin .../images/filetypes/file_extension_indd.png | Bin .../images/filetypes/file_extension_iso.png | Bin .../images/filetypes/file_extension_jar.png | Bin .../images/filetypes/file_extension_jpeg.png | Bin .../images/filetypes/file_extension_jpg.png | Bin .../images/filetypes/file_extension_lnk.png | Bin .../images/filetypes/file_extension_log.png | Bin .../images/filetypes/file_extension_m4a.png | Bin .../images/filetypes/file_extension_m4b.png | Bin .../images/filetypes/file_extension_m4p.png | Bin .../images/filetypes/file_extension_m4v.png | Bin .../images/filetypes/file_extension_mcd.png | Bin .../images/filetypes/file_extension_mdb.png | Bin .../images/filetypes/file_extension_mid.png | Bin .../images/filetypes/file_extension_mov.png | Bin .../images/filetypes/file_extension_mp2.png | Bin .../images/filetypes/file_extension_mp4.png | Bin .../images/filetypes/file_extension_mpeg.png | Bin .../images/filetypes/file_extension_mpg.png | Bin .../images/filetypes/file_extension_msi.png | Bin .../images/filetypes/file_extension_mswmm.png | Bin .../images/filetypes/file_extension_ogg.png | Bin .../images/filetypes/file_extension_pdf.png | Bin .../images/filetypes/file_extension_png.png | Bin .../images/filetypes/file_extension_pps.png | Bin .../images/filetypes/file_extension_ps.png | Bin .../images/filetypes/file_extension_psd.png | Bin .../images/filetypes/file_extension_pst.png | Bin .../images/filetypes/file_extension_ptb.png | Bin .../images/filetypes/file_extension_pub.png | Bin .../images/filetypes/file_extension_qbb.png | Bin .../images/filetypes/file_extension_qbw.png | Bin .../images/filetypes/file_extension_qxd.png | Bin .../images/filetypes/file_extension_ram.png | Bin .../images/filetypes/file_extension_rar.png | Bin .../images/filetypes/file_extension_rm.png | Bin .../images/filetypes/file_extension_rmvb.png | Bin .../images/filetypes/file_extension_rtf.png | Bin .../images/filetypes/file_extension_sea.png | Bin .../images/filetypes/file_extension_ses.png | Bin .../images/filetypes/file_extension_sit.png | Bin .../images/filetypes/file_extension_sitx.png | Bin .../images/filetypes/file_extension_ss.png | Bin .../images/filetypes/file_extension_swf.png | Bin .../images/filetypes/file_extension_tgz.png | Bin .../images/filetypes/file_extension_thm.png | Bin .../images/filetypes/file_extension_tif.png | Bin .../images/filetypes/file_extension_tmp.png | Bin .../filetypes/file_extension_torrent.png | Bin .../images/filetypes/file_extension_ttf.png | Bin .../images/filetypes/file_extension_txt.png | Bin .../images/filetypes/file_extension_vcd.png | Bin .../images/filetypes/file_extension_vob.png | Bin .../images/filetypes/file_extension_wav.png | Bin .../images/filetypes/file_extension_wma.png | Bin .../images/filetypes/file_extension_wmv.png | Bin .../images/filetypes/file_extension_wps.png | Bin .../images/filetypes/file_extension_xls.png | Bin .../images/filetypes/file_extension_xpi.png | Bin .../images/filetypes/file_extension_zip.png | Bin .../filemanager/images/filetypes/unknown.png | Bin .../Content/filemanager/images/find.png | Bin .../Content/filemanager/images/folder-add.png | Bin .../filemanager/images/folder-delete.png | Bin .../filemanager/images/folder-download.png | Bin .../filemanager/images/folder-green.png | Bin .../filemanager/images/folder-opened.png | Bin .../Content/filemanager/images/folder.png | Bin .../filemanager/images/loading-dir.gif | Bin .../Content/filemanager/images/loading.gif | Bin .../Content/filemanager/images/paste.png | Bin .../Content/filemanager/images/preview.png | Bin .../Content/filemanager/images/rename.png | Bin .../Content/filemanager/images/search.png | Bin .../Content/filemanager/images/select.png | Bin .../Content/filemanager/images/upload-big.png | Bin .../Content/filemanager/images/view-list.png | Bin .../Content/filemanager/images/view-tile.png | Bin .../Content/filemanager/index.html | 0 .../Content/filemanager/js/custom.js | 0 .../Content/filemanager/js/directory.js | 0 .../Content/filemanager/js/file.js | 0 .../filemanager/js/fileman-modified.zip | Bin .../Content/filemanager/js/filetypes.js | 0 .../filemanager/js/jquery-1.10.2.min.js | 0 .../filemanager/js/jquery-dateFormat.min.js | 0 .../js/jquery-ui-1.10.4.custom.min.js | 0 .../Content/filemanager/js/main.js | 0 .../Content/filemanager/js/main.min.js | 0 .../Content/filemanager/js/mini-main.js | 0 .../Content/filemanager/js/tiny_mce_popup.js | 0 .../Content/filemanager/js/utils.js | 0 .../Content/filemanager/lang/bg.json | 0 .../Content/filemanager/lang/de.json | 0 .../Content/filemanager/lang/en.json | 0 .../Content/filemanager/lang/fr.json | 0 .../Controllers/RoxyFileManagerController.cs | 828 ++++++++++++++++++ .../Administration/SmartStore.Admin.csproj | 269 ++++++ .../Shared/EditorTemplates/RichEditor.cshtml | 2 +- .../Content/filemanager/asp_net/main.ashx | 719 --------------- .../Content/filemanager/conf.json | 34 - .../SmartStore.Web/SmartStore.Web.csproj | 268 ------ 274 files changed, 1132 insertions(+), 1022 deletions(-) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/LICENSE.txt (100%) create mode 100644 src/Presentation/SmartStore.Web/Administration/Content/filemanager/conf.json rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/animated-overlay.gif (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-bg_flat_0_aaaaaa_40x100.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-bg_flat_75_ffffff_40x100.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-bg_glass_55_fbf9ee_1x400.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-bg_glass_65_ffffff_1x400.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-bg_glass_75_dadada_1x400.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-bg_glass_75_e6e6e6_1x400.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-bg_glass_95_fef1ec_1x400.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-icons_222222_256x240.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-icons_2e83ff_256x240.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-icons_454545_256x240.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-icons_888888_256x240.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/images/ui-icons_cd0a0a_256x240.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/jquery-ui-1.10.4.custom.css (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/jquery-ui-1.10.4.custom.min.css (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/main.css (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/css/main.min.css (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/action-folder-paste.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/action-folder-rename.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/arrow_down.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/arrow_up.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/blank.gif (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/copy.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/cut.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/dir-minus.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/dir-plus.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/file-add.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/file-delete.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/file-download.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/file-duplicate.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/file-upload.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_3gp.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_7z.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ace.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ai.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_aif.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_aiff.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_amr.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_asf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_asx.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_bat.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_bin.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_bmp.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_bup.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_cab.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_cbr.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_cda.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_cdl.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_cdr.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_chm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_dat.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_divx.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_dll.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_dmg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_doc.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_dss.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_dvf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_dwg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_eml.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_eps.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_exe.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_fla.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_flv.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_gif.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_gz.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_hqx.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_htm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_html.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ifo.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_indd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_iso.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_jar.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_jpeg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_jpg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_lnk.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_log.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_m4a.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_m4b.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_m4p.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_m4v.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mcd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mdb.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mid.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mov.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mp2.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mp4.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mpeg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mpg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_msi.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_mswmm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ogg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_pdf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_png.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_pps.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ps.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_psd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_pst.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ptb.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_pub.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_qbb.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_qbw.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_qxd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ram.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_rar.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_rm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_rmvb.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_rtf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_sea.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ses.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_sit.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_sitx.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ss.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_swf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_tgz.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_thm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_tif.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_tmp.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_torrent.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_ttf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_txt.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_vcd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_vob.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_wav.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_wma.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_wmv.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_wps.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_xls.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_xpi.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/file_extension_zip.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/big/unknown.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_3gp.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_7z.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ace.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ai.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_aif.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_aiff.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_amr.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_asf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_asx.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_bat.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_bin.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_bmp.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_bup.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_cab.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_cbr.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_cda.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_cdl.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_cdr.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_chm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_dat.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_divx.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_dll.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_dmg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_doc.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_dss.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_dvf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_dwg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_eml.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_eps.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_exe.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_fla.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_flv.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_gif.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_gz.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_hqx.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_htm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_html.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ifo.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_indd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_iso.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_jar.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_jpeg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_jpg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_lnk.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_log.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_m4a.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_m4b.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_m4p.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_m4v.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mcd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mdb.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mid.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mov.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mp2.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mp4.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mpeg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mpg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_msi.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_mswmm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ogg.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_pdf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_png.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_pps.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ps.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_psd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_pst.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ptb.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_pub.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_qbb.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_qbw.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_qxd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ram.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_rar.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_rm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_rmvb.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_rtf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_sea.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ses.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_sit.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_sitx.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ss.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_swf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_tgz.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_thm.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_tif.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_tmp.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_torrent.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_ttf.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_txt.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_vcd.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_vob.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_wav.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_wma.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_wmv.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_wps.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_xls.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_xpi.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/file_extension_zip.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/filetypes/unknown.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/find.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/folder-add.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/folder-delete.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/folder-download.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/folder-green.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/folder-opened.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/folder.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/loading-dir.gif (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/loading.gif (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/paste.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/preview.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/rename.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/search.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/select.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/upload-big.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/view-list.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/images/view-tile.png (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/index.html (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/custom.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/directory.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/file.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/fileman-modified.zip (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/filetypes.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/jquery-1.10.2.min.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/jquery-dateFormat.min.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/jquery-ui-1.10.4.custom.min.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/main.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/main.min.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/mini-main.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/tiny_mce_popup.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/js/utils.js (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/lang/bg.json (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/lang/de.json (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/lang/en.json (100%) rename src/Presentation/SmartStore.Web/{ => Administration}/Content/filemanager/lang/fr.json (100%) create mode 100644 src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs delete mode 100644 src/Presentation/SmartStore.Web/Content/filemanager/asp_net/main.ashx delete mode 100644 src/Presentation/SmartStore.Web/Content/filemanager/conf.json diff --git a/changelog.md b/changelog.md index 8efe9bbc11..47f492ff48 100644 --- a/changelog.md +++ b/changelog.md @@ -62,6 +62,7 @@ * Payone: Hash string incorrect for frontend API payments where the order has more than 9 products * Export mail notification: Download link not working if SSL is enabled * Discount rule has spent amount including sub total option can cause wrong discount calculation if the cart contains a product several times +* #986 File uploads possible through /content/filemanager/index.html ## SmartStore.NET 2.5 diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/LICENSE.txt b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/LICENSE.txt similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/LICENSE.txt rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/LICENSE.txt diff --git a/src/Presentation/SmartStore.Web/Administration/Content/filemanager/conf.json b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/conf.json new file mode 100644 index 0000000000..c08220bdfe --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/conf.json @@ -0,0 +1,33 @@ +{ + "FILES_ROOT": "~/Media/Uploaded", + "SESSION_PATH_KEY": "", + "THUMBS_VIEW_WIDTH": "140", + "THUMBS_VIEW_HEIGHT": "120", + "PREVIEW_THUMB_WIDTH": "300", + "PREVIEW_THUMB_HEIGHT": "200", + "MAX_IMAGE_WIDTH": "0", + "MAX_IMAGE_HEIGHT": "0", + "INTEGRATION": "ckeditor", + "DIRLIST": "../../../Admin/RoxyFileManager/ProcessRequest?a=DIRLIST", + "CREATEDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=CREATEDIR", + "DELETEDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=DELETEDIR", + "MOVEDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=MOVEDIR", + "COPYDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=COPYDIR", + "RENAMEDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=RENAMEDIR", + "FILESLIST": "../../../Admin/RoxyFileManager/ProcessRequest?a=FILESLIST", + "UPLOAD": "../../../Admin/RoxyFileManager/ProcessRequest?a=UPLOAD", + "DOWNLOAD": "../../../Admin/RoxyFileManager/ProcessRequest?a=DOWNLOAD", + "DOWNLOADDIR": "../../../Admin/RoxyFileManager/ProcessRequest?a=DOWNLOADDIR", + "DELETEFILE": "../../../Admin/RoxyFileManager/ProcessRequest?a=DELETEFILE", + "MOVEFILE": "../../../Admin/RoxyFileManager/ProcessRequest?a=MOVEFILE", + "COPYFILE": "../../../Admin/RoxyFileManager/ProcessRequest?a=COPYFILE", + "RENAMEFILE": "../../../Admin/RoxyFileManager/ProcessRequest?a=RENAMEFILE", + "GENERATETHUMB": "../../../Admin/RoxyFileManager/ProcessRequest?a=GENERATETHUMB", + "DEFAULTVIEW": "list", + "FORBIDDEN_UPLOADS": "zip js jsp jsb mhtml mht xhtml xht php phtml php3 php4 php5 phps shtml jhtml pl sh py cgi exe scr dll msi vbs bat com pif cmd vxd cpl htpasswd htaccess", + "ALLOWED_UPLOADS": "", + "FILEPERMISSIONS": "0644", + "DIRPERMISSIONS": "0755", + "LANG": "auto", + "DATEFORMAT": "dd.MM.yyyy HH:mm" +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/animated-overlay.gif b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/animated-overlay.gif similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/animated-overlay.gif rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/animated-overlay.gif diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_flat_0_aaaaaa_40x100.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_flat_0_aaaaaa_40x100.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_flat_0_aaaaaa_40x100.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_flat_0_aaaaaa_40x100.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_flat_75_ffffff_40x100.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_flat_75_ffffff_40x100.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_flat_75_ffffff_40x100.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_flat_75_ffffff_40x100.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_55_fbf9ee_1x400.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_55_fbf9ee_1x400.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_55_fbf9ee_1x400.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_55_fbf9ee_1x400.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_65_ffffff_1x400.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_65_ffffff_1x400.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_65_ffffff_1x400.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_65_ffffff_1x400.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_75_dadada_1x400.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_75_dadada_1x400.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_75_dadada_1x400.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_75_dadada_1x400.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_75_e6e6e6_1x400.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_75_e6e6e6_1x400.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_75_e6e6e6_1x400.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_75_e6e6e6_1x400.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_95_fef1ec_1x400.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_95_fef1ec_1x400.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_glass_95_fef1ec_1x400.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_glass_95_fef1ec_1x400.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-bg_highlight-soft_75_cccccc_1x100.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_222222_256x240.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_222222_256x240.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_222222_256x240.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_222222_256x240.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_2e83ff_256x240.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_2e83ff_256x240.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_2e83ff_256x240.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_2e83ff_256x240.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_454545_256x240.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_454545_256x240.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_454545_256x240.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_454545_256x240.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_888888_256x240.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_888888_256x240.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_888888_256x240.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_888888_256x240.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_cd0a0a_256x240.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_cd0a0a_256x240.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/images/ui-icons_cd0a0a_256x240.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/images/ui-icons_cd0a0a_256x240.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/jquery-ui-1.10.4.custom.css b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/jquery-ui-1.10.4.custom.css similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/jquery-ui-1.10.4.custom.css rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/jquery-ui-1.10.4.custom.css diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/jquery-ui-1.10.4.custom.min.css b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/jquery-ui-1.10.4.custom.min.css similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/jquery-ui-1.10.4.custom.min.css rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/jquery-ui-1.10.4.custom.min.css diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/main.css b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/main.css similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/main.css rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/main.css diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/css/main.min.css b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/main.min.css similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/css/main.min.css rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/css/main.min.css diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/action-folder-paste.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-paste.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/action-folder-paste.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-paste.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/action-folder-rename.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-rename.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/action-folder-rename.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/action-folder-rename.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/arrow_down.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_down.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/arrow_down.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_down.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/arrow_up.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_up.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/arrow_up.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/arrow_up.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/blank.gif b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/blank.gif similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/blank.gif rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/blank.gif diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/copy.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/copy.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/copy.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/copy.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/cut.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/cut.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/cut.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/cut.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/dir-minus.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-minus.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/dir-minus.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-minus.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/dir-plus.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-plus.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/dir-plus.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/dir-plus.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/file-add.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-add.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/file-add.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-add.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/file-delete.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-delete.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/file-delete.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-delete.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/file-download.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-download.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/file-download.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-download.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/file-duplicate.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-duplicate.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/file-duplicate.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-duplicate.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/file-upload.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-upload.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/file-upload.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/file-upload.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_3gp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_3gp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_3gp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_3gp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_7z.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_7z.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_7z.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_7z.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ace.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ace.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ace.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ace.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ai.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ai.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ai.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ai.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_aif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_aif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_aiff.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aiff.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_aiff.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_aiff.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_amr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_amr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_amr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_amr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_asf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_asf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_asx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_asx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_asx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bat.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bat.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bat.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bat.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bin.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bin.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bin.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bin.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bmp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bmp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bmp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bmp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bup.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bup.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_bup.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_bup.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cab.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cab.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cab.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cab.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cbr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cbr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cbr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cbr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cda.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cda.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cda.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cda.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cdl.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdl.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cdl.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdl.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cdr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_cdr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_cdr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_chm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_chm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_chm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_chm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dat.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dat.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dat.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dat.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_divx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_divx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_divx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_divx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dll.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dll.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dll.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dll.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dmg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dmg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dmg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dmg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_doc.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_doc.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_doc.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_doc.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dss.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dss.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dss.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dss.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dvf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dvf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dvf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dvf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dwg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dwg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_dwg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_dwg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_eml.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eml.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_eml.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eml.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_eps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_eps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_eps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_exe.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_exe.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_exe.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_exe.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_fla.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_fla.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_fla.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_fla.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_flv.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_flv.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_flv.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_flv.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_gif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_gif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_gz.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gz.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_gz.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_gz.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_hqx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_hqx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_hqx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_hqx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_htm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_htm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_htm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_htm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_html.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_html.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_html.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_html.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ifo.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ifo.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ifo.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ifo.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_indd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_indd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_indd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_indd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_iso.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_iso.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_iso.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_iso.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jar.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jar.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jar.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jar.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jpeg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpeg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jpeg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpeg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jpg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_jpg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_jpg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_lnk.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_lnk.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_lnk.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_lnk.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_log.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_log.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_log.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_log.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4a.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4a.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4a.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4a.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4b.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4b.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4b.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4b.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4p.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4p.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4p.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4p.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4v.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4v.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_m4v.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_m4v.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mcd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mcd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mcd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mcd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mdb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mdb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mdb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mdb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mid.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mid.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mid.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mid.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mov.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mov.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mov.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mov.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mp2.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp2.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mp2.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp2.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mp4.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp4.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mp4.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mp4.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mpeg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpeg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mpeg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpeg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mpg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mpg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mpg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_msi.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_msi.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_msi.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_msi.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mswmm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mswmm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_mswmm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_mswmm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ogg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ogg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ogg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ogg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pdf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pdf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pdf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pdf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_png.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_png.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_png.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_png.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_psd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_psd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_psd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_psd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pst.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pst.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pst.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pst.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ptb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ptb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ptb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ptb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pub.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pub.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_pub.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_pub.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qbb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qbb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qbw.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbw.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qbw.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qbw.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qxd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qxd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_qxd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_qxd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ram.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ram.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ram.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ram.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rar.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rar.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rar.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rar.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rmvb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rmvb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rmvb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rmvb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rtf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rtf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_rtf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_rtf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sea.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sea.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sea.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sea.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ses.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ses.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ses.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ses.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sit.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sit.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sit.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sit.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sitx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sitx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_sitx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_sitx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ss.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ss.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ss.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ss.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_swf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_swf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_swf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_swf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tgz.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tgz.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tgz.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tgz.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_thm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_thm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_thm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_thm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tmp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tmp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_tmp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_tmp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_torrent.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_torrent.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_torrent.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_torrent.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ttf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ttf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_ttf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_ttf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_txt.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_txt.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_txt.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_txt.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_vcd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vcd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_vcd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vcd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_vob.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vob.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_vob.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_vob.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wav.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wav.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wav.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wav.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wma.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wma.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wma.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wma.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wmv.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wmv.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wmv.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wmv.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_wps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_wps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_xls.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xls.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_xls.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xls.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_xpi.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xpi.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_xpi.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_xpi.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_zip.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_zip.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/file_extension_zip.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/file_extension_zip.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/unknown.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/unknown.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/big/unknown.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/big/unknown.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_3gp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_3gp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_3gp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_3gp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_7z.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_7z.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_7z.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_7z.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ace.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ace.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ace.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ace.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ai.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ai.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ai.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ai.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_aif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_aif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_aiff.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aiff.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_aiff.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_aiff.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_amr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_amr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_amr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_amr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_asf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_asf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_asx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_asx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_asx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bat.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bat.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bat.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bat.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bin.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bin.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bin.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bin.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bmp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bmp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bmp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bmp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bup.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bup.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_bup.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_bup.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cab.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cab.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cab.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cab.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cbr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cbr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cbr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cbr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cda.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cda.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cda.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cda.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cdl.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdl.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cdl.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdl.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cdr.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdr.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_cdr.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_cdr.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_chm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_chm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_chm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_chm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dat.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dat.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dat.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dat.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_divx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_divx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_divx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_divx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dll.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dll.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dll.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dll.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dmg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dmg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dmg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dmg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_doc.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_doc.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_doc.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_doc.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dss.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dss.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dss.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dss.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dvf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dvf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dvf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dvf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dwg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dwg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_dwg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_dwg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_eml.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eml.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_eml.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eml.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_eps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_eps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_eps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_exe.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_exe.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_exe.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_exe.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_fla.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_fla.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_fla.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_fla.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_flv.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_flv.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_flv.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_flv.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_gif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_gif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_gz.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gz.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_gz.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_gz.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_hqx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_hqx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_hqx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_hqx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_htm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_htm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_htm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_htm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_html.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_html.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_html.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_html.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ifo.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ifo.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ifo.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ifo.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_indd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_indd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_indd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_indd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_iso.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_iso.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_iso.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_iso.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jar.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jar.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jar.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jar.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jpeg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpeg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jpeg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpeg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jpg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_jpg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_jpg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_lnk.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_lnk.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_lnk.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_lnk.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_log.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_log.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_log.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_log.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4a.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4a.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4a.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4a.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4b.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4b.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4b.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4b.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4p.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4p.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4p.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4p.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4v.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4v.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_m4v.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_m4v.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mcd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mcd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mcd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mcd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mdb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mdb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mdb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mdb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mid.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mid.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mid.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mid.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mov.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mov.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mov.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mov.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mp2.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp2.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mp2.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp2.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mp4.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp4.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mp4.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mp4.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mpeg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpeg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mpeg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpeg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mpg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mpg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mpg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_msi.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_msi.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_msi.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_msi.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mswmm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mswmm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_mswmm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_mswmm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ogg.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ogg.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ogg.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ogg.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pdf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pdf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pdf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pdf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_png.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_png.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_png.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_png.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_psd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_psd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_psd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_psd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pst.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pst.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pst.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pst.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ptb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ptb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ptb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ptb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pub.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pub.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_pub.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_pub.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qbb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qbb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qbw.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbw.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qbw.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qbw.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qxd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qxd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_qxd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_qxd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ram.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ram.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ram.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ram.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rar.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rar.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rar.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rar.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rmvb.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rmvb.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rmvb.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rmvb.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rtf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rtf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_rtf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_rtf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sea.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sea.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sea.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sea.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ses.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ses.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ses.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ses.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sit.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sit.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sit.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sit.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sitx.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sitx.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_sitx.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_sitx.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ss.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ss.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ss.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ss.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_swf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_swf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_swf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_swf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tgz.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tgz.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tgz.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tgz.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_thm.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_thm.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_thm.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_thm.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tif.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tif.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tif.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tif.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tmp.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tmp.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_tmp.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_tmp.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_torrent.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_torrent.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_torrent.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_torrent.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ttf.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ttf.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_ttf.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_ttf.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_txt.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_txt.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_txt.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_txt.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_vcd.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vcd.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_vcd.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vcd.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_vob.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vob.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_vob.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_vob.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wav.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wav.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wav.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wav.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wma.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wma.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wma.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wma.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wmv.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wmv.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wmv.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wmv.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wps.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wps.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_wps.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_wps.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_xls.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xls.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_xls.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xls.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_xpi.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xpi.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_xpi.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_xpi.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_zip.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_zip.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/file_extension_zip.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/file_extension_zip.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/unknown.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/unknown.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/filetypes/unknown.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/filetypes/unknown.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/find.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/find.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/find.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/find.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder-add.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-add.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder-add.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-add.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder-delete.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-delete.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder-delete.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-delete.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder-download.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-download.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder-download.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-download.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder-green.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-green.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder-green.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-green.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder-opened.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-opened.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder-opened.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder-opened.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/folder.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/folder.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/folder.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/loading-dir.gif b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/loading-dir.gif similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/loading-dir.gif rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/loading-dir.gif diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/loading.gif b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/loading.gif similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/loading.gif rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/loading.gif diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/paste.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/paste.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/paste.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/paste.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/preview.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/preview.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/preview.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/preview.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/rename.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/rename.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/rename.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/rename.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/search.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/search.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/search.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/search.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/select.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/select.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/select.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/select.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/upload-big.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/upload-big.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/upload-big.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/upload-big.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/view-list.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-list.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/view-list.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-list.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/images/view-tile.png b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-tile.png similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/images/view-tile.png rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/images/view-tile.png diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/index.html b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/index.html similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/index.html rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/index.html diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/custom.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/custom.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/custom.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/custom.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/directory.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/directory.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/directory.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/directory.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/file.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/file.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/file.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/fileman-modified.zip b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/fileman-modified.zip similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/fileman-modified.zip rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/fileman-modified.zip diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/filetypes.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/filetypes.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/filetypes.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/filetypes.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-1.10.2.min.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-1.10.2.min.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-1.10.2.min.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-1.10.2.min.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-dateFormat.min.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-dateFormat.min.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-dateFormat.min.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-dateFormat.min.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-ui-1.10.4.custom.min.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-ui-1.10.4.custom.min.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/jquery-ui-1.10.4.custom.min.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/jquery-ui-1.10.4.custom.min.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/main.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/main.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/main.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/main.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/main.min.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/main.min.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/main.min.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/main.min.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/mini-main.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/mini-main.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/mini-main.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/mini-main.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/tiny_mce_popup.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/tiny_mce_popup.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/tiny_mce_popup.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/tiny_mce_popup.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/js/utils.js b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/utils.js similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/js/utils.js rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/js/utils.js diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/lang/bg.json b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/bg.json similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/lang/bg.json rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/bg.json diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/lang/de.json b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/de.json similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/lang/de.json rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/de.json diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/lang/en.json b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/en.json similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/lang/en.json rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/en.json diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/lang/fr.json b/src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/fr.json similarity index 100% rename from src/Presentation/SmartStore.Web/Content/filemanager/lang/fr.json rename to src/Presentation/SmartStore.Web/Administration/Content/filemanager/lang/fr.json diff --git a/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs b/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs new file mode 100644 index 0000000000..e0c2ca36c2 --- /dev/null +++ b/src/Presentation/SmartStore.Web/Administration/Controllers/RoxyFileManagerController.cs @@ -0,0 +1,828 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.IO.Compression; +using System.Text.RegularExpressions; +using System.Web; +using SmartStore.Services.Security; +using SmartStore.Web.Framework.Controllers; +using SmartStore.Web.Framework.Security; + +namespace SmartStore.Admin.Controllers +{ + [AdminAuthorize] + public class RoxyFileManagerController : AdminControllerBase + { + private const string CONFIG_FILE = "~/Administration/Content/filemanager/conf.json"; + + private Dictionary _lang = null; + private Dictionary _settings = null; + + private readonly IPermissionService _permissionService; + private readonly HttpContextBase _context; + private readonly HttpResponseBase _response; + + public RoxyFileManagerController( + IPermissionService permissionService, + HttpContextBase context) + { + _permissionService = permissionService; + _context = context; + _response = _context.Response; + } + + #region Utilities + + private string FixPath(string path) + { + if (path == null) + { + path = ""; + } + + if (!path.StartsWith("~")) + { + if (!path.StartsWith("/")) + path = "/" + path; + path = "~" + path; + } + + //var filesRoot = GetSetting("FILES_ROOT"); + //if (!path.ToLowerInvariant().Contains(filesRoot.ToLowerInvariant())) + //{ + // path = filesRoot; + //} + + return _context.Server.MapPath(path); + } + + private string GetLangFile() + { + //return "../lang/en.json"; + + var filename = "../lang/" + GetSetting("LANG") + ".json"; + + if (!System.IO.File.Exists(_context.Server.MapPath(filename))) + filename = "../lang/en.json"; + + return filename; + } + + protected string LangRes(string name) + { + string ret = name; + if (_lang == null) + _lang = ParseJSON(GetLangFile()); + if (_lang.ContainsKey(name)) + ret = _lang[name]; + + return ret; + } + + protected string GetFileType(string ext) + { + string ret = "file"; + ext = ext.ToLower(); + if (ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif") + ret = "image"; + else if (ext == ".swf" || ext == ".flv") + ret = "flash"; + return ret; + } + + protected bool CanHandleFile(string filename) + { + bool ret = false; + FileInfo file = new FileInfo(filename); + string ext = file.Extension.Replace(".", "").ToLower(); + string setting = GetSetting("FORBIDDEN_UPLOADS").Trim().ToLower(); + if (setting != "") + { + ArrayList tmp = new ArrayList(); + tmp.AddRange(Regex.Split(setting, "\\s+")); + if (!tmp.Contains(ext)) + ret = true; + } + setting = GetSetting("ALLOWED_UPLOADS").Trim().ToLower(); + if (setting != "") + { + ArrayList tmp = new ArrayList(); + tmp.AddRange(Regex.Split(setting, "\\s+")); + if (!tmp.Contains(ext)) + ret = false; + } + + return ret; + } + + protected Dictionary ParseJSON(string file) + { + Dictionary ret = new Dictionary(); + string json = ""; + try + { + json = System.IO.File.ReadAllText(_context.Server.MapPath(file), System.Text.Encoding.UTF8); + } + catch { } + + json = json.Trim(); + if (json != "") + { + if (json.StartsWith("{")) + json = json.Substring(1, json.Length - 2); + json = json.Trim(); + json = json.Substring(1, json.Length - 2); + string[] lines = Regex.Split(json, "\"\\s*,\\s*\""); + foreach (string line in lines) + { + string[] tmp = Regex.Split(line, "\"\\s*:\\s*\""); + try + { + if (tmp[0] != "" && !ret.ContainsKey(tmp[0])) + { + ret.Add(tmp[0], tmp[1]); + } + } + catch { } + } + } + return ret; + } + + protected string GetFilesRoot() + { + string ret = GetSetting("FILES_ROOT"); + if (GetSetting("SESSION_PATH_KEY") != "" && _context.Session[GetSetting("SESSION_PATH_KEY")] != null) + ret = (string)_context.Session[GetSetting("SESSION_PATH_KEY")]; + + if (ret == "") + ret = _context.Server.MapPath("../Uploads"); + else + ret = FixPath(ret); + return ret; + } + + protected void LoadConf() + { + if (_settings == null) + _settings = ParseJSON(CONFIG_FILE); + } + + protected string GetSetting(string name) + { + string ret = ""; + LoadConf(); + if (_settings.ContainsKey(name)) + ret = _settings[name]; + + return ret; + } + + protected void CheckPath(string path) + { + if (FixPath(path).IndexOf(GetFilesRoot()) != 0) + { + throw new Exception("Access to " + path + " is denied"); + } + } + + protected void VerifyAction(string action) + { + string setting = GetSetting(action); + if (setting.IndexOf("?") > -1) + setting = setting.Substring(0, setting.IndexOf("?")); + if (!setting.StartsWith("/")) + setting = "/" + setting; + setting = ".." + setting; + + if (_context.Server.MapPath(setting) != _context.Server.MapPath(_context.Request.Url.LocalPath)) + throw new Exception(LangRes("E_ActionDisabled")); + } + + protected string GetResultStr(string type, string msg) + { + return "{\"res\":\"" + type + "\",\"msg\":\"" + msg.Replace("\"", "\\\"") + "\"}"; + } + + protected string GetSuccessRes(string msg) + { + return GetResultStr("ok", msg); + } + + protected string GetSuccessRes() + { + return GetSuccessRes(""); + } + + protected string GetErrorRes(string msg) + { + return GetResultStr("error", msg); + } + + private void _copyDir(string path, string dest) + { + if (!Directory.Exists(dest)) + Directory.CreateDirectory(dest); + foreach (string f in Directory.GetFiles(path)) + { + FileInfo file = new FileInfo(f); + if (!System.IO.File.Exists(Path.Combine(dest, file.Name))) + { + System.IO.File.Copy(f, Path.Combine(dest, file.Name)); + } + } + foreach (string d in Directory.GetDirectories(path)) + { + DirectoryInfo dir = new DirectoryInfo(d); + _copyDir(d, Path.Combine(dest, dir.Name)); + } + } + + protected void CopyDir(string path, string newPath) + { + CheckPath(path); + CheckPath(newPath); + DirectoryInfo dir = new DirectoryInfo(FixPath(path)); + DirectoryInfo newDir = new DirectoryInfo(FixPath(newPath + "/" + dir.Name)); + + if (!dir.Exists) + { + throw new Exception(LangRes("E_CopyDirInvalidPath")); + } + else if (newDir.Exists) + { + throw new Exception(LangRes("E_DirAlreadyExists")); + } + else + { + _copyDir(dir.FullName, newDir.FullName); + } + _response.Write(GetSuccessRes()); + } + + protected string MakeUniqueFilename(string dir, string filename) + { + string ret = filename; + int i = 0; + while (System.IO.File.Exists(Path.Combine(dir, ret))) + { + i++; + ret = Path.GetFileNameWithoutExtension(filename) + " - Copy " + i.ToString() + Path.GetExtension(filename); + } + return ret; + } + + protected void CopyFile(string path, string newPath) + { + CheckPath(path); + FileInfo file = new FileInfo(FixPath(path)); + newPath = FixPath(newPath); + if (!file.Exists) + throw new Exception(LangRes("E_CopyFileInvalisPath")); + else + { + string newName = MakeUniqueFilename(newPath, file.Name); + try + { + System.IO.File.Copy(file.FullName, Path.Combine(newPath, newName)); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_CopyFile")); + } + } + } + + protected void CreateDir(string path, string name) + { + CheckPath(path); + path = FixPath(path); + if (!Directory.Exists(path)) + throw new Exception(LangRes("E_CreateDirInvalidPath")); + else + { + try + { + path = Path.Combine(path, name); + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_CreateDirFailed")); + } + } + } + + protected void DeleteDir(string path) + { + CheckPath(path); + path = FixPath(path); + if (!Directory.Exists(path)) + throw new Exception(LangRes("E_DeleteDirInvalidPath")); + else if (path == GetFilesRoot()) + throw new Exception(LangRes("E_CannotDeleteRoot")); + else if (Directory.GetDirectories(path).Length > 0 || Directory.GetFiles(path).Length > 0) + throw new Exception(LangRes("E_DeleteNonEmpty")); + else + { + try + { + Directory.Delete(path); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_CannotDeleteDir")); + } + } + } + + protected void DeleteFile(string path) + { + CheckPath(path); + path = FixPath(path); + if (!System.IO.File.Exists(path)) + throw new Exception(LangRes("E_DeleteFileInvalidPath")); + else + { + try + { + System.IO.File.Delete(path); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_DeletеFile")); + } + } + } + + private List GetFiles(string path, string type) + { + List ret = new List(); + if (type == "#") + type = ""; + string[] files = Directory.GetFiles(path); + foreach (string f in files) + { + if ((GetFileType(new FileInfo(f).Extension) == type) || (type == "")) + ret.Add(f); + } + return ret; + } + + private ArrayList ListDirs(string path) + { + string[] dirs = Directory.GetDirectories(path); + ArrayList ret = new ArrayList(); + foreach (string dir in dirs) + { + ret.Add(dir); + ret.AddRange(ListDirs(dir)); + } + return ret; + } + + protected void ListDirTree(string type) + { + DirectoryInfo d = new DirectoryInfo(GetFilesRoot()); + if (!d.Exists) + throw new Exception("Invalid files root directory. Check your configuration."); + + ArrayList dirs = ListDirs(d.FullName); + dirs.Insert(0, d.FullName); + + string localPath = _context.Server.MapPath("~/"); + _response.Write("["); + for (int i = 0; i < dirs.Count; i++) + { + string dir = (string)dirs[i]; + _response.Write("{\"p\":\"/" + dir.Replace(localPath, "").Replace("\\", "/") + "\",\"f\":\"" + GetFiles(dir, type).Count.ToString() + "\",\"d\":\"" + Directory.GetDirectories(dir).Length.ToString() + "\"}"); + if (i < dirs.Count - 1) + _response.Write(","); + } + _response.Write("]"); + } + + protected double LinuxTimestamp(DateTime d) + { + DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0).ToLocalTime(); + TimeSpan timeSpan = (d.ToLocalTime() - epoch); + + return timeSpan.TotalSeconds; + + } + + protected void ListFiles(string path, string type) + { + CheckPath(path); + string fullPath = FixPath(path); + List files = GetFiles(fullPath, type); + _response.Write("["); + for (int i = 0; i < files.Count; i++) + { + FileInfo f = new FileInfo(files[i]); + int w = 0, h = 0; + if (GetFileType(f.Extension) == "image") + { + try + { + FileStream fs = new FileStream(f.FullName, FileMode.Open); + Image img = Image.FromStream(fs); + w = img.Width; + h = img.Height; + fs.Close(); + fs.Dispose(); + img.Dispose(); + } + catch (Exception ex) + { + throw ex; + } + } + _response.Write("{"); + _response.Write("\"p\":\"" + path + "/" + f.Name + "\""); + _response.Write(",\"t\":\"" + Math.Ceiling(LinuxTimestamp(f.LastWriteTime)).ToString() + "\""); + _response.Write(",\"s\":\"" + f.Length.ToString() + "\""); + _response.Write(",\"w\":\"" + w.ToString() + "\""); + _response.Write(",\"h\":\"" + h.ToString() + "\""); + _response.Write("}"); + if (i < files.Count - 1) + _response.Write(","); + } + _response.Write("]"); + } + + public void DownloadDir(string path) + { + path = FixPath(path); + if (!Directory.Exists(path)) + throw new Exception(LangRes("E_CreateArchive")); + string dirName = new FileInfo(path).Name; + string tmpZip = _context.Server.MapPath("../tmp/" + dirName + ".zip"); + if (System.IO.File.Exists(tmpZip)) + System.IO.File.Delete(tmpZip); + ZipFile.CreateFromDirectory(path, tmpZip, CompressionLevel.Fastest, true); + _response.Clear(); + _response.Headers.Add("Content-Disposition", "attachment; filename=\"" + dirName + ".zip\""); + _response.ContentType = "application/force-download"; + _response.TransmitFile(tmpZip); + _response.Flush(); + System.IO.File.Delete(tmpZip); + _response.End(); + } + + protected void DownloadFile(string path) + { + CheckPath(path); + FileInfo file = new FileInfo(FixPath(path)); + if (file.Exists) + { + _response.Clear(); + _response.Headers.Add("Content-Disposition", "attachment; filename=\"" + file.Name + "\""); + _response.ContentType = "application/force-download"; + _response.TransmitFile(file.FullName); + _response.Flush(); + _response.End(); + } + } + + protected void MoveDir(string path, string newPath) + { + CheckPath(path); + CheckPath(newPath); + DirectoryInfo source = new DirectoryInfo(FixPath(path)); + DirectoryInfo dest = new DirectoryInfo(FixPath(Path.Combine(newPath, source.Name))); + if (dest.FullName.IndexOf(source.FullName) == 0) + throw new Exception(LangRes("E_CannotMoveDirToChild")); + else if (!source.Exists) + throw new Exception(LangRes("E_MoveDirInvalisPath")); + else if (dest.Exists) + throw new Exception(LangRes("E_DirAlreadyExists")); + else + { + try + { + source.MoveTo(dest.FullName); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_MoveDir") + " \"" + path + "\""); + } + } + } + + protected void MoveFile(string path, string newPath) + { + CheckPath(path); + CheckPath(newPath); + FileInfo source = new FileInfo(FixPath(path)); + FileInfo dest = new FileInfo(FixPath(newPath)); + if (!source.Exists) + throw new Exception(LangRes("E_MoveFileInvalisPath")); + else if (dest.Exists) + throw new Exception(LangRes("E_MoveFileAlreadyExists")); + else + { + try + { + source.MoveTo(dest.FullName); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_MoveFile") + " \"" + path + "\""); + } + } + } + + protected void RenameDir(string path, string name) + { + CheckPath(path); + DirectoryInfo source = new DirectoryInfo(FixPath(path)); + DirectoryInfo dest = new DirectoryInfo(Path.Combine(source.Parent.FullName, name)); + if (source.FullName == GetFilesRoot()) + throw new Exception(LangRes("E_CannotRenameRoot")); + else if (!source.Exists) + throw new Exception(LangRes("E_RenameDirInvalidPath")); + else if (dest.Exists) + throw new Exception(LangRes("E_DirAlreadyExists")); + else + { + try + { + source.MoveTo(dest.FullName); + _response.Write(GetSuccessRes()); + } + catch + { + throw new Exception(LangRes("E_RenameDir") + " \"" + path + "\""); + } + } + } + + protected void RenameFile(string path, string name) + { + CheckPath(path); + FileInfo source = new FileInfo(FixPath(path)); + FileInfo dest = new FileInfo(Path.Combine(source.Directory.FullName, name)); + if (!source.Exists) + throw new Exception(LangRes("E_RenameFileInvalidPath")); + else if (!CanHandleFile(name)) + throw new Exception(LangRes("E_FileExtensionForbidden")); + else + { + try + { + source.MoveTo(dest.FullName); + _response.Write(GetSuccessRes()); + } + catch (Exception ex) + { + throw new Exception(ex.Message + "; " + LangRes("E_RenameFile") + " \"" + path + "\""); + } + } + } + + public bool ThumbnailCallback() + { + return false; + } + + protected void ShowThumbnail(string path, int width, int height) + { + CheckPath(path); + FileStream fs = new FileStream(FixPath(path), FileMode.Open); + Bitmap img = new Bitmap(Bitmap.FromStream(fs)); + fs.Close(); + fs.Dispose(); + int cropWidth = img.Width, cropHeight = img.Height; + int cropX = 0, cropY = 0; + + double imgRatio = (double)img.Width / (double)img.Height; + + if (height == 0) + height = Convert.ToInt32(Math.Floor((double)width / imgRatio)); + + if (width > img.Width) + width = img.Width; + if (height > img.Height) + height = img.Height; + + double cropRatio = (double)width / (double)height; + cropWidth = Convert.ToInt32(Math.Floor((double)img.Height * cropRatio)); + cropHeight = Convert.ToInt32(Math.Floor((double)cropWidth / cropRatio)); + if (cropWidth > img.Width) + { + cropWidth = img.Width; + cropHeight = Convert.ToInt32(Math.Floor((double)cropWidth / cropRatio)); + } + if (cropHeight > img.Height) + { + cropHeight = img.Height; + cropWidth = Convert.ToInt32(Math.Floor((double)cropHeight * cropRatio)); + } + if (cropWidth < img.Width) + { + cropX = Convert.ToInt32(Math.Floor((double)(img.Width - cropWidth) / 2)); + } + if (cropHeight < img.Height) + { + cropY = Convert.ToInt32(Math.Floor((double)(img.Height - cropHeight) / 2)); + } + + Rectangle area = new Rectangle(cropX, cropY, cropWidth, cropHeight); + Bitmap cropImg = img.Clone(area, System.Drawing.Imaging.PixelFormat.DontCare); + img.Dispose(); + Image.GetThumbnailImageAbort imgCallback = new Image.GetThumbnailImageAbort(ThumbnailCallback); + + _response.AddHeader("Content-Type", "image/png"); + cropImg.GetThumbnailImage(width, height, imgCallback, IntPtr.Zero).Save(_response.OutputStream, ImageFormat.Png); + _response.OutputStream.Close(); + cropImg.Dispose(); + } + + private ImageFormat GetImageFormat(string filename) + { + ImageFormat ret = ImageFormat.Jpeg; + switch (new FileInfo(filename).Extension.ToLower()) + { + case ".png": + ret = ImageFormat.Png; + break; + case ".gif": + ret = ImageFormat.Gif; + break; + } + return ret; + } + + protected void ImageResize(string path, string dest, int width, int height) + { + FileStream fs = new FileStream(path, FileMode.Open); + Image img = Image.FromStream(fs); + fs.Close(); + fs.Dispose(); + float ratio = (float)img.Width / (float)img.Height; + if ((img.Width <= width && img.Height <= height) || (width == 0 && height == 0)) + return; + + int newWidth = width; + int newHeight = Convert.ToInt16(Math.Floor((float)newWidth / ratio)); + if ((height > 0 && newHeight > height) || (width == 0)) + { + newHeight = height; + newWidth = Convert.ToInt16(Math.Floor((float)newHeight * ratio)); + } + Bitmap newImg = new Bitmap(newWidth, newHeight); + Graphics g = Graphics.FromImage((Image)newImg); + g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; + g.DrawImage(img, 0, 0, newWidth, newHeight); + img.Dispose(); + g.Dispose(); + if (dest != "") + { + newImg.Save(dest, GetImageFormat(dest)); + } + newImg.Dispose(); + } + + protected void Upload(string path) + { + CheckPath(path); + path = FixPath(path); + string res = GetSuccessRes(); + try + { + for (int i = 0; i < Request.Files.Count; i++) + { + if (CanHandleFile(Request.Files[i].FileName)) + { + string filename = MakeUniqueFilename(path, Request.Files[i].FileName); + string dest = Path.Combine(path, filename); + Request.Files[i].SaveAs(dest); + if (GetFileType(new FileInfo(filename).Extension) == "image") + { + int w = 0; + int h = 0; + int.TryParse(GetSetting("MAX_IMAGE_WIDTH"), out w); + int.TryParse(GetSetting("MAX_IMAGE_HEIGHT"), out h); + ImageResize(dest, dest, w, h); + } + } + else + res = GetSuccessRes(LangRes("E_UploadNotAll")); + } + } + catch (Exception ex) + { + res = GetErrorRes(ex.Message); + } + _response.Write(""); + } + + public bool IsReusable + { + get + { + return false; + } + } + + #endregion + + public void ProcessRequest() + { + if (!_permissionService.Authorize(StandardPermissionProvider.UploadPictures)) + { + _response.Write(GetErrorRes("You do not have the required permission")); + return; + } + + string action = "DIRLIST"; + try + { + if (_context.Request["a"] != null) + action = (string)_context.Request["a"]; + + //VerifyAction(action); + switch (action.ToUpper()) + { + case "DIRLIST": + ListDirTree(_context.Request["type"]); + break; + case "FILESLIST": + ListFiles(_context.Request["d"], _context.Request["type"]); + break; + case "COPYDIR": + CopyDir(_context.Request["d"], _context.Request["n"]); + break; + case "COPYFILE": + CopyFile(_context.Request["f"], _context.Request["n"]); + break; + case "CREATEDIR": + CreateDir(_context.Request["d"], _context.Request["n"]); + break; + case "DELETEDIR": + DeleteDir(_context.Request["d"]); + break; + case "DELETEFILE": + DeleteFile(_context.Request["f"]); + break; + case "DOWNLOAD": + DownloadFile(_context.Request["f"]); + break; + case "DOWNLOADDIR": + DownloadDir(_context.Request["d"]); + break; + case "MOVEDIR": + MoveDir(_context.Request["d"], _context.Request["n"]); + break; + case "MOVEFILE": + MoveFile(_context.Request["f"], _context.Request["n"]); + break; + case "RENAMEDIR": + RenameDir(_context.Request["d"], _context.Request["n"]); + break; + case "RENAMEFILE": + RenameFile(_context.Request["f"], _context.Request["n"]); + break; + case "GENERATETHUMB": + int w = 140, h = 0; + int.TryParse(_context.Request["width"].Replace("px", ""), out w); + int.TryParse(_context.Request["height"].Replace("px", ""), out h); + ShowThumbnail(_context.Request["f"], w, h); + break; + case "UPLOAD": + Upload(_context.Request["d"]); + break; + default: + _response.Write(GetErrorRes("This action is not implemented.")); + break; + } + } + catch (Exception ex) + { + if (action == "UPLOAD") + { + _response.Write(""); + } + else + { + _response.Write(GetErrorRes(ex.Message)); + } + } + } + } +} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj index 15852be9a4..603f623aa1 100644 --- a/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj +++ b/src/Presentation/SmartStore.Web/Administration/SmartStore.Admin.csproj @@ -102,6 +102,7 @@ + False @@ -213,6 +214,7 @@ + @@ -429,6 +431,267 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -461,6 +724,12 @@ + + + + + + diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml index 1ba69f79da..04db8e0e26 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Shared/EditorTemplates/RichEditor.cshtml @@ -29,7 +29,7 @@ "); - } - else{ - _r.Write(GetErrorRes(ex.Message)); - } - } - - } - private string FixPath(string path) - { - if (!path.StartsWith("~")){ - if (!path.StartsWith("/")) - path = "/" + path; - path = "~" + path; - } - return _context.Server.MapPath(path); - } - private string GetLangFile(){ - return "../lang/en.json"; - } - protected string LangRes(string name) - { - string ret = name; - if (_lang == null) - _lang = ParseJSON(GetLangFile()); - if (_lang.ContainsKey(name)) - ret = _lang[name]; - - return ret; - } - protected string GetFileType(string ext){ - string ret = "file"; - ext = ext.ToLower(); - if(ext == ".jpg" || ext == ".jpeg" || ext == ".png" || ext == ".gif") - ret = "image"; - else if(ext == ".swf" || ext == ".flv") - ret = "flash"; - return ret; - } - protected bool CanHandleFile(string filename) - { - bool ret = false; - FileInfo file = new FileInfo(filename); - string ext = file.Extension.Replace(".", "").ToLower(); - string setting = GetSetting("FORBIDDEN_UPLOADS").Trim().ToLower(); - if (setting != "") - { - ArrayList tmp = new ArrayList(); - tmp.AddRange(Regex.Split(setting, "\\s+")); - if (!tmp.Contains(ext)) - ret = true; - } - setting = GetSetting("ALLOWED_UPLOADS").Trim().ToLower(); - if (setting != "") - { - ArrayList tmp = new ArrayList(); - tmp.AddRange(Regex.Split(setting, "\\s+")); - if (!tmp.Contains(ext)) - ret = false; - } - - return ret; - } - protected Dictionary ParseJSON(string file){ - Dictionary ret = new Dictionary(); - string json = ""; - try{ - json = File.ReadAllText(_context.Server.MapPath(file), System.Text.Encoding.UTF8); - } - catch {} - - json = json.Trim(); - if(json != ""){ - if (json.StartsWith("{")) - json = json.Substring(1, json.Length - 2); - json = json.Trim(); - json = json.Substring(1, json.Length - 2); - string[] lines = Regex.Split(json, "\"\\s*,\\s*\""); - foreach(string line in lines){ - string[] tmp = Regex.Split(line, "\"\\s*:\\s*\""); - try{ - if (tmp[0] != "" && !ret.ContainsKey(tmp[0])) - { - ret.Add(tmp[0], tmp[1]); - } - } - catch {} - } - } - return ret; - } - protected string GetFilesRoot(){ - string ret = GetSetting("FILES_ROOT"); - if (GetSetting("SESSION_PATH_KEY") != "" && _context.Session[GetSetting("SESSION_PATH_KEY")] != null) - ret = (string)_context.Session[GetSetting("SESSION_PATH_KEY")]; - - if(ret == "") - ret = _context.Server.MapPath("../Uploads"); - else - ret = FixPath(ret); - return ret; - } - protected void LoadConf(){ - if(_settings == null) - _settings = ParseJSON(confFile); - } - protected string GetSetting(string name){ - string ret = ""; - LoadConf(); - if(_settings.ContainsKey(name)) - ret = _settings[name]; - - return ret; - } - protected void CheckPath(string path) - { - if (FixPath(path).IndexOf(GetFilesRoot()) != 0) - { - throw new Exception("Access to " + path + " is denied"); - } - } - protected void VerifyAction(string action) - { - string setting = GetSetting(action); - if (setting.IndexOf("?") > -1) - setting = setting.Substring(0, setting.IndexOf("?")); - if (!setting.StartsWith("/")) - setting = "/" + setting; - setting = ".." + setting; - - if (_context.Server.MapPath(setting) != _context.Server.MapPath(_context.Request.Url.LocalPath)) - throw new Exception(LangRes("E_ActionDisabled")); - } - protected string GetResultStr(string type, string msg) - { - return "{\"res\":\"" + type + "\",\"msg\":\"" + msg.Replace("\"","\\\"") + "\"}"; - } - protected string GetSuccessRes(string msg) - { - return GetResultStr("ok", msg); - } - protected string GetSuccessRes() - { - return GetSuccessRes(""); - } - protected string GetErrorRes(string msg) - { - return GetResultStr("error", msg); - } - private void _copyDir(string path, string dest){ - if(!Directory.Exists(dest)) - Directory.CreateDirectory(dest); - foreach(string f in Directory.GetFiles(path)){ - FileInfo file = new FileInfo(f); - if (!File.Exists(Path.Combine(dest, file.Name))){ - File.Copy(f, Path.Combine(dest, file.Name)); - } - } - foreach (string d in Directory.GetDirectories(path)) - { - DirectoryInfo dir = new DirectoryInfo(d); - _copyDir(d, Path.Combine(dest, dir.Name)); - } - } - protected void CopyDir(string path, string newPath) - { - CheckPath(path); - CheckPath(newPath); - DirectoryInfo dir = new DirectoryInfo(FixPath(path)); - DirectoryInfo newDir = new DirectoryInfo(FixPath(newPath + "/" + dir.Name)); - - if (!dir.Exists) - { - throw new Exception(LangRes("E_CopyDirInvalidPath")); - } - else if (newDir.Exists) - { - throw new Exception(LangRes("E_DirAlreadyExists")); - } - else{ - _copyDir(dir.FullName, newDir.FullName); - } - _r.Write(GetSuccessRes()); - } - protected string MakeUniqueFilename(string dir, string filename){ - string ret = filename; - int i = 0; - while (File.Exists(Path.Combine(dir, ret))) - { - i++; - ret = Path.GetFileNameWithoutExtension(filename) + " - Copy " + i.ToString() + Path.GetExtension(filename); - } - return ret; - } - protected void CopyFile(string path, string newPath) - { - CheckPath(path); - FileInfo file = new FileInfo(FixPath(path)); - newPath = FixPath(newPath); - if (!file.Exists) - throw new Exception(LangRes("E_CopyFileInvalisPath")); - else{ - string newName = MakeUniqueFilename(newPath, file.Name); - try{ - File.Copy(file.FullName, Path.Combine(newPath, newName)); - _r.Write(GetSuccessRes()); - } - catch { - throw new Exception(LangRes("E_CopyFile")); - } - } - } - protected void CreateDir(string path, string name) - { - CheckPath(path); - path = FixPath(path); - if(!Directory.Exists(path)) - throw new Exception(LangRes("E_CreateDirInvalidPath")); - else{ - try - { - path = Path.Combine(path, name); - if(!Directory.Exists(path)) - Directory.CreateDirectory(path); - _r.Write(GetSuccessRes()); - } - catch - { - throw new Exception(LangRes("E_CreateDirFailed")); - } - } - } - protected void DeleteDir(string path) - { - CheckPath(path); - path = FixPath(path); - if (!Directory.Exists(path)) - throw new Exception(LangRes("E_DeleteDirInvalidPath")); - else if (path == GetFilesRoot()) - throw new Exception(LangRes("E_CannotDeleteRoot")); - else if(Directory.GetDirectories(path).Length > 0 || Directory.GetFiles(path).Length > 0) - throw new Exception(LangRes("E_DeleteNonEmpty")); - else - { - try - { - Directory.Delete(path); - _r.Write(GetSuccessRes()); - } - catch - { - throw new Exception(LangRes("E_CannotDeleteDir")); - } - } - } - protected void DeleteFile(string path) - { - CheckPath(path); - path = FixPath(path); - if (!File.Exists(path)) - throw new Exception(LangRes("E_DeleteFileInvalidPath")); - else - { - try - { - File.Delete(path); - _r.Write(GetSuccessRes()); - } - catch - { - throw new Exception(LangRes("E_DeletеFile")); - } - } - } - private List GetFiles(string path, string type){ - List ret = new List(); - if(type == "#") - type = ""; - string[] files = Directory.GetFiles(path); - foreach(string f in files){ - if ((GetFileType(new FileInfo(f).Extension) == type) || (type == "")) - ret.Add(f); - } - return ret; - } - private ArrayList ListDirs(string path){ - string[] dirs = Directory.GetDirectories(path); - ArrayList ret = new ArrayList(); - foreach(string dir in dirs){ - ret.Add(dir); - ret.AddRange(ListDirs(dir)); - } - return ret; - } - protected void ListDirTree(string type) - { - DirectoryInfo d = new DirectoryInfo(GetFilesRoot()); - if(!d.Exists) - throw new Exception("Invalid files root directory. Check your configuration."); - - ArrayList dirs = ListDirs(d.FullName); - dirs.Insert(0, d.FullName); - - string localPath = _context.Server.MapPath("~/"); - _r.Write("["); - for(int i = 0; i files = GetFiles(fullPath, type); - _r.Write("["); - for(int i = 0; i < files.Count; i++){ - FileInfo f = new FileInfo(files[i]); - int w = 0, h = 0; - if (GetFileType(f.Extension) == "image"){ - try{ - FileStream fs = new FileStream(f.FullName, FileMode.Open); - Image img = Image.FromStream(fs); - w = img.Width; - h = img.Height; - fs.Close(); - fs.Dispose(); - img.Dispose(); - } - catch(Exception ex) - { - throw ex; - } - } - _r.Write("{"); - _r.Write("\"p\":\""+path + "/" + f.Name+"\""); - _r.Write(",\"t\":\"" + Math.Ceiling(LinuxTimestamp(f.LastWriteTime)).ToString() + "\""); - _r.Write(",\"s\":\""+f.Length.ToString()+"\""); - _r.Write(",\"w\":\""+w.ToString()+"\""); - _r.Write(",\"h\":\""+h.ToString()+"\""); - _r.Write("}"); - if (i < files.Count - 1) - _r.Write(","); - } - _r.Write("]"); - } - public void DownloadDir(string path) - { - path = FixPath(path); - if(!Directory.Exists(path)) - throw new Exception(LangRes("E_CreateArchive")); - string dirName = new FileInfo(path).Name; - string tmpZip = _context.Server.MapPath("../tmp/" + dirName + ".zip"); - if(File.Exists(tmpZip)) - File.Delete(tmpZip); - ZipFile.CreateFromDirectory(path, tmpZip,CompressionLevel.Fastest, true); - _r.Clear(); - _r.Headers.Add("Content-Disposition", "attachment; filename=\"" + dirName + ".zip\""); - _r.ContentType = "application/force-download"; - _r.TransmitFile(tmpZip); - _r.Flush(); - File.Delete(tmpZip); - _r.End(); - } - protected void DownloadFile(string path) - { - CheckPath(path); - FileInfo file = new FileInfo(FixPath(path)); - if(file.Exists){ - _r.Clear(); - _r.Headers.Add("Content-Disposition", "attachment; filename=\"" + file.Name + "\""); - _r.ContentType = "application/force-download"; - _r.TransmitFile(file.FullName); - _r.Flush(); - _r.End(); - } - } - protected void MoveDir(string path, string newPath) - { - CheckPath(path); - CheckPath(newPath); - DirectoryInfo source = new DirectoryInfo(FixPath(path)); - DirectoryInfo dest = new DirectoryInfo(FixPath(Path.Combine(newPath, source.Name))); - if(dest.FullName.IndexOf(source.FullName) == 0) - throw new Exception(LangRes("E_CannotMoveDirToChild")); - else if (!source.Exists) - throw new Exception(LangRes("E_MoveDirInvalisPath")); - else if (dest.Exists) - throw new Exception(LangRes("E_DirAlreadyExists")); - else{ - try{ - source.MoveTo(dest.FullName); - _r.Write(GetSuccessRes()); - } - catch { - throw new Exception(LangRes("E_MoveDir") + " \"" + path + "\""); - } - } - - } - protected void MoveFile(string path, string newPath) - { - CheckPath(path); - CheckPath(newPath); - FileInfo source = new FileInfo(FixPath(path)); - FileInfo dest = new FileInfo(FixPath(newPath)); - if (!source.Exists) - throw new Exception(LangRes("E_MoveFileInvalisPath")); - else if (dest.Exists) - throw new Exception(LangRes("E_MoveFileAlreadyExists")); - else - { - try - { - source.MoveTo(dest.FullName); - _r.Write(GetSuccessRes()); - } - catch - { - throw new Exception(LangRes("E_MoveFile") + " \"" + path + "\""); - } - } - } - protected void RenameDir(string path, string name) - { - CheckPath(path); - DirectoryInfo source = new DirectoryInfo(FixPath(path)); - DirectoryInfo dest = new DirectoryInfo(Path.Combine(source.Parent.FullName, name)); - if(source.FullName == GetFilesRoot()) - throw new Exception(LangRes("E_CannotRenameRoot")); - else if (!source.Exists) - throw new Exception(LangRes("E_RenameDirInvalidPath")); - else if (dest.Exists) - throw new Exception(LangRes("E_DirAlreadyExists")); - else - { - try - { - source.MoveTo(dest.FullName); - _r.Write(GetSuccessRes()); - } - catch - { - throw new Exception(LangRes("E_RenameDir") + " \"" + path + "\""); - } - } - } - protected void RenameFile(string path, string name) - { - CheckPath(path); - FileInfo source = new FileInfo(FixPath(path)); - FileInfo dest = new FileInfo(Path.Combine(source.Directory.FullName, name)); - if (!source.Exists) - throw new Exception(LangRes("E_RenameFileInvalidPath")); - else if (!CanHandleFile(name)) - throw new Exception(LangRes("E_FileExtensionForbidden")); - else - { - try - { - source.MoveTo(dest.FullName); - _r.Write(GetSuccessRes()); - } - catch (Exception ex) - { - throw new Exception(ex.Message + "; " + LangRes("E_RenameFile") + " \"" + path + "\""); - } - } - } - public bool ThumbnailCallback() - { - return false; - } - - protected void ShowThumbnail(string path, int width, int height) - { - CheckPath(path); - FileStream fs = new FileStream(FixPath(path), FileMode.Open); - Bitmap img = new Bitmap(Bitmap.FromStream(fs)); - fs.Close(); - fs.Dispose(); - int cropWidth = img.Width, cropHeight = img.Height; - int cropX = 0, cropY = 0; - - double imgRatio = (double)img.Width / (double)img.Height; - - if(height == 0) - height = Convert.ToInt32(Math.Floor((double)width / imgRatio)); - - if (width > img.Width) - width = img.Width; - if (height > img.Height) - height = img.Height; - - double cropRatio = (double)width / (double)height; - cropWidth = Convert.ToInt32(Math.Floor((double)img.Height * cropRatio)); - cropHeight = Convert.ToInt32(Math.Floor((double)cropWidth / cropRatio)); - if (cropWidth > img.Width) - { - cropWidth = img.Width; - cropHeight = Convert.ToInt32(Math.Floor((double)cropWidth / cropRatio)); - } - if (cropHeight > img.Height) - { - cropHeight = img.Height; - cropWidth = Convert.ToInt32(Math.Floor((double)cropHeight * cropRatio)); - } - if(cropWidth < img.Width){ - cropX = Convert.ToInt32(Math.Floor((double)(img.Width - cropWidth) / 2)); - } - if(cropHeight < img.Height){ - cropY = Convert.ToInt32(Math.Floor((double)(img.Height - cropHeight) / 2)); - } - - Rectangle area = new Rectangle(cropX, cropY, cropWidth, cropHeight); - Bitmap cropImg = img.Clone(area, System.Drawing.Imaging.PixelFormat.DontCare); - img.Dispose(); - Image.GetThumbnailImageAbort imgCallback = new Image.GetThumbnailImageAbort(ThumbnailCallback); - - _r.AddHeader("Content-Type", "image/png"); - cropImg.GetThumbnailImage(width, height, imgCallback, IntPtr.Zero).Save(_r.OutputStream, ImageFormat.Png); - _r.OutputStream.Close(); - cropImg.Dispose(); - } - private ImageFormat GetImageFormat(string filename){ - ImageFormat ret = ImageFormat.Jpeg; - switch(new FileInfo(filename).Extension.ToLower()){ - case ".png": ret = ImageFormat.Png; break; - case ".gif": ret = ImageFormat.Gif; break; - } - return ret; - } - protected void ImageResize(string path, string dest, int width, int height) - { - FileStream fs = new FileStream(path, FileMode.Open); - Image img = Image.FromStream(fs); - fs.Close(); - fs.Dispose(); - float ratio = (float)img.Width / (float)img.Height; - if ((img.Width <= width && img.Height <= height) || (width == 0 && height == 0)) - return; - - int newWidth = width; - int newHeight = Convert.ToInt16(Math.Floor((float)newWidth / ratio)); - if ((height > 0 && newHeight > height) || (width == 0)) - { - newHeight = height; - newWidth = Convert.ToInt16(Math.Floor((float)newHeight * ratio)); - } - Bitmap newImg = new Bitmap(newWidth, newHeight); - Graphics g = Graphics.FromImage((Image)newImg); - g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; - g.DrawImage(img, 0, 0, newWidth, newHeight); - img.Dispose(); - g.Dispose(); - if(dest != ""){ - newImg.Save(dest, GetImageFormat(dest)); - } - newImg.Dispose(); - } - - protected void Upload(string path) - { - CheckPath(path); - path = FixPath(path); - string res = GetSuccessRes(); - try{ - for(int i = 0; i < HttpContext.Current.Request.Files.Count; i++){ - if (CanHandleFile(HttpContext.Current.Request.Files[i].FileName)) - { - string filename = MakeUniqueFilename(path, HttpContext.Current.Request.Files[i].FileName); - string dest = Path.Combine(path, filename); - HttpContext.Current.Request.Files[i].SaveAs(dest); - if(GetFileType(new FileInfo(filename).Extension) == "image"){ - int w = 0; - int h = 0; - int.TryParse(GetSetting("MAX_IMAGE_WIDTH"), out w); - int.TryParse(GetSetting("MAX_IMAGE_HEIGHT"), out h); - ImageResize(dest, dest, w, h); - } - } - else - res = GetSuccessRes(LangRes("E_UploadNotAll")); - } - } - catch(Exception ex){ - res = GetErrorRes(ex.Message); - } - _r.Write(""); - } - - public bool IsReusable { - get { - return false; - } - } - -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/Content/filemanager/conf.json b/src/Presentation/SmartStore.Web/Content/filemanager/conf.json deleted file mode 100644 index 3df29bf505..0000000000 --- a/src/Presentation/SmartStore.Web/Content/filemanager/conf.json +++ /dev/null @@ -1,34 +0,0 @@ -{ -"FILES_ROOT": "~/Media/Uploaded", -"SESSION_PATH_KEY": "", -"THUMBS_VIEW_WIDTH": "140", -"THUMBS_VIEW_HEIGHT": "120", -"PREVIEW_THUMB_WIDTH":"300", -"PREVIEW_THUMB_HEIGHT":"200", -"MAX_IMAGE_WIDTH": "0", -"MAX_IMAGE_HEIGHT": "0", -"INTEGRATION": "ckeditor", -"DIRLIST": "asp_net/main.ashx?a=DIRLIST", -"CREATEDIR": "asp_net/main.ashx?a=CREATEDIR", -"DELETEDIR": "asp_net/main.ashx?a=DELETEDIR", -"MOVEDIR": "asp_net/main.ashx?a=MOVEDIR", -"COPYDIR": "asp_net/main.ashx?a=COPYDIR", -"RENAMEDIR": "asp_net/main.ashx?a=RENAMEDIR", -"FILESLIST": "asp_net/main.ashx?a=FILESLIST", -"UPLOAD": "asp_net/main.ashx?a=UPLOAD", -"DOWNLOAD": "asp_net/main.ashx?a=DOWNLOAD", -"DOWNLOADDIR": "asp_net/main.ashx?a=DOWNLOADDIR", -"DOWNLOADDIR": "asp_net/main.ashx?a=DOWNLOADDIR", -"DELETEFILE": "asp_net/main.ashx?a=DELETEFILE", -"MOVEFILE": "asp_net/main.ashx?a=MOVEFILE", -"COPYFILE": "asp_net/main.ashx?a=COPYFILE", -"RENAMEFILE": "asp_net/main.ashx?a=RENAMEFILE", -"GENERATETHUMB": "asp_net/main.ashx?a=GENERATETHUMB", -"DEFAULTVIEW": "list", -"FORBIDDEN_UPLOADS": "zip js jsp jsb mhtml mht xhtml xht php phtml php3 php4 php5 phps shtml jhtml pl sh py cgi exe scr dll msi vbs bat com pif cmd vxd cpl htpasswd htaccess", -"ALLOWED_UPLOADS": "", -"FILEPERMISSIONS": "0644", -"DIRPERMISSIONS": "0755", -"LANG": "auto", -"DATEFORMAT": "dd.MM.yyyy HH:mm" -} \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj index 2bd35ba917..8340a62bfb 100644 --- a/src/Presentation/SmartStore.Web/SmartStore.Web.csproj +++ b/src/Presentation/SmartStore.Web/SmartStore.Web.csproj @@ -645,267 +645,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -919,13 +658,6 @@ - - - - - - - From 8e1c3f47188692020652696177667d4149bf59dc Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 23 Jun 2016 09:39:37 +0200 Subject: [PATCH 256/285] Delete old static files of file manager by migration --- .../Migrations/201605201911421_ExportRevision.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs index 485fa9fdec..486bff7969 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs @@ -5,6 +5,7 @@ namespace SmartStore.Data.Migrations using Core.Domain; using Core.Domain.DataExchange; using Setup; + using Utilities; public partial class ExportRevision : DbMigration, ILocaleResourcesProvider, IDataSeeder { @@ -58,6 +59,9 @@ public void Seed(SmartObjectContext context) context.ColumnDelete("ExportDeployment", "IsPublic"); } + + var oldFileManagerPath = CommonHelper.MapPath("~/Content/filemanager"); + FileSystemHelper.ClearDirectory(oldFileManagerPath, true); } public void MigrateLocaleResources(LocaleResourcesBuilder builder) From e79c397d0e39d03a22de7418744a880da833b9e8 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 23 Jun 2016 14:05:05 +0200 Subject: [PATCH 257/285] Resolves #950 make contact form comply with current German law --- changelog.md | 1 + .../Domain/Customers/CustomerSettings.cs | 6 +++++ .../201605201911421_ExportRevision.cs | 17 ++++++++++++ .../Settings/CustomerUserSettingsModel.cs | 3 +++ .../Views/Setting/CustomerUser.cshtml | 9 +++++++ .../Controllers/HomeController.cs | 10 +++++-- .../Models/Common/ContactUsModel.cs | 5 ++++ .../Validators/Common/ContactUsValidator.cs | 5 ++++ .../Views/Home/ContactUs.cshtml | 27 +++++++++++++++---- 9 files changed, 76 insertions(+), 7 deletions(-) diff --git a/changelog.md b/changelog.md index 47f492ff48..0dac88a9fd 100644 --- a/changelog.md +++ b/changelog.md @@ -19,6 +19,7 @@ * #477 Implement option to specify the number of exported and imported pictures * #859 Make checkout attributes suitable for multi-stores * Product details: Select attribute and gift card values by query string parameters +* #950 make contact form comply with current German law ### Improvements * Major improvements in Importer: better field mapping, higher performance, bug fixes etc. diff --git a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs index d0dbf678e5..e6d7f56f56 100644 --- a/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs +++ b/src/Libraries/SmartStore.Core/Domain/Customers/CustomerSettings.cs @@ -24,6 +24,7 @@ public CustomerSettings() NewsletterEnabled = true; OnlineCustomerMinutes = 20; StoreLastVisitedPage = true; + DisplayPrivacyAgreementOnContactUs = false; } /// @@ -151,6 +152,11 @@ public CustomerSettings() /// public bool StoreLastVisitedPage { get; set; } + /// + /// Gets or sets a value indicating whether to display a checkbox to the customer where he can agree to privacy terms + /// + public bool DisplayPrivacyAgreementOnContactUs { get; set; } + #region Form fields /// diff --git a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs index 486bff7969..d168932260 100644 --- a/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs +++ b/src/Libraries/SmartStore.Data/Migrations/201605201911421_ExportRevision.cs @@ -174,6 +174,23 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder) "This function is not available for guests.", "Diese Funktion steht für Gäste nicht zur Verfügung."); + builder.AddOrUpdate("ContactUs.PrivacyAgreement", + "Privacy consent", + "Einwilligungserklärung Datenschutz"); + + builder.AddOrUpdate("ContactUs.PrivacyAgreement.MustBeAccepted", + "Please agree to the storage of your data.", + "Bitte stimmen Sie der Speicherung Ihrer Daten zu."); + + builder.AddOrUpdate("ContactUs.PrivacyAgreement.DetailText", + "Yes I've read the privacy terms and agree that my data given by me can be stored electronically. My data will thereby only be used to process my inquiry.", + "Ja, ich habe die Datenschutzerklärung zur Kenntnis genommen und bin damit einverstanden, dass die von mir angegebenen Daten elektronisch erhoben und gespeichert werden. Meine Daten werden dabei nur zur Bearbeitung meiner Anfrage genutzt."); + + builder.AddOrUpdate("Admin.Configuration.Settings.CustomerUser.DisplayPrivacyAgreementOnContactUs", + "Get privacy consent for contact requests", + "Einwilligungserklärung im Kontaktformular fordern", + "Specifies whether a checkbox will be displayed on the contact page which requests the user to agree on storage of his data.", + "Bestimmt ob im Kontaktformular eine Checkbox angezeigt wird, die den Benutzer auffordert der Speicherung seiner Daten zuzustimmen."); builder.Delete( diff --git a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs index bfbb495345..f395584d6c 100644 --- a/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs +++ b/src/Presentation/SmartStore.Web/Administration/Models/Settings/CustomerUserSettingsModel.cs @@ -89,6 +89,9 @@ public partial class CustomerSettingsModel [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.StoreLastVisitedPage")] public bool StoreLastVisitedPage { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.DisplayPrivacyAgreementOnContactUs")] + public bool DisplayPrivacyAgreementOnContactUs { get; set; } + [SmartResourceDisplayName("Admin.Configuration.Settings.CustomerUser.GenderEnabled")] public bool GenderEnabled { get; set; } diff --git a/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml b/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml index 1d868a640d..ccb0eb2876 100644 --- a/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml +++ b/src/Presentation/SmartStore.Web/Administration/Views/Setting/CustomerUser.cshtml @@ -259,6 +259,15 @@ @Html.ValidationMessageFor(model => model.CustomerSettings.StoreLastVisitedPage) + + + @Html.SmartLabelFor(model => model.CustomerSettings.DisplayPrivacyAgreementOnContactUs) + + + @Html.SettingEditorFor(model => model.CustomerSettings.DisplayPrivacyAgreementOnContactUs) + @Html.ValidationMessageFor(model => model.CustomerSettings.DisplayPrivacyAgreementOnContactUs) + + } @helper TabCustomerFormFields() diff --git a/src/Presentation/SmartStore.Web/Controllers/HomeController.cs b/src/Presentation/SmartStore.Web/Controllers/HomeController.cs index e16d5e80b7..58e6eb4ace 100644 --- a/src/Presentation/SmartStore.Web/Controllers/HomeController.cs +++ b/src/Presentation/SmartStore.Web/Controllers/HomeController.cs @@ -6,6 +6,7 @@ using SmartStore.Core.Domain.Catalog; using SmartStore.Core.Domain.Cms; using SmartStore.Core.Domain.Common; +using SmartStore.Core.Domain.Customers; using SmartStore.Core.Domain.Messages; using SmartStore.Core.Infrastructure; using SmartStore.Core.Localization; @@ -41,6 +42,7 @@ public partial class HomeController : PublicControllerBase private readonly Lazy _sitemapGenerator; private readonly Lazy _captchaSettings; private readonly Lazy _commonSettings; + private readonly Lazy _customerSettings; #endregion @@ -56,7 +58,8 @@ public HomeController( Lazy emailAccountService, Lazy sitemapGenerator, Lazy captchaSettings, - Lazy commonSettings) + Lazy commonSettings, + Lazy customerSettings) { this._services = services; this._categoryService = categoryService; @@ -68,6 +71,7 @@ public HomeController( this._sitemapGenerator = sitemapGenerator; this._captchaSettings = captchaSettings; this._commonSettings = commonSettings; + this._customerSettings = customerSettings; } #endregion @@ -119,7 +123,9 @@ public ActionResult ContactUs() { Email = _services.WorkContext.CurrentCustomer.Email, FullName = _services.WorkContext.CurrentCustomer.GetFullName(), - DisplayCaptcha = _captchaSettings.Value.Enabled && _captchaSettings.Value.ShowOnContactUsPage + DisplayCaptcha = _captchaSettings.Value.Enabled && _captchaSettings.Value.ShowOnContactUsPage, + DisplayPrivacyAgreement = _customerSettings.Value.DisplayPrivacyAgreementOnContactUs + }; return View(model); diff --git a/src/Presentation/SmartStore.Web/Models/Common/ContactUsModel.cs b/src/Presentation/SmartStore.Web/Models/Common/ContactUsModel.cs index f20fd69129..db9ab838bc 100644 --- a/src/Presentation/SmartStore.Web/Models/Common/ContactUsModel.cs +++ b/src/Presentation/SmartStore.Web/Models/Common/ContactUsModel.cs @@ -10,6 +10,11 @@ namespace SmartStore.Web.Models.Common [Validator(typeof(ContactUsValidator))] public partial class ContactUsModel : ModelBase { + [SmartResourceDisplayName("ContactUs.PrivacyAgreement")] + public bool PrivacyAgreement { get; set; } + + public bool DisplayPrivacyAgreement { get; set; } + [AllowHtml] [SmartResourceDisplayName("ContactUs.Email")] [DataType(DataType.EmailAddress)] diff --git a/src/Presentation/SmartStore.Web/Validators/Common/ContactUsValidator.cs b/src/Presentation/SmartStore.Web/Validators/Common/ContactUsValidator.cs index 7e9b037037..9e42f7ed02 100644 --- a/src/Presentation/SmartStore.Web/Validators/Common/ContactUsValidator.cs +++ b/src/Presentation/SmartStore.Web/Validators/Common/ContactUsValidator.cs @@ -8,6 +8,11 @@ public class ContactUsValidator : AbstractValidator { public ContactUsValidator(ILocalizationService localizationService) { + RuleFor(x => x.PrivacyAgreement) + .Must(x => x == true) + .WithMessage(localizationService.GetResource("ContactUs.PrivacyAgreement.MustBeAccepted")) + .When(x => x.DisplayPrivacyAgreement == true); + RuleFor(x => x.Email).NotEmpty().WithMessage(localizationService.GetResource("ContactUs.Email.Required")); RuleFor(x => x.Email).EmailAddress().WithMessage(localizationService.GetResource("Common.WrongEmail")); RuleFor(x => x.FullName).NotEmpty().WithMessage(localizationService.GetResource("ContactUs.FullName.Required")); diff --git a/src/Presentation/SmartStore.Web/Views/Home/ContactUs.cshtml b/src/Presentation/SmartStore.Web/Views/Home/ContactUs.cshtml index d0b173f3f2..7105b3d794 100644 --- a/src/Presentation/SmartStore.Web/Views/Home/ContactUs.cshtml +++ b/src/Presentation/SmartStore.Web/Views/Home/ContactUs.cshtml @@ -24,11 +24,6 @@ {
    - @if (!ViewData.ModelState.IsValid) - { - @Html.ValidationSummary(true) - } - @T("PageTitle.ContactUs")
    @@ -55,6 +50,28 @@
    + @if(Model.DisplayPrivacyAgreement) + { + @Html.HiddenFor(model => model.DisplayPrivacyAgreement) + +
    + @Html.LabelFor(model => model.PrivacyAgreement, new { @class = "control-label required", @for = "PrivacyAgreement" }) +
    +
    + +
    +
    + @Html.ValidationMessageFor(model => model.PrivacyAgreement) +
    +
    +
    + } + @if (Model.DisplayCaptcha) {
    From da1e3d1cd857bbb41b615b85e6d90d55820edfb3 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 23 Jun 2016 14:53:38 +0200 Subject: [PATCH 258/285] Increased version number to 2.6.0 --- SmartStoreNET.Tasks.Targets | 2 +- src/AssemblyVersionInfo.cs | 6 +- .../SmartStore.AmazonPay/Description.txt | 2 +- .../SmartStore.Clickatell/Description.txt | 2 +- .../SmartStore.DevTools/Description.txt | 2 +- .../SmartStore.DiscountRules/Description.txt | 2 +- .../SmartStore.FacebookAuth/Description.txt | 2 +- .../Description.txt | 2 +- .../Description.txt | 2 +- .../SmartStore.OfflinePayment/Description.txt | 2 +- src/Plugins/SmartStore.PayPal/Description.txt | 2 +- .../SmartStore.Shipping/Description.txt | 2 +- .../Description.txt | 2 +- src/Plugins/SmartStore.Tax/Description.txt | 2 +- src/Plugins/SmartStore.WebApi/Description.txt | 2 +- .../Localization/App/de/all.smres.xml | 339 ++++++++++++++++-- .../App_Data/Localization/App/de/head.txt | 2 +- .../Localization/App/en/all.smres.xml | 333 +++++++++++++++-- .../App_Data/Localization/App/en/head.txt | 2 +- 19 files changed, 616 insertions(+), 94 deletions(-) diff --git a/SmartStoreNET.Tasks.Targets b/SmartStoreNET.Tasks.Targets index 4fb755fcf5..f184eaa7eb 100644 --- a/SmartStoreNET.Tasks.Targets +++ b/SmartStoreNET.Tasks.Targets @@ -44,7 +44,7 @@ x86 $(BUILD_NUMBER) - 2.5.0 + 2.6.0 $(StageFolder) .$(Version) diff --git a/src/AssemblyVersionInfo.cs b/src/AssemblyVersionInfo.cs index 67335b18c1..f74f0edf78 100644 --- a/src/AssemblyVersionInfo.cs +++ b/src/AssemblyVersionInfo.cs @@ -9,7 +9,7 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("2.5.0.0")] +[assembly: AssemblyVersion("2.6.0.0")] -[assembly: AssemblyFileVersion("2.5.0.0")] -[assembly: AssemblyInformationalVersion("2.5.0.0")] +[assembly: AssemblyFileVersion("2.6.0.0")] +[assembly: AssemblyInformationalVersion("2.6.0.0")] diff --git a/src/Plugins/SmartStore.AmazonPay/Description.txt b/src/Plugins/SmartStore.AmazonPay/Description.txt index 3e203915e6..8237c4a7ad 100644 --- a/src/Plugins/SmartStore.AmazonPay/Description.txt +++ b/src/Plugins/SmartStore.AmazonPay/Description.txt @@ -1,6 +1,6 @@ FriendlyName: Pay with Amazon SystemName: SmartStore.AmazonPay -Version: 2.5.0 +Version: 2.6.0 Group: Payment MinAppVersion: 2.5.0 Author: SmartStore AG diff --git a/src/Plugins/SmartStore.Clickatell/Description.txt b/src/Plugins/SmartStore.Clickatell/Description.txt index d5e561a2fc..177b509843 100644 --- a/src/Plugins/SmartStore.Clickatell/Description.txt +++ b/src/Plugins/SmartStore.Clickatell/Description.txt @@ -1,7 +1,7 @@ FriendlyName: Clickatell SMS Provider SystemName: SmartStore.Clickatell Group: Mobile -Version: 2.5.0 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.Clickatell.dll diff --git a/src/Plugins/SmartStore.DevTools/Description.txt b/src/Plugins/SmartStore.DevTools/Description.txt index fff9df57de..b169d8b16a 100644 --- a/src/Plugins/SmartStore.DevTools/Description.txt +++ b/src/Plugins/SmartStore.DevTools/Description.txt @@ -1,7 +1,7 @@ FriendlyName: SmartStore.NET Developer Tools (MiniProfiler and other goodies) SystemName: SmartStore.DevTools Group: Developer -Version: 2.5.0 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.DevTools.dll diff --git a/src/Plugins/SmartStore.DiscountRules/Description.txt b/src/Plugins/SmartStore.DiscountRules/Description.txt index a2336d8b8e..f4bb29d728 100644 --- a/src/Plugins/SmartStore.DiscountRules/Description.txt +++ b/src/Plugins/SmartStore.DiscountRules/Description.txt @@ -2,7 +2,7 @@ Description: Contains common discount requirement rule providers like "Billing country is", "Customer role is", "Had spent amount" etc. Group: Marketing SystemName: SmartStore.DiscountRules -Version: 2.5.0.1 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 0 FileName: SmartStore.DiscountRules.dll diff --git a/src/Plugins/SmartStore.FacebookAuth/Description.txt b/src/Plugins/SmartStore.FacebookAuth/Description.txt index 6e028c02d4..b5411fc4ac 100644 --- a/src/Plugins/SmartStore.FacebookAuth/Description.txt +++ b/src/Plugins/SmartStore.FacebookAuth/Description.txt @@ -1,7 +1,7 @@ FriendlyName: Facebook SystemName: SmartStore.FacebookAuth Group: Security -Version: 2.5.0 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 5 FileName: SmartStore.FacebookAuth.dll diff --git a/src/Plugins/SmartStore.GoogleAnalytics/Description.txt b/src/Plugins/SmartStore.GoogleAnalytics/Description.txt index 908e329118..59bf21b018 100644 --- a/src/Plugins/SmartStore.GoogleAnalytics/Description.txt +++ b/src/Plugins/SmartStore.GoogleAnalytics/Description.txt @@ -1,7 +1,7 @@ FriendlyName: Google Analytics SystemName: SmartStore.GoogleAnalytics Group: Analytics -Version: 2.5.0 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.GoogleAnalytics.dll diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt index 8187db5c15..5bc3e9ad59 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt @@ -1,7 +1,7 @@ FriendlyName: Google Merchant Center (GMC) feed SystemName: SmartStore.GoogleMerchantCenter Group: Marketing -Version: 2.5.0.1 +Version: 2.6.0 MinAppVersion: 2.5.0 Author: SmartStore AG DisplayOrder: 1 diff --git a/src/Plugins/SmartStore.OfflinePayment/Description.txt b/src/Plugins/SmartStore.OfflinePayment/Description.txt index 0b09221978..db6f0241f9 100644 --- a/src/Plugins/SmartStore.OfflinePayment/Description.txt +++ b/src/Plugins/SmartStore.OfflinePayment/Description.txt @@ -2,7 +2,7 @@ Description: Contains common offline payment methods like Direct Debit, Invoice, Prepayment etc. Group: Payment SystemName: SmartStore.OfflinePayment -Version: 2.5.0 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 0 FileName: SmartStore.OfflinePayment.dll diff --git a/src/Plugins/SmartStore.PayPal/Description.txt b/src/Plugins/SmartStore.PayPal/Description.txt index c78ff1bd32..75a43e5adb 100644 --- a/src/Plugins/SmartStore.PayPal/Description.txt +++ b/src/Plugins/SmartStore.PayPal/Description.txt @@ -2,7 +2,7 @@ Description: Provides the PayPal payment methods PayPal Standard, PayPal Direct, PayPal Express and PayPal PLUS. SystemName: SmartStore.PayPal Group: Payment -Version: 2.5.0.2 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.PayPal.dll diff --git a/src/Plugins/SmartStore.Shipping/Description.txt b/src/Plugins/SmartStore.Shipping/Description.txt index 5d85eddbb8..f241173e84 100644 --- a/src/Plugins/SmartStore.Shipping/Description.txt +++ b/src/Plugins/SmartStore.Shipping/Description.txt @@ -2,7 +2,7 @@ Description: Provides shipping methods for fixed rate shipping and computation based on weight. SystemName: SmartStore.Shipping Group: Shipping -Version: 2.5.0 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.Shipping.dll diff --git a/src/Plugins/SmartStore.ShippingByWeight/Description.txt b/src/Plugins/SmartStore.ShippingByWeight/Description.txt index 76876783c9..218a277c2b 100644 --- a/src/Plugins/SmartStore.ShippingByWeight/Description.txt +++ b/src/Plugins/SmartStore.ShippingByWeight/Description.txt @@ -1,7 +1,7 @@ FriendlyName: Shipping by weight SystemName: SmartStore.ShippingByWeight Group: Shipping -Version: 2.5.0.1 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.ShippingByWeight.dll diff --git a/src/Plugins/SmartStore.Tax/Description.txt b/src/Plugins/SmartStore.Tax/Description.txt index 79d79ec7a8..ec211f9efe 100644 --- a/src/Plugins/SmartStore.Tax/Description.txt +++ b/src/Plugins/SmartStore.Tax/Description.txt @@ -2,7 +2,7 @@ Description: Contains default tax providers like FixedRate, ByRegion etc. Group: Tax SystemName: SmartStore.Tax -Version: 2.5.0 +Version: 2.6.0 MinAppVersion: 2.5.0 DisplayOrder: 0 FileName: SmartStore.Tax.dll diff --git a/src/Plugins/SmartStore.WebApi/Description.txt b/src/Plugins/SmartStore.WebApi/Description.txt index eacdc805f8..646ac2fd0d 100644 --- a/src/Plugins/SmartStore.WebApi/Description.txt +++ b/src/Plugins/SmartStore.WebApi/Description.txt @@ -1,6 +1,6 @@ FriendlyName: SmartStore.NET Web Api SystemName: SmartStore.WebApi -Version: 2.5.0.1 +Version: 2.6.0 Group: Api MinAppVersion: 2.5.0 Author: SmartStore AG diff --git a/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/all.smres.xml b/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/all.smres.xml index e7e5d5e8fa..8e379a8055 100644 --- a/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/all.smres.xml +++ b/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/all.smres.xml @@ -3136,6 +3136,12 @@ Bearbeiten + + Die E-Mail wurde erfolgreich versendet. + + + Bitte geben Sie eine E-Mail-Adresse ein. + Objekt @@ -3187,6 +3193,12 @@ Als XML exportieren + + Die Datei ist in Benutzung und kann daher nicht geöffnet werden. + + + Datei nicht gefunden + {0} Dateien wurden gelöscht @@ -3196,6 +3208,9 @@ {0} Verzeichnisse wurden gelöscht + + FTP-Status {0} ({1}). + Allgemein @@ -3220,6 +3235,12 @@ Verbergen + + HTTP-Status {0} ({1}). + + + Ignorieren + Importdatei @@ -3232,6 +3253,9 @@ Info + + Letzte Ausführung + Lizenzieren @@ -3256,9 +3280,6 @@ Es wurden keine Einträge ausgewählt. - - Es sind keine temporären Dateien vorhanden. - von @@ -3328,6 +3349,9 @@ Anzeigen + + Werte für Überspringen und Begrenzen müssen größer oder gleich 0 sein. + Ausgelassen @@ -3361,12 +3385,12 @@ Alle Shops + + Erfolgreich am + Der Vorgang wurde erfolgreich ausgeführt. - - Temporäre Dateien - Zeilen insgesamt @@ -4045,6 +4069,9 @@ Die E-Mail wurde erfolgreich versandt. + + Test der E-Mail-Funktion. + Das E-Mail-Konto wurde erfolgreich bearbeitet. @@ -4474,6 +4501,9 @@ Regionale Einstellungen + + Sie müssen zunächst speichern, bevor Sie Einschränkungen festlegen können. + Einstellungen @@ -4666,6 +4696,12 @@ Legt fest, ob das Standardbild bei Warengruppen ausgeblendet werden soll. Das Standardbild wird angezeigt, wenn der Warengruppe kein Bild zugeordnet ist. + + Standardbild bei Herstellern ausblenden + + + Legt fest, ob das Standardbild bei Herstellern ausgeblendet werden soll. Das Standardbild wird angezeigt, wenn dem Hersteller kein Bild zugeordnet ist. + Standardbild bei Produkten ausblenden @@ -4972,6 +5008,12 @@ Bestimmt ob Hersteller auf der Homepage als Bilder oder textuelle Links angezeigt werden. + + Bilder von Herstellern anzeigen + + + Legt fest, ob Herstellerbilder auf der Produktdetailseite angezeigt werden sollen. + Zeige Hersteller auf der Homepage @@ -5242,6 +5284,18 @@ Bestimmt ob Kundennummern hinterlegt werden können. + + Kundennummern + + + Legt fest, ob Kundennummern vergeben werden und ob diese automatisch vergeben werden sollen. + + + Darstellung der Kundennummer + + + Legt die Darstellung und Handhabung der Kundennummer gegenüber dem Kunden fest. + Kundeneinstellungen @@ -5278,6 +5332,12 @@ Bestimmt ob Kunden ihre Kundennummer in Ihrem Account-Bereich einsehen können. + + Einwilligungserklärung im Kontaktformular fordern + + + Bestimmt ob im Kontaktformular eine Checkbox angezeigt wird, die den Benutzer auffordert der Speicherung seiner Daten zuzustimmen. + Automatische Registrierung aktiviert @@ -6382,6 +6442,12 @@ Legen Sie einen Startwert für Bestellnummern fest. Dies ist nützlich, wenn Sie Ihre Aufträge ab einem Bestimmten Wert starten lassen möchten. Der Wert muss größer sein, als die aktuell höchste Bestellnummer und gilt für zukünftige Bestellungen. + + Anzahl der Aufträge pro Seite + + + Legt die Anzahl der dargestellten Aufträge pro Seite fest. + Auftragseinstellungen @@ -6712,6 +6778,21 @@ Legt fest ob das Produktgewicht im Warenkorb angezeigt wird. + + Zustimmung zur E-Mail Weitergabe an Dritte + + + Legt fest, ob Kunden bei einer Bestellung der Weitergabe ihrer E-Mail Adresse an Dritte zustimmen können und ob die Checkbox dafür standardmäßig aktiviert ist. + + + Text für E-Mail Weitergabe + + + Mit der Übermittlung und Speicherung meiner E-Mail-Adresse durch dritte Parteien bin ich einverstanden. + + + Legt den Text für die Zustimmung zur Weitergabe der E-Mail Adresse an Dritte fest. Wählen Sie bitte einen konkreten Grund für die Weitergabe, z.B. 'Mit der Übermittlung und Speicherung meiner E-Mail-Adresse zur Abwicklung des Käuferschutzes durch Trusted Shops bin ich einverstanden.' + Wunschzettel @@ -8629,6 +8710,9 @@ Dashboard + + Neues Profil + Standardwert @@ -8644,9 +8728,6 @@ Die gespeicherten Feldzuordnungen sind aufgrund der Änderung des Trennzeichens ungültig und wurden zurückgesetzt. - - Die folgenden Objekteigenschaft wurden mehrfach zugeodnet und deshalb ignoriert: {0} - Trennzeichen @@ -8708,10 +8789,10 @@ Ein System-Exportprofil kann nicht gelöscht werden. - Zum Schluss aufräumen + Nach erfolgreicher Veröffentlichung aufräumen - Legt fest, ob nicht mehr benötigte Dateien nach der Bereitstellung gelöscht werden sollen. + Legt fest, ob nicht mehr benötigte Dateien gelöscht werden sollen, nachdem alle Veröffentlichungen erfolgreich waren. Einstellungen übernehmen von @@ -8726,10 +8807,10 @@ Export von Profil "{0}" ist abgeschlossen - E-Mail-Addressen an + E-Mail Adressen an - Legt die E-Mail Addressen fest, an die die Benachrichtigung geschickt werden soll. + Legt die E-Mail Adressen fest, an die die Benachrichtigung geschickt werden soll. Die folgenden spezifischen Angaben werden durch den Provider beim Export berücksichtigt. @@ -8743,17 +8824,17 @@ Legt fest, ob die Exportdateien in einem ZIP-Archiv zusammengefasst werden sollen. Das Archiv verbleibt im temporären Ordner des Exportprofils ohne weitere Vearbeitung. - - ZIP-Archiv erstellen + + Mindestens eine Datei konnte nicht kopiert werden. Legt fest, ob die Exportdateien in einem ZIP-Archiv zusammengefasst und nur das Archiv bereitgestellt werden soll. - Art der Bereitstellung + Art der Veröffentlichung - Legt die Art Bereitstellung fest. + Legt die Art Veröffentlichung fest. E-Mail Konto @@ -8762,10 +8843,10 @@ Legt das E-Mail Konto fest, über welches die Daten verschickt werden sollen. - E-Mail-Addressen an + E-Mail Adressen an - Legt die E-Mail Addressen fest, an die die Daten verschickt werden soll. + Legt die E-Mail Adressen fest, an die die Daten verschickt werden soll. E-Mail Betreff @@ -8785,20 +8866,20 @@ Legt fest, aus welcher Art die Exportdateien per HTTP übertragen werden sollen. - - In öffentlichen Ordner kopieren - Legt fest, ob die exportierten Daten in einen übers Internet zugänglichen Ordner kopiert werden sollen. - Name + Name des Profils - Legt den Namen der Bereitstellung fest. + Legt den Namen des Veröffentlichungsprofils fest. + + + Es liegen keine Veröffentlichungsprofile vor. - Legen Sie über <b>Hinzufügen</b> ein oder mehrere Veröffentlichungsprofile an, um festzulegen wie mit den Exportdateien weiter zu verfahren ist. + Legen Sie über <b>Neues Profil</b> ein oder mehrere Veröffentlichungsprofile an, um festzulegen wie mit den Exportdateien weiter zu verfahren ist. Passiver Modus @@ -8812,6 +8893,18 @@ Legt das Passwort fest. + + Veröffentlichungsprofile + + + Veröffentlichungsziel + + + Name des Unterordners + + + Legt den Namen eines Unterordners fest, in den die Daten veröffentlicht werden sollen. + URL\Host @@ -8830,6 +8923,9 @@ Legt fest, ob einen SSL (Secure Sockets Layer) Verbindung genutzt werden soll. + + Bei einer großen Anzahl an Exportdateien wird empfohlen die Option <b>ZIP-Archiv erstellen</b> zu benutzen. Das spart Zeit und vermeidet Probleme, wie z.B. ein volles E-Mail Postfach. + E-Mail Benachrichtigung @@ -8860,6 +8956,9 @@ Legt das Muster fest, nach dem Dateinamen erzeugt werden. + + Bitte ein gültiges Muster für Dateinamen eingeben. Beispiel: %Store.Id%-%Profile.Id%-%File.Index%-%Profile.SeoName% + ID des Exportprofils;Ordername des Exportprofils;SEO Name des Exportprofils;Shop ID;SEO Name des Shops;Mit 1 beginnender Dateiindex;Zufallszahl;UTC Zeitstempel @@ -8875,6 +8974,12 @@ Nach der Verfügbarkeitsmenge filtern. + + Rechnungsländer + + + Nach Rechnungsländern filtern. + Warengruppen @@ -8905,6 +9010,18 @@ Nach empfohlenen Produkten filtern. Wird nur bei der Filterung nach Warengruppen und Hersteller angewendet. + + Hat x Bestellungen + + + Nach der Anzahl der getätigten Bestellungen filtern. + + + Hat Betrag x ausgegeben + + + Nach dem insgesamt ausgegebenen Betrag filtern. + Produkt-ID bis @@ -8917,12 +9034,42 @@ Nach der Produkt-ID filtern. + + Nur aktive Kunden + + + Nach aktiven bzw. inaktiven Kunden filtern. + + + Nur aktive Abonnenten + + + Nach aktiven bzw. inaktiven Newsletter Abonnenten filtern. + Veröffentlicht Nach Veröffentlichung filtern. + + Nur MwSt. befreite Kunden + + + Nach MwSt. befreiten Kunden filtern. + + + Zuletzt aktiv von + + + Nach dem Datum der letzten Shop Aktivität filtern. + + + Zuletzt aktiv bis + + + Nach dem Datum der letzten Shop-Aktivität filtern. + Hersteller @@ -8968,6 +9115,12 @@ Nach Produkttyp filtern. + + Versandländer + + + Nach Versandländern filtern. + Versandstatus @@ -8992,14 +9145,14 @@ Nach fehlender Herstellerzuordnung filtern. - - Bitte einen gültigen Ordner- und Dateinamen eingeben. Beispiel für Dateinamen: %File.Index%-%Profile.Id%-gmc-%Store.Name% - - Ordnername + Ordnerpfad - Legt den Namen des Ordners fest, in den die Daten exportiert werden. + Legt den relativen Pfad des Ordners fest, in den die Daten exportiert werden. + + + Bitte einen gültigen, relativen Ordnerpfad für die zu exportierenden Daten eingeben. Systemprofil @@ -9040,6 +9193,12 @@ Diese Option wird in der Vorschau nicht berücksichtigt. + + Mit den folgenden Einstellungen lassen sich die zu exportierenden Daten aufteilen. Dazu zählt<ul><li>Das Überspringen der ersten n Datensätze</li><li>Die maximale Anzahl zu exportierender Datensätze</li><li>Die Anzahl der Datensätze pro Exportdatei</li><li>Daten von jedem Shop in eine separate Datei exportieren</li></ul>Standardmäßig werden alle Daten eines Shops in eine Datei exportiert. + + + Einstellungen zur Aufteilung müssen größer oder gleich 0 sein. + Per Shop @@ -9196,6 +9355,15 @@ Der Systemname des Exportprofils. + + Die folgende Liste enthält Systemprofile, die von Plugins wie bspw. dem <a href='http://community.smartstore.com/index.php?/files/file/85-smartstorenet-common-export-providers/' target='_blank'>Datenexporte Plugin</a> bereitgestellt werden. Sie können Systemprofile nach Belieben anpassen, aber keine Neuen erstellen. Für diese Profile stehen außerdem zusätzliche Aktions-Buttons zur Verfügung. Sie finden diese über den entsprechenden Listen, wie z.B. der Produkt- oder Auftragsliste. + + + Systemprofile + + + Benutzerprofile + Importdatei hinzufügen... @@ -9241,11 +9409,14 @@ Es wurden keine Importprofile gefunden. - - Laden Sie bitte eine Importdatei hoch, legen Sie einen aussagekräftigen Namen für das Importprofil fest und speichern Sie. + + Anzahl der Bilder + + + Legt die Anzahl der zu importierenden Bilder pro Objekt fest. - - Wählen Sie bitte ein Objekt aus, das Sie importieren möchten. + + Wählen Sie bitte das zu importierende Objekt und laden Sie eine Importdatei hoch. {0} von {1} Datensätzen verarbeitet @@ -9500,7 +9671,7 @@ Zahlung wurde erfasst - Fehler bei Zahlungserfassung für Auftrag #{0}. Fehler: {1} + Es ist ein Fehler bei der Zahlungsbuchung zu Auftrag {0} aufgetreten. Auftrag wurde gelöscht @@ -9607,6 +9778,12 @@ Auftragsdetails bearbeiten + + Akzeptiert Weitergabe der E-Mail + + + Gibt an, ob der Kunde bei der Bestellung einer Weitergabe seiner E-Mail Adresse an Dritte zugestimmt hat oder nicht. + Partner @@ -11560,6 +11737,9 @@ Abbruch erzwungen durch Herunterfahren der Anwendung + + Die geplante Aufgabe "{0}" wurde abgebrochen + Abbruchanforderung wurde übermittelt. @@ -11617,6 +11797,9 @@ Datum der nächsten geplanten Ausführung + + Fehler beim Ausführen der Aufgabe "{0}" + Jetzt ausführen @@ -11866,6 +12049,9 @@ Es wird empfohlen, nur eine Versandkosten-Offline-Berechnungsmethode zu benutzen. + + Die Erreichbarkeit der Sitemap konnte nicht überprüft werden. + Die Sitemap für den Shop ist erreichbar. @@ -12169,6 +12355,9 @@ Bitte geben Sie Benutzername und Passwort ein + + Der Wert muss größer 0 sein. + API ist nicht erreichbar. @@ -12577,6 +12766,15 @@ Fortsetzen + + In die Zwischenablage kopieren + + + Kopieren ist fehlgeschlagen. + + + Kopiert! + Anzahl @@ -12769,6 +12967,9 @@ Lade nächsten Schritt… + + Diese Funktion steht für Gäste nicht zur Verfügung. + Verschiedenes @@ -12778,6 +12979,9 @@ Mehr Info + + Mein + Navigation @@ -12793,6 +12997,12 @@ Nein, abbrechen + + Es sind keine Dateien vorhanden. + + + Es wurde keine Datei hochgeladen. + Der Vorgang wurde aus Sicherheitsgründen nicht ausgeführt. @@ -12865,6 +13075,9 @@ Veröffentlicht + + Veröffentlichung + Frage @@ -13036,6 +13249,15 @@ Namen eingeben + + Einwilligungserklärung Datenschutz + + + Ja, ich habe die <a href="{0}">Datenschutzerklärung</a> zur Kenntnis genommen und bin damit einverstanden, dass die von mir angegebenen Daten elektronisch erhoben und gespeichert werden. Meine Daten werden dabei nur zur Bearbeitung meiner Anfrage genutzt. + + + Bitte stimmen Sie der Speicherung Ihrer Daten zu. + Ihre Anfrage wurde erfolgreich übermittelt. @@ -13252,6 +13474,27 @@ Benutzernamen anzeigen + + Automatisch vergeben + + + Deaktiviert + + + Aktiviert + + + Anzeigen + + + Immer editierbar + + + Editierbar falls leer + + + Nicht anzeigen + Lesbar @@ -13277,7 +13520,10 @@ FTP - HTTP + HTTP POST + + + Öffentlicher Ordner Detailbeschreibung @@ -13295,7 +13541,7 @@ Produktname + Kurzbeschreibung - Nicht spezifiziert + Keine Kurzbeschreibung @@ -13447,6 +13693,15 @@ Nicht anzeigen + + Aktiviert anzeigen + + + Deaktiviert anzeigen + + + Nicht anzeigen + Abgebrochen @@ -14807,6 +15062,9 @@ Datenschutzerklärung + + Private Nachrichten sind deaktiviert. + Posteingang @@ -14937,7 +15195,10 @@ Nicht am Lager - Grundpreis: {0} pro {1} + Inhalt: {0} {1} ({2} / {3} {1}) + + + {0} {1} ({2} / {3} {1}) Home @@ -15138,7 +15399,7 @@ Dieses Produkt ist ausverkauft. - Dieses Produkt ist ausverkauft. + Keine Bundle-Bestandteile vorhanden Das Produkt {0} wurde nicht gefunden. diff --git a/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/head.txt b/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/head.txt index fb2d850500..f822a9cb76 100644 --- a/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/head.txt +++ b/src/Presentation/SmartStore.Web/App_Data/Localization/App/de/head.txt @@ -1 +1 @@ -201601262000441_ImportFramework1 \ No newline at end of file +201605201911421_ExportRevision \ No newline at end of file diff --git a/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/all.smres.xml b/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/all.smres.xml index 6b493fa664..734f05e1de 100644 --- a/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/all.smres.xml +++ b/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/all.smres.xml @@ -3232,6 +3232,12 @@ Edit + + The email has been successfully sent. + + + Please enter an email address. + Object @@ -3283,6 +3289,12 @@ Export to XML + + The file is in use and cannot be opened. + + + File not found + {0} files were deleted @@ -3292,6 +3304,9 @@ {0} folders were deleted + + FTP status {0} ({1}). + General @@ -3316,6 +3331,12 @@ Hide + + HTTP status {0} ({1}). + + + Ignore + Import file @@ -3328,6 +3349,9 @@ Info + + Last run + License @@ -3352,9 +3376,6 @@ No entries have been selected. - - There are no temporary files. - of @@ -3424,6 +3445,9 @@ Show + + Values for skip and limit must be greater than or equal to 0. + Skipped @@ -3457,12 +3481,12 @@ All stores + + Successful on + The task was successfully processed. - - Temporary files - Total rows @@ -4171,6 +4195,9 @@ Email has been successfully sent. + + Testing email functionality. + The email account has been updated successfully. @@ -4627,6 +4654,9 @@ Regional Settings + + You need to save before you can specify restrictions. + Settings @@ -4751,7 +4781,7 @@ Check the box to allow customers to use the 'Compare Products' option in your store - Amount of displayed products per page + Number of displayed products per page Determines the amount of displayed products per page. @@ -4822,6 +4852,12 @@ Specifies whether to hide the default image for categories. The default image is shown when no image is assigned to a category. + + Hide default picture for manufacturers + + + Specifies whether to hide the default image for manufacturers. The default image is shown when no image is assigned to a manufacturer. + Hide default picture for products @@ -5128,6 +5164,12 @@ Specifies whether manufacturers will be displayed as images or textual links on the homepage. + + Show manufacturer pictures + + + Specifies whether to show manufacturer pictures on product detail page. + Show manufacturers on homepage @@ -5398,6 +5440,18 @@ Specifies whether customer numbers can be saved. + + Customer numbers + + + Specifies whether to assign customer numbers and whether they should be created automatically. + + + Customer number presentation + + + Specifies the presentation and handling of the customer number to the customer. + Customer settings @@ -5434,6 +5488,12 @@ Specifies whether customer numbers will be displayed to customers in their account area. + + Get privacy consent for contact requests + + + Specifies whether a checkbox will be displayed on the contact page which requests the user to agree on storage of his data. + Auto register enabled @@ -6550,6 +6610,12 @@ Set the order ID counter. This is useful if you want your orders to start at a certain number. This only affects orders created going forward. The value must be greater than the current maximum order ID. + + Number of displayed orders per page + + + Specifies the number of displayed orders per page. + Order settings @@ -6880,6 +6946,21 @@ Determines whether the product weight is shown in shopping cart + + Consent for email transfer to third parties + + + Specifies whether customers can agree to a transferring of their email address to third parties when ordering, and whether the checkbox is enabled by default during checkout. + + + Text for email transfer consent + + + I agree to the transfer and storage of my email address by third parties. + + + Specifies the text to be displayed to the customer. Please choose a specific reason, e.g. 'I agree to the transfer and storage of my email address for TrustedShops buyer protection.' + Wishlist @@ -8887,6 +8968,9 @@ Store Statistics + + New profile + Default Value @@ -8902,9 +8986,6 @@ The stored field assignments are invalid due to the change of the delimiter and were reset. - - The following object properties were multiple assigned and thus ignored: {0} - Delimiter @@ -8966,10 +9047,10 @@ Cannot delete a system export profile. - Clean up at the end + Clean up after successful deployment - Specifies whether to delete unneeded files after deployment. + Specifies whether to delete unneeded files after all deployments succeeded. Apply settings from @@ -9001,17 +9082,17 @@ Specifies whether to combine the export files in temporary a ZIP archive. The archive remains in the temporary folder of the export profile without further processing. - - Create ZIP archive + + At least one file could not be copied. Specifies whether to combine the export files in a ZIP archive and only to deploy the archive. - Type of deployment + Publishing type - Specifies the deployment type. + Specifies the type of publishing. Email account @@ -9043,20 +9124,20 @@ Specifies how to transmit the export files via HTTP. - - Copy to public folder - Specifies whether to copy the exported data into a folder that is accessible through the internet. - Name + Name of profile - Specifies the name of the deployment. + Specifies the name of the publishing profile. + + + There are no publishing profiles. - Click <b>Insert</b> to add one or multiple publishing profiles to specify how to further proceed with the export files. + Click <b>New profile</b> to add one or multiple publishing profiles to specify how to further proceed with the export files. Passive mode @@ -9070,6 +9151,18 @@ Specifies the password. + + Publishing profiles + + + Publishing target + + + Name of subfolder + + + Specifies the name of a subfolder where to publish the data. + URL\Host @@ -9088,6 +9181,9 @@ Specifies whether to use a SSL (Secure Sockets Layer) connection. + + If there are a large number of export files, it is recommended to use the option <b>Create ZIP archive</b>. This saves time and avoids problems, such as a full email mailbox. + Email notification @@ -9118,6 +9214,9 @@ Specifies the pattern for creating file names. + + Please enter a valid pattern for file names. Example for file names: %Store.Id%-%Profile.Id%-%File.Index%-%Profile.SeoName% + ID of export profil;Folder name of export profil;SEO name of export profil;Store ID;SEO name of store;One based file index;Random number;UTC timestamp @@ -9133,6 +9232,12 @@ Filter by availability quantity. + + Billing countries + + + Filter by billing countries. + Categories @@ -9163,6 +9268,18 @@ Filter by featured products. Is only applied when the filtering by categories and manufacturers. + + Has placed x orders + + + Filter by number of placed orders. + + + Has spent amount x + + + Filter by spent amount. + Product Id to @@ -9175,12 +9292,42 @@ Filter by product identifier. + + Only active customers + + + Filter by active or inactive customers. + + + Only active subscribers + + + Filter by active or inactive newsletter subscribers. + Published Filter by publishing. + + Only tax exempt customers + + + Filter by tax exempt customers. + + + Last activity from + + + Filter by date of last store activity. + + + Last active until + + + Filter by date of last store activity. + Manufacturer @@ -9226,6 +9373,12 @@ Filter by product type. + + Shipping countries + + + Filter by shipping countries. + Shipping status @@ -9250,14 +9403,14 @@ Filter by missing manufacturer mapping. - - Please enter a valid folder and file name. Example for file names: %File.Index%-%Profile.Id%-gmc-%Store.Name% - - Folder name + Folder path - Specifies the name of the folder where to export the data. + Specifies the relative path of the folder where to export the data. + + + Please enter a valid, relative folder path for the export data. System profile @@ -9298,6 +9451,12 @@ This option is not taken into account in the preview. + + With the following settings you can partition the data to be exported. This includes<ul><li>Skipping the first n records</li><li>The maximum number of records to be exported</li><li>The number of records per export file</li><li>Export data for each shop in a separate file</li></ul>By default, all data of a store will be exported into one file. + + + Partitioning setting values must be greater than or equal to 0. + Per store @@ -9454,6 +9613,15 @@ The system name of the export profile. + + The following list contains system profiles, which are provided by plugins such as the <a href='http://community.smartstore.com/index.php?/files/file/85-smartstorenet-common-export-providers/' target='_blank'>Data Export Plugin</a>. You can customize system profiles as desired, but cannot create new ones. These profiles also add additional action buttons. You will find these above data lists, such as the product or order list. + + + System profiles + + + User profiles + Add import file... @@ -9499,11 +9667,14 @@ There were no import profiles found. - - Please upload an import file, enter a meaningful name for the import profile and save. + + Number of pictures + + + Specifies the number of images per object to be imported. - - Please select an object that you want to import. + + Please select the import object and upload an import file. {0} of {1} records processed @@ -9773,7 +9944,7 @@ Order has been captured - Error capturing order #{0}. Error: {1} + Unable to capture payment for order {0}. Order has been deleted @@ -9886,6 +10057,12 @@ Affiliate + + Accepts transfer of email + + + Indicates whether the customer has agreed to a transfer of his email address to third parties. + Affiliate @@ -11893,6 +12070,9 @@ Abnormally aborted due to application shutdown + + The scheduled task "{0}" has been canceled + Cancellation request has been submitted. @@ -11950,6 +12130,9 @@ Date of the next scheduled execution + + Error while running scheduled task "{0}" + Run now @@ -12223,6 +12406,9 @@ Only one offline shipping rate computation method is recommended to use + + The reachability of the sitemap could not be validated. + The sitemap for the store is reachable. @@ -12544,6 +12730,9 @@ Please enter username and password + + The value must be greater than 0. + API not available. @@ -12952,6 +13141,15 @@ Continue + + Copy to clipboard + + + Failed to copy. + + + Copied! + Count @@ -13144,6 +13342,9 @@ Loading next step... + + This function is not available for guests. + Miscellaneous @@ -13153,6 +13354,9 @@ More info + + My + Navigation @@ -13168,6 +13372,12 @@ No, cancel + + There are no files available. + + + There was no file uploaded. + The operation was not carried out for security reasons. @@ -13240,6 +13450,9 @@ Published + + Publishing + Question @@ -13411,6 +13624,15 @@ Enter your name + + Privacy consent + + + Yes I've read the <a href="{0}">privacy terms</a> and agree that my data given by me can be stored electronically. My data will thereby only be used to process my inquiry. + + + Please agree to the storage of your data. + Your enquiry has been successfully sent to the store owner. @@ -13624,6 +13846,27 @@ Show usernames + + Automatically assigned + + + Disabled + + + Enabled + + + Display + + + Always editable + + + Editable if empty + + + Do not display + Clear @@ -13649,7 +13892,10 @@ FTP - HTTP + HTTP POST + + + Public folder Description @@ -13667,7 +13913,7 @@ Product name + short description - Not specified + None Short description @@ -13819,6 +14065,15 @@ Do not show + + Show activated + + + Show deactivated + + + Do not show + Cancelled @@ -15156,6 +15411,9 @@ Privacy Notice + + Private messages are disabled. + Inbox @@ -15286,7 +15544,10 @@ Out of stock - base price: {0} per {1} + Content: {0} {1} ({2} / {3} {1}) + + + {0} {1} ({2} / {3} {1}) Top @@ -15487,7 +15748,7 @@ This product is sold out. - This product is sold out. + No bundle items available The product {0} was not found. diff --git a/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/head.txt b/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/head.txt index fb2d850500..f822a9cb76 100644 --- a/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/head.txt +++ b/src/Presentation/SmartStore.Web/App_Data/Localization/App/en/head.txt @@ -1 +1 @@ -201601262000441_ImportFramework1 \ No newline at end of file +201605201911421_ExportRevision \ No newline at end of file From f15dcc25220a96d4f326978e048c15b781124f1d Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 30 Jun 2016 10:55:41 +0200 Subject: [PATCH 259/285] PayPal PLUS throws an exception if an initially empty configuration page is saved --- src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs | 3 ++- src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj | 2 +- .../SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs index d7752e61f5..f81f6c2d7f 100644 --- a/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs +++ b/src/Plugins/SmartStore.PayPal/Models/ApiConfigurationModels.cs @@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations; using System.Net; using System.Web.Mvc; -using SmartStore.PayPal.Services; using SmartStore.PayPal.Settings; using SmartStore.Web.Framework; using SmartStore.Web.Framework.Modelling; @@ -164,6 +163,7 @@ public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) { SecurityProtocol = settings.SecurityProtocol; UseSandbox = settings.UseSandbox; + TransactMode = (int)Settings.TransactMode.AuthorizeAndCapture; AdditionalFee = settings.AdditionalFee; AdditionalFeePercentage = settings.AdditionalFeePercentage; @@ -179,6 +179,7 @@ public void Copy(PayPalPlusPaymentSettings settings, bool fromSettings) { settings.SecurityProtocol = SecurityProtocol; settings.UseSandbox = UseSandbox; + settings.TransactMode = Settings.TransactMode.AuthorizeAndCapture; settings.AdditionalFee = AdditionalFee; settings.AdditionalFeePercentage = AdditionalFeePercentage; diff --git a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj index 2690f97cb2..e82c4a4097 100644 --- a/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj +++ b/src/Plugins/SmartStore.PayPal/SmartStore.PayPal.csproj @@ -70,7 +70,7 @@ ..\..\packages\Newtonsoft.Json.8.0.3\lib\net45\Newtonsoft.Json.dll - True + False diff --git a/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs b/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs index 54fa987d14..b7355b4299 100644 --- a/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs +++ b/src/Plugins/SmartStore.PayPal/Validators/PayPalPlusConfigValidator.cs @@ -25,7 +25,7 @@ public PayPalPlusConfigValidator(ILocalizationService localize, Func x.ThirdPartyPaymentMethods) - .Must(x => x.Count <= 5) + .Must(x => x == null || x.Count <= 5) .WithMessage(localize.GetResource("Plugins.Payments.PayPalPlus.ValidateThirdPartyPaymentMethods")); } } From ab3c8b192840817ba25d92a1574888f8e6a119f7 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 4 Jul 2016 12:10:31 +0200 Subject: [PATCH 260/285] Export all product categories rather than all of current store (causes shop connector to not import all categories) --- .../SmartStore.Services/DataExchange/Export/DataExporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 41cb53a69f..26d20b72a8 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -293,7 +293,7 @@ private IExportDataSegmenterProvider CreateSegmenter(DataExporterContext ctx, in x => _productAttributeService.Value.GetProductVariantAttributesByProductIds(x, null), x => _productAttributeService.Value.GetProductVariantAttributeCombinations(x), x => _productService.Value.GetTierPricesByProductIds(x, (ctx.Projection.CurrencyId ?? 0) != 0 ? ctx.ContextCustomer : null, ctx.Store.Id), - x => _categoryService.Value.GetProductCategoriesByProductIds(x), + x => _categoryService.Value.GetProductCategoriesByProductIds(x, null, true), x => _manufacturerService.Value.GetProductManufacturersByProductIds(x), x => _productService.Value.GetProductPicturesByProductIds(x), x => _productService.Value.GetProductTagsByProductIds(x), From dfb6e116f640cfc5e71d5a1631183c7e74046276 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 4 Jul 2016 20:38:03 +0200 Subject: [PATCH 261/285] SKU, EAN, MPN of last attribute combination was exported for all combinations --- .../Export/DynamicEntityHelper.cs | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs index e925553e9f..70ef30f356 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs @@ -1013,19 +1013,17 @@ private List Convert(DataExporterContext ctx, Product product) //var productValues = new Dictionary(); var dbContext = _dbContext as DbContext; - product = _dbContext.Attach(product); - - var entry = dbContext.Entry(product); - - // the returned object is not the entity and is not being tracked by the context. - // it also does not have any relationships set to other objects. - // CurrentValues only includes database (thus primitive) values. - var productClone = entry.CurrentValues.ToObject() as Product; - - _dbContext.DetachEntity(product); - foreach (var combination in combinations.Where(x => x.IsActive)) { + product = _dbContext.Attach(product); + var entry = dbContext.Entry(product); + + // the returned object is not the entity and is not being tracked by the context. + // it also does not have any relationships set to other objects. + // CurrentValues only includes database (thus primitive) values. + var productClone = entry.CurrentValues.ToObject() as Product; + _dbContext.DetachEntity(product); + var dynObject = ToDynamic(ctx, productClone, combinations, combination); result.Add(dynObject); } From 07a38ef3d9a18119a867b1b0295581b746eb718c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 4 Jul 2016 20:43:48 +0200 Subject: [PATCH 262/285] GMC: Id should be unique when exporting attribute combinations as products --- .../DataExchange/Export/DynamicEntityHelper.cs | 5 +++++ src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt | 2 +- .../Providers/GmcXmlExportProvider.cs | 3 ++- src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md | 4 ++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs index 70ef30f356..c8687ac97e 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs @@ -585,6 +585,11 @@ private dynamic ToDynamic( dynObject._AttributeCombinationId = (combination == null ? 0 : combination.Id); dynObject._DetailUrl = ctx.Store.Url.EnsureEndsWith("/") + (string)dynObject.SeName; + if (combination == null) + dynObject._UniqueId = product.Id.ToString(); + else + dynObject._UniqueId = string.Concat(product.Id, "-", combination.Id); + dynObject.Price = CalculatePrice(ctx, product, combination != null); dynObject._BasePriceInfo = product.GetBasePriceInfo(_services.Localization, _priceFormatter.Value, _currencyService.Value, _taxService.Value, diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt index 5bc3e9ad59..6f343b6b69 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Description.txt @@ -1,7 +1,7 @@ FriendlyName: Google Merchant Center (GMC) feed SystemName: SmartStore.GoogleMerchantCenter Group: Marketing -Version: 2.6.0 +Version: 2.6.0.1 MinAppVersion: 2.5.0 Author: SmartStore AG DisplayOrder: 1 diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index bc9047d888..9be5e6e3ca 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -204,6 +204,7 @@ protected override void Export(ExportExecuteContext context) string mainImageUrl = product._MainPictureUrl; var futureSpecialPrice = product._FutureSpecialPrice as decimal?; var price = (decimal)product.Price; + var uniqueId = (string)product._UniqueId; string brand = product._Brand; string gtin = product.Gtin; string mpn = product.ManufacturerPartNumber; @@ -244,7 +245,7 @@ protected override void Export(ExportExecuteContext context) } } - writer.WriteElementString("g", "id", _googleNamespace, entity.Id.ToString()); + writer.WriteElementString("g", "id", _googleNamespace, uniqueId); writer.WriteStartElement("title"); writer.WriteCData(((string)product.Name).Truncate(70).RemoveInvalidXmlChars()); diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md index c368c87dd7..c51035cfc6 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md @@ -1,5 +1,9 @@ #Release Notes +##Google Merchant Center (GMC) 2.6.0.1 + ###Bugfixes + * Id should be unique when exporting attribute combinations as products + ##Google Merchant Center (GMC) 2.5.0.1 ###Bugfixes * GMC feed does not generate the sale price if the sale price is set for a future date From 7526f3f8cee44564e3912cdd86d4bb8499456618 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 4 Jul 2016 20:56:04 +0200 Subject: [PATCH 263/285] GMC: No special price exported when the special price period was not specified --- .../Providers/GmcXmlExportProvider.cs | 19 +++++++++++++------ .../changelog.md | 1 + 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs index 9be5e6e3ca..3edd31e361 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/Providers/GmcXmlExportProvider.cs @@ -202,7 +202,6 @@ protected override void Export(ExportExecuteContext context) string category = (gmc == null ? null : gmc.Taxonomy); string productType = product._CategoryPath; string mainImageUrl = product._MainPictureUrl; - var futureSpecialPrice = product._FutureSpecialPrice as decimal?; var price = (decimal)product.Price; var uniqueId = (string)product._UniqueId; string brand = product._Brand; @@ -211,6 +210,10 @@ protected override void Export(ExportExecuteContext context) string condition = "new"; string availability = "in stock"; + var specialPrice = product._FutureSpecialPrice as decimal?; + if (!specialPrice.HasValue) + specialPrice = product._SpecialPrice; + if (category.IsEmpty()) category = config.DefaultGoogleCategory; @@ -296,13 +299,17 @@ protected override void Export(ExportExecuteContext context) writer.WriteElementString("g", "availability_date", _googleNamespace, availabilityDate); } - if (config.SpecialPrice && futureSpecialPrice.HasValue && entity.SpecialPriceStartDateTimeUtc.HasValue && entity.SpecialPriceEndDateTimeUtc.HasValue) + if (config.SpecialPrice && specialPrice.HasValue) { - var specialPriceDate = "{0}/{1}".FormatInvariant( - entity.SpecialPriceStartDateTimeUtc.Value.ToString(dateFormat), entity.SpecialPriceEndDateTimeUtc.Value.ToString(dateFormat)); + writer.WriteElementString("g", "sale_price", _googleNamespace, specialPrice.Value.FormatInvariant() + " " + (string)currency.CurrencyCode); + + if (entity.SpecialPriceStartDateTimeUtc.HasValue && entity.SpecialPriceEndDateTimeUtc.HasValue) + { + var specialPriceDate = "{0}/{1}".FormatInvariant( + entity.SpecialPriceStartDateTimeUtc.Value.ToString(dateFormat), entity.SpecialPriceEndDateTimeUtc.Value.ToString(dateFormat)); - writer.WriteElementString("g", "sale_price", _googleNamespace, futureSpecialPrice.Value.FormatInvariant() + " " + (string)currency.CurrencyCode); - writer.WriteElementString("g", "sale_price_effective_date", _googleNamespace, specialPriceDate); + writer.WriteElementString("g", "sale_price_effective_date", _googleNamespace, specialPriceDate); + } price = (product._RegularPrice as decimal?) ?? price; } diff --git a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md index c51035cfc6..2c1f7000cf 100644 --- a/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md +++ b/src/Plugins/SmartStore.GoogleMerchantCenter/changelog.md @@ -3,6 +3,7 @@ ##Google Merchant Center (GMC) 2.6.0.1 ###Bugfixes * Id should be unique when exporting attribute combinations as products + * No special price exported when the special price period was not specified ##Google Merchant Center (GMC) 2.5.0.1 ###Bugfixes From 1a6ff6fb6b6394a9eb2cf9d28d5def49e63d7031 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 7 Jul 2016 15:25:51 +0200 Subject: [PATCH 264/285] GMC: Attribute price adjustments were ignored when exporting attribute combinations as products --- .../Catalog/IProductAttributeParser.cs | 19 +++--- .../Catalog/ProductAttributeParser.cs | 39 ++++++------ .../Export/DynamicEntityHelper.cs | 60 ++++++++++++------- 3 files changed, 63 insertions(+), 55 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs index a02648a747..f8565562e5 100644 --- a/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/IProductAttributeParser.cs @@ -33,21 +33,20 @@ public partial interface IProductAttributeParser IEnumerable ParseProductVariantAttributeValues(string attributesXml); /// - /// Get list of localized product variant attribute values + /// Get list of product variant attribute values /// /// Map of combined attributes /// Product variant attributes - /// Language identifier - /// List of localized product variant attribute values - IList ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes, int languageId = 0); + /// List of product variant attribute values + IList ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes); - /// - /// Gets selected product variant attribute value - /// + /// + /// Gets selected product variant attribute value + /// /// XML formatted attributes - /// Product variant attribute identifier - /// Product variant attribute value - IList ParseValues(string attributesXml, int productVariantAttributeId); + /// Product variant attribute identifier + /// Product variant attribute value + IList ParseValues(string attributesXml, int productVariantAttributeId); /// /// Adds an attribute diff --git a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs index 0afe47049b..cf4531f322 100644 --- a/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs +++ b/src/Libraries/SmartStore.Services/Catalog/ProductAttributeParser.cs @@ -13,14 +13,13 @@ using SmartStore.Core.Caching; using SmartStore.Core.Data; using SmartStore.Core.Domain.Catalog; -using SmartStore.Services.Localization; namespace SmartStore.Services.Catalog { - /// - /// Product attribute parser - /// - public partial class ProductAttributeParser : IProductAttributeParser + /// + /// Product attribute parser + /// + public partial class ProductAttributeParser : IProductAttributeParser { // 0 = ProductId, 1 = AttributeXml Hash private const string ATTRIBUTECOMBINATION_BY_ID_HASH = "SmartStore.parsedattributecombination.id-{0}-{1}"; @@ -194,12 +193,12 @@ where id.HasValue() return values; } - public virtual IList ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes, int languageId = 0) + public virtual IList ParseProductVariantAttributeValues(Multimap attributeCombination, IEnumerable attributes) { - var values = new List(); + var result = new List(); if (attributeCombination == null || !attributeCombination.Any()) - return values; + return result; var allValueIds = new List(); @@ -219,28 +218,24 @@ public virtual IList ParseProductVariantAttributeValues(Multimap x.Id == id); - if (attributeValue != null) + if (attributeValue != null && !result.Any(x => x.Id == attributeValue.Id)) { - var value = attributeValue.GetLocalized(x => x.Name, languageId, true, false); - - if (!values.Any(x => x.IsCaseInsensitiveEqual(value))) - values.Add(value); + result.Add(attributeValue); break; } } } - return values; + return result; } - - /// - /// Gets selected product variant attribute value - /// - /// Attributes - /// Product variant attribute identifier - /// Product variant attribute value - public virtual IList ParseValues(string attributesXml, int productVariantAttributeId) + /// + /// Gets selected product variant attribute value + /// + /// Attributes + /// Product variant attribute identifier + /// Product variant attribute value + public virtual IList ParseValues(string attributesXml, int productVariantAttributeId) { var selectedProductVariantAttributeValues = new List(); try diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs index c8687ac97e..288d05ab59 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DynamicEntityHelper.cs @@ -136,15 +136,30 @@ private void PrepareProductDescription(DataExporterContext ctx, dynamic dynObjec return price; } - private decimal CalculatePrice(DataExporterContext ctx, Product product, bool forAttributeCombination) + private decimal CalculatePrice( + DataExporterContext ctx, + Product product, + ProductVariantAttributeCombination combination, + IList attributeValues) { - decimal price = product.Price; + var price = product.Price; + var priceCalculationContext = ctx.ProductExportContext as PriceCalculationContext; - // price type - if (ctx.Projection.PriceType.HasValue && !forAttributeCombination) + if (combination != null) { - var priceCalculationContext = ctx.ProductExportContext as PriceCalculationContext; + // price for attribute combination + var attributesTotalPriceBase = decimal.Zero; + + if (attributeValues != null) + { + attributeValues.Each(x => attributesTotalPriceBase += _priceCalculationService.Value.GetProductVariantAttributeValuePriceAdjustment(x)); + } + price = _priceCalculationService.Value.GetFinalPrice(product, null, ctx.ContextCustomer, attributesTotalPriceBase, true, 1, null, priceCalculationContext); + } + else if (ctx.Projection.PriceType.HasValue) + { + // price for product if (ctx.Projection.PriceType.Value == PriceDisplayType.LowestPrice) { bool displayFromMessage; @@ -163,7 +178,6 @@ private decimal CalculatePrice(DataExporterContext ctx, Product product, bool fo return ConvertPrice(ctx, product, price) ?? price; } - private List GetLocalized(DataExporterContext ctx, T entity, params Expression>[] keySelectors) where T : BaseEntity, ILocalizedEntity { @@ -567,10 +581,11 @@ private dynamic ToDynamic( var productTags = ctx.ProductExportContext.ProductTags.Load(product.Id); var specificationAttributes = ctx.ProductExportContext.ProductSpecificationAttributes.Load(product.Id); + var variantAttributes = (combination != null ? _productAttributeParser.Value.DeserializeProductVariantAttributes(combination.AttributesXml) : null); + var variantAttributeValues = (combination != null ? _productAttributeParser.Value.ParseProductVariantAttributeValues(variantAttributes, productAttributes) : null); + if (pictureIds.Length > 0) - { productPictures = productPictures.Where(x => pictureIds.Contains(x.PictureId)); - } productPictures = productPictures.Take(numberOfPictures); @@ -590,7 +605,7 @@ private dynamic ToDynamic( else dynObject._UniqueId = string.Concat(product.Id, "-", combination.Id); - dynObject.Price = CalculatePrice(ctx, product, combination != null); + dynObject.Price = CalculatePrice(ctx, product, combination, variantAttributeValues); dynObject._BasePriceInfo = product.GetBasePriceInfo(_services.Localization, _priceFormatter.Value, _currencyService.Value, _taxService.Value, _priceCalculationService.Value, ctx.ContextCurrency, decimal.Zero, true); @@ -602,22 +617,19 @@ private dynamic ToDynamic( if (combination != null) { - if (ctx.Supports(ExportFeatures.UsesAttributeCombination) || - ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) + if (ctx.Supports(ExportFeatures.UsesAttributeCombination)) { - var variantAttributes = _productAttributeParser.Value.DeserializeProductVariantAttributes(combination.AttributesXml); - var variantAttributeValues = _productAttributeParser.Value.ParseProductVariantAttributeValues(variantAttributes, productAttributes, languageId); + dynObject._AttributeCombination = variantAttributes; + dynObject._AttributeCombinationValues = variantAttributeValues; + } - if (ctx.Supports(ExportFeatures.UsesAttributeCombination)) - { - dynObject._AttributeCombination = variantAttributes; - dynObject._AttributeCombinationValues = variantAttributeValues; - } + if (ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) + { + var valueNames = variantAttributeValues + .Select(x => x.GetLocalized(y => y.Name, languageId, true, false)) + .ToList(); - if (ctx.Projection.AttributeCombinationValueMerging == ExportAttributeValueMerging.AppendAllValuesToName) - { - dynObject.Name = ((string)dynObject.Name).Grow(string.Join(", ", variantAttributeValues), " "); - } + dynObject.Name = ((string)dynObject.Name).Grow(string.Join(", ", valueNames), " "); } var attributeQueryString = _productAttributeParser.Value.SerializeQueryData(combination.AttributesXml, product.Id); @@ -884,7 +896,9 @@ private dynamic ToDynamic( { decimal tmpSpecialPrice = product.SpecialPrice.Value; product.SpecialPrice = null; - dynObject._RegularPrice = CalculatePrice(ctx, product, combination != null); + + dynObject._RegularPrice = CalculatePrice(ctx, product, combination, variantAttributeValues); + product.SpecialPrice = tmpSpecialPrice; } } From 783bf8729ba587ade2c6113af774871bf88e46e5 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 11 Jul 2016 12:16:16 +0200 Subject: [PATCH 265/285] GMC: Associated products that are not individually visible are not exported anymore. GMC rejects them because the frontend redirects to the grouped product. --- .../SmartStore.Services/DataExchange/Export/DataExporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 26d20b72a8..e6b182c270 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -686,7 +686,7 @@ private List GetProducts(DataExporterContext ctx, int skip) OrderBy = ProductSortingEnum.Position, PageSize = int.MaxValue, StoreId = (ctx.Request.Profile.PerStore ? ctx.Store.Id : ctx.Filter.StoreId), - VisibleIndividuallyOnly = false, + VisibleIndividuallyOnly = true, ParentGroupedProductId = product.Id }; From d9455aa4935943c69cc187b8bd18051cb2284f38 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Mon, 11 Jul 2016 18:41:11 +0200 Subject: [PATCH 266/285] Fixes "Cannot insert the value NULL into column 'IsPublic', table 'dbo.ExportDeployment'; column does not allow nulls. INSERT fails" --- ...71_FixExportDeploymentIsPublic.Designer.cs | 29 ++++ ...07111548571_FixExportDeploymentIsPublic.cs | 21 +++ ...111548571_FixExportDeploymentIsPublic.resx | 126 ++++++++++++++++++ .../SmartStore.Data/SmartStore.Data.csproj | 7 + 4 files changed, 183 insertions(+) create mode 100644 src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.Designer.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.cs create mode 100644 src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.resx diff --git a/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.Designer.cs b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.Designer.cs new file mode 100644 index 0000000000..16b788408f --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.Designer.cs @@ -0,0 +1,29 @@ +// +namespace SmartStore.Data.Migrations +{ + using System.CodeDom.Compiler; + using System.Data.Entity.Migrations; + using System.Data.Entity.Migrations.Infrastructure; + using System.Resources; + + [GeneratedCode("EntityFramework.Migrations", "6.1.3-40302")] + public sealed partial class FixExportDeploymentIsPublic : IMigrationMetadata + { + private readonly ResourceManager Resources = new ResourceManager(typeof(FixExportDeploymentIsPublic)); + + string IMigrationMetadata.Id + { + get { return "201607111548571_FixExportDeploymentIsPublic"; } + } + + string IMigrationMetadata.Source + { + get { return null; } + } + + string IMigrationMetadata.Target + { + get { return Resources.GetString("Target"); } + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.cs b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.cs new file mode 100644 index 0000000000..f48207c9fc --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.cs @@ -0,0 +1,21 @@ +namespace SmartStore.Data.Migrations +{ + using System.Data.Entity.Migrations; + using System.Web.Hosting; + using Core.Data; + + public partial class FixExportDeploymentIsPublic : DbMigration + { + public override void Up() + { + if (HostingEnvironment.IsHosted && DataSettings.Current.IsSqlServer) + { + Sql("IF EXISTS(SELECT TOP 1 1 FROM sys.objects o INNER JOIN sys.columns c ON o.object_id = c.object_id WHERE o.name = 'ExportDeployment' AND c.name = 'IsPublic') ALTER TABLE [dbo].[ExportDeployment] DROP COLUMN [IsPublic];"); + } + } + + public override void Down() + { + } + } +} diff --git a/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.resx b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.resx new file mode 100644 index 0000000000..55e598ca59 --- /dev/null +++ b/src/Libraries/SmartStore.Data/Migrations/201607111548571_FixExportDeploymentIsPublic.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + H4sIAAAAAAAEAOy923LcuLIo+D4R8w8OP50zsY/ddq8+sXZH95yQZMlWbF+0JNnee70oKBYkcZlFVvOiS0/Ml83DfNL8wgDgDQQSd5CsUteLXSISCSCRSCQSmYn/7//5f3/7X4/r9MU9Ksokz35/+ebVTy9foCzOV0l2+/vLurr5H39/+b/+z//9f/vteLV+fPGtg/uZwOGaWfn7y7uq2vz6+nUZ36F1VL5aJ3GRl/lN9SrO16+jVf767U8//fvrN29eI4ziJcb14sVv53VWJWtE/8B/HuVZjDZVHaWf8hVKy/Y7LrmgWF98jtao3EQx+v3lxToqqosqL9Crd1EVvXxxkCYR7sYFSm9evoiyLK+iCnfy168luqiKPLu92OAPUXr5tEEY7iZKS9R2/tcB3HQcP70l43g9VOxQxXVZ5WtLhG9+bgnzmq/uRN6XPeEw6Y4xiasnMmpKvt9fXuabJH75gm/p16O0IFAj0h5R+mKwJHtF65XNf//2ggP6t54p3r76n69+evXTv704qtOqLtDvGaqrIkr/7cVZfZ0m8X+gp8v8B8p+z+o0ZXuK+4rLRh/wp7Mi36CiejpHN23/T1cvX7we13vNV+yrMXWawZ1m1c9vX774jBuPrlPUMwJDCDqq9yhDRVSh1VlUVajICA5ESSm0zrV18VRWaE1+d21i/sPr6OWLT9HjR5TdVne/v8Q/X744SR7RqvvS9uNrluBlhytVRY10TZ1mcVqv0Gl2keAmo03X4GGepyjKgGFq8JVnUVk+5MUKF1QoxsP3RdkhnJwWl0mVTk/xw3z1NHkjn1AVYY4mZCtnaewdKuMi2TQCZ4b25pmrj8kaL4vVZU4XdOnLyecoW6HioPyerG5R5YutwfLPPJueDk1T34togzfYCgsxoe8m9S/u8ofRvPmN/BAzNyoCyJciyQsqldXy3UB4XEa3gefit9fD7qvek6PHI7zZ3ObFk8vOHD2+YjDsN2d5W5pt+W8//WQ0yZbs9S4pN2n09IWwvA2jGvPPBaoqOhZr3sEi4Sa5rQsK/arFs+cgZw56Ow0HfYvSOsROYdkspZWeuk48+zGPozT5E626Nh24t8XRMK+AcM/G8raa6bCbWkDFirLbOrq1ZBEAD5k6hOnzvsjrzfwCum9/qabnWt+fo/vkljKQZCZfvjhHKQUo75JNY08RV9bVAH5S5OvzPIUWdA91dZHXRUzGl2tBL6OCqtduMqXvlqcoafHsJYi8Lc1G+Gai5dLOTEt15U48Rfu49h81ukD5Ecahaj3Awe0kjW5P13iwJ0mKNOT+xWy0miNulfqexwIfuulaKu/8z4muKrhSZlLR3UzGOSqpjCvlApSDlMtQGWAvG0diVArdCV137YxDHURB43DuJaxe1vlqVx2tlzm6dK0vdIQ5LcnqOkvr2yQThIiu6mVex5DwCadV+QsFULfSihAnoXCGinVSksV5jmJq1LcWCBcorom97hWPay8I5G0FukyyPfybXGS9/eWXKdoerKGTtyxdvEeUt1FBlhW8rfM8fMVVGZawGlJYwxpwr0XMonIxGLbVy1csov3qdV69E62gkwKhC8yqG9qen/J8GT0eP6L1xvvWCyNqFXGCh5sCddWDuEruvS+fTstGqjXM74crqHw0FUq8ZJhYMPEnDkM55qZd5Hj92wskUq2k/+6FkLytUGeJJVWR1idi8gvzYEYHcmf+JfuA18cZVen9sB2kaf7wvkZlhc8l3/LKG6GHTUS4J8Lr7h1mzK9V3GEif14ma23d42zlWNPX1uR2bCOSBjymjQpElW5UCmlwSrGPax9k5QMWZ9JONeVXjRgdd4spEkU6V+4twxtkXpK8QbGX5woZham0m7L8c72+RsWXGyLBSr8BTGHUbZaP3xKD1j60BG36hMlFDToKpY+DumIX47izEjBQNshgveUEi9hLWrCIgsmMF4dRidoOEOp2im7nQ6ddnQ2ZrNeowRYw1exDbKvjFC8TRBDzw36XkLfV0eh9nfStNr9trz1L3K7uBjLEFeQxnuV08lYMvNLDNnSSF+uo8t2wO2wXUVpN3vWD1TrJjvL1mvEYni6SoQxmYzq4uUnSBC8XX2qHsTi9QykKEEfRGa4O4jivARfuKWxXYfjoY1RWp5uD1Qof0VThDKYOIxqJVyAiKb9k4HnS2tmkrD7mt0nmekDF9SkXYVEtReGsELQkldxNdKL/igEb1ACxVNj9ARBbvfUwSVM8zf3cq7rJwwJ9HYPIO8zB2faa1/RU3W5hrgaNRuw3DyNo2VJASMX2u7HqUUsNwjCEnNj6mylVj48fiUITpQd1dUdUmpgCqU45qhrgNBhVEObErJbtBGE1oF6f5WUFj60vBgcilgq9BkCcutjEesr7SMvlnRwXw73kYGy7Sc/8cA9pEdi5cYnQL67YtkvnCJ8xMIv8QU20YNdGIGAXYQihqxIw+y4/RMXqLE+yqvyQYCTkxh3stwAn6b0cDhiDAth2JN1Vp9FeIwAD4o+DkQtAHtBWBF7c5bT+ET7EnmKlDO47DwWSXwok0F4O6WXW6QnqEIe0XufZqxbB/kwvb+skKcoqkOlWr77O0pDu0B+mFcximyibPnz7iBzQCjG2RnuFViH87T7JYvHoqmmRCYCdbFjt0nwzV0NvJ2/on8mG6EpRqvHlD3SrfJdnqLn8mLytk+hxppb8ztvyk0yzhsCtsNtoe5hhA+SKhE2bL7feq9lFquwcByl2cQQg7egYys/o3pHLem9+lxQoJurZqxbHfnuWt6XZMCcKi6K+IWVr+AjiaFIGcx58yD8iQsTTco6gqcu7AiHTBn8O0CAWtKhIYq4xRz+a+vpfeKld5t8ibxPvNoROzeWwc45PwXgOMO6Oaz+h6i6XmJDGMFdD5Ua8JWODmB4aOF1pq1ifb9mNQDKs0V4B7I4ggNh5EMrrWDimh0vUSIuAm7r9PqQQS+p9aKpMJzNmkgonl05vM0zrozuyEiaRSoxgmUMe8dqkhQhzW99jZddDwRxh2i9veVsyI0NYF8CptNXr6wLdJ5FGTIS5Ed4GPcjpnOu4nwuLX7nre/lv9TekAdy4Wlz7Na9Y8y2pfBe9iTeGaTC4zmvlYLPBrOe/+IL6dXzdrCYxWvVXMqE9BmSXT1LXAqdlfZjmt73Dl/WSJrXLVwyO7XDFbTtziR6n92IjgycW5XBOvx1GkKUYWl8NgAM7QeUCK4FA3mzUdMWDhwiC/V4gbytUPF+oVLyWzQbKo+xi2GyZ3Ts1UbeztfiOWLdMR2U8r8IjvYxup08ivUz8n2GebFNTh76xbcuTHWZkgVN2TXQjyOxTsBXFaTfkjZ/KLTNI+GiPFQohFQrB7XoM4ZfRp8hXdVyd49M4enA5x0VVhHv0aoRnOxS/tkvbskGqW2kIN4uSeh5VzK2eG1E+oHRzU6f/hcpLzClpEGSfcxdc8nC5ZvrhWDmWW696SCZKDgIQ4+NAKOvgThZLS4sMH86PFRZjWZ0rbj1LRiStIQaAmlXziwXt6O8rffbnBa00JM35R6EVuE2aqZkm8A0hZb8lZYKhT7NVcp+s6ihNn3z1kGUuwC7ucqwJz6gnnuD+zdnerGGBHdei9SYNENEXNh9LhyfcPeT+QBMmcpUe87u4JLpDBTvtN9rTRb0OdtQPhLFDR5UobtDefQyHtPdwOoi3Ldf0xY96+kUXZfVNFBMVpMD7aKX10g3T7PsqUS3wMI2clu+Tm+ooKrwvezo8IbQVEiOVFOhLdYcp3mwnAZ4CozgH5UcT1xxEqNVENyaBiFg3OlituD54j+m0fJc/ZGke+d+Tt3h8Z+5rljYLvEPoPcZPnVv8lxsBp2MGohbN8eMmad5Qehc98TjNUNBAcYoiBNt/iMqLCGtNKNSsjrFZBqjg3pAMIge3BUKs4ujamRGyWawmp+U5yVxdBHCI7hEdPcUpajrlK+NYjGeoSHLv1dfjpHs/Rey5Vk6pS/lxRqoEyIcRMsEtlqcJWXlR2mFsnAF7+zWKkzWxTZ0V+Ff7SPLfX764IKnW8f7p0P1g+U5Oy+PSm5zM84G+jINVHHIvmd3jpYnRNT6H3ie3Ko9//KOOWluHl8hujmsU48F9lOC6Scpg9XQPA3vqvGElWcCRf8wfmlG3KUm8nQfzKrl5ogaBk7zo+niI8OnLD/FhFP+g74OSd8C90/iQ0yDBeNrQEp9A+kOvt0ZBD/14lpJ1vQ4zSQ3G6DEcRkoAtGqRJWgGZRWvBdLBw/rpsK6qwbjgsbYI9PekvEuTsgqDtF38KcLMi+X6yH7jfPmJtXOKLom97UsjJMF3oC/patoG2oPJEb2GnaiNiw3GE6WWAzHH2fs1kMtrBw8FFlfr5+CIqTNnHWcVKsog/NWKrRFmNDFTtIJt1jbx4eMyQc2a9Bb4eA9FZXVQYdF5XVfoKF9fJ1l7rReQCXGfscyjqd6IC22a+GvM31FyezfdUhyfY4Kj/56sJsT+YVra9DuNrzzpEfkJk3A3FuGCK8KkSdwiN3FxgMk9Kp5IZUvrSacGYhVMvGjVadIlanefIMfuHt8nFJV1gUiXFOpkkFcp+zYP1qw7pK+I7dGSH2PUZqSts1WK6I2WxgIVxrzetHeGCpKvKZThY4SUUCM0TjbRlDVieerLZkvst0jQSaYrvWqhL/PBVj44xsihBGcYBaitiyArDVQePldjQMGfZ1Quc1AaAzn6JzWzKk9QJoBdtXwg9a6SgcqckKTwtsTvTiaNwUvjY9WZxRRuYjyIbAACnGPH2UtEZd9ZQEX/ITDZGEBYx3GcJRSPcggtjKL3HISs4zyYY5/7S/BwboVKnzvf/tIT6E2bvrQ/tij7D1dRjEddQTY+TS3H8TbhDgqBOoIT5SlTLBWnLIyjNP0WFQlW6qBzpLL7inqKCTKoJZslk6qOU8Wjthq4xWhth+g8LlZPV/LgGFBkQrZcyoUjIOtEZtzdsKKzPKjY3TGEtMMcmG2XWRsO0N2+GOIMoVDgBRHCy6e5UxWdkmG0Sm3/a+/XLG9L4+U70Zvm3cyE8NT4WpKzUoyHG8AnteuYiDG4UatryvZUbn3zu0RoYetdVR7l9SbP2MRuzpYpAdNkGU+6maH+kXSV+fLogIkQz9YPRHeMv8w1RzHx4M1WUJ3lBzhhW1IC225OgIlhCouEfgyC8cLYItGiajlfHmfZtwnXAMYBAcqHAkL7pavpH5N3jjDqUOy3Ynlbmq14ooAbEv8SqGWdAX++WInDvMJ8OmtYT5pEM7zO1C6jUAE9+4CZQK/UkYi+UA6AraXN8qaKRFJdJH8iv7ZHoT/lZY53LBRXPG5nj5wWz5ex89hk6bPogfQ8ym6VPltheCBwbFx4/4UtjhzatlvwcPf723yffhNhRetbgh4+TfJ8Q+jLR4cji/4CEjjfGFthGzENJ6vrb6o6ICZJHVcmJqfjAfwylHQ9sNbdP6FVEr1q6+8Vd4X4akh0mGRRMbist3+ZLCSdn+GaJnFRbmGTxOKj/CRJUaY+HfwcKPjxM3rwldun5WURZWUSIkJqmhSXLje3ogiBIbQ3t5y8cRMnI5z+OUf24sU09chUx4r5dCOnrdR5NUiu0WSLxnIFh/W50PVVkRrJ+kqNsQsGuV1j8O1XsXLdUHIFWH882c/rFBk9SB4oWeUGTX5r1b7EFChLfx+UGgbd8P4DPkAwzzdbWmwGNL2Enz7TKNNo9ESYp3/oZ7aGu9nQW4FCt9zaKyzn6fgRCyqs+0UznpEH/4fpbpCkp2PlfZPb3jPywvV434RFtN9t5G1pNgLDuALrK/o0Lz6gx29RWs/fequYfszJnjN1TIWrFmx7zhp8tv2PWgOu/cqRtxXotDXysPdFFixxQxwyAEjuK7VwykntRc3MGSaTFLMg66breTeUrNDlXb2+zphXv12RtYlDt+cG5zlevcivQjqmaHjEMDKIq3XFbhKKKCF5NX3EkKKu7b0JF9Y0bSiUxLCiDZ0KaA1i2jIagHnXg5iFdFMdUu/gUO/VEHlbA9G88/d0RA+GiOr33o+olVjtaNxLHHYNjXRpZOEyslS7bg3ksM9KDuCtyWHar1N5W4E0/FC+Y6flCdZ66iGv6YL6mPydv55DDcKGB2B53HD/Q7L2RMAJ9nmXQGddf0Pu8Gzksb9sYLHt5cPk8oEl919CRoy51TBEf1xJHac/+kOyDuUVJpAfPskGTPofRJYEEiJ76bHNISGzOukznLCPatiuqIZ9HMIOxiHsXfOfkX1Y5Rg33txFtzioXFAUQKAQpw04O43/uQPGu9chVMInyAkEJnwjQYOkGG+sYQHy5zWRWO2wQwRjTeLyqj8QhM4jJTkkGGafMh2Yik8cRqtGZ0oCFRZLuihReUlO9VCdBacK7V5u2go87zcfwnjobo8EC5cgb4albpdKz0hq+MlFkEzqdiV4Bvo4VBdkoAuOCYRfcLG3F3h/BdGk4l5YMi2w5HhZ5LNsfU5ql9Gt/7EMI9kvLeelFcqOq9urg2V2leyjYPZXH95UZE/151kF8j0vy9ua45nfWd7bXegt4aDv3Ni+GqPGZvlIjMZN2fJNGI0/3HY+YnFQlslthldRd1cyx+Nwi7yEclrSZw/9717CmCOHk+Z/rtMAWqtG5oV7RJLaOr/U1ZcbipTqvfa3NN4PY6jykmvezDCtKjOsGdefwMliuvzvDoN1NbGa5klXta1JoW5a1WXY2sTrxlrieBAeYaUsor3uJ29Lc46ZKLDTyLsiZFjndseQQntzm83Nbh/xPY2FP4Lt1568rUCKU4sm2IUGebsNf2IeYp9MCzst24wE3l4kzL6UVUWeEmxbmRjJXqNxedjFcBN3Vlh4nnMZD2h3Na1jO0L1pYbBUHnENGTK6oEeWuNKKnm1NFDWN33KR40khIUv4Gay30UMdpF58pQvdJU0mRuur6bUZkQJrS5RtHtul7cloZu/v9oOLSKNFyzJ2XPxRx0VqDy/vZ58UI0Nd/UvvBLWaMIEcs1r7TM05BfHPNmRkIqGEGrtxyT7wWSoWySRja22tT16lqHeaaasOe1Iw8ODjgnAOwT7fUaxblsava+TvtXmt20cQdk/8Pi18E7uA6CabCPp2poqCzo5qaPmxcDJx2KQ9TxMQ8ePeExleH0V2qaeTX51Y9HXv0nqoXn3OPbiT95WIO0ATs5qG9blnpA3zAWvpX+HexYNZqCaF33HoNCzviyEoLRIwEJez1q+PizvocpEaXuKb16Q/4DSzU2dZqgs/U/wAspgYuUF8WBpu0KfoGmnqt03X5os4KZ3vivwe1S2Awzn1jDqoEq9Fwh8xVUVFHpNDZkKr6vmxYB4ry6P8rVjjn5S+xWDYjt4rO2MLnY9lFmjaQw9Tn8tRSjtlKhNyu0dRpDRmYm9GgAHtobKBSYGgbxZ1jFPcc+v+9zEGltMlN3W0a3/nW2gNWgfmFuEWI6WzZ5Q0TV3q8ESJyzzgHuTgKOREN5JJA42myK/R6sW3xHgZmsbRplX4ZEGTp0RNMHEPqeN28ike2wnS6V7LN0SB6jxBjsqBHfXMYTtWYnZn2GvASctgL/1V6oKTloAMWgVWZQe1NUd2dOagLVzFGO+dTk9dRmAXqkQ71UGhRBqKeirMhyvmWTzk9pDySw3o7tJlOE4YZtsr95mbPkL4WXKffM0dRDH+Jg6T4P4z/tkhYopnyXTWsZAwakSJFdDzUGSGlUQtgCzWl4nrpO8qNdneeliIqB1y1c9ir0Ilbd1mW+SOJRZOoS77fyHmdOzg9WqoBbQif1DdiErm1K+9EsKFCZiqSA5ABBb7ZGioGyr6SILCHVyKFd0kwHyl2dtZ7wEGsWxl2jytiiVtkaikdkK4al0UV//C8Uq6fi3aQKUPjcLofTr/rcEn8A8DRhRWZGeePt8tXhCTXGHr5G7doao57EhNOJRuiOMi2FZy8E47Qm6DrZAYO/oT1XXGgD/LcBT+u8Fv7wtSqD3RV5vJs5jZ5o3eWs8/EGpTvnaUyaH2RyIBA1yDnmOW8RfKyf0sIbl0vyKBeKkOVMGS3MWwF+at53wEukUx16uK4TMs5fGz3KNB75/VIsM+B7JSlzw90dSeeIkLtq5tZYUTQqd5r+9kJC3RQmkTXIW6MKBtOUdDRHUIdj7Re8kTTGxWkOot7ECL8GNAp0BeS8wW9TBOhIG21n0RG6TgyL7hKq7fDXlTZKEY47qokBZ/HSEa87QaNPYeVQNCrDae/zvzmvhMnpsN9QQhrdvkT53YkCxclFfV1gkpqdZnF4StKFc7VWNHT/O09glaQzPTUzcmeYa4ajReUbaSp15Rtg2NuvIcEMWS9m+sZFwxLtIQlSEKD1BaGqayluemsDylqemdotfdf8ZUMJNzqTdWp+2FSpWJmsCK951tkIr24Sw1s08RMXqLE+yqvyOCoTZzt+n9+gOxT/yeojxnvMoLTQ+SwrVTvUI5cN+cHOTpEmA1yL788FmchpQp2pyqMHojwqE5dcR5q2x7uTMUhgTwTD9RJIuz6KGC7SZ8BXS8gdayaZk0hEe3d+/naWh48dNUlBDwqc8G9J8z9Tmf6Foenqy66tJ7voOXScB3rfuUR3EdN/8kKerGfhDbHgmxmQaPoyyH7McgLk2ZxExbJunR3M2R8NVhjwZczR5eh3NoFy0uylVAPsQ1qnXfY0PBEXyJ5U0NEVFFJOfg2owe9OzLBlZ4+eoZJIBTyjhN8SQPi/BxUZnGu1Ffd3r6PMO+awu4ruoRHMa8M+ixDV0sLOANMf06eelbY6cz7HA2dTNbcnsVuNAD1xv7/UkexI+R+TqjXms1+ja4kNEshC1VpzPedW/aOZLNBLasqku7xLcvwh/ptFKH6Js9eXe4WQlvUcdXwGB96l0jV7xgMOdKlQuuGGAQLYOf0qnxKYFyB9xXCLpmrMXYmd4+lpGt+hDUpLXF8AOQoBX7Q3x0Fk5lHBXrQC1fcrzfXJDT4naQUCAV19LtPqeVHfCYPTQwqAMqtgOjtYigaAK/qZxokL/uSKhs3y5U8+w1JDk0e6LJT0biuCeMeW2PTtHK4TWaMVKyONGvQc6ykJpmUILLAxGX8P66Vrcd3kkclcqkn1cIj54OS526dXGSBoLkLy04wAkQo+H8kvLCQpChydsGjQlKDD33jDytvo7Vk/zcSPVfG3QXkqfdrc132GHGppNtvsorBU1tK2+0MqUKRQEo46PJZfTSoe1BVcvNwjbfpnL2+ro5btCRztoCGTtkwAT3VpOJFA6appru0MNjaLbfRTWpRraVqBwitYcSrvRgGCdzkvg+AuZvWCRt9UZ5RipAOUANJNOIbzwJvaDOC27ztJ3YKMApq4O4VFeb2aymJ9jYmxI/upZTIJ9a/Nkz7lAGTnHzjGypql5hvUJH7ZoCq6J2yHPy3XcQW2TSxtzJzJA2W/JRmYnYQM3HQ0sSZXjuZLVEUckAZXuyjJ4ry2Z6aFXeMs+QarJYRwTyTv0JIief7boqzm85pslFc2RPrV/cN/Q5O7Ac4xmlpFM7b7c2TYaHXFqko1bm5p2i/jQzus7272tEyDH7WnZIQumxn/ECyQb3i2yPAIRad081mbOIZpAvTpbpQirWdH0XhKNgD+iSfhC8bdUUTooyzwmLs+rTlmBbz0CakkyzU+nVfnbdy0uGsHrGuAi0lghVTxUMuA+Ex8qEQoVXTsL8VBJf3HpqVcSFHu9Ut5WEG2wmSdviWS/GdM4z3xw9tjKM61GEphd7IPLDbj4d1pswMW6V0prAN9+DSr4LlAcf0PwEDgOozTKhre+3O+CJjbdzmVAm0gyKB3ZIG8XyK1NBSeIDSVw+BunwA5BJqMJeN3UufE4yMLOo6b3IdrLP3lbQXSQyyKKf2CKz+RFTiOAw57uKM8gV9/0dyhN7lHx5Fh9dt3H2LmOX/QS3ztblz+5IygLcTXIALGDIwCpE+AYyiuPFIsyhFDaG931K5LSKYgnnctDemHs5drFEHIdyNYrvFoczyuYHbNz9EeNnN6IaA0EIzT7daBYByGylQVbBKGOS2Eun85RVObZSV403DS/GaTlX0TN3kEuCPw6oI7MMpta/nm+6bw9qujmpo28mPpiZbVOMn3c8N9+CvLayEi2hUlat0Xxe64naoYmkrM0BAGcO0Ewz8NmTpWzI7wJ+flT8Jj2O5tq/QfY2c6iotVvLG8Im/s8h4rsFIfwsAxmgQzjFrJM/ia8JFGBeYi4RUxmsQyjdOyCKB6YW3K6GEupKxaePWNIwYCThhw2aIyx0BC0l0iB9P12tsiq7neFVoBrXhmMvstBLn2F/G3um6CAar8LytvSOFabPlBsa7FFjxX+tN5Mn+GEOED/USdFgDfLiQ2NwLcMHwrvaXkZPR4/IoYarqgwoiPMBrd58RRsIz7Ks6rI0xC6Rrj3Dk5L6umFfAk22esEghCioXGw1RmGvQJE4iCuTesItmnjil7mariVgDKd4tsLdnlbAsUmfhxrop2CquQHq39hvmGtJ8GV8+Y+b4aGTkuMCS97FAfwUvUQqOaSa36Zxauc1sLO8WohrgvC120uJ++wdgnCvdSSt8WT7DknsuDHKjFcgjx0JVZmTZlmdQDjpmHFoOss3ALbrywFEz/FKWq2Zs/VQBCdoSLJvfNPUE8ais/TdfGiwtMudX9Z6GwRKHfiaZZUSZRusyRju2gkxa7GNeSiawSolVdjaFsLmnT/n1ssi8nPLOW5k1imTI8/fcxvHSQyrnVL/IsYLHtpLG+LIdM2XeKEy229HYKJIzO4lBmYKwF+WL0KMEEuqWCD3kawDUEXEVC5srdhLrQFMoYQJwR+L1LkbTVZoXGPHvJClaD6zTSGGo15aKLXeo8zAmypYxnzsd9WuN8C1ab3/PYjukep/1ufeVGFiwaybP0Eg8+Wy2fTJ3v1XmeO+oTOG+YWfS2md9vATI6KAhVztDWjv4WG0wridJbFvv4jeVaRM0RUql5L+psTF7kpMTLlRam0hFNW6Evf7DsYDiK/eYv8lYBqL//lbbF08s5sFOoARGfQ36y1SeItNdUoV6LAv+C6lEMJq1QB6rVmzwqa5aXfeF0X7BjPfrUqVmsIb1nCQ6FW6pD7wVuBrK//hWKlY/4vk3lBza+xEoeoKIDTUmvePnxqHuwKiLDPWOmLcyIZyrIxKEfHYuVqDD8IUgWYIElVsLYmJTZtir73LLS07wOQrucMpJ+NqXsq10H693XL4ede+Mvbao+d3u5/YS7EHG/n5EZixVsrPXtcAe+sCIWiaVWA8AsUiu/Qqk7RZVT+cGB7Uq18xSLZM728LY1B85dpDJoHmF1U9h3TZnUbY54dP24IR6rjRt+ECUlsbgGkrfx9i4zDoOq7+ZIdF4W/kvMZa3zntYv152NEYzmLyrHucbZybbWOY8wnru2yZJuOwU7LD8kKL2zfCcJ/3pJVcYawHBfSkprV1ZuDAw36PH9opXU/7CSLiMfCUZ4R1wBiLfxE0dG2xmsO7oCDQG3eJ0UrV0UuTrHCnzs97XFBPCAwulc9kv2OJm+rIb2vFtdgWcYbvDs6EO3KdxynWJ9NA6qRbN8kl/Utj16NQdnreggCuLAHwbxUy6+FzyrMX/X19wvwOS/Ai7S+nb/VYNFeUXZb463ZbgbM36gijJHEPiGsxAErz17xmPaLaupFhUf0vsjrzfzMjVuev9HRQ33z2Z4dbhGMV9/lHVqjb1GREFQu1hFSv3w1QrNfd/K2KKECcO4sUYd+q+FtmPPalNxPcbvobjTuuflvz+2T86Gtv5DSwWkqHa9MQ1nOyPFc4xEWZgV/yEtlyrhAFpeP+W1+lsRkBWxPLoQP1To9zFeMCjSdM1zjPNYlHP6Mqoe8+DH57J4VCRZMT3QJH7V2Lf98VRTn8WN8h08FiLxU5YxakZRH2gicqIeM8EpZi8nYowMWU/doa9hnHRJnRj8yDlwypBGUeixjUL9ERH23rPfSd0mBYhKZ9apDst9R5W1p79emMSA2E6N5l/eXSZKlmj+G93fX7eRjThD4k9XEDHuSF2s86bSFadvzzdLDCy2ywMq7RdNvLOdlbjtGKtuOsxWe2eAalt3x5lNEE895nnJaLHvRLG/rGdipJzxqH0bxj9MMtxD/8AwbOIqqKM1vX0kw7lk06ASDN/YhctYGi75ewOVfwnqg478OVtDRtRVCJjmVNXYm5jrVgBqP4yxE5tNPUVbfRNSmUFyiNVYo3G6OWlkCodsLEnlby+xP3xL0gLuqDsGbpGVXndX8AN0mIA3AyzyqPR/v+Xg2Pm6FewA25jDtuXjPxbNxMVWUyMsGrRLkzMRjRHselrfVnyreBDqdvF3mpsl8xy/yssQ6eOrPZTyqPZ9tK5+Z76NNcrpPqLrLXbxL2/rlqxGiPWMoJpQlVJMWSr+vTrK9kew47xBjIvPvgRMP/qNGNVodY35KD6oqiu8c8862oR3lKxDhniflbTEE847Xxz3Bk0CuukZMRYxA3IwIoLaiUa0UBvIqOUmAaAbNno73Sz7ox8Tbw0v7niiH26dkjUibM7QsD+lPJEEb4EK/asAHg6IcSrAlKkBtraEMKou+j2rphsB8NRwJW8PLLjrqZxBJvZfPCklXJHnh/TgdYaf5Y6ZJq1pvkiBhzPn8g7vMZxnaOdqkT0HGZ9TOLGM6Opq8icM4nrwNfX6iQHoIcSKd3oU0pKfLBRZul0Xi/XwARuOUk7HZ/OI4r/2fKsGdWJHLuyhN+1kIkNyk35nhBCeBdQQ+db25VmE6IJbkuhFdjYHBgYxgVDrOGNBLtRl3y123YfHslRvNMlUdL36ZxOGzNbzqTlWGjesDIOYf4Vle8DkybH0VSzyTYQhkfbYvS02q9IlabqJrLsrUWsyLxHuHbiK8qvGuStcCrmWH1OJaf72JklsXN7deXnU49rJK3pZGWkwVmKvVMSdqOJDOuaS7tZd3urmbWLOIPNwR+mXIodqvRufVOJFFFJ8qG9WuSRqIpk/4/7zOmKHyhYQ83gWOYfE6LUrPiG8criKG2m+9av+srW0sKz+jh/IjIkvd00G/F5kwxr3klLcFU8w70f9CR7kw8iSs7WvCEJdPKCox0zaPYntFwo4w7deLYr2oNY2JXu1a+NGwc0K6qWNip3N8a5n7HV6+Wem2wwgrpUe2Xyz7xfKcFsvpepMXFW71JnHKzzWqv18c27Y4TvIUc85CbWOOIG2GiVgOgylMAqUfycavI5fRD+SHockm8CXzP2fiRXKSoHRF/pohWVPHFUd5dpPc1sXYW3Aq28PxIxY2rHvehMmY0nqd9UkRJm7tHJVYnp5mNyofkTBNtS9UYOTkjYpw2ZjGz2cAV8ejLeZqDD5cHsuhhOtjBajfQyBPWeyeDYNwZ5f96RWDar+pKpZ1kJQYDX+oM76+mSbzkVE2jon2Z5o27rFaSGunNJ/kvVN+mHQhnlo+G9HU6vo09Z5BGyMbuLCZ684+WexmuTL3inkkwvId2qT5k2N8Bo9iL9HkbbW7kq9IW2ZRB9KvZ1RqBqYMcb4wcJsJlHpT7yUTpiFtltggaWKranNZRFm5TujLVyGmAsI5jkmiQgkGczjoNlaoQOHq2lvh5lg/eUvG192BGIG2Z/Dwa8DBhblGJusxuUefmPyVHt5jLj5oquRMN7IwKn5jvuqBh3OVDEY4VUkB/ZxyH/3sk6P6e8VD3tYW2ycnyvJBxDb51ZJqerltGPMdJDV3fp9gwi4aZn5atptit3g93X4CGW/9zFgiD2HemeOMiP8k2+IcxtKOexoT7SxW0xlV/i83NyXy9I+nbmN+KA6jKr67SP70vAc4w4u8eWRke5zqyNOT9E1pG/0xWBDbP5PNQRHfhXAMIrWGx6f8dbFBOYLDvrz0MT7IS6u4BTPQj3QsqYFeDiVRJQMY6Os1q0pqslCclidpdFv2s+obASZrKZgWilcHltLpE9nLmOU6ntZPaH2Nik7opPn1yxf0YaXfX/4ksMAIlqhHWH1FJDM/6iu9UVdqDsAtLOAo2kyIapIUx3LvCRpdschbWnCCLhIiOs9o2JjZNH3CHUk2uLsk8T8Z4KjyG5MpOCjLPE4oCbv9jT5Y0AhqvDXT5XnVvdPI9f84W71olq2y1rDIBxsMVAHvC3REmJh4Sf/+8v8Qhm/aYC8QmQa7IXCNvBmPCTfyJXuHyB724oDqXCTSqoyjlSg2MUVX4y/toiFhX1iDKjF7JFklHhCTLMbzltoMhUNieM4kneyb40vw/oAyckS0mUOTfrCveor96ZvliKmj3W+vGWY14GGsX626vhkyMFhFyr0stDXrwk3tHt8qxzEX0yrnbSc4Fuvy7S4EPCRdSrlWXQ3iXL6GDeNqWgOYd/yutrIlG2Llaapf0SMokBQ52acthj9CuFNLFez6DKsTnIPdWJC45wdZ+YCKK8onKqZg4GR81oDYchuLGOA3iIG3g9eAjs/EbcBcmLRM4BfjtQt8nibuXk1+0Cuse2EdLK7Q6oiYZGg2HRmX6KtCHDmuZcOVBu1B2wAt1ClJNhTDpz5EbZX4gHrVoZd2GoIG6cICWpEFbMGcEsuvWuUIZli7yjkyab+tstgibm2dWmbk4CA2bEFsGJDHas56P716JRp2nFhI0ocZmEdC011im7Ho0U3zeLWEZaExboCRlFIyPDuB/ZmRqUBam7Q/qrgYg/VPew2XMTIOEEEh1hoeIjPnLQAzwFhmTOsy9sMkJde+XQPabo7hg1OBQ29OCnF1OVCjfTN8eFFO11++gooeLawLWYRmFAaF7VOgdKOYQWLp5stoP2SeTlxEXh2m+S25x9AbeARIiC87IBuGFBHvlLFH2v0ZWFA6Jzth9CG9P8rX9Mq8ZxwVl/DAMg5s4WyZUEAP8KGMwbeDD2UjmIkVZfNj0nxXZzkTZPMM0FlC3w69av+XWyJBcNAgOYK0MkrCbUC2SRj58lypHsIcVkrlPBkZK5sqW8OY7SNjpkzDv+k7BWNy7wCLbWw/Y46HsABjjufJiDGH57uXOeu2z9NqZSUPCJ5oWhirowyP11wyhjOcyDoxxwlEQtddkGrvkrLJJH+wwZNCEkO2o1Hdv6gqQUzVwdswlbIN6IxsxrhepGlXug1huirTkaVvwVzsB6BJ9+Mc/VEnBWpcfLWdhmrZUCbofmXaP4CuAJx+Ep3EmhHpZpBxRiQy6UdXf2k97grzTXKPiieaO0CjYY2AFTqcg/I2Rg3xGtvPybZPVW/m08JAOhtxFVNvac46rLNVik4rtD6oqiK5rivURE1dDSU6hjPBoeBDaXUHBjXqinzvYca8rYcPmxHOtxZsWMDIstPX2p4F0g7F8Cwtq2e0ELw4n2tvBw/YurEswdfwLJrz8tIHb3FA9ow8Hwu7MG9I1ULelUV4bwcNPW37vbmhP/JqmECooOA2F/uPtBmLY/rWCErpKObjUul8mXShq7M1XGooE3n4iXl0h7dy2RgWYNAdFKLktZqbiBpdC63FHAKGGJOFs+FKEP8S1nNVR2bgKxWdd8GK3jL1aBgjntCIJWlFhRR05TltkwD/qdraGrmoHdF8AlI7nyZdYettFWcb7uhQnZn4eYd3d9U4FmLgHdzl2/YvNihObpImDUlvRjNlYHVtBSvDFR2YWtODHWRvsxHNx+hmc7wLLA+P5At97+5KwpEy9nPABUYWKtBYBRo6dAcK4TFalssvFY/hzrBwPHjDpHcwhi3dSJQM7ibb1eRdbMtRdst4rcFLf/kV5z/2xXcsE75xX38NnqVXYbctX0a3iuwwImxgjw0Ws1wFw8UBk780OL9FRRJlVT8tR/n6OskooJU/iykeBeEUKBxoatyhpR1kbDs6n1ywnVOTnm2TW41qfIYHOgMUW8HxO3y+sxjWdiyNHTzpGYzqH3VEM8R+zRKvVcHi2YqlMeoQsD5GA19yM4A6uh0cD82pSc/YetvG+647gIPYD8DQz0nAb49Uf0aivBuK3lJnimAZBlea44Qx7hrLL2F9s51vi0WwNcY2fnA0tfeVjFstWVOJzGKVUDwBloq6P/Jlo1u1W7t6jAa83Eoy4g+LVcWjWHpxWR0NTPV/FyvaVmnyC6vrz0Inv7qIyFsO7/KHLM2jlZa3xuCBuYtDDpkNu35OzltwX2bkLpjSRrcBo5qLcdhlggo8apIjlc2tLmMCGBzisB7Shsck6C0zwVM+W3qPVg9lBhZVT5VJB9h6W8CgOkOEADkBW+6ieUHa+1mZcHdNCOfoPkEPpnawMbRi720AHXZgroVdYkXlCObbtuE52jmW/IDSzU2dZiTn65ipjDhIWl3LtExNZ/6Vty5naHjJbBlbawc2N59r59mC8ZuKi7H/Z/RQ0hhTbTJXARJi6g7IholFxDuVzFXa/Rm4UjonJm0vnsyV9L7L/9kzjopLeGAZBzokcwXRA3woY/Dt4EPZCGZiRdn8mDTf1Vk+Db/Zw24wePBE9As94Hb8WKEii9KDuroj5G1cLLlH5aS0MaoNkUpV0YZ8Zh3Yqez1VkOaYb1bzbGNbWQxAXCSF/Wa5qDWMrgICnFzD2XDugBqKz4NYg2Wd2IGzpITd3fY6DLfJLEhH41hpYxEwaw5iUO+ECvBvZiLl2AC7w4zXdF/3xd5vVFzEgMoZSNrDmKRAuzD9G3r9kxZ/+diPGA+TJoeam2DEGu4xkDINEOeQnw1mGXMt6V8B3R9XoE3mg9jvtsC9YvhF72WxAw4vArGIJdxH8jXW8KCkjHMqsOJ82PSPK2wGCt+KVbmT9JBwBArUjgbNgQRm79EF0h/U/ViBkZSUdek+XHNhTlKex4YgwXkot20ecB9n43rdvLE0OWs/1pGt+hDgntTPPWJ8KWMp6ylevWAreDyNgTcoOIZg+3jUqOhzMC0RnNo0o/Fnz0AR9JIPit+apbxXNzbtAawLiizt5RvR4NYimlH82bSCVph2c2d3nepeZSDk27vthfoPN7dYUFJz+fa4cW52CVm07nPCZATMNwuusxJez8r2+2gq9z75KY6iorV1Rnu8l1UotX3pLobOEjGLpp6EFt2VWy4UteMTCxC3B8usMKwVzPwnuE0GHEiiGFxxhwpET0L6fgFrKViSletUd0gwJ6yVbC8DDUayow8rZxDk350dbaLh7+yS8yOkUdVZ+Pmcau7o4iaD2Yppgbn06Qzo4rLqq2f8wqZnJEGOKnKSkCsVVYG7+6wpqTncymr4lxs/xnpHD3g5XOWYwRlt360tndVJYgNAXgbhlQ2t1NWepORzMCtJvO3ExZ8aCBmioC2prHYA88/Ng25LRixWZuk4nfJhrqfq4k0BgNTgbcQVmm/x1h3Z3uBOz7DeoXnYfs3l67fzZm5YxUdX4ygVUxna4uDG4CyXEu4entYEBzCjJwIzpFJ+z2CZd0LSDc2xh4rHHRAZwMes7nPSjizm7Inc+nOMhqb8tRmCzxXzlFVF9k5+qNGJpERMDisDjCQdpoz2MSO6cyqMcyiLavmaSf05KHThnJPViF40F5IAWilmeS02aOoqJg3q5V3M4o6sJYyBrfTVORNye8P5U9Kh9ssDHo2ixqinQqTXgy1FlSRuZFotw1pjcmZcDf3D+0wluDXndxFhFHo3CpkFSbn1F30tdANYgk23UHPi7M8Tb/lFR5FG2BNPhxk5YPquV55HTAdEQdulYZI0RTErUPnt45hDYYyA88azJ0R2/a1llPS71D8I6/5nMXCZ7nSbogAVOLBulYqvWnrkPYgjHHruN12eDOwvu18GykZfOUFrSlxXWBS3J5FT9TKeJolBK/uXkdRC7atjCvYmVdUjZlfbAQ5mRl1ZhZzicEMGPWDqbc1XNhd4QlsY8ojMgQmvOl0d27YPMCtuqWxvFC2Hd0C7K+bb5Mu8XUXWw1kWu/x3H/Mb6+Y34RnpAtAUQfieQbEhs9VrUA2Ra7zW8fZBuOZgZkN5s6kF1zVrWBfrZ0NAp6IYXfTsKYawcy8uZPmNCMu1HGfJde5cttWvGqwEKPtLIPRfCIX9XUZF0nzYLhZljWwijRlDAttnToGbmqh1GvKzszAaHri7wTb4fHeRxX6hErilH91UuRrLd8p6sAJ4VlwuzTw8obmZzuD3sxhQtUT36QXbL1tYb7L3Jb1hhqTMh7TzOJsJ/ZlfqYTyW7Sh6HWcmeKm5skxV/Qlc6nRoAETxMdkNVZQsA8e+4raRfmOAnICGt0Nl3YafAgTrlM0GRQilMpBA6fS1P760kJestk6ssfFdTjmOV0qponGz2O1FvO5aPKC/J8VrKOiqfjx/guym7ROV5qR3WBm4if5L4fupqgEwipZOX5oW0FZN2279OIQuM+zcCGxrNg0hcFmu1gUPqHHWeOqoRnyTH6hXkR7MzcTAgS3IL7RvUXY7vDKP5xmuG+xD/sDCy6ihALSurYMKW22Z2yPJuOZgbeNp3PnbDcyAaj8/PU1JuZp3fR+dNwLAsy9A66gv6jRjVaHa+jJD2oqii+o/fuJ4nieCWvAjExCG3DwormbJ82X5qD9UOZgXn102dkyEwWPHExQ7hqBhKr8xHLKmgY1pFNx00ATDrq89bJWd1I5uVRcL5MusDW2wZOZRYby2J2Yo8lzHzClm0VYGfFitkqblaPaDHRC8ypSV+Yaoux9+l6kxcV7tsN3T7iO7SqU3QZlT+kfC2vAjH0CNqGkRXNQMks2J5PY2XQd2gGBtQT36QTbb0kuyU1F2O+40dr5pNXgZ8PdWQ+RTPLMJ++QzMwn574O8d8uKE0b1yVOzZR84RYQc54A6w97wHtQHqoisGX37p1Q5mNZ+WzZnjEv5nvtHSM61RPuE6Fa6CiWzXrqKi+XP8LxRUpQo+YEWJqSouyLK8oll+/lugoLQh/lL+/rIpaZGaC+gJV7Kta5csXzXeGr9pHzAQe5apHj0dRhW7zIkEglr78SYsL/yLhjRCatkiL4mMeR2nyJ1q1Mwh3iofSd+1jlN3W0S2MrS0z6xy6qAoaw1lS5pN2j4PTIj9DxTopy6R7bxlCzMNokY6fPRcRju/GdT3M0xTsFf5uVLkJW5Wh6KKHDYekGo4WSetLAdKkdz/RdYScaSWrpikzWDFtUplPqLrLwSkfQ+gRYimC8Kq4x6IV7NkIwJjYVFxllYrmLYgW5WGa35JHBCFcXZmemxqDLchKneVcg6J7pAbCMbxLpaOPSnQay82zJK7qAsTRFpkSRIVpBGFMHpJNKinQWjL7AJgeNUqTe1Q8XSZrsKdsuenAhwQ5irGzaYds0fZhxidJWkmEmKaOaaNKphrDGPBWA/8pyuqbiE69qu8smBa1DqcVsrYDFxsUJzdJTHWhnn6KHsMV9HISrPaF3iCBYlMB79iYeTOmxLuMQN1rKDVF9C0qkigbQuuP8vV1kkUy4uhraRv+Rx3RL1+zBJQzbLnrKCy6btqECW5rpHxnaLIEi863CTF0Uri9owMlcH9/pzs9JKjAxzhY1egLtWg+o4dSJru7Mi2S40csY7MoPairO3JmaxaRXJlWwWsb699ChjAzD1SboJGe4Ni3qPWIIhkGs17Qt+mlvaClWkQ0AwKEo00nYahzMG9kwJsg/NSjBjvwAgeMHX5ExRC7CqEZ/WQLgXnXygQNeeNBiqZ5aUODRsztDtMLzAFvcPCR6ZRDUmtDJDKKjdNva0fLJE+FhznKbavt2zhDGdw/PpOc7rjB57wBjx1iiiJbtNIdR5Z8SUtbKNWEjJskqUQs2zDBrbcTDKHboKmAjag3R0WNe2p0Ta4HrT0K7JVJb4QITqnkHzu9aTUYNlYK1lnGcWk6qnURMyC9hhAk3Wpk7jnAlTi6/dHOZCrXKphgFg2ar4UCTV+o33hQhrCSpRQJPIxer7vDR3mqV17DhsMRgIFVKoeNEq3PutYKRZ2oJVaW3i/dpBOfIipxpX1py/VWLNj5EDRqyRxILY7Xl2i9SSWrAIIzNVw9qRDzMMbHUAVODsRAqhOwlcLeN4bQD7zIyxJXTBUoeRj9wJvtRG7FHQEYHIYBDxX4VAx6Gpmj1yDVn7kY9y7wjDXysdPy5HoTJbfgIurK9Kum2ViUC2YMYnQ6/YgqfFrULXUY0qDPUYlX73eU3N6BZBwBmKJ7l2BuKCU95WG0SEdOKxBGzklIJ4yfslgli4dig3P/+HYYPuvzd/lGSBXD5W7tOXTMtbD6+vBquHxk6ijuEYcK/C02mztHUa93deiHLL3YFC7KTZvovBzYJoYLWN5PYUwsU0Ky1796KsLQmvGBlaT042+tddSDsU9MOv4G+Yq7IRbJp6khH6S6IkRG4ApcQUQNfoCQ3Fj9iZmnqZL1xgCKobBwIGWaq3cVNUYopuaiIXN1c/kPD50FUfecgZQNv3ce0BCBRQWQAaSkAwnG9/JXWAZiWRhjRZRxCxCpYlBLPjp9ZYh2gouBgn4GLUDLahixP1lZ94Sr3o0CoCQIqBgaBA/Si/OgUJELxDkxhbrMIQra8CDyEXCQED0YVxkFKXhEMxGB82aRk2IMqB/HeGq9yTJGBxBHzXUOFOrDepmOiuQBoOSDEYEhwjD+WQrCALgAqkiJ7EOQwyRNmYfFVFThQA2GM64RgD4cwpmI1DpzDVkCFFQSYPWj4quo6DS4nhmQS0Cs0AJD0KtzWFNqgSKQfCACLEQaxoVOQRMR1cRaIWnwKF9TD+3Bkw+mhwCnHgcP7s0wIFKAPlJSu2jNIw+8q94TD1CeYUiF4gtWAFVp3lFQpU7DWCGtWoIuAJU6E6yeSlDuB+V4uKQPgajEJXUQsQYR1K1tXsVFAoxCfnKg4Ppi3BJVophHNS27dK4gVwebTZqg1WXO9lMkihJePipVNYhYjHexglZKrNDWJZ2CIJQb+NOEbj20zfi6SiFp1uOcdsn1DcOu0QqSgRUMRgjVC0A4EC1AO3icoQT81djNWyrex3BaMTwCV4h2I5k+RgaRaOzKHoo2Uhdy9vVfOcmMqmsHb4JFQWCV67ye8kaNy9f7KBwg/Ky0ndDrJtIqFgQY1zSiuCWJuRamFaNi61ZkdCHglKSblWi9bjWEf0hpJsJqhyVUUVDMUBuUYp5au+Eb1jOZAGo+Kj2DOZJrHu5iPYhUBwoQTj4YCBwiERdHpCAPiHGW4+io5XFgk5Sh5HW0DCCtqmAxczJqGwFIqsQeiK76NQqC2w1Uv1b9CDnrhgDHixkQUlNRO2R1fQVxpbFwejJr2pyW4KoAwCtZ2B5wbemARnEBaY8NvPJUB0OqbkAdOgDdfpkxRfB1oo7qtF08Smyu3K2m8IzLTNkR4zmVMJWHGaHzKKaxr3IrAgumP/cz0N42BBaXXEY1/Q9FFUUorqndxRiFlgKmmBSUVgck6yfBuAvz2XhUXdLv3ya1vaii38mnnJNZlShVR8Zx507TMULhRRUW00ITM+oCMDtcHP9UU+SwQtyXhcNacKLzslzfta5UX43rOg9eqahOQW2lYsrDTEh/Gkl7JSm0mAs1HmsyKdFZzFEXUmw/UeoeyCdNyyUeSqfhtmAp+00FvJneuZCovrqI1psUDSlE5NThIPVDGlfwphCHDtL9hiQp3vTp06GM388D6COBlA8IrgDRh03YoqCQBOEMMRVDy4otXgQyGYpiG7cmy6xb9Tm6T9CDgc7DAWpXwBje26kOxjojiT6gdHNTpxnxlB0VaGkmr2k4XCmCsFSVN6NYm7JmHMjdZWNSOryKQPLRCbAQvZj8UApCiagmdnglDXZOpEOaKpgeApx6HDy4NxeBSAH6SEnt43KvDTKUQCq8DMEKAfztZw8iVKUhU/rfm1WUD9moPkRRTaI1BZXNmpzYZ7/PzqakLgAlH5cIDNGNzRmnIBKAbA6K0DxzepJwYJphjKGlROky4OmowqGbgyxXbPo7CU1YGM0IGFAJNSI9GVgkAA1G6fxCMkebTVDJGQ2M0Tw2YwnDEw0umBgCRb2EBpuUUSU2GDiTtc6MJYToYNDJOAQmsANtaEZBg7A6EE4+GAgcok2XdFJBFxDVxKF0TZsqecpB6LqvkqLGNJhLdkIZOq+GtOpyZ3m4gnxYynoqZ3k+DaiB5zzchsJzfjJKtvlUDcnYQFuOr+GXCQnYNABQD2Zm1yVIDzpSevEgmvUzQEpXof6cymOahwQK65EIZNJ5hdXImhDz2Iq6PLxXZ7jHd1GJVt+T6o7JqyuSRldFPjhNTYhsTA5hBdV0iGXsFOpUD6VHvhpSHMtpCFfQDxSsp6KfhWRStwGQUjpHoSj5lZ1QY3KOa1mOd1R5SsKOG5pc7pEs1xrRz4Bo5NQAKZV4bcptncRjME1JAiAtt1IdVcLLh6SqBlFKklVcQTRlCxPrsVDb2hWqr2Q3WMdlY4nXda4ck2dRS7SUhByEfFxjQFkCLDB2VYVnyoXJpqW/GpLdy4kwBtSPYQSvIoleRYNRQu6+Mio7H527ZGRaiwIPqDsDc/Be52ke18RGhdEbBBppDkKqBARUAZYK45cSlFIHRDqx0B46r2chKazBXZEBI1nePs3MTvwDFJrwchW4SoRIa8GySXg4Qymf5MjnCg8X+qBal3Jgi0GqVqcn/eZao0LDCvuEFNZiXAprhSfFZvJzydP0W17RdMv0upR9MhVwblGAK1xN5LX83VgUuCV5WiUpX112BPDdmivgqRxghzCtq5DvhihAIsse51HtJ6btQYsceGMogN4yfnjn6jRLyKvaiiOUqoJK4VDUg5UZ4dkgpT6jQj/taR58FulKfNJIT0xpXfOBy1CYkNj01G/YIkB17aS6pJwdHme6Eh5qEmmuApcPWlELTEI7ep5KQUwVXkjHFN6qCko+lX4EwpkNTKUVOVFqLl1IRxNDWuhooB37nGMW3hbTux7B0BpvCLCS1MWCe+BF52oBI5+YcuMX1K5OinytIp0KXKGtyWvBYRfcu29Kf2Y56nlJd5lbEI4BNh7bUCcw0RjEE5Osf2PvSmFEEYEUApaHBcU18+yfSlgLuCY2mPSv+2ljeCSQqo0HqgDvZamJs60E4QwuzRfkJT18Lk7WUfF0/BjfRdktOsfTNLzVBxzytZUUZ3JdXfihh1yX2lePF6Tm8FphWFLSP4xpOIY2HOSoUgiqjRFOTS7JW4tK3UJbRz5SXVUwebv05UgFSbUNTZ3nXtK+wminq2I/WIUJLyhR5zHogW83Xp0k8HaigJYPUF4JoqHs1UkFBRUNTBxMy7R8NX5kUkm7MazRwEZVNHQzptYYKUAr7mHNibht9NSnKdOxlWxZgyXBlCzItgOGvsunzIG4o6cvr8ZvSYtUVUDLhymvBNGRf6xTQT8FYuiWf/yOdoDgNxvKKaDlA5RXgmPbjCmnQDwb5YYXTa/OuqdIZXQDYHWDE6vIaTZ6gFVLNgAzJAWVs6Gg2m+vGyzkZipKMlT0Zb+9JjOxjtoPv73GIDHaVHWUfspXKC27gu512qFm++XFxSaKySXL/7h4+eJxnWbl7y/vqmrz6+vXJUVdvloncZGX+U31Ks7Xr6NV/vrtTz/9++s3b16vGxyv49ER+Teut31LWHnGx32ulDyltkInSVFW76Iquo5KPB9Hq7UAdoEV8OrL9b9QXNErukduy/utJ3XXYJuDoYkMEueQQBODcAdOfrfnFNIU1fVfkT69AgOfBhqe4GGRyacjRMyMS+rhmhdxlEbFWftgaid5V3jkeVqvs+FvnvfktS+eygqtye8xFva7ObbTLE7rFcIaZYJrRxuuZ0KpBebyLCrLB3yUxwUVIg8ycsghAHP8XeUx0uGrOabLpEo5YrafzHEc5qunMYrmizmGT6iK/gM9PTQmEhbTuMQO4zvEvOjNIx0V2uEFaMZ8Nsf1MVlj1lpd5t0RncUoFJrjPUfZChUH5fdkReUzi5YvM8fa1PhnnnFDZ7/bYvteRJvWFwFCOiq2xX1xlz8AMyUU2uI9zMkNMb+g+TKLtVwkeYGlKbeW+6+Wa/kyugWWM/0qYvrtNSfe+R3ktbCFcBs6vyGZbVfRo/INJ4tdq8ckGMxM9i5V7Wl2MHHvst213iXlJo2eWkcMFtO4ZGtmG38gTkR+E90icZhkac1tnWDq+jNG0X6yUJQIEfiB9B+3hjU+5rjzyZ9o1XbfVxzw+FyEggGOaTin6QOPY/hqoVi0KZN4XOx3C2yEIAgrYW1OjRFGrswBqwShPS5g3YwKtofr+4xWXrwuSdVlwuLSqtsqE7seH9Vp89YNxNZ9oTner1nyR40uUE4O6GOsXJE5zpM0uj1d4/4Qs7o4dKDYQrWvUk6fJx+WP3Kc1ddpUt7xWjHz+RkrOI2UuagK6ipdUntbgH2Mw+i6lWnRTLPmw+5BXe/F5TQusccI7BpckY3Zh/hGnaX1bcJZHMYlNhgv8zoW1hXzeWtWwRkq1klZJkNiOZ8VwGNz4H49im3d7cKaOYenB1lcw9et4SBdKklz7lF5cBlwjrr6tnLNSYFQF47IqRyjEguDUvR4/IjWG844x3y2wtVu340DPodwVGaOlTp/c9i6bzZStllZjWvgWNCyJVOv4KUkd56mntIaY3CR0GC1XdBHQsn49moDYpK+aBktnFjIv2QfsBg8o0mjRx3kyizWa5rmD+9pGPpl/i2v+KUrFs9xbpBb0TCbYwZHX6uYN6WxJTY2nhWIj/0+32luQXnTRYr6Sh04QtZQ9sgqTyOBSIs8hu7bnJLnc72+RsWXm29N8qMRqnHRMz6zC5HQvozIRko7sqMaxXRM2SwDiDWHkvATp6ExPuLi319u/ptMt29n7r976Pfd3fJMtO6a5bGw3y2U1k0f3zPq0vDZRgE+2GyK/F60MwzfLcZZILyXrb5kwjY3LrEw025WEozjktm5lGfOwzS/bR9tcOBLZe2JeLJp7pK4mo2nii2w8AXCQyD5rPlesd8XnyXlezEmwlpdfyJJ3TQqiOnh87xeX83oRcZhv1tgiyrBbNF9M8fSPrbzXwgfH6qIuyoRCq3xfs7laPuy7eJu5vkhX0ZXopqU55v2JZw/FFp4ckVlOxrOi4v5vvg8Mg8AOUydsva0e4koXMYly+1O3VtJ/DjZ71t3RAljCvdQk+fWj9/XiURDbkos9MaSvF7EH5iHrxaGmybkamSzaT4t4bXd1TnJi3UkqgRCqT3miyitYKxNiYXJb7VOsk4Sja19oxKrS1H4YmJUYNHDLikBT8hRwdyXEu9QioS4gf6j/eVGH28J3W/0hUtdUn6M8NkAPtFyRUueQ0lXPua3SQYaccVSO8xdJiMpcgFga/aqIWWHz14lyUVisFVJa06zU9FQLpH1mc92My+iGr7Ou0dhcbyJMt5XoPtogweLk0LwZmU+W13DVAh/u0+yGHBq5got+ihEXRxZRly0bPeG39e6r9aY3oKY3tpg+meyIYaWKBVdGrkiC63gLs9QcznAKQVsgcX6iR4hbMznOaT8Uno9XQO+ru7tSnJR62U1p5GVomyzlWv0VrhsH4MDLoyHIlucsLsMX2ZhV3vIP6KqQsVpCXgUi6UWmO8KhFS4gXKrK0FUJDGImS+zkNs1jWS+zL9FnNo5Ltk1N+Nndh3fMfonVN3lnm6bY1wu0VoaBNsqo6Sxxo5xxuG58/Q2I4m97kjSBv7OcFy0PZzJ6naejMmicuFLdf2pDKrhlOgA2/D1dYHum+Qd3IY5Ktk1cb4Qc3c3on583WFxvG2Gq07DzWE9MLcjcQVVFVsTZglokUORBc7W5aKteyRa7WAIC1mQV/pGpEA2Pt+3HFGaL8/HA3OfuCRkFOHOHeXPuuySAe7r3a/nZ76NJyOR3MV3RTb3TQUeGY0epxH3oIuLBMa8lW9JmVyn6DRbJffJqo7SlJP7IMCs4QB3NMeaZN2LpRa2tjpNpYiFwiXv9TomQmusr4m3cUDx0uEMXR258glD7DcYDW9R3amLwqMOPqB2xUPYK1mNj9lFvYY1LKbYSb2SoIch7HtPXc5g+oAQTmOQNyIFcrARHsTc1dW4ZHnl5OJHzXWQfLBYH1FW30QxyVBR4B2tgi5AZDDmrbyv+NDz5ouNv8DwNPzYVWD4btGftg6kNPBlNr6nf9RJgb5Ud6jodTDOCxWCsG5hUDdg/KNyi/VbY7mFF35MFI2D1YrDxq9lLbTN7HZJvfnZHb5bGF3aOvzMst8tvLWytFmeTN7xkd8WUG6z/h678CYJfhjCnhrHj5ukoMawd9FTCVOGh7FvhTqDUAzQ2pJDWWg3UXkRYWULwSwDFFtI01FN4VJfKLXqNXHwO7gtEBJ1U7HUzpuwryj6pALFNuuyfwKPX5hMgY38aisdPcUp+oiy2+qOl2AQhGsLZ6hIcmEeZTAOrVAFg6IRJDEEYeUfd5dsjrMIn/8EoTgqssEpT8bAl1n5iCRkJUdpV7u5shF8RiRQy3lYnpbHpUBb+snGmNgn6+TZjCuy0smI4Tm7xysWV24uHXnsUiAbM2Ye//hHHVHTDW/HHBVZX3jQ+gf3UZJG10kqoJdDubUEDwKGsJiHJFNgF0stTgP5QzP21nVSuHwAyq1OScnNE7V3nORF179DhM+mwklJDmhxYRHFP2iKYZL5Xgix4wstz9vyJ3mEg7f56z3yNqkpBE9tsq7X8LzDELYtRI+6FngIS7qhVVs14bcmoNhq1ZF957B+OqyrSnArEEqtMX9Pyrs0KSsFeh7EgjKNrEkRZvczfA4TLWMwhMVlAT4N0apJzEcbjUps7I8CKmscX9IVgGb4am0NPSI3tJAdtCmw2IM2KE6iFOjduMQNY38dd5msgcs6JaRbi+2FnbY9Hs6Cw1rz4nFWoaKEGA0CsNr1iOAZYUEQ+ygBrU7Ahu2pAK1OYpcJatYhJxm5Iqv9HJXDU/dH+fo6yej5FhiHFthqLFgmNq97HWw2acKfFUAAc/zfUXJ7xz+C0H6zoA5wzrM/2X1PVjyS9pMFvYDxfLAeT79HqMWLAsyhLZVgkQIt6dkX1I0qUNTZNuUAUYw1uUfFE5lEwX7GlZlj7fTIr1ki3GTzZTaaf4na7QwwVIilDpg/oaisC0T6JsE+gnBo4WAtuhkJhQ54yQ8lbhbAAn+drVJELy9Fa59QaIv3DBUk5Bw2EklAHNsgNFA30UM4jyJvTE1Y/ipHwoJtjRtPv3F7+fF0WBwceeRVp/HkIf96+ru3PQbvHrgyKws75pQYk0hw0uCK7HsqQwyV22OHxA9f9nzcHtsbyPIorzd5JsYMQeVWbu8gVjds3SzQq3+q/Mv4dQxh6XHQ3LFhbQHyOGALt0bqBXqCy+P9rd17fIv4y4lYhq82ymfYgKHDvMKHdSlWoNjGzJlEgmmTfrIx1zVzLfPug8r3bnJqXI1jrOxWTCy1wJxQJyQB5fDZppe36CL5k7es9l8d3QLLy/wCn0vjCsavg7Xv/xfoOkQotDQ3n0eZEI43KljcxXViw9fuOABuo5klvDFpNww3N1GdVt8S9PBJ0BKFwq1RuFrp6Rkt0iBxiRaR1ZxG2WqbO0yyiH97hiuyuTFZI/H5kOGrhdhBOXl/LRN0uVGBjYfLZ8Td+befrLxuiigrE8FpbVQwx8JbNpwqzDoZ4XIPrpp71QTM8htSidu58OpOJ2lNEU0UbgjzHoPQw9KnxDINa3VNy8wfbtiYgZzXKZJlBTQAt3IDgC1uowKLs3OTP0iSbkEstdHJWxdMGDVQbGOEKyt8UqDaLPtimeiEK4dzae1MGhIBQTi1ED0RBukS0sha4aBcWuomADzbKcDcZgl8lRsEsDDsPlZFRAQUZ9cdPm+PSGbuVD1lMYPJRQgrq2+r7RHXzYsP6BF4sZQrst7Wm4diwX29K3rGqkIrrpqbSnJNGUTzHNC5K58qHNuvf7ZXvzBGodDewwL2rXBRYmBdZXtuGf2Fx4T5BZK0QkXvZMftoWKphekxWaHLu3p9nQl5bbkic5xtoocxtv7jQsbAZ23E2xah3vNgw5KBZTyHPYTI16KcZgcYOgCLbPk7GdJrm24gQn59tsABH1V3pEj7UhsL3FmBmpsiMRZwVLRtfB7oMn+MzeVOX4th+/UW2YWq21XqaXmCRW49xOvzfCUUP39Vm81zEYRhWYTuTKvGsv2My/YfiGIdle0Z2ImBw3GuJ8suwav+x5DQXk8sFWSeRTKYvXeRGtfeB2j3fID2vi/7Y/O43E1BowG0N0lMHZcZ600AVQ1G7a60meLbfvUNHkkjVYSbQw2spUBtTAxCjBBfZuuM2PlQSPwRR8XPWGlUTZbnswYKzC6vHFihm2ZFwX0w4/8FoqCeAydOwYPBuG+HDje7xg2t/KVJ+QPs7BiP+zYOVl56ppedl29RkUQZmL8iyHwp8LvPoxXSiXYQz0y9/jl058n1GyJf0XPLGLKruQYOyjK5zdCqt67w8XZAudWRe2cyXZyW0PvZw9dlzmaDrvSfa84XgSuykFMTZIWkJ7YvdfXlhqKgGgeUlU4E2Zrdj2Udv32OxeSwo6mrL62bzGVi3y4XQbnUaOO+eLHRf94a/pYoSpOodOH0uF00qLWVpEYEqNwcO0n7gz8JOZLZ7zYM3KX15zl4+O6wXR3lWVXkKZSmRQbz/A+8YddcgMU2+yoLkdFhua1quwQ39TObRHo37vzBRLgE3aRynO+DRARDYLvGzTQI4+KPOipQeX57DURosIWWx/yD1b/qshLfsxAKLQ7t9HAtQyyWzuNGOZ/KSJcDtDGOCmzu3rMf0jcMhcIpwx2Wiu/qH83xkYX9azL2ck9edRoZ17X3vk54o8qoxMJfoOxfvflacHYGvsy+nwJKT3xQbge+zEZ+ZhVqsgbyspMpsApMAfI7uCR3OH7E7ZeClsJ8tpGN+0QRYrmlpGFyh/uImh6Ng6xR1N3+gzEYiOwQfKwKO3cPNQ8X32dzQ7IQK+OlX/rHnHZYHBhZXnUaPv4YZbc1eVCJ15CY7xZGGNHZ1drRlUY/AgGRVptNnXJ7a/NlGRfLbc8f2zjmNi9Egw/79kUWONuXaNu6R+IVHQxhcUQcnruVNyIFWt6JN7x76/N2jF9oRyBqZZFF6UFd3eFGW3+ocxRTWvrsEirMDjuHHbppdpNOy5BpH5bJVNZCpHn7ye6wQKhyuiI0uUl4cwVUbo+9NXroGgHAzNv6Qib2Mv+BuGXIfrfEdhBjnb2U4RyVWmnG98kKFbLcU1D51qz2k7yo12d56XnV3aNxWMeKutMs2st8k8Q8iv7jUotffOPD+hHfs4PVCm/K3F7IfN7HotguDcoWAdYGxeO6OCSVp1kdtEUeRf9xsdVBSABZy0cFFkeUJrszdzrpPloo5Z30HGvh/VeL+4AEn4S5m4Dmk83htqxIw+Lhdvhuj002k1C5PXaaBQ3E25TsZZadzIr8hZWrnJpVRL0v8noDyqm+ZBecCRSCpd17eMnSfV5CSJGlCapPo4K9wDLkmX0I8URqGxUBAdQ2isdVHEoqTyMTt0+CPW/unsp6utCqad3wfRZMM532a0VSb5plQhuD4qFGBZb4RAcM5vNy16JhTl5t/vHWhiAk5RNKbS6+mrTWEtRAseW8XFRRVQt4uSL7/sJoxVILE2KTSxxGLBRa421ylEvtkzIge447qosCZfET8HgiCGHTQlPvHAtlHjNbYt/ny+ix3Y8g84IcysbTD4zAZD7b8nV9XeVVlJ5mcYo7BrE3D+HYwvGjroUewr6FS1K/f/5BNRYY0rNF5dhgSNsWW5GgGBsP4diCYiw8hGMLuK649mAIR/mE5XxCdMwoPUEIJJkBeIi2QWIagIdoGySzAbiFJbWpwmmmw1dL/oC5zoXT4GzoXJFt78gyhvrXfDfHdo5u6myFVlDsNV9mg/UhKlZneZJV5XdUIDy3vAOOBMRin7tD8Y+8HoIbpOc8NaRHi2LQsgTEfveWuXNB5RbOPDc3SZoAyRxHBQ4a+UaikW+s3ZfIuQALXXzwxeLgCLMIpGKoIS36j2sBjtH9VztMogo6fLXEBIzZbYSfovIHWqmpKYOx6/PR/f1bscfNVztMx4+bpKBOL5/yjE+AAQK44v8vFAFU5svdOPhdUqC4eoeuE95fTgZkY4Lqqx3EdAf5kKeAOUoG5dMSxEFyKKeWDqPsh3jUAgGc8YuLFQRww396JEdNypywtq+ZSTH35U7YT68j3jzKF9rvC1Qnaf014R1iDGGx0mqsMxbJn3SZ0uiMKIYSOqrg/FsTmVQN6d/iOSqFbAw6WBvpuCFpcBT0hCF8WoBGJIey8oTotTzFgBRgNk7mRXwXlUhqhQUBbM5VCewnPiqwt/g1Jy/Y3teV2WMlBy68pDd11ezRSiudcSWbC58wmZB34VqGPUado3WUZEK2WQmIeRsfIhJJ1x7WP+dVn0xy3I4CzELuxTHaVJd3Ce5xhD9TP+EPUbb6ci8cAtSgW3MF1R3yv5b4vPYhKSv/92QAlC6PypihmebCKuyzuVSughciltuT85JfiLneJzf0zBaQuQCULsxlhmYa5ura5rGw3y2kdolW35PqDmQyodAOL/DqKPP5L8C4YXjVgz9nCzRuVUCGW4DH4ORQ9twP3fvxZRY7M2AhtrcMn5ZdD2jiyQjIZAIA2I8dH4Y30PkMKrfRtuJkQ9IAiHosV+SAE4iS4sssdHGUkZOGqG4z322xAR0cFVhYJVFZCo8X9B9tuGkgO1U4oTRvAsAzlqq9xAjg8uQY+K6oO6HrE2lQ4q40FM2vT54FzC4RLn8DybxKEzaAt9FiqQNm8K5ZLLWhpKy/rn2V99O1j+Alt8NVdncGarZXcNASENc2QDJIQCxUBu3VrO+V7BT5o7tUS0AWAq7IZqPqqkrVHgDAxjs3RtmQyEpMkyYUW/Qdy8/vQBZ59ruF2yV97ZjsMpzDJfPdWr4ekShbSMI2Bdu1PWNFxPOE3qNx3Z7hulNuz/5bKe3zOBAgtxar1NEx7y6fQO99tvgZa4qsVTiM3UjE6GI2MsIyDaeG9S9vhsBJpfabLZZDXJIJKbfGRXZ2J+g0z36f/yy3cyuI3Fw16ZR8Vk2HxeVhL2nV7Zbkl0UU/8ADgy5K+TILrMT1EdJTRgWWt5kIvnbly6zuJumzMCBaofAvsHr8jRUsJo9VNKfJom8TuHvvvjsYQOC1aW0/3pnMyOeoqouMPOSAfBP9jFA5qS3K+hOxUaDAutAMFFaRCmf3OkdRmWcnedHMFm9r5wpt8NJpR/Tszps4hEJnvHLvESWg/bzBKSTFUhtOjW5umoMvx6zDdwurz2qdZKBb4bjEhtLM8oXDFSUgu+lOtNimn9M/j7DwDbHxj7E5bf46FNstuc+iolVdxOhatsTWPgdhHJfYKDsDjaEraah8sUP7lr9t2I0KszAqyMMXQiJuGGKJ3XYv74BwMC+BJ6BzkHgGOKYReeRfzn4b2XkhbPPLcNQiEF2nqBUVMG45lE2/L6PH40ckkGFUYHVpeYTXzm1ePAk5CMdFDqJvS9/Jk9M21CO5O5ouR5AOAV5hg3GGEF2zvrwmtC5oHBDAnPJx/6KZn1RYzIoV1wUJBm1DJ0LdxkFY3W7kDDFNs+745sVzuVi+Dxyw5rqw3BaAzWaT609xij6i7FaINGcLLPGdoSLJBTccrsjybovW5tObsAVW5rjAD9mE05xCRe+dZkmVRCm4wPmyZ7zO6QTggo/5rd8SZxA5rG5l7WkWNtMkeP4Qi5eyOsER8vZW7F1mToI5GIPS7C1eTApjmMgYTKOc22edOHE8LprzNHGcEUMF15/+49awkLdcc5NnM8ox3NRHdI9SwamX+W5ljS8q0GdrXGKOkbzIByIcFVhs3Bv4uZeNy3MvYW8H8EiEx377jzZHmhtUFKgQcI0KdtPSLuWSgtycZjF3scB8ttkv6cvFH6KSV9nZgq0RUTRpOptnJEACdxadax53NY6J9jqmTTFeSyxdaqmHei0o2Jtcu6banRU0iqWV/34cP8blwO46BNt9yX9S5GsZd/NlNpwpwzkusVrbQd6eCvBiXHmOIuBeL7K8aWvNDodPTYotIWSLL3bC3UcoS9EzEM9YYvQ5SD2PgR0alwOgvO5ENgr4iQCnpwFCGc4gQ57cjLeUJ1l8h1Z1ii6j8oenFxmDycWDTFl9Gq7xP9wfYC4XXginn2wETJ4dP24Io4opl7kyC+EvpOO1TcVra7hQbN+bL9lxUfCCf1RgMWt4EzuvRXHMfrewCkTUC7WoBHzjEjuMx9kKxNd9t+xfTV8ehnvIlFn2UZwR5rPNFvwhWa34F5OHr1Zeg7eE1c9QEQMX71yhPV7QuiIUWtgf8odvqBBXLft9ayT9QZyGeKS9R+NkF5bWnUbAN23zOIavtpjEDYP9bn/GPs9TaWb6rsxmIZ6uUuHmsPm2NWz4tQjChj0aBzZU1P1rseFFWnM5Tpsvizj6SR5qUD/QsFTSPZShIokDuSPz2FyS8GlRbDtn/wd6at7DHGEavlphEpDY1AdSSVqnkbQ1WS3Ex5d3aI2+RUVClHo/Jh6hcuBgTf1p2Jc2yp2Smk9zHiL/QgzXOmp72RnITxcDA1xvWy0Lwp2m5V3mRZmCB3j2uwU24lQo3tkyn81xfRCfGv9g/c54fpufJTF53AC4zmeLlgyD+FCt08N8JeyP7Hfr29ouucVnVD3kxQ/w4laAsXJ+xwvuia6W7ulMMYwPhrFu5fgxvsP6HaKPFqgbk4FujWhrO+Xrb96NzSWuQ1p1W4Wc6j1Yt3dgxbyRDkkjyWL/mONC4WWjUZHtef8kL9ZRVSX8wxJi6XzxUdIFWl+nSXnH7x7M5yUF6y446rzLyRMhx9kKzyg3L1zR1ogwyibtpwBKWgvmqqtJq2/7UTawkWY3NPvDKP5xmuF+xT/CuWhJkDpwlDGmaZgrWJ6KgHkVAvve75qbxKcoq28iek4oLtEa70O+djwIowOrmqHZVpXuW0LeruJjwIavc6gNS50A2hj/MNzEY3M5EGhR7Llo67ioleVhmIhD5uTyqcGwZ6GtY6FzRKZq1U6db0Qyi8spHlmNYBr+6TWiNxJN6Y0TtrcSbFZvc+8cPx0VeVleoDQNwlE8NpeNTYvi+XLVUttSk02geU/Wc1NiUblsSer6E00926jsEV4pkF0UoPSZF6Fwa9jjHzWq0Yo+YHZQVVF8558LA0TpwC6GeKZhG6ZxHhFXZGdMiG4RMUOLLCIU2rA4ry3ZakoniejP1n2z2B+FR21sn7Px1x4/JWskulAPX7dx4QVbbn6LbL7NOMmLhM+wOXy1CwwTw8FsMYhMN3y1CSfjg8jsaou96L5ZXKKhTfrEd6T/aI1H7NKowMLGeMRZFo9sah/GnGGSfpg/LI/c/HMdoV+WvP66wGv6kuZK4/w7+s92uICuMZ8trlLorh2DL5zzZVY9XBEra5SmT0InmZKtEfLsUP2kPIvJQcyrq090MSc+l2v9UG57sBYl0ajAzodJdGGy2r3ygg9woV8srqxLVGTCgIavNopfWYo5c4avtjFjFyU/X8Nnq/G9QzdRnVZYqq0wNyZRWgqDhUC2Zt0eRetNlNx63oJ2WJxuAGRVt9Vs+5x32R1NZtxGxgW6JB0jc7kf1WHYVtbG+mazfzZR6TwDAMW7uGzChQRNo/xN5THnrlTKMA7WqjeQYQUodsH9Vo37rQ/un9W4f5bjXkjUfUYP5UdUVagI58QE43QQfKaIJpJ/YOti7ikV3LxKv10CjhkP27vh0fcJRWVdoCYvve+mz6By2vKV9bd1w58iIek5MesLHu6JlV1u5y6g2/l/l+AmS2+BzGNzZ0gFij1PPnOePF1v8oK8l3ST+MbOjlA5cKOm/ray4kme4ikV8bDf7e4eofzk7HdbH34I37jE1jQWIJ3Sj4QLkm++WFwSRT/4KyL6xTZa5kvGn3TY7+bYsMQ5SVC6In9x5zGuyJ4bjvLsJrmtC+CmXAJiMaOPVRGJl9XMZ5vgToKgC3nhYjpHRTbXb2WdVqfZjXCTN3y34LsmQRvuA0nRJqivQunWCOqLpywOE9Q0IHKJaVLVnujmJFhI00VeFzESMmswn5cKj6IRz4+ViG5UYDtSMSU2+902xuZUeJNi+GyL66IqJAGkXYktxsM8TyF8zXcbzTKLwXPyqGBrxMLxI1Ga3qFNmgd4uInH5nK3qkUxjZRo9UYgpuzG2vTnrxSGUpPCbn7DrEAqoVi627e4vrlHPlTV5rKIsnKd0LylEM1kMH6t6NuwVSKbQ7HoGsmXWV3VNCcc4bKm+2x7RQLfJ7lfJtGa4I3SuGTpqxzC28k9+iRkjxgVWK1FwXWh+7Zl+1YQu8MIlfOOtbc7UPJiMVBhoS1KiVGhraFO7KVbWAH+8z7Bo5NGLgDlNne+rShsWYG7+uUKF7CZeJ9dFbOPJ1bQyJnPVnNE5KpgqGC/2894Y94QzRRQ+VLq1ZebmxJxW033zfJmH7jPt/J/iKr47iL5k+Nh5rPFDODlRJPOjenef116+zzK1xv6qoFKi5AC2d6g/jPZHBTxnXAjK5ZaYE5RlPE5Q/uP82/ZB2WZxwk1JIoBdKho1dAm+fAVm+gZ01MRL6epKcTHcfAsOKAvrES2VTR31RhCAG3BaK8XkEMsS8jc98q7w5dRcYugA7lRh1lclp397TXID+Ysc3GX0D+beMIrLFurIiHvuB+Rxd745SvMlQa1BSPlqE4DKOSTA6ZA35Yn33ANBOAagy57ck5DvbmZpmPYK0ZWq9INitBiXsEGpns3Uk9bEavn9PcIA0w80Dm/iW7xLDbRh0lKlNf+WU+D2earyKbcZq7HOD1pyiELOe0caj/GtO4YVrdXCZnPF6fl5zpNf395E6Ulr27qRu/NPO+SkmqMVwebTZoQj9c2WZBmU1HX49mog+4SERmwk6oBz7nqUQfgJmU3PfeNllhzyxNxSGddug8rhhhqydjhDM7aYUTmDvtW80LfST9OaNHMzQhds+3/l9Gt+ngCgUvSSA0wJgcREbHv6cOJoMadCzLbGNNimoTxkVRWQ6ZHWB5BYfTbqEM+l6PmOXqIitVZnmRV+SHB/cD7z9cSrb4n1V3reanKEaatLGYFE6oY8IW2Ic8ZGOMKwCf6Dm+j+qkjQziB0x3Cbc4uQp0QhxcOqScf8dhCShwe9zYykH78ehbqbK3EjytKMlTwIL0xt/3S/112H9pcSOQaOC2HeuS+Zx1RgpSbKKammhU6SYqyIpx2HZWoAXn5orsk6a7dWsvaH+lR2rwj3QF8irLkBpXVZf4DZb+/fPvTm7cvX9CnYEn8X3rz8sXjOs3KX2M6jVGW5RUd+u8v76pq8+vr1yVtsXy1TuIiL/Ob6lWcr19Hq/w1xvXz6zdvXqPV+jVfvUVrhOWnf++wlOVqlKWHuTTuXITzTRKPeeq3/0ACM3RMco5uXsj46bfXfMXfAJ4kbf/+MiEkpcuZPmhG80Y2N6cECtFevnxB2I5cAfas91qJnr3SbJrJ7qMivouK/7aOHv87i68qxFeXhN5mcVqv0Gl2kWC00aZDek0umiy7dlp27jm4oEIxfWHaHd3g6xNgnJdJlYahWBNBHADRJ1RFbXhKGQzhKJ9eIJzhaCcEJLtzxznKsKA6KL8nK7K1eWBqMPwzz8KMsUH3vYg27fNFkr6Z47q4yx9Gc+A+ysOcKEGe67LPScaIOUscdDjk3GxPcfZWUy31o0fYUrezsh+S+i9ffIoeP6Lstrr7/eXffvrJGuk4IM10So1nAWtDlRAk8Hxn4K3DDLTPI2pWgpl20MVdB59H+kxU8ifqVelnMqND/AjTCK+Y/nqKN5vH31/+X7TSry9O//NKoMcV8S3J62z1by/oWvr1xZsX/7d1d9gHgoN36G8uHaLPgw0v1/K8H6ZnP5OeeQqyvqdTdfJtsE46r3jz5dry0TNZpVq5+8ZlLloaHdUpeZ5GI9et0X/Nkj9qdIHy5tU/FXJbvfAkjW5P17jrnUeuEv0vP9niP69SHw0xoJrPPNHnjmRiFadZ003A2jkqAQvWzq47j+2or9nLz58ctp+OnpMoXh3ygArYaUnSJJ+l9W2SeRz8TsvLvI7lbO9xLOL9C58JpxpZxDwtbGOG++UXa9T9edQPsfFcyy8qn9U8e8/LSYFQZ+X32Wguo8fjR7TeeNmjMJJ2w2oi/MD9ykSIdNnBfCy6zWKg/OOBx21J+Qi5PE2fCcMvvgWHlqx9SqYAxtYg6iGxr37JPuQksO3Wi88P0jR/eF+jssL797e88kLmprVCNqGoIFeBiGYsaPCQxDZVQubVjt7H2SoQJuczgpUMOMjKB/7qfGclARmQvRRoam2JBPhcr69R8eWGrI3Sh6knPtL1nl7tFc7zYCD2LWQ7JhpqejHS6WbkGmN2lDM7Lx1sNkV+77cRjFOgyuWbmfmHZg9zQmbNps+MP5v0uk1DNbWiJRTlTUIWu+089MlHlCYya5ZrE/iGRSrzeQiF9yQv1lFlc0Ekx3URpVXofh6s1kl2lK/XzL25n39LGeRMdnBzk6QJ5nI/0vmfyN4hGr48QiGX3w4Suj3xda/iTHToU3bZj4U+RmVlts+8sUcedIcgPf2Y3yZZKJUa46MchmWvAUqjPgKxW3aag4BApkCYjBDyxLTrjojBvD/G2zIYWrqzuzJ1JTU6ApgyaTBk4B7shIlc9kZZGMe+NlTaQcvuKnqtEYweUUfeLHYwGnHVfXpyxDiJeRG0XU9vQiJ7GwTZP5PNWV5WUQrd8bpZtO7yDDXH5CD4TqLHgNg89j/zQw2Up2FnpeckngvUzli2W6u3wbIMcvXxkDfvpJyWEzg8XN4VCJnj/9kWP14iqEhiDreTtbVJJ3iZf4u8tOcFHR+WMNaO85f8Vda+k79uYA//UDxyepth6h3dkVCzSTiEVUyeCYNMpykuuDddXxfoPokgBvU9fu+CN9hhmt8SnfCZsOji189m0UdmxgyT0C1zvam1U3r5N3bXFi2uI9bo5iKFP+dVaJRDxgzPnWY7L6JVYXiqzXrrIvJ8OxvQa3eOY+IZlHZnZ8VsOxrSgu/tTIG7QIM1aCwDweqH8VtSJhgaC/jkPlm1776688YkSvHFHX1sIuxKO8HQoXEGv+DqGKd9P9pvqsN5QnU4Qp0u9lJ6pHl0fr1Y0UYPQRQQ8sRhdntRrwNpH0HwdcgusZaZcoP17F8olL295yDelpiZix918CUSZfVNFJMwqQLvMBVsUja/13XowvsqAc6TEzZ4Wr5PbqqjqPA6bXY4/Hf2c/RHnRToS3WHirNRLkPXDAoU36AkaG/L7YVVTSanSmKiNBysVlyTXt0/Ld/lD1maR37GgBaH39R8zdJm+XbovEb2qbuy+XIj4HPylWyRHD9ukuZdyHfRkwyjkcmwRUidDShCf+7+EJUXEUnOH2JWx5gcLkG5+j63oHhgxAXt4LZAiNX6XMY1QnSJHkP5SJ2juC4KzxugHsnRU5yiRmz4yTsW3xkqktxzmfYY6eZP0XotrFN6c9a/4OIjy0JFIGEhS/OZRWmHrbHI96dwFCfrKCWpvvCvkubsevN3fJ4lIax4m3ToehAHu9PyuPQiIZPtxY9JsKpDjJbZPV5iGFlzIeV5rKry+Mc/6qg1DXhI8uY0RfEd3EcJrpukDE4PczjYR6fdK8mCjfdj/tCMtfVq85sGrP0nN0/0BH6SF13/DhE+UPmgPYziHzStEsnM5+kfSg53BN9pQ0N8uOiPpV4qBd2+8Mwk63odYmIafNFjKHx04GjVokpQeFUUszkBPqyfDuuqymXB2aZLhkB/T8q7NCkrf4TtWk4R5kssmEfWEif7K9a3Kaok9rLijBAE3zq+pKtpG2iPGUfkNm6qNi42GE+UOg/E6OKEaaO/RLlM1iGuP1jc7aVKIMydkeo4q1BRevNiK71GWNHEDNRKuFnbxOeFywQ1a9dL5uPNE5XVQVUVyXVdoaN8fZ1k9MQzKbPi/ndPCJTtEwI+o/iOktu76Zbv+JgSHP33ZDUh9g/T0qbflULLnB5xWIET6q4hjJfJxIE62xKrCI48uUfFE5lVe9PLuLaP4aXTS79mCX8DatCPcW2ffhxGJWq3V29DQY/rE4rKukCkd0pV2T73V9/EwZr1HAm9TfTNkB/jphwskod1tkpRk1UWMIv5XgU06M9QcVqhdQgzzQghIUNIfBd3eWP5wVJ7AkfMbnd/Jp4XWn8El8xiHY387eJfSzKtMR6c5418/1yVgC244tA15S5BdjixSXvHVR7l9SbP2HACp5O8gCWUL203SfTime6Cfow64CFbt4Vd3TwwaJ/NW4uU+CzZIjbilsD+MYd5hTWw4N5V5ImS0AulY7swLlZ7R6aRo2KYO5uzhLqmOCRH6ir6KPjEz+0i+RP5jGDk1lVe5hf4+BhXPGa3Z1QaHF/GtwihMu9QHf48ym41NnsHDgnojRjWJrWFPl87YbMIZaDZZtvHTVSn1bcEPXxyiwg3939vRNczUYba0RwmWTSkx8VUu6Yf3HYuPIEEj0Ys2a9+lJO065lOy/rZxeXwM3rwWcGn5WURZWXC+xkZbIX9OrhikHi9ZaFeWd5d0geT2caSPLMlBQZ/mChFXUWvuL3pdLJtiPLs1In2tL9+Pkkiu5E5mO77mr4zx5P2vE5R4IcPLzZocgNVm6ADjvK23XU6d74QyM5RWWG9neqebI54KVYzw1eH9EzqUe40VQzi6ImwQ+OTFxp5R2HJQckTe6vke1H4+LEqIvJm65R6JXsB90xEmtYg9ou9FniUp3nxAT2Cb4f4Im83r+ZhmcD3exNvjO3aby6kyG3UM+GhhbWp9oLPtRNcda+uBHJkjqe8XZZfds0Rsmxo3JogYjlJMZv3Ll9eFqUPyQpd3tXr64zJseiCqI0fX9669cysT+4iuWeQhl+eiYQexucqH5u6XsKxJ61nIECHhqoXfrhOSyxUm/sEmwVky17P7HZ24f1eci9mkhqsr+n3VEN5gsVcPQTrzi96bTmQDVLfc2EILmQpat+Hce2/DDc+QzacxFUluKcCQ/e9r0ZYX429f4W2d7vnX7F3P/hrHQBpqN9NElNC9ieMZ7JFLawpwbRtZIJD/hElNr8DKhGEzbnfM+agcc/qrs79vdOn1MlUBH0mCwAeYije25oXJBdhlGfCIjszC61QuYxunzHlLQ9QtsT7FhVJlIHx38+EqBPkOYSTDM6TO9HziDZlTL97lLwJducgeaN0BM4x8s82WPagLJPbDK/A7nA+QbadfQw4Z7/0f/pz4RPOcKv8n+swb+4FSxNGzzRf6urLDUVJBznFEzQsPzyTPXQK1yiFWTOM79IW+UVBS70NApnuqpVX7Z4JL/oKuO7/ni5XnyLq0TlIwCt2lr2CG/i2nHsd6ohN8l7gT0weTu+ksE2yZp89izl9ZFWRpwST352MdJnOwCdvg0ecPLclPEkAdkCzhdeFm6+YblyFn8dES4boLAVBWskKQ8rx5TjW7CnpNC8u/qijApXnt9eh+9kcXVf/wtPEpiYPnwaPHsJnaMjR122SbSYIS+u3HLHvtCH/nfZjkv0I9ISU/SHPPFyley7gecjVbjjv66RvqM6SP2qUUJQ3CdK9emJ7AV32Dx18LbwcvQE0fi/TtvhCBoUTPRQ1eblCedyDkeBOyI4fcd/KUN5A+2DyZYLJ++y9z0QkLe1ZoQybNHPYkYe4mjhUj2qHsEb7jCVk8mljlsaCpHxGsXzP6U1jGkcWBhXJHRcGUxB3wO1M7/hXevl5O14EBm7U/5p+2cbimuiRRRalB3V1R0Rk4810jmJMsGciwrtN2X079xThx2smLNZb6yezdcoc8AKibc0KgbF/Ibx1mf9AYVYIRXcQx6gswyHFf97jU3PhlTnGeNGd5EW9PiNPzTyPFXaZb5LYfnm11fyCH5de3EZPK5qdoc8OViu8d4bPSrtrQQh0fVDueCYLhA7InkXbaju+QMg8+puY20SqarXIwQPlcyOJvR73/Ob7MvjHqKxILzyt8C0WyZQ7YqPJnTxPQrspgZ6T8Hlf5PXGUQK1dYNHdfg/nRH48PW53XW81nIIgULWHqRU7cXKJH54uyme6LJ8JjJqJ8TDc2O3gFY8Y85tiPc8mJaOBQ7RcbNsEHzq2/zlLsQWOz+0mXHbs7FDvi6uvt8NY5v41bk3AgKfS0Ja8wK3VfM9cRxWCFxt4t2AqJocvoGTOnfceVQXBcriJ+jdLEfEDcJzLGQML1//7rwqL6PHdlfyP2F/iyTxhu7CDB/aK7wW0tMsTnFXJ/PlGzV2/DhPY5eksT6R+UwjHDU6z0hb2TDPCNvGZh0Zbshisdo3NhJjWPAnZG+I0hOEpqapvOWpCSxveWpqt/jDJGWnfDI5I/qkODZvhYqOyZrAmnqNtZ3VxO8knKOHqFid5XiDK7+jAmHW8nMdObpD8Y+8HtyrQ58ahQaCBcV2SoDER8nWyeTmJkkT70R4vda/CTJG6kNDjjHkPYsCYTlyhOd/rKc4TTvGQmqHmQjSpWCKqTA+z1SH5Q+0kpHOu6dH9/dvgyE7ftwkBT05fcqzIc9DQLz/haIwY2f58l2ChVv1Dl0PD6y7xZH0aA5iuh98yNNVoLkSkQdkBAb5YZT9CHaU4vAGW2Is3tOj0CjbZ3dCoz29jgJtSK2EpkpB6/8XZk3UWNkrkj+bl52J33wUj5PaTYI+GLvJGjhHJROj7ymNNiSPSnjiiIgD9hqfO3udKHzXz2pSt0ShzalnURLKk7c7KTbHmTA0bVGSswpehJu6MRlOYuuaOlvq9l5osMeFc7SOkozJZOmQD+lDRCKl2sPt57zq8/95uXfHMdpUl3cJ7mmEP1N/0Q9Rtvpyb6PkWj9I+LXEh4YPCeaDZ/MUw+IvEtKq9q231fw8vdxXoTHrvE9u6CniGbJONzT7yRtqes3f1xKtvifVnSMLcdW9uzJ6rS64HWdOZn0mDNrpScwsOz1gJMPjcxPZkdr/Fmpi++Fp2XWVZtmLPHM/dMjwmW8T8CRyjge7IUHHwVTAHmO4YJQLlBGFPVQPG3ThuvcJlSWTadw7w1U3I1Tp87Q5zyD8+oX9TKRfP56gDjILqmtny8bKhwgwJ8ktaZD51PeYfUOTX1vOMZpZRjL1NWt3Xmr266lJNm5tatpNdhcY/g6wS3zjHcB9WnaogihGHzGLZ0O6IK+3zInU/z5tbm76pqfjc+lS0X5EIjzD8Kjdto91lOeSUWbBDZpSUccOptKLuujl3dXLtiuPrNn0eVl5Fndnbojq5RVLMRxGaZQN6X2c1LdycnedkIewsLcMrP3jytPadmVjPDF/X+gu2dC72eex8haU5JdFFP9IstuAl4vUmW5apYTeEKJQV5jdQxmB0M2xC3UL4BlZMfohOQWONDUD3E+JduPJrQcWukdVFxnJKI+eTYKUAHFYgSZucf0nhN3pHEVlnp3kRcMrYZT0luMQPf8anPddkJr6NlgFMHEJ5jzf2YlubsjBJwy6g9U6ySQuZnxCM+u0NiNBESLcbFvcWCw2yJz6EB1FxbPaJP2l5VlUtIqDl+WpsQu5XbOydX2uVtlJ9r9eXVz+///tXVtv3LiS/iuDeVwsTjazOMBikbOA7TgTA8nEYzsZ7L4ISjfdFqKWeiV1Eh9g//tS1I2X4lWkbsnLTNwsFquKn3gtVs187RHuFQb+OlFRB8H3F3Q3zOy5umFOeEGzkXEOujq3zPQKbEyXmNuJbGVr+vYj9sHzpnyIv19/R5SmLmwwkyvcnYe8eB7puDJd/iprS43PKBk+MojwlW8pB5KgnMP8K7IIG5HLZfT5mSXI78dscWqyOxf1Y7TWN31btza8dvZfj8jBu5v3Iu/xWK03Aoer512K2ETjTj1Qs7lFRZLL/RPM1hD1ZQDhNupizzSLwWSTPvRuyFaMLKmSOHW8/GFrL/51BLE4/uldftjIl0ZpBKxnDXpQYLDuYMamz2WXCMn6143Asnkf2eb1UC5iXzosYrUrY5dglNdZTWwxlhp38XZGG6zJO/QVpWOD2eVFZep4YsSxznbk9TnBySTS/28Okf4Djo9mT64PyFdKR4xUVBSo8MVvlmNPo7yQRX2Fle3GHeo2ySrfxqUQNoXb1HvyTiIhiOnAABsZg2iVvD41mX3tAiWhsN7ojI5JPsWi57YgDuzdkL0RYI6/FX1T5Ed3GLK1R+YEcReDrjsulm+gjCIeMwKVdygeeUnS7uEvn5voNp6Y9c8hl+7J3ke128go4Bx42k/8aw8nQrbHUubeMLsntD+n6CEuv2ykt7U70b877EQv8Peg3nn83SXHWJ5dfz/VEAOc5/gtuTV7cnYhMDWZLWp8/B/T/n942r0btP4HngruzlnUVh+V1Rxj6/Qhuy6KcYN4K5JranSZRg6bxToTDDnydpWlZ0D+dZ2Nlgaz8C/LS0fLnEmKSw/7SyIQDRtvX+VN+TbZ79Go4Ff4z0M9atyiYketJxyc9TpOkpOa0dre5d8+oYIe3or829fuF8nQ34R2q0cOX+e56aaS8zYKjtvWNDyC+DN0W427PHVx2WBqj1p43eCVWxpi1fSx+AmoCQF1n54P3pl6cfByCCJuHomr7t9ktzUHTh9Ywyo0idx8gwIz9s6TiQk37mzD+jjLGGwPT+iIPsVFUrPaCNKITjbdaZTu0nKAM+EJIoS/1rLmGg4shPNGQBJkwgKu38ZPgmVqfR0OcKmd26DrxrEYfkunb/W2fXiXH/LbZFdH6V6GW/jb6phe5ntq/hp3v9rcQHbvzP9A1be8+OK7b26L5BgXz+TT6TKtubwlgriMfNdEWF5/x1pmB0TCeo+VD2ZmIaa5833L/UcZCR2udVSJAlne9u7NIVMFtsPFu7xmYGEUc3+TN3lxxF1GRVb3xN750QnweZ4/p0n5NIcPv9eLL2CC9hkv/3Vex8i/zva4M8dOgXZrsPcxeeW6kQFo6acQwdbTl/Huy02G+e++bNE5yMPL9HnfP8/uhTTFpf/7ODs/xmSpXTygIx6zN3PkFGQ4+JTUOU3EhGujGQd+9NY93P3Zx9vt43bY+9nF2+3iO5QSqzY9vZEe7qfrl15WC7+N4RJ6GC7ysrxHafqzB/30oPngSGfZ3orZaZ3kifCU4cOMX/UoArxbxicz7rM/z+iM9iTPyUVVxbunDT3MpnSz32Awlcd5dmO14gOqj7zonnULJKaZT12uAt4kLt4kTa0xPm50gHusy+cki4tnp8N+7Rfp8kTyPd4Cwo6eVoxdvsSNfH+3RZIXI8OD1c88vPsc10wNDuXt/YJz76I+5CEEvUOn9NlOWgu2ISS+EhJjj+V4udv5ZmnyhsdlhK7vRf3civo86r/Hw8JDkYyMMoKZeHm32ixhdo75Vtna46Z7lO3rI784TftO8+ghSUu6kXkCzLLHTrN/t7/GbLeZ+rUBy9vUJcO7vLd5IfM3N7t5K3EvWCpruOgsS21kCxfGjdfNfZmafyag2q/RY3xOKzy4EQTiWgG+u6v4eIqTw1bujqDPwtF1Ep703JgZzXTT32qHjzfZPhP50Q6WXTZmeOHWTILNG1IxOPzYuCWLX8b58LBXrJem+jh8rZ1EPsNR1kvgdMPO2AOv3zzy+ndLXsYDyR/oW/kO1R/uBj0eYOW8BkaRLEaNHqN+K1NBuGZrEbW+Guw7zJHrp/GDgN/9IOCO4ttsv/lzMn2P4vJcoDZr1ka+D9186xKALmx4u7saF6HdTUPdNbYYeo0/l6zc0DD7E0aTwujmeMKbf9zmY7KZN2JBMPQmT+uEwkFYY9PXLH14yfrgM/5B0pfkNEaEh/gLGlO/8QT/kI1byWMUv0lQuq//8v8Kquv0qzx7TA7nIoau3p22aNffqyL2lsL7Kk/Px6xzSvfB8Q6V57S6yR6FSyC3TG1NtCMsXR3vyH4RyNcP8aro/jnb/QB+/SbWHiwRXT43XAaL/+svN+VHso36z18ecN+73IHk52KHwDff1uI1vNTi0cOAw2Mq1duGseZ8KcprqipQdfQ0R95Ffq/C6Pqbu65A1fErTtLE6CC0gBGJnW5GxQdqeNxXha8RvWF4meey6xSjcRz3UeDnENff68Xua3RK8y1lAWlX704vaR6VvlTzLbc9rEI9rzMG2IxfYxtdmLo8Cje5L3XgaxBbwD64QFWdHoo4K48JCZU43qoQx1EOYPjbaI4ObB8TGF71NHtK34wtbqhc+o2wNwoZ7Cq6j5uh+ktIvqL31Bt1xyt+KycBy6no57mLr3MXl9c89fdd/6vV3PsHbuyybz3MF/nXBFsl5KOAm7Id+lqIjrnv9XCiNN8mH8QNxouvxTP+sx5KfZ3+dNhozpW8HQN5Xkx9eHws0SifM3L1P4bBZVztnu6Tf45aItzij5AEzlqEO0QdY5WEJbdbAri58v5PcrrA7MZeA6cozoagfx5nWNw17TqwCetJxyB1mXB5fpE4A4P16HaBOvpZG2zYcqMnsvASPKHXyU4crnqQE9c60TYeopvXc1f1t+WWBJZlZNzpTYNO/Q20aDnZ8QxGZvobVLHs5r5mmEhdDYi6DGIOfduxMO3VtimnXmXacvtcxvYkLb6dBEPNED35OinJ5NdFsXDpyo6H8QfatuXUl0xjdpakqo77Kmn5LeE0VA3Zm7fub+BtO7Ntaq19SYtvfYDb1gzRky339n8PsdPdpbxvVOS4Mbe10rSmlArQye8kQ1M55Jw5di1sO3GOXgMvYPZc/pr3Nk/TT3kdfnLWRAYm6cxHfmRY0Yus/OYSvY2uG6ITap/iLjn2Vu3f6veQVGIgVUcno4ahScJAo6gMuBNuKsQn+TR0CW9qhgDHZZoffhRw+OrL2ma3eenwsnqoGXB1dIe+JujbW5SeHs9p5rgRXUXHMgo7L2666qNE+SsuW4uHODikBd16b84VftXf3NH0k7fhpvbmH252nNwQGmT+NyofsBVTD6z+yC05ybB+UZb5LiE9212jkGjojS/YHSqJ21rUpUDiwH+d7X+pl69DjqROonuUPv5t+PH9Oa2SU5rssAj/+PXlr/wn8yFr0qf+ckHuverTjHIX70VzYDX2UhkAyVl5QAJWtn8RmsSfMSqat/xXeVZWRYzNLX7zSbZLTnHK24MjNBweak17lnzJa3RCWX0lr9LbpF06sZXYft8M1wM6e7x6QYHKAGvJP0mASyLbioBGiy2ijC3dBsQYnVaBL+GWi95zl9E98KlQ3czXZnpZLJwEeso7TJV8LGEQQAommQCY5ne6kvbNLnEXANaHuDggfpM4AEMKBFXH/4AgtQbI3ADVnJROBc48TdcxOdeSsiAjP6x+CiZqrGPW7Q9UI1Fo254KiJJGRkGC7ucwc6RpL3pAS6uI0SyIyWfDC+edgrcweCuzq1DrpJPoV2osB6ZH+aJJ0AX7GsnF6imCYI4zwQToM/C1kjRs4ly1EEzqFmRAr0v6+YdAoQ0KZoOf3EtvKtxVcYWIx3a2QxHspbgojNHysthiS1aPKUYdCyzNBqXORc8jiP7tb397KfTcwKlzvKQ59b+tHQCgV+nCu14B2vHf8OLAYP+JTggJRrjZgNG7PvVPQXRL664GeKY00STDO3ZDogQeZzqFJ0CV0o1d0qTa/W4mZGkWyBYDxGZxZdPHM8BK/rxhalRdJmn9VhN+2uGEKc30ZTXurRcNRm2xxp8fDSRuUFZFsBILnbxaoUFR+rLNTF6dRjaT12y46twC13HB0UnLyDD8uPqLjl4Vk7bmv+xon8QkJKd21P5ffufRljOXDt1v09x8MAKzgnBFYW5AIAuFQRKnj0mLLe1i4AQ+mRM6E+rFHwVOkIWWA6fBp3aeFVP3stPjuKRbOHdvdpmFTv/j2scU+EmyrPtnHk3696gXJ2zyOi1KK77BnWpXl+nH4cdJBhfhHTgkS2Bs9SpPAC71u3dJm+q30bPDTHdNajNcbBhmVl0+B8wU8RFmg1k7va5qLIOWbELZZkYym+XZEgeyHmGaYWz+hfj8+JpwKe4CL2XciUnR1f3jDv3vOSlQ/ThWfqS9pLGLEhgUhynfzBhGa2Uzjs19iBDhnk++ouL5oY6YLu1bmojpVKbAag9oMRr6woVU1TDYsJrXKNnmxsTlOdunqI5ncFFVRfL5XKEmeGk0lOgmOYoS6GC6dMoTKKlmaiEF4pBTo8zGQTEq19VEhqH2cqDbYnU1h6OuH8z61mVuOGe6c0Ew8wow7Wz5EyXqRheCj/4cRBKccllHW5zQELI2dLDFa2RzrrUYXK1mWpsRVNMPVnb3PjMPVXXa+seY3D0VE1790c0y7NiCtV8BMtoYwWEZTgUMKuRKjO3QsIOOVCSwPAjUrPvfzwhk3SxdYVHYW80MtwCwTT/T2Q9vy1iY35/QLnlMdqSoP+pYD9hg+SG5ZJQbAaBEvTVAERb9w6n+MzLRi36Wp8eDGRBCPf1V6GogZUcY5imgO4TGvhNWKWvSPsxgoYOrubahgfNDjs2jwTb7gK3SYG7MU9kX9LECl7KKGGQGfbGp0o2sFiiVLFYIM0ZSA8Bl5lwk6VOgM38EiJl2+Jwog5PWTAu0T3GRxFnVj61X+fFzkhHC2R1EFLJB0FKSb8mtRKWoiRRL8jhR4W81e/PFA3X6eXcsRufeqhvA889zTILmf8wSOUYZIhoLbMEmh0e5gRYNPVrspeFvvWOiCSQ3O/qtecjrIKc/khQIgS6f+hhysWic9NjRAywXc9bIK/EpTs89SNUahsHFtMgl6pqI2RKGxLATnsJAudHWAs88g7lhPftycsLnGDMtDFe3+ovu4+MpRa/zb1max3x+VuoMpiNgzl/6HxeOBli9+ZHAGn82LDwkqMD61fGe5VmYKbdil0wvBBNBZrFeekYW6tdNpHEZ9DFpjJZuAbBa/pZyHhBNuG20w8/cG8Uu5/E6AuJ10jIyDD+uPiBer4pJW7MHxOtDtlrl2ltUpE7dBDthmrJJQlpBatlMc/NHhF1vrrwlgm6qRdVo4NUV5gPf9fcKFVmcXpyrp5pj48TEJW9c9Iin0oCRS024+hFQqZ4NIGfD4pu8OB9JDGPfwJMfJPRtMpyoX1ePi0GX9YDgIT8lu6lRQBoVYdD+vA0cNMqsBwgR+e/vRX4+SVFAkQid1/48yUREGhRFCAQdmWECgseooUGuJQwhgNz2PRYSLzMMOuZ9Oe2IQ8gXsPiQiT2m60JCaPLVi2W/Trp+IWLNBqIPxT5Inh/V2oW0yTBpf1l7ep9GDZOGWIPP3Pur2DRPC5opl7nmqJl9hdvF2PxYxgf0NsHSFM8RHBx0obFUaclBeViCzURTZdQyaXf2cKog1oBvxX6M+AEgZj6mzIUvIuG8Ex+5a10yonopRQkCXrJOhp1BkTUBZvluHfPAZkK3DjvgzO3W8XvyWF3FxT66PRe7p7hE+7+S6kmig3s3avwPOykYZsOP4UaSqeIu97oYYQLsitkhwqx1YIVcuzTQWANJDsozwaLHCgGe0Ga97ukqLgtrH+lPYaGroSVBbbI1kjPOmB6dd9n0R16h5a+zaylFCZpf142hQZHlr7Pv0DeM9tscMyi7wWkV55OA4Iw4YPnqzy4hrVZxkgnhzOskqFmOLwcukw1DrlhhumU+v8P7p+RUJ6Na9EzWCckG9Ot/XDeAej2WP411opITI1hu114LjBzhwIEtCBNQ0aZjPYHI+FiirzDvJW0txsnvHb1mivp5Tf8rZ/QZVyvVucjqjIgogK9xoPUwJTK3tGFKNrAGpvVZxeq3f8Az/ahihc31jS1WwOOsP+faNieCXMVFRaX6C5mWUgMTXiJuScIXbid9pKCbSZsLSBcpQGgVs9QSYDblXOWErtmnKwFby7+DXwKwJryRd8LV3BfzV09o9yU/8yHPhJ/lI5hAyQxlYuk0b5tBtdSihQxqprFnGEBKNDQa7viqM277ducCa3y4jZ/J0eNNltR8PZ1Aqk6n2Ya5/RtfuO5jRUEfozapnlgMPrqLDLVGvvo52MEBqJRStLA3JG4A8QtKmwsTvu5s+Kxh8BX3xLv8EFH/rjtSftLA0TEnDnzZJICkWpVJE+rcQmWzMLijlTJpjhNxEVBbxc5zPlRNud+0hdPsW03/+AkXA5KHzjYgsxqokGe+9+fP5a5ImlSTE4f/oNsWn1OzpauHhajTKkCC9fsaV+g9KmsHzuhNkR+nQwnbOHcaxhatHh+cQiYt0p2xFIA85D/hsRB4DF0x36r28TFJ8S8omig0Q98gy2j4de33s4MqRrubmf0+LnYpF9iw1kI7MhCimSNs9qJzu5t0kvCFopkCwanXx2Y1Uted7zKtyos6OHlyjIvn6++7pzg7oDv8RVydC9zE7lkBr5aAhVb3o/koQ0Rgb8SaXwJhAtIrDB4aPUwaUnTAMqBB/viJiRkwwVh+NjBcxrsvNxmWZfcl6CY3yOwjEZ4RSUqz+sWuTLNV7JhluFu+z8fyQDehB8gYzM3tCPLnGZ3R/voYJ+lFVcW7J3LZ/iZRLLXtU0MFgRwoOZfADKRYfcYpWC+jY59kxtU3JXbUCL9TB09jiJjg8EzBJAMcJbwMY4GQJTdVcHQZNUfLtwRsUcOYVJVxHftDDGi2MJhtTKNqzga/m+MpLyos2yMeYKP73RPan1P0EJdf5A8faSJm78cUmO8iGRkYjlxJmHeMUp3D4IXVyaTBVsIkO9QyzpjDZX6oMDJwaVe2BxVWp9VBBTeU5o0PLKjD+H4NluuHFR8QiC4Ms3iy7nsvYKP0MtwEPs65Mr/N0/RTXmG8t1cudUcNTMnVfX/4hX+vajA+5BFfT3su1taF0411ZRa3uHz7zAGIUGjwCm/U+VanwQQwU1veqs058FX/cJGV3xRHqRQJ36vdz5OMYqMw5ussS2KuBWFrEHHWHLJX+ZHMlIYDGFVl6rGLbppPI9v/vqERS2pqq+YmhhGcCNc1E3DAbMSWSPI0LlnlCZ4YP51s813mpPnBcjiiqkw9HNFNM5c29O8bGo6kprZqbmIY1f8WkytxvShkmBp+nOYq0B5JnoYj2DzLwE8n24zOtuSi8Q59TdC3tyg9PZ7TrI68YrrXk9SffM8nkwO4/AaINjSEmfWIVdtz4pAp0Lk6tFTSPp8NUX69FyBjLBBMTOVlIMtpWJt1LDMH83ZGrRUNVStwwnKE0/ocruwxNK2b1TWuUz3XHxWugYrOvTDfozdJUVav4yr+HJfiPU5d6x5V/SMckpu2+ZnqzPb3+pLqGP/j1/3nHPd1/DkdqggDDsc4/n4VV+hAwkaI7OlSsBGaQNMU/ld9kAg005dATfSFGvbv8l2cJv9E+67HgYYAGqhJgEzXeJwdzuSJmNhmXwQ21ZeaqIfuq4KcxZb5udiBrYFkUiUFSo0Ut6g4JmWJMd4dcgsSiCRQ6yKVpmX27Y7QKlsMtchS6PTM0xTSjfwM6kNKDLh2FxYg765Q1kJXbmirfhkiNVdPobJYT2TYrKI9dUPaFvqnfEIDfQnEvy/UKVD7rYEDYV8Cit8V6gbANs7pe1Q95dCnwxOAwyFHo2uzwsMzHsa+4ukU+m64crBFlkTT4HDCJLQ1FEHNDKW6r6hbSImfUFcCfj9doYb9kJFX4D8UQQ0MpTqYySdc9WxrPNXeJrvqXED93ZeAJuoKzXpA0QpHoOiPqCWK3scE18b9U0eaTgp0hAc4kErVawyhTgSUJl9R8fyQHCHt2WKwUYbCzNp0DGGZwWkahc1pMtvG+3CAb5K0gicybRUj0YRaZpIqPi6BQgXLjsoYl23F93F2fowJpBXGYalUctCUxrJohNC3zlKYaX5/QrvkMdmRLRAV7VNmAxm9yhpwHWO7wNU/tG+PxMlRSQ7OlcoaTtIZy2UjkWmfPsTQfo0uVPQWKTdr51NcJHE2xBq9yo+fkyyW9ItJJYVcynoaef88x+SXj1kCTTtsMSQDS+FmHXOTaKbe5v/23xFfUS6QmSTWuOT1bAP3mpqlJbfASFtDtzDoX76Ji4K+CFwQ9KW685oEFbdFAq7lqTLwrGYo1jQyOK0IbQxFUBN1qZb79Xc8nWdxenGunuoTtWZokp4nqMkhKdQ1NNKRAFuSDQxVBrVLisvIaBNDaGWnenShoiGzEz5CLGtEyb+lMOH/e5GfT7JG2kJFSy2FpqU2frPQSPs7xL8tMtxSsBlzpXsKlky1qWApNVLAeXsFKWAySAqY0lAKRcvq1sy6UTK8UGXS7jTat1BZa+FGmjJpI02xphEwO6XQHEgFNQwSGpwpSbbAQ5HsHMlos8smvpO2Iu80lkJrUiYrFmBMphw2I0OiVY9PQgGoyJPAavJUumMgMTGCeB4k0oAHQyKZbeOyFZSM0EgMs1WTNIw50PsSShgHEmJLcQzkMBNAf7xNBz0Wj7jpUvCYmyYwb6qJIa5qrqHQNNkQaW+xIM2kGploAkR5hZchLI1iNcISajckbIhIYAvCEqhWjhyprg+HeIRi7w1lYL8NxbrxkXlXKo6NTDE4LjIUWlim0o0AVQZDMTVc4n8s5I1QZVAjVLFuHYUyhDdbqsFdJAHXVQKVbouIeSCyf/0MXpVy5eBWkSXR3jHl4C1A+zt8p5Qb3DMMAdTEaakvgi8Uu1IT0ftTD1iDvliqiOmxiTTqkdCwlBK8OZMRW5wSP6DjKYWHEphMd2o8UBrehikkEElUt2PGLXfHlPKGBQrVaadps3eoJtvLrzF5AnhtwdLojFzkZYmZp/JWRRLQyAKVzsjN2kd6582VgwZmSbQntGBgFOCoFqSDz2xBUnNB1M1rG9UfqTFBkMQjNKYYPDJjKLRf7PEUJwdo3BqK4C+0K9WNTM0KSDUo8RTgeMQTGZx7vkNVhQrN8CwjlJ2JQrRaE8QlHkf/QsnhCepTrhxWnyExa/B1gsFdwmqLJIpmKSpNy1xMGqFZrhxqkyPRzfrP2U4x6dOl4JxPE2gPu/nYFsABN08CH2rzVEYty63KlcvbNLUq79Kn8dhTk5v4DEY1qfndL+M4JXf1ktDpfbHuUImX6MRn28DVrNVW7tEmUKg851oipG+4O5pW+EmIJKoz7ujidEoTtH/IW/rEQgr5ckSgMJOhJddL0LkoGdxzG153RwOdMSh7F0sz71ZzL1cmUr75va4Qw0K0iUAic1ZlqQym3v5FKDjf9qWySbYnMPBQlDfFlMr8FE2bkr8Uk2ENIFVADqC2kUgnhkHbQoPUMwi183o0uL5TdRRu7EMF/tUGlQuM88PHIsh97NmaCv/6houJv/wLVn1T09BvDfR2gamDGQV6UzFYRPlMwt4cwhxPj2NldN+aXbSMWUW5qrKXDURT3WsFJSdobAe5Kl8oeDflQ1wcUOVgyrai3ABShVUKLtOEeBpTfo8sQYhPkH6J0qgMvTRxU615ThI1HGHlaJKRQgo12KcwfT3JIxd7FdnledStz1G7qE+U44l5ZbmS8IMPoqj6GYeCC7dxAZjJnqMEMaB8FDGvrBhJAGUl6i3FZPRjmajnC5gHJAxgCugBUGMC5bsee9XbPahKaZ7Ev7rcfprUkz3/cleRM51cUZbQey9NqHq/seyPGhSDp5xYv2oAVwxKxSVnKQyDoIaQD4JyYi/9uAQzXCZpHR2556wwAkcazgRGGBqhdBc0ZGAt11qgDfkJcC9+GQayZ7z2ZuieaiqXxiJRiOUx/9yU1JQ+JXVYJjMvGaP+RSOwWoYpFUtg9vlks/6FX2Cy9cDXl011rsi7+t1hrF59OAKIqAYk/2LU7x8BKvpdoPHf4/xZffNZyx4D26upOspXzPIm1eRK8e+siVKyN9RgTdAsYmFY88jnfpNqnvp8YebpLmGssMNXCokcaOwRykIaxgY1fCX/Q+pMZgGe3oPrR3WFkDgBYgwwTFRBA5wn24iNDiCdalk6hTJAtIJGC0UUAldcuastffkf0eEKpNYwqq5VUQyyQCsrj5ug5iWJmQCz1sU98GDg1iT61Zy0SrBlnWsHeDGLlUE2aIp+KTusDqSWEGlDrGUkQT1oQ3hcyfCq6fEgkAZDw0QmYMKNKHY9IJ3/nQ8UyIRUVgYoce55LtgK3YQUAvI6ntWCUCFlBJYHMZP+IwHJg30o85hEEphGbxxNxWBmUgebobkZRpBxuCtTBMuRRPoBb9Ic2ChuYvR2MTOIAVc28JCCtySAkG+0qhu3hbCS28SmWugXIHqOKl2c5NThjlkF/1jmpJUqDWIMhZOSnFqrlEQbQI1lmEQRsMr0sMCYxQTnCAbBvmiL2oTrCmJi/SRuUjvYJ7o2c7IR0JxsyrCQGwgK1kZMoQrCtlYTO8B0emyaWCu8iZTLSeO6WuWVWputGxdpRhLVJZI2a2xSNZ9JjGLCl4mMo2IOR7txXwgZDpSTj4Zh7wGi+/h4StEQEVGuOEepWLpwcRmbZYsk5uKEKvchGSPWcV1UWUKp9whyd8IXQk2S6vIokmPUV8xdIpH/2Sq8ql3cTKXLlEgkF9ndZYqP7klqDj/68ww0fbqjrqCH+Ah3Od1H4vk9iURTA//RyZ/gzGUiVfBWpb+lWcUQaDIJaEu4WQWmtTddH55WaSeAKoRRhDi6pCr1qx91SQxcvb4cWTCFmYi+g8ZwpF5HlSM6qK5EX5pGIzITuncQGQzJK9YWK/rt2ZalslsbmtGCztOb5Euk2aq+WIrOo+gTf7ckvq7BswGQTi6465sBJqo0qQUHjXZVVDU2cRQhhqVw6kFBrqPBvU7uxwdXUOzeRvvxQSG1GS7KENmeTNPa3dAuDfXofl2EOfqw3XIb8CR+FRdCjg/1PG16BvkVW1mRyP9WNryqXaD36Bbvz57iEu3/SqonqgVRcV0Vb+owdflQ9qSqNEy9uyGY73bgLzcDXMGTImBN6RdvlCPAk2k+0v1ubB+2lt9xYS7z9CkGNAMiRRJgQKTTIwz1wMwH9ioCyQyUSyElfYiFkSJ5A2Fiko3Bj1m034W+kl90zGOaLk+E/JvgKPwqzWfSuO/CRHh63UGnwYgGvnItWUJPgoM1hflVmbLDdSPUhdzQbvl4wjXt+ZjkI5oBD6QMM9QBSVXaL1mVK2XEAbdBZ0tp/fd36LgAfO4XzZMlFbl2R+DliZIs60379Wsy2XgwkOrLkBOH+DhmN4Viwyil9b9vnNoMcK6gCMhkBIwcpnUVeJElX2qAo8unpOYleowYZkZymW/YHEPRTZZUSZwq1pKqCr7XkXAmpXbq0WRHGm+Mbq0sNqW3i7RuIHWVnOBFuFl6K4coV0NSp0hI8CRaTkWumMrhBFTNlK7OKiXjI+Ph2SSqqQukCzFrTaO2Tt3gavLq+VFLSAemv0CGqYPdI0P5J4aLOGUiCRfHOzrtWPSmyI8qe6jIQxgEzrDWLmyUCdNGm+IhtzAERbx2M/Tp4SLF/k0k8r9xE1LcNTWl2etcRvYUiHIMj+0gpb6v3Z2whHR47RgvS0LnEq80J9G6kmNcPF9/3z3F2QHdYdMOKdSAbYm2ksoobE631iBwvjZ2h0KnmWu2JWAWuZFGIH8Ya89Sr0ttSd445WSorRNi6NPkzSOMTJPg+TOTYt+uq+J/+z6vicAEadGbBB5IFdQK3wVnF35lQrj75gGESX63UWaJ2AxrSqOwtHLFoKxvRB9VNjeZaWQGCYYOpg1TkNCVvKq1EMQwecwiNnWuaCMFtWI+ARL6NtOKIlEvwwHM10ZYqNOwuTiD25hDQR3SHGCiteY7VOZPczXHkAMu6lnLjAHQelcE4CHmu6PYKHLYuaWxoBNNRfWB0VWelVURJ/Wsgnen/eKkC8X8kEdigipgf+eLt35N5BC3mV02SBJ3NesHXTIuD2anE3cYWJIiVytllSNkVpNQKchMgcJkLROtNpJjeNQBSdvuu8dkshRp4ww7PFNTm6unUwtv/AxuBrWpPHOmvc+kpgN2JeM4hscTkJmv2bgoUu6NM+wQKV5trp5OLbxxJPoZ1G53kULKQONZTVJf8UDbf1sTzKRSNcUduT79osduYgqsjM7WNFRdru96zOcEN61p3bhOjFzz7httZH3cGY7Q/+mXf9VfvWiY1IbHvYyKvuzViyYpavsD/rPKi/iA3ud7lJbk11cv7s649hE1f71GZXIYWLzCPDNE8kQPTDuam+yxDvhO0mlyEnUkXXGft72K93EVXxRVUse1xMU7/C2RdL/Ey6Q+D/qM9jfZh3N1OldYZXT8nDKHzq9eqNt/9UKQ+VUTaK/0oQIWM8EqoA/Z5TlJ973cb+K05LabMhZX2Pq/I/x705f406zQ4bnn9EeeGTJqzYc3qSjb40/uAR1PKWZWfsju46/IRbaPJXqHDvHu+bZO40WcZmRM9B3Bmv3V6yQ+FPGxbHkM9fGfGMP74/f/+n+Jqu+Y4rUIAA== + + + dbo + + \ No newline at end of file diff --git a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj index 715e87aaf6..ff7fd39e2b 100644 --- a/src/Libraries/SmartStore.Data/SmartStore.Data.csproj +++ b/src/Libraries/SmartStore.Data/SmartStore.Data.csproj @@ -404,6 +404,10 @@ 201605201911421_ExportRevision.cs + + + 201607111548571_FixExportDeploymentIsPublic.cs + @@ -752,6 +756,9 @@ 201605201911421_ExportRevision.cs + + 201607111548571_FixExportDeploymentIsPublic.cs + From e6ef17b71e5c8f07cf965c7cb730d63bce4c3d68 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 13 Jul 2016 11:05:59 +0200 Subject: [PATCH 267/285] Export: Projected customer id was ignored in price calculation --- .../DataExchange/Export/DataExporter.cs | 14 +++++++++++--- .../Export/Internal/DataExporterContext.cs | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index e6b182c270..387b360a06 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -969,9 +969,12 @@ private List Init(DataExporterContext ctx, int? totalRecords = null) ctx.ContextCurrency = _services.WorkContext.WorkingCurrency; if (ctx.Projection.CustomerId.HasValue) - ctx.ContextCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); - else - ctx.ContextCustomer = _services.WorkContext.CurrentCustomer; + { + ctx.OldCurrentCustomer = _services.WorkContext.CurrentCustomer; + _services.WorkContext.CurrentCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); + } + + ctx.ContextCustomer = _services.WorkContext.CurrentCustomer; if (ctx.Projection.LanguageId.HasValue) ctx.ContextLanguage = _languageService.Value.GetLanguageById(ctx.Projection.LanguageId.Value); @@ -1286,6 +1289,11 @@ private void ExportCoreOuter(DataExporterContext ctx) _exportProfileService.Value.UpdateExportProfile(ctx.Request.Profile); } + + if (ctx.OldCurrentCustomer != null) + { + _services.WorkContext.CurrentCustomer = ctx.OldCurrentCustomer; + } } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs index 5c6c1315dd..4c41efd0d4 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs @@ -89,6 +89,7 @@ public bool Supports(ExportFeatures feature) public ExportFilter Filter { get; private set; } public ExportProjection Projection { get; private set; } public Currency ContextCurrency { get; set; } + public Customer OldCurrentCustomer { get; set; } public Customer ContextCustomer { get; set; } public Language ContextLanguage { get; set; } From e654f1530f540ba1d540afecd1080b29109aa60e Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 13 Jul 2016 11:41:44 +0200 Subject: [PATCH 268/285] Revert "Export: Projected customer id was ignored in price calculation" This reverts commit e6ef17b71e5c8f07cf965c7cb730d63bce4c3d68. --- .../DataExchange/Export/DataExporter.cs | 14 +++----------- .../Export/Internal/DataExporterContext.cs | 1 - 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs index 387b360a06..e6b182c270 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/DataExporter.cs @@ -969,12 +969,9 @@ private List Init(DataExporterContext ctx, int? totalRecords = null) ctx.ContextCurrency = _services.WorkContext.WorkingCurrency; if (ctx.Projection.CustomerId.HasValue) - { - ctx.OldCurrentCustomer = _services.WorkContext.CurrentCustomer; - _services.WorkContext.CurrentCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); - } - - ctx.ContextCustomer = _services.WorkContext.CurrentCustomer; + ctx.ContextCustomer = _customerService.GetCustomerById(ctx.Projection.CustomerId.Value); + else + ctx.ContextCustomer = _services.WorkContext.CurrentCustomer; if (ctx.Projection.LanguageId.HasValue) ctx.ContextLanguage = _languageService.Value.GetLanguageById(ctx.Projection.LanguageId.Value); @@ -1289,11 +1286,6 @@ private void ExportCoreOuter(DataExporterContext ctx) _exportProfileService.Value.UpdateExportProfile(ctx.Request.Profile); } - - if (ctx.OldCurrentCustomer != null) - { - _services.WorkContext.CurrentCustomer = ctx.OldCurrentCustomer; - } } catch (Exception exception) { diff --git a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs index 4c41efd0d4..5c6c1315dd 100644 --- a/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs +++ b/src/Libraries/SmartStore.Services/DataExchange/Export/Internal/DataExporterContext.cs @@ -89,7 +89,6 @@ public bool Supports(ExportFeatures feature) public ExportFilter Filter { get; private set; } public ExportProjection Projection { get; private set; } public Currency ContextCurrency { get; set; } - public Customer OldCurrentCustomer { get; set; } public Customer ContextCustomer { get; set; } public Language ContextLanguage { get; set; } From 53d614128ee9922d0d319224480dd3c51279b823 Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Fri, 12 Aug 2016 15:08:21 +0200 Subject: [PATCH 269/285] Awarded reward points should be rounded towards zero rather than to nearest integer --- .../SmartStore.Services/Orders/OrderProcessingService.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs index d92e6212a0..0993597da4 100644 --- a/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs +++ b/src/Libraries/SmartStore.Services/Orders/OrderProcessingService.cs @@ -242,12 +242,8 @@ protected void AwardRewardPoints(Order order, decimal? amount = null) if (order.RewardPointsWereAdded) return; - // Truncate increases the risk of inaccuracy of rounding - //int points = (int)Math.Truncate((amount ?? order.OrderTotal) / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); - - // why are points awarded for OrderTotal? wouldn't be OrderSubtotalInclTax better? - - int points = (int)Math.Round((amount ?? order.OrderTotal) / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); + // Trucate same as Floor for positive amounts + int points = (int)Math.Truncate((amount ?? order.OrderTotal) / _rewardPointsSettings.PointsForPurchases_Amount * _rewardPointsSettings.PointsForPurchases_Points); if (points == 0) return; From 972e2170f9c9874a3895259e0ade9ca0218b935c Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Wed, 17 Aug 2016 15:32:34 +0200 Subject: [PATCH 270/285] Added PayPal partner attribution Id as request header --- src/Plugins/SmartStore.PayPal/Description.txt | 2 +- src/Plugins/SmartStore.PayPal/Services/PayPalService.cs | 1 + src/Plugins/SmartStore.PayPal/changelog.md | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Plugins/SmartStore.PayPal/Description.txt b/src/Plugins/SmartStore.PayPal/Description.txt index 75a43e5adb..84ba16461c 100644 --- a/src/Plugins/SmartStore.PayPal/Description.txt +++ b/src/Plugins/SmartStore.PayPal/Description.txt @@ -2,7 +2,7 @@ Description: Provides the PayPal payment methods PayPal Standard, PayPal Direct, PayPal Express and PayPal PLUS. SystemName: SmartStore.PayPal Group: Payment -Version: 2.6.0 +Version: 2.6.0.1 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.PayPal.dll diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index f5819da195..0b41486b94 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -451,6 +451,7 @@ public PayPalResponse CallApi(string method, string path, string accessToken, Pa request.Headers["Authorization"] = "Bearer " + accessToken.EmptyNull(); } + request.Headers["PayPal-Partner-Attribution-Id"] = "SmartStoreAG_Cart_PayPalPlus"; if (data.HasValue() && (method.IsCaseInsensitiveEqual("POST") || method.IsCaseInsensitiveEqual("PUT") || method.IsCaseInsensitiveEqual("PATCH"))) { diff --git a/src/Plugins/SmartStore.PayPal/changelog.md b/src/Plugins/SmartStore.PayPal/changelog.md index 70ed0765b7..80c08c683b 100644 --- a/src/Plugins/SmartStore.PayPal/changelog.md +++ b/src/Plugins/SmartStore.PayPal/changelog.md @@ -1,5 +1,9 @@ #Release Notes +##PayPal 2.6.0.1 +###Improvements +* Added PayPal partner attribution Id as request header + ##Paypal 2.5.0.2 ###New Features * PayPal PLUS payment provider From 6cf721ef06b7cc2f1e04754afca5e672049c096b Mon Sep 17 00:00:00 2001 From: Marcus Gesing Date: Thu, 1 Sep 2016 16:11:29 +0200 Subject: [PATCH 271/285] PayPal PLUS: Integration review through PayPal --- .../Controllers/PayPalPlusController.cs | 26 ++ src/Plugins/SmartStore.PayPal/Description.txt | 2 +- .../Services/IPayPalService.cs | 6 + .../Services/PayPalService.cs | 237 +++++++++++------- .../PayPalPlus/PaymentWallScripting.cshtml | 18 +- src/Plugins/SmartStore.PayPal/changelog.md | 4 + 6 files changed, 200 insertions(+), 93 deletions(-) diff --git a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs index 1c20f4b032..d2fc262e9b 100644 --- a/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs +++ b/src/Plugins/SmartStore.PayPal/Controllers/PayPalPlusController.cs @@ -194,6 +194,9 @@ public ActionResult Configure(PayPalPlusConfigurationModel model, FormCollection var storeDependingSettingHelper = new StoreDependingSettingHelper(ViewData); var storeScope = this.GetActiveStoreScopeConfiguration(Services.StoreService, Services.WorkContext); var settings = Services.Settings.LoadSetting(storeScope); + var oldClientId = settings.ClientId; + var oldSecret = settings.Secret; + var oldProfileId = settings.ExperienceProfileId; var validator = new PayPalPlusConfigValidator(Services.Localization, x => { @@ -209,6 +212,15 @@ public ActionResult Configure(PayPalPlusConfigurationModel model, FormCollection model.Copy(settings, false); + // credentials changed: reset profile and webhook id to avoid errors + if (!oldClientId.IsCaseInsensitiveEqual(settings.ClientId) || !oldSecret.IsCaseInsensitiveEqual(settings.Secret)) + { + if (oldProfileId.IsCaseInsensitiveEqual(settings.ExperienceProfileId)) + settings.ExperienceProfileId = null; + + settings.WebhookId = null; + } + storeDependingSettingHelper.UpdateSettings(settings, form, storeScope, Services.Settings); Services.Settings.SaveSetting(settings, x => x.UseSandbox, 0, false); @@ -314,6 +326,20 @@ public ActionResult PaymentWall() return View(model); } + [HttpPost] + public ActionResult PatchShipping() + { + var store = Services.StoreContext.CurrentStore; + var customer = Services.WorkContext.CurrentCustomer; + var settings = Services.Settings.LoadSetting(store.Id); + var cart = customer.GetCartItems(ShoppingCartType.ShoppingCart, store.Id); + var session = HttpContext.GetPayPalSessionData(); + + var apiResult = PayPalService.PatchShipping(settings, session, cart, PayPalPlusProvider.SystemName); + + return new JsonResult { Data = new { success = apiResult.Success, error = apiResult.ErrorMessage } }; + } + public ActionResult CheckoutCompleted() { var instruct = _httpContext.Session[PayPalPlusProvider.CheckoutCompletedKey] as string; diff --git a/src/Plugins/SmartStore.PayPal/Description.txt b/src/Plugins/SmartStore.PayPal/Description.txt index 84ba16461c..7123a24071 100644 --- a/src/Plugins/SmartStore.PayPal/Description.txt +++ b/src/Plugins/SmartStore.PayPal/Description.txt @@ -2,7 +2,7 @@ Description: Provides the PayPal payment methods PayPal Standard, PayPal Direct, PayPal Express and PayPal PLUS. SystemName: SmartStore.PayPal Group: Payment -Version: 2.6.0.1 +Version: 2.6.0.2 MinAppVersion: 2.5.0 DisplayOrder: 1 FileName: SmartStore.PayPal.dll diff --git a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs index 6a799e16a1..2f4b94c1c9 100644 --- a/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/IPayPalService.cs @@ -36,6 +36,12 @@ PayPalResponse CreatePayment( string returnUrl, string cancelUrl); + PayPalResponse PatchShipping( + PayPalApiSettingsBase settings, + PayPalSessionData session, + List cart, + string providerSystemName); + PayPalResponse ExecutePayment(PayPalApiSettingsBase settings, PayPalSessionData session); PayPalResponse Refund(PayPalApiSettingsBase settings, PayPalSessionData session, RefundPaymentRequest request); diff --git a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs index 0b41486b94..3d5d66d20b 100644 --- a/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs +++ b/src/Plugins/SmartStore.PayPal/Services/PayPalService.cs @@ -41,6 +41,7 @@ public class PayPalService : IPayPalService private readonly IOrderService _orderService; private readonly IOrderProcessingService _orderProcessingService; private readonly IOrderTotalCalculationService _orderTotalCalculationService; + private readonly IGenericAttributeService _genericAttributeService; private readonly IPaymentService _paymentService; private readonly IPriceCalculationService _priceCalculationService; private readonly ITaxService _taxService; @@ -54,6 +55,7 @@ public PayPalService( IOrderService orderService, IOrderProcessingService orderProcessingService, IOrderTotalCalculationService orderTotalCalculationService, + IGenericAttributeService genericAttributeService, IPaymentService paymentService, IPriceCalculationService priceCalculationService, ITaxService taxService, @@ -66,6 +68,7 @@ public PayPalService( _orderService = orderService; _orderProcessingService = orderProcessingService; _orderTotalCalculationService = orderTotalCalculationService; + _genericAttributeService = genericAttributeService; _paymentService = paymentService; _priceCalculationService = priceCalculationService; _taxService = taxService; @@ -113,6 +116,99 @@ private Dictionary CreateAddress(Address addr, bool addRecipient return dic; } + private Dictionary CreateAmount( + Store store, + Customer customer, + List cart, + string providerSystemName, + List> items) + { + var amount = new Dictionary(); + var amountDetails = new Dictionary(); + var language = _services.WorkContext.WorkingLanguage; + var currencyCode = store.PrimaryStoreCurrency.CurrencyCode; + var includingTax = (_services.WorkContext.GetTaxDisplayTypeFor(customer, store.Id) == TaxDisplayType.IncludingTax); + + Discount orderAppliedDiscount; + List appliedGiftCards; + int redeemedRewardPoints = 0; + decimal redeemedRewardPointsAmount; + decimal orderDiscountInclTax; + decimal totalOrderItems = decimal.Zero; + + var shipping = (_orderTotalCalculationService.GetShoppingCartShippingTotal(cart) ?? decimal.Zero); + + var paymentFee = _paymentService.GetAdditionalHandlingFee(cart, providerSystemName); + + var total = (_orderTotalCalculationService.GetShoppingCartTotal(cart, out orderDiscountInclTax, out orderAppliedDiscount, out appliedGiftCards, + out redeemedRewardPoints, out redeemedRewardPointsAmount) ?? decimal.Zero); + + // line items + foreach (var item in cart) + { + decimal unitPriceTaxRate = decimal.Zero; + decimal unitPrice = _priceCalculationService.GetUnitPrice(item, true); + decimal productPrice = _taxService.GetProductPrice(item.Item.Product, unitPrice, includingTax, customer, out unitPriceTaxRate); + + if (items != null) + { + var line = new Dictionary(); + line.Add("quantity", item.Item.Quantity); + line.Add("name", item.Item.Product.GetLocalized(x => x.Name, language.Id, true, false).Truncate(127)); + line.Add("price", productPrice.FormatInvariant()); + line.Add("currency", currencyCode); + line.Add("sku", item.Item.Product.Sku.Truncate(50)); + items.Add(line); + } + + totalOrderItems += (productPrice * item.Item.Quantity); + } + + var itemsPlusMisc = (totalOrderItems + shipping + paymentFee); + + if (total != itemsPlusMisc) + { + if (items != null) + { + // e.g. discount applied to cart total + var line = new Dictionary(); + line.Add("quantity", "1"); + line.Add("name", T("Plugins.SmartStore.PayPal.Other").Text.Truncate(127)); + line.Add("price", (total - itemsPlusMisc).FormatInvariant()); + line.Add("currency", currencyCode); + items.Add(line); + } + + totalOrderItems += (total - itemsPlusMisc); + } + + // fill amount object + amountDetails.Add("shipping", shipping.FormatInvariant()); + amountDetails.Add("subtotal", totalOrderItems.FormatInvariant()); + if (!includingTax) + { + // "To avoid rounding errors we recommend not submitting tax amounts on line item basis. + // Calculated tax amounts for the entire shopping basket may be submitted in the amount objects. + // In this case the item amounts will be treated as amounts excluding tax. + // In a B2C scenario, where taxes are included, no taxes should be submitted to PayPal." + + SortedDictionary taxRates = null; + var taxTotal = _orderTotalCalculationService.GetTaxTotal(cart, out taxRates); + + amountDetails.Add("tax", taxTotal.FormatInvariant()); + } + if (paymentFee != decimal.Zero) + { + amountDetails.Add("handling_fee", paymentFee.FormatInvariant()); + } + + amount.Add("total", total.FormatInvariant()); + amount.Add("currency", currencyCode); + amount.Add("details", amountDetails); + + return amount; + } + private string ToInfoString(dynamic json) { var sb = new StringBuilder(); @@ -412,7 +508,7 @@ public PaymentStatus GetPaymentStatus(string state, string reasonCode, PaymentSt public PayPalResponse CallApi(string method, string path, string accessToken, PayPalApiSettingsBase settings, string data) { - var isJson = (data.HasValue() && data.StartsWith("{")); + var isJson = (data.HasValue() && (data.StartsWith("{") || data.StartsWith("["))); var encoding = (isJson ? Encoding.UTF8 : Encoding.ASCII); var result = new PayPalResponse(); HttpWebResponse webResponse = null; @@ -600,34 +696,16 @@ public PayPalResponse CreatePayment( { var store = _services.StoreContext.CurrentStore; var customer = _services.WorkContext.CurrentCustomer; - var language = _services.WorkContext.WorkingLanguage; - var currencyCode = store.PrimaryStoreCurrency.CurrencyCode; - - var dateOfBirth = customer.GetAttribute(SystemCustomerAttributeNames.DateOfBirth); - Discount orderAppliedDiscount; - List appliedGiftCards; - int redeemedRewardPoints = 0; - decimal redeemedRewardPointsAmount; - decimal orderDiscountInclTax; - decimal totalOrderItems = decimal.Zero; + //var dateOfBirth = customer.GetAttribute(SystemCustomerAttributeNames.DateOfBirth); - var includingTax = (_services.WorkContext.GetTaxDisplayTypeFor(customer, store.Id) == TaxDisplayType.IncludingTax); - - var shipping = (_orderTotalCalculationService.GetShoppingCartShippingTotal(cart) ?? decimal.Zero); - - var paymentFee = _paymentService.GetAdditionalHandlingFee(cart, providerSystemName); - - var total = (_orderTotalCalculationService.GetShoppingCartTotal(cart, out orderDiscountInclTax, out orderAppliedDiscount, out appliedGiftCards, - out redeemedRewardPoints, out redeemedRewardPointsAmount) ?? decimal.Zero); + _genericAttributeService.SaveAttribute(customer, SystemCustomerAttributeNames.SelectedPaymentMethod, PayPalPlusProvider.SystemName, store.Id); var data = new Dictionary(); var redirectUrls = new Dictionary(); var payer = new Dictionary(); - var payerInfo = new Dictionary(); + //var payerInfo = new Dictionary(); var transaction = new Dictionary(); - var amount = new Dictionary(); - var amountDetails = new Dictionary(); var items = new List>(); var itemList = new Dictionary(); @@ -651,80 +729,23 @@ public PayPalResponse CreatePayment( data.Add("redirect_urls", redirectUrls); // payer, payer_info - if (dateOfBirth.HasValue) - { - payerInfo.Add("birth_date", dateOfBirth.Value.ToString("yyyy-MM-dd")); - } - if (customer.BillingAddress != null) - { - payerInfo.Add("billing_address", CreateAddress(customer.BillingAddress, false)); - } + // paypal review: do not transmit + //if (dateOfBirth.HasValue) + //{ + // payerInfo.Add("birth_date", dateOfBirth.Value.ToString("yyyy-MM-dd")); + //} + //if (customer.BillingAddress != null) + //{ + // payerInfo.Add("billing_address", CreateAddress(customer.BillingAddress, false)); + //} payer.Add("payment_method", "paypal"); - payer.Add("payer_info", payerInfo); + //payer.Add("payer_info", payerInfo); data.Add("payer", payer); - // line items - foreach (var item in cart) - { - decimal unitPriceTaxRate = decimal.Zero; - decimal unitPrice = _priceCalculationService.GetUnitPrice(item, true); - decimal productPrice = _taxService.GetProductPrice(item.Item.Product, unitPrice, includingTax, customer, out unitPriceTaxRate); - - var line = new Dictionary(); - line.Add("quantity", item.Item.Quantity); - line.Add("name", item.Item.Product.GetLocalized(x => x.Name, language.Id, true, false).Truncate(127)); - line.Add("price", productPrice.FormatInvariant()); - line.Add("currency", currencyCode); - line.Add("sku", item.Item.Product.Sku.Truncate(50)); - items.Add(line); - - totalOrderItems += (productPrice * item.Item.Quantity); - } - - var itemsPlusMisc = (totalOrderItems + shipping + paymentFee); - - if (total != itemsPlusMisc) - { - var line = new Dictionary(); - line.Add("quantity", "1"); - line.Add("name", T("Plugins.SmartStore.PayPal.Other").Text.Truncate(127)); - line.Add("price", (total - itemsPlusMisc).FormatInvariant()); - line.Add("currency", currencyCode); - items.Add(line); - - totalOrderItems += (total - itemsPlusMisc); - } + var amount = CreateAmount(store, customer, cart, providerSystemName, items); itemList.Add("items", items); - if (customer.ShippingAddress != null) - { - itemList.Add("shipping_address", CreateAddress(customer.ShippingAddress, true)); - } - - // transactions - amountDetails.Add("shipping", shipping.FormatInvariant()); - amountDetails.Add("subtotal", totalOrderItems.FormatInvariant()); - if (!includingTax) - { - // "To avoid rounding errors we recommend not submitting tax amounts on line item basis. - // Calculated tax amounts for the entire shopping basket may be submitted in the amount objects. - // In this case the item amounts will be treated as amounts excluding tax. - // In a B2C scenario, where taxes are included, no taxes should be submitted to PayPal." - - SortedDictionary taxRates = null; - var taxTotal = _orderTotalCalculationService.GetTaxTotal(cart, out taxRates); - - amountDetails.Add("tax", taxTotal.FormatInvariant()); - } - if (paymentFee != decimal.Zero) - { - amountDetails.Add("handling_fee", paymentFee.FormatInvariant()); - } - - amount.Add("total", total.FormatInvariant()); - amount.Add("currency", currencyCode); - amount.Add("details", amountDetails); transaction.Add("amount", amount); transaction.Add("item_list", itemList); @@ -744,6 +765,42 @@ public PayPalResponse CreatePayment( return result; } + public PayPalResponse PatchShipping( + PayPalApiSettingsBase settings, + PayPalSessionData session, + List cart, + string providerSystemName) + { + var data = new List>(); + var amountTotal = new Dictionary(); + + var store = _services.StoreContext.CurrentStore; + var customer = _services.WorkContext.CurrentCustomer; + + if (customer.ShippingAddress != null) + { + var shippingAddress = new Dictionary(); + shippingAddress.Add("op", "add"); + shippingAddress.Add("path", "/transactions/0/item_list/shipping_address"); + shippingAddress.Add("value", CreateAddress(customer.ShippingAddress, true)); + data.Add(shippingAddress); + } + + // update of whole amount object required. patching single amount values not possible (MALFORMED_REQUEST). + var amount = CreateAmount(store, customer, cart, providerSystemName, null); + + amountTotal.Add("op", "replace"); + amountTotal.Add("path", "/transactions/0/amount"); + amountTotal.Add("value", amount); + data.Add(amountTotal); + + var result = CallApi("PATCH", "/v1/payments/payment/{0}".FormatInvariant(session.PaymentId), session.AccessToken, settings, JsonConvert.SerializeObject(data)); + + //Logger.InsertLog(LogLevel.Information, "PayPal PLUS", JsonConvert.SerializeObject(data, Formatting.Indented) + "\r\n\r\n" + (result.Json != null ? result.Json.ToString() : "")); + + return result; + } + public PayPalResponse ExecutePayment(PayPalApiSettingsBase settings, PayPalSessionData session) { var data = new Dictionary(); diff --git a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml index 212ae653cc..c1f2cdf6f0 100644 --- a/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml +++ b/src/Plugins/SmartStore.PayPal/Views/PayPalPlus/PaymentWallScripting.cshtml @@ -1,4 +1,5 @@ -@using SmartStore.PayPal.Models; +@using SmartStore.PayPal +@using SmartStore.PayPal.Models @model PayPalPlusCheckoutModel