From a61f230d0afdf6c15dd595922ee6661fcb9a957e Mon Sep 17 00:00:00 2001 From: "l.gabrysiak" Date: Wed, 21 Aug 2024 16:29:02 +0200 Subject: [PATCH] init --- Dockerfile | 24 +- .../Caching/DistributedCacheManager.cs | 7 - .../Configuration/NopConfigurationDefaults.cs | 6 - src/Libraries/Nop.Core/NopVersion.cs | 2 +- .../Nop.Data/Migrations/MigrationManager.cs | 49 +- src/Libraries/Nop.Data/Nop.Data.csproj | 2 +- .../External/ExternalAuthenticationService.cs | 13 +- .../Catalog/CopyProductService.cs | 10 - .../Catalog/NopCatalogDefaults.cs | 2 +- .../Catalog/PriceCalculationService.cs | 4 +- .../Nop.Services/Catalog/ProductExtensions.cs | 6 +- .../Nop.Services/Catalog/ProductService.cs | 42 +- .../Customers/CustomerRegistrationService.cs | 12 +- .../ExportImport/ImportManager.cs | 11 +- .../Nop.Services/Html/BBCodeHelper.cs | 4 +- .../RoxyFileman/FileRoxyFilemanService.cs | 3 +- .../Orders/CheckoutAttributeFormatter.cs | 2 +- .../Orders/IOrderReportService.cs | 4 +- .../Nop.Services/Orders/OrderReportService.cs | 27 +- .../Orders/ShoppingCartService.cs | 63 +- .../ScheduleTasks/TaskScheduler.cs | 2 +- .../Nop.Services/Seo/SitemapGenerator.cs | 4 +- .../Nop.Services/Shipping/ShipmentService.cs | 18 +- src/NopCommerce.sln | 15 + .../Views/Configure.cshtml | 3 +- .../plugin.json | 2 +- .../PayPal/Checkout/VoidRequest.cs | 23 - .../PayPalCommerceDefaults.cs | 6 - .../Services/ServiceManager.cs | 26 +- .../plugin.json | 2 +- .../Services/EasyPostService.cs | 5 +- .../Nop.Plugin.Shipping.EasyPost/plugin.json | 2 +- .../Controllers/ShipStationController.cs | 163 +++++ .../Infrastructure/NopStartup.cs | 37 + .../Models/ShipStationModel.cs | 44 ++ .../Nop.Plugin.Shipping.ShipStation.csproj | 57 ++ .../Nop.Plugin.Shipping.ShipStation/Notes.txt | 32 + .../PackingType.cs | 18 + .../RouteProvider.cs | 22 + .../ServiceType.cs | 20 + .../Services/IShipStationService.cs | 48 ++ .../Services/ShipStationService.cs | 669 ++++++++++++++++++ .../ShipStationComputationMethod.cs | 185 +++++ .../ShipStationServiceRate.cs | 33 + .../ShipStationSettings.cs | 42 ++ .../Views/Configure.cshtml | 147 ++++ .../Views/_ViewImports.cshtml | 8 + .../Nop.Plugin.Shipping.ShipStation/logo.png | Bin 0 -> 1648 bytes .../plugin.json | 11 + .../Services/FacebookPixelService.cs | 2 +- .../plugin.json | 2 +- .../Mvc/Routing/NopRedirectResultExecutor.cs | 6 +- .../Admin/NopNestedSettingTagHelper.cs | 9 +- .../Admin/Controllers/CommonController.cs | 2 - .../Admin/Controllers/ReportController.cs | 2 +- .../Admin/Controllers/VendorController.cs | 15 +- .../Admin/Factories/ReportModelFactory.cs | 6 +- .../Models/Settings/CommonConfigModel.cs | 4 +- .../Admin/Models/Settings/DataConfigModel.cs | 4 +- .../Admin/Views/Setting/AppSettings.cshtml | 2 +- .../Admin/Views/Setting/GeneralCommon.cshtml | 2 +- .../Setting/_AppSettings.AzureBlob.cshtml | 50 +- .../Setting/_GeneralCommon.Sitemap.cshtml | 76 +- .../Shared/EditorTemplates/Decimal.cshtml | 2 +- .../EditorTemplates/DecimalNullable.cshtml | 2 +- .../Shared/EditorTemplates/Double.cshtml | 2 +- .../Views/Shared/EditorTemplates/Int32.cshtml | 2 +- .../EditorTemplates/Int32Nullable.cshtml | 2 +- .../Nop.Web/Areas/Admin/sitemap.config | 3 +- .../Nop.Web/Controllers/CheckoutController.cs | 31 +- .../Nop.Web/Controllers/CustomerController.cs | 35 +- .../Nop.Web/Controllers/VendorController.cs | 28 +- .../Nop.Web/Factories/AddressModelFactory.cs | 1 - .../Nop.Web/Factories/ProductModelFactory.cs | 4 +- .../Models/Common/AddressAttributeModel.cs | 2 - src/Presentation/Nop.Web/Nop.Web.csproj | 2 - src/Presentation/Nop.Web/Program.cs | 5 - .../DefaultClean/Content/css/styles.css | 9 +- .../DefaultClean/Content/css/styles.rtl.css | 9 +- .../Nop.Web/Views/Boards/Forum.cshtml | 1 + .../Nop.Web/Views/Boards/Topic.cshtml | 1 + .../Nop.Web/Views/Customer/Addresses.cshtml | 1 + .../Nop.Web/Views/Customer/Avatar.cshtml | 2 +- .../Product/_ProductEstimateShipping.cshtml | 18 +- .../Components/EuCookieLaw/Default.cshtml | 3 +- .../Components/NewsletterBox/Default.cshtml | 1 + .../Default.cshtml | 8 +- .../Views/Shared/_AddressAttributes.cshtml | 2 +- .../Nop.Web/Views/Shared/_Poll.cshtml | 1 + .../Nop.Web/Views/Shared/_Root.Head.cshtml | 1 - .../Nop.Web/Views/Vendor/ApplyVendor.cshtml | 2 +- .../Nop.Web/Views/Vendor/Info.cshtml | 2 +- .../wwwroot/js/public.billingaddress.js | 22 +- .../wwwroot/js/public.onepagecheckout.js | 60 +- src/Tests/Nop.Tests/BaseNopTest.cs | 5 - .../Caching/DistributedCacheManagerTests.cs | 53 -- 96 files changed, 1799 insertions(+), 642 deletions(-) delete mode 100644 src/Plugins/Nop.Plugin.Payments.PayPalCommerce/PayPal/Checkout/VoidRequest.cs create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/Controllers/ShipStationController.cs create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/Infrastructure/NopStartup.cs create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/Models/ShipStationModel.cs create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/Nop.Plugin.Shipping.ShipStation.csproj create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/Notes.txt create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/PackingType.cs create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/RouteProvider.cs create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/ServiceType.cs create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/Services/IShipStationService.cs create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/Services/ShipStationService.cs create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/ShipStationComputationMethod.cs create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/ShipStationServiceRate.cs create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/ShipStationSettings.cs create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/Views/Configure.cshtml create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/Views/_ViewImports.cshtml create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/logo.png create mode 100644 src/Plugins/Nop.Plugin.Shipping.ShipStation/plugin.json delete mode 100644 src/Tests/Nop.Tests/Nop.Core.Tests/Caching/DistributedCacheManagerTests.cs diff --git a/Dockerfile b/Dockerfile index 7fd930c..ae8270e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -39,6 +39,8 @@ WORKDIR /src/Plugins/Nop.Plugin.Shipping.EasyPost RUN dotnet build Nop.Plugin.Shipping.EasyPost.csproj -c Release WORKDIR /src/Plugins/Nop.Plugin.Shipping.FixedByWeightByTotal RUN dotnet build Nop.Plugin.Shipping.FixedByWeightByTotal.csproj -c Release +WORKDIR /src/Plugins/Nop.Plugin.Shipping.ShipStation +RUN dotnet build Nop.Plugin.Shipping.ShipStation.csproj -c Release WORKDIR /src/Plugins/Nop.Plugin.Shipping.UPS RUN dotnet build Nop.Plugin.Shipping.UPS.csproj -c Release WORKDIR /src/Plugins/Nop.Plugin.Tax.Avalara @@ -60,24 +62,6 @@ RUN dotnet build Nop.Plugin.Widgets.What3words.csproj -c Release WORKDIR /src/Presentation/Nop.Web RUN dotnet publish Nop.Web.csproj -c Release -o /app/published -WORKDIR /app/published - -RUN mkdir logs -RUN mkdir bin - -RUN chmod 775 App_Data/ -RUN chmod 775 App_Data/DataProtectionKeys -RUN chmod 775 bin -RUN chmod 775 logs -RUN chmod 775 Plugins -RUN chmod 775 wwwroot/bundles -RUN chmod 775 wwwroot/db_backups -RUN chmod 775 wwwroot/files/exportimport -RUN chmod 775 wwwroot/icons -RUN chmod 775 wwwroot/images -RUN chmod 775 wwwroot/images/thumbs -RUN chmod 775 wwwroot/images/uploaded - # create the runtime instance FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine AS runtime @@ -94,7 +78,9 @@ RUN apk add tzdata --no-cache COPY ./entrypoint.sh /entrypoint.sh RUN chmod 755 /entrypoint.sh -WORKDIR /app +WORKDIR /app +RUN mkdir bin +RUN mkdir logs COPY --from=build /app/published . diff --git a/src/Libraries/Nop.Core/Caching/DistributedCacheManager.cs b/src/Libraries/Nop.Core/Caching/DistributedCacheManager.cs index 4d4e1ff..1c88f57 100644 --- a/src/Libraries/Nop.Core/Caching/DistributedCacheManager.cs +++ b/src/Libraries/Nop.Core/Caching/DistributedCacheManager.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -81,9 +80,6 @@ namespace Nop.Core.Caching var item = JsonConvert.DeserializeObject(json); _perRequestCache.Set(key.Key, item); - using var _ = await _locker.LockAsync(); - _keys.Add(key.Key); - return (true, item); } @@ -103,9 +99,6 @@ namespace Nop.Core.Caching var item = JsonConvert.DeserializeObject(json); _perRequestCache.Set(key.Key, item); - using var _ = _locker.Lock(); - _keys.Add(key.Key); - return (true, item); } diff --git a/src/Libraries/Nop.Core/Configuration/NopConfigurationDefaults.cs b/src/Libraries/Nop.Core/Configuration/NopConfigurationDefaults.cs index 38bf61f..ea609ce 100644 --- a/src/Libraries/Nop.Core/Configuration/NopConfigurationDefaults.cs +++ b/src/Libraries/Nop.Core/Configuration/NopConfigurationDefaults.cs @@ -9,11 +9,5 @@ /// Gets the path to file that contains app settings /// public static string AppSettingsFilePath => "App_Data/appsettings.json"; - - /// - /// Gets the path to file that contains app settings for specific hosting environment - /// - /// 0 - Environment name - public static string AppSettingsEnvironmentFilePath => "App_Data/appsettings.{0}.json"; } } \ No newline at end of file diff --git a/src/Libraries/Nop.Core/NopVersion.cs b/src/Libraries/Nop.Core/NopVersion.cs index 36a71e2..ccdb2ec 100644 --- a/src/Libraries/Nop.Core/NopVersion.cs +++ b/src/Libraries/Nop.Core/NopVersion.cs @@ -13,7 +13,7 @@ /// /// Gets the minor store version /// - public const string MINOR_VERSION = "4"; + public const string MINOR_VERSION = "0"; /// /// Gets the full store version diff --git a/src/Libraries/Nop.Data/Migrations/MigrationManager.cs b/src/Libraries/Nop.Data/Migrations/MigrationManager.cs index 912f3ce..38b5509 100644 --- a/src/Libraries/Nop.Data/Migrations/MigrationManager.cs +++ b/src/Libraries/Nop.Data/Migrations/MigrationManager.cs @@ -42,20 +42,19 @@ namespace Nop.Data.Migrations #endregion #region Utils - + /// - /// Returns the instances for found types implementing FluentMigrator.IMigration which ready to Up process + /// Returns the instances for found types implementing FluentMigrator.IMigration /// /// Assembly to find migrations - /// Type of migration process; pass MigrationProcessType.NoMatter to load all migrations + /// Type of migration process; pass null to load all migrations /// The instances for found types implementing FluentMigrator.IMigration - protected virtual IEnumerable GetUpMigrations(Assembly assembly, MigrationProcessType migrationProcessType = MigrationProcessType.NoMatter) + protected virtual IEnumerable GetMigrations(Assembly assembly, MigrationProcessType migrationProcessType = MigrationProcessType.NoMatter) { var migrations = _filteringMigrationSource .GetMigrations(t => { var migrationAttribute = t.GetCustomAttribute(); - if (migrationAttribute is null || _versionLoader.Value.VersionInfo.HasAppliedMigration(migrationAttribute.Version)) return false; @@ -65,40 +64,12 @@ namespace Nop.Data.Migrations return false; return assembly == null || t.Assembly == assembly; - - }) ?? Enumerable.Empty(); - - return migrations - .Select(m => _migrationRunnerConventions.GetMigrationInfoForMigration(m)) - .OrderBy(migration => migration.Version); - } - - /// - /// Returns the instances for found types implementing FluentMigrator.IMigration which ready to Down process - /// - /// Assembly to find migrations - /// Type of migration process; pass MigrationProcessType.NoMatter to load all migrations - /// The instances for found types implementing FluentMigrator.IMigration - protected virtual IEnumerable GetDownMigrations(Assembly assembly, MigrationProcessType migrationProcessType = MigrationProcessType.NoMatter) - { - var migrations = _filteringMigrationSource - .GetMigrations(t => - { - var migrationAttribute = t.GetCustomAttribute(); - - if (migrationAttribute is null || !_versionLoader.Value.VersionInfo.HasAppliedMigration(migrationAttribute.Version)) - return false; - - if (migrationAttribute.TargetMigrationProcess != MigrationProcessType.NoMatter && - migrationProcessType != MigrationProcessType.NoMatter && - migrationProcessType != migrationAttribute.TargetMigrationProcess) - return false; - - return assembly == null || t.Assembly == assembly; }) ?? Enumerable.Empty(); return migrations .Select(m => _migrationRunnerConventions.GetMigrationInfoForMigration(m)) + //.OrderBy(m => m.Migration.GetType().GetCustomAttribute().MigrationTarget) + //.ThenBy(migration => migration.Version); .OrderBy(migration => migration.Version); } @@ -116,7 +87,7 @@ namespace Nop.Data.Migrations if (assembly is null) throw new ArgumentNullException(nameof(assembly)); - foreach (var migrationInfo in GetUpMigrations(assembly, migrationProcessType)) + foreach (var migrationInfo in GetMigrations(assembly, migrationProcessType)) { _migrationRunner.Up(migrationInfo.Migration); @@ -131,7 +102,7 @@ namespace Nop.Data.Migrations } /// - /// Executes all found (and applied) migrations + /// Executes all found (and unapplied) migrations /// /// Assembly to find the migration public void ApplyDownMigrations(Assembly assembly) @@ -139,7 +110,9 @@ namespace Nop.Data.Migrations if(assembly is null) throw new ArgumentNullException(nameof(assembly)); - foreach (var migrationInfo in GetDownMigrations(assembly).Reverse()) + var migrations = GetMigrations(assembly).Reverse(); + + foreach (var migrationInfo in migrations) { _migrationRunner.Down(migrationInfo.Migration); _versionLoader.Value.DeleteVersion(migrationInfo.Version); diff --git a/src/Libraries/Nop.Data/Nop.Data.csproj b/src/Libraries/Nop.Data/Nop.Data.csproj index 24c7e2b..cd8b0d2 100644 --- a/src/Libraries/Nop.Data/Nop.Data.csproj +++ b/src/Libraries/Nop.Data/Nop.Data.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/Libraries/Nop.Services/Authentication/External/ExternalAuthenticationService.cs b/src/Libraries/Nop.Services/Authentication/External/ExternalAuthenticationService.cs index b3f1f96..9a071c0 100644 --- a/src/Libraries/Nop.Services/Authentication/External/ExternalAuthenticationService.cs +++ b/src/Libraries/Nop.Services/Authentication/External/ExternalAuthenticationService.cs @@ -4,8 +4,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Routing; using Nop.Core; using Nop.Core.Domain.Customers; using Nop.Core.Domain.Localization; @@ -14,6 +12,7 @@ using Nop.Core.Http.Extensions; using Nop.Data; using Nop.Services.Common; using Nop.Services.Customers; +using Nop.Services.Html; using Nop.Services.Localization; using Nop.Services.Messages; @@ -28,7 +27,6 @@ namespace Nop.Services.Authentication.External private readonly CustomerSettings _customerSettings; private readonly ExternalAuthenticationSettings _externalAuthenticationSettings; - private readonly IActionContextAccessor _actionContextAccessor; private readonly IAuthenticationPluginManager _authenticationPluginManager; private readonly ICustomerRegistrationService _customerRegistrationService; private readonly ICustomerService _customerService; @@ -38,7 +36,6 @@ namespace Nop.Services.Authentication.External private readonly ILocalizationService _localizationService; private readonly IRepository _externalAuthenticationRecordRepository; private readonly IStoreContext _storeContext; - private readonly IUrlHelperFactory _urlHelperFactory; private readonly IWorkContext _workContext; private readonly IWorkflowMessageService _workflowMessageService; private readonly LocalizationSettings _localizationSettings; @@ -49,7 +46,6 @@ namespace Nop.Services.Authentication.External public ExternalAuthenticationService(CustomerSettings customerSettings, ExternalAuthenticationSettings externalAuthenticationSettings, - IActionContextAccessor actionContextAccessor, IAuthenticationPluginManager authenticationPluginManager, ICustomerRegistrationService customerRegistrationService, ICustomerService customerService, @@ -59,14 +55,12 @@ namespace Nop.Services.Authentication.External ILocalizationService localizationService, IRepository externalAuthenticationRecordRepository, IStoreContext storeContext, - IUrlHelperFactory urlHelperFactory, IWorkContext workContext, IWorkflowMessageService workflowMessageService, LocalizationSettings localizationSettings) { _customerSettings = customerSettings; _externalAuthenticationSettings = externalAuthenticationSettings; - _actionContextAccessor = actionContextAccessor; _authenticationPluginManager = authenticationPluginManager; _customerRegistrationService = customerRegistrationService; _customerService = customerService; @@ -76,7 +70,6 @@ namespace Nop.Services.Authentication.External _localizationService = localizationService; _externalAuthenticationRecordRepository = externalAuthenticationRecordRepository; _storeContext = storeContext; - _urlHelperFactory = urlHelperFactory; _workContext = workContext; _workflowMessageService = workflowMessageService; _localizationSettings = localizationSettings; @@ -247,10 +240,8 @@ namespace Nop.Services.Authentication.External /// Result of an authentication protected virtual IActionResult SuccessfulAuthentication(string returnUrl) { - var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext); - //redirect to the return URL if it's specified - if (!string.IsNullOrEmpty(returnUrl) && urlHelper.IsLocalUrl(returnUrl)) + if (!string.IsNullOrEmpty(returnUrl)) return new RedirectResult(returnUrl); return new RedirectToRouteResult("Homepage", null); diff --git a/src/Libraries/Nop.Services/Catalog/CopyProductService.cs b/src/Libraries/Nop.Services/Catalog/CopyProductService.cs index 5062ed6..699758c 100644 --- a/src/Libraries/Nop.Services/Catalog/CopyProductService.cs +++ b/src/Libraries/Nop.Services/Catalog/CopyProductService.cs @@ -5,10 +5,8 @@ using System.Threading.Tasks; using Nop.Core.Domain.Catalog; using Nop.Core.Domain.Discounts; using Nop.Core.Domain.Media; -using Nop.Core.Infrastructure; using Nop.Services.Localization; using Nop.Services.Media; -using Nop.Services.Security; using Nop.Services.Seo; using Nop.Services.Stores; @@ -653,7 +651,6 @@ namespace Nop.Services.Catalog MetaTitle = product.MetaTitle, AllowCustomerReviews = product.AllowCustomerReviews, LimitedToStores = product.LimitedToStores, - SubjectToAcl = product.SubjectToAcl, Sku = newSku, ManufacturerPartNumber = product.ManufacturerPartNumber, Gtin = product.Gtin, @@ -806,18 +803,11 @@ namespace Nop.Services.Catalog await CopyAttributesMappingAsync(product, productCopy, originalNewPictureIdentifiers); //product <-> discounts mapping await CopyDiscountsMappingAsync(product, productCopy); - //store mapping var selectedStoreIds = await _storeMappingService.GetStoresIdsWithAccessAsync(product); foreach (var id in selectedStoreIds) await _storeMappingService.InsertStoreMappingAsync(productCopy, id); - //customer role mapping - var aclService = EngineContext.Current.Resolve(); - var customerRoleIds = await aclService.GetCustomerRoleIdsWithAccessAsync(product); - foreach (var id in customerRoleIds) - await aclService.InsertAclRecordAsync(productCopy, id); - //tier prices await CopyTierPricesAsync(product, productCopy); diff --git a/src/Libraries/Nop.Services/Catalog/NopCatalogDefaults.cs b/src/Libraries/Nop.Services/Catalog/NopCatalogDefaults.cs index 6789324..2e1730f 100644 --- a/src/Libraries/Nop.Services/Catalog/NopCatalogDefaults.cs +++ b/src/Libraries/Nop.Services/Catalog/NopCatalogDefaults.cs @@ -407,7 +407,7 @@ namespace Nop.Services.Catalog /// /// Gets a key pattern to clear cache /// - public static string FilterableSpecificationAttributeOptionsPrefix => "Nop.specificationattributeoption"; + public static string FilterableSpecificationAttributeOptionsPrefix => "Nop.filterablespecificationattributeoptions"; /// /// Gets a key for specification attribute groups caching by product id diff --git a/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs b/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs index 9cea2ad..9dd32e4 100644 --- a/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs +++ b/src/Libraries/Nop.Services/Catalog/PriceCalculationService.cs @@ -465,7 +465,7 @@ namespace Nop.Services.Catalog if (value.PriceAdjustmentUsePercentage) { if (!productPrice.HasValue) - productPrice = (await GetFinalPriceAsync(product, customer)).finalPrice; + productPrice = (await GetFinalPriceAsync(product, customer)).priceWithoutDiscounts; adjustment = (decimal)((float)productPrice * (float)value.PriceAdjustment / 100f); } @@ -479,7 +479,7 @@ namespace Nop.Services.Catalog //bundled product var associatedProduct = await _productService.GetProductByIdAsync(value.AssociatedProductId); if (associatedProduct != null) - adjustment = (await GetFinalPriceAsync(associatedProduct, customer)).finalPrice * value.Quantity; + adjustment = (await GetFinalPriceAsync(associatedProduct, customer)).priceWithoutDiscounts * value.Quantity; break; default: diff --git a/src/Libraries/Nop.Services/Catalog/ProductExtensions.cs b/src/Libraries/Nop.Services/Catalog/ProductExtensions.cs index fa894d8..262b7ce 100644 --- a/src/Libraries/Nop.Services/Catalog/ProductExtensions.cs +++ b/src/Libraries/Nop.Services/Catalog/ProductExtensions.cs @@ -41,16 +41,16 @@ namespace Nop.Services.Catalog keyGroup = localizedProperty.LocaleKeyGroup, key = localizedProperty.LocaleKey } into localizedProperties - from localizedProperty in localizedProperties.DefaultIfEmpty(new LocalizedProperty { LocaleValue = product.Name }) + from localizedProperty in localizedProperties.DefaultIfEmpty(new LocalizedProperty {LocaleValue = product.Name}) select new { localizedProperty, product }; if (orderBy == ProductSortingEnum.NameAsc) productsQuery = from item in query - orderby item.localizedProperty.LocaleValue, item.product.Name + orderby item.localizedProperty.LocaleValue select item.product; else productsQuery = from item in query - orderby item.localizedProperty.LocaleValue descending, item.product.Name descending + orderby item.localizedProperty.LocaleValue descending select item.product; return productsQuery; diff --git a/src/Libraries/Nop.Services/Catalog/ProductService.cs b/src/Libraries/Nop.Services/Catalog/ProductService.cs index 2d2a69a..247e39d 100644 --- a/src/Libraries/Nop.Services/Catalog/ProductService.cs +++ b/src/Libraries/Nop.Services/Catalog/ProductService.cs @@ -872,21 +872,6 @@ namespace Nop.Services.Catalog (searchSku && p.Sku == keywords) select p.Id; - if (searchLocalizedValue) - { - productsByKeywords = productsByKeywords.Union( - from lp in _localizedPropertyRepository.Table - let checkName = lp.LocaleKey == nameof(Product.Name) && - lp.LocaleValue.Contains(keywords) - let checkShortDesc = searchDescriptions && - lp.LocaleKey == nameof(Product.ShortDescription) && - lp.LocaleValue.Contains(keywords) - where - lp.LocaleKeyGroup == nameof(Product) && lp.LanguageId == languageId && (checkName || checkShortDesc) - - select lp.EntityId); - } - //search by SKU for ProductAttributeCombination if (searchSku) { @@ -901,7 +886,7 @@ namespace Nop.Services.Catalog productsByKeywords = productsByKeywords.Union( from pptm in _productTagMappingRepository.Table join pt in _productTagRepository.Table on pptm.ProductTagId equals pt.Id - where pt.Name.Contains(keywords) + where pt.Name == keywords select pptm.ProductId ); @@ -912,12 +897,31 @@ namespace Nop.Services.Catalog join lp in _localizedPropertyRepository.Table on pptm.ProductTagId equals lp.EntityId where lp.LocaleKeyGroup == nameof(ProductTag) && lp.LocaleKey == nameof(ProductTag.Name) && - lp.LocaleValue.Contains(keywords) && - lp.LanguageId == languageId - select pptm.ProductId); + lp.LocaleValue.Contains(keywords) + select lp.EntityId); } } + if (searchLocalizedValue) + { + productsByKeywords = productsByKeywords.Union( + from lp in _localizedPropertyRepository.Table + let checkName = lp.LocaleKey == nameof(Product.Name) && + lp.LocaleValue.Contains(keywords) + let checkShortDesc = searchDescriptions && + lp.LocaleKey == nameof(Product.ShortDescription) && + lp.LocaleValue.Contains(keywords) + let checkProductTags = searchProductTags && + lp.LocaleKeyGroup == nameof(ProductTag) && + lp.LocaleKey == nameof(ProductTag.Name) && + lp.LocaleValue.Contains(keywords) + where + lp.LocaleKeyGroup == nameof(Product) && lp.LanguageId == languageId && (checkName || checkShortDesc) || + checkProductTags + + select lp.EntityId); + } + productsQuery = from p in productsQuery join pbk in productsByKeywords on p.Id equals pbk diff --git a/src/Libraries/Nop.Services/Customers/CustomerRegistrationService.cs b/src/Libraries/Nop.Services/Customers/CustomerRegistrationService.cs index 14ed356..ca5f11c 100644 --- a/src/Libraries/Nop.Services/Customers/CustomerRegistrationService.cs +++ b/src/Libraries/Nop.Services/Customers/CustomerRegistrationService.cs @@ -2,8 +2,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Routing; using Nop.Core; using Nop.Core.Domain.Customers; using Nop.Core.Events; @@ -27,7 +25,6 @@ namespace Nop.Services.Customers #region Fields private readonly CustomerSettings _customerSettings; - private readonly IActionContextAccessor _actionContextAccessor; private readonly IAuthenticationService _authenticationService; private readonly ICustomerActivityService _customerActivityService; private readonly ICustomerService _customerService; @@ -42,7 +39,6 @@ namespace Nop.Services.Customers private readonly IShoppingCartService _shoppingCartService; private readonly IStoreContext _storeContext; private readonly IStoreService _storeService; - private readonly IUrlHelperFactory _urlHelperFactory; private readonly IWorkContext _workContext; private readonly IWorkflowMessageService _workflowMessageService; private readonly RewardPointsSettings _rewardPointsSettings; @@ -52,7 +48,6 @@ namespace Nop.Services.Customers #region Ctor public CustomerRegistrationService(CustomerSettings customerSettings, - IActionContextAccessor actionContextAccessor, IAuthenticationService authenticationService, ICustomerActivityService customerActivityService, ICustomerService customerService, @@ -67,13 +62,11 @@ namespace Nop.Services.Customers IShoppingCartService shoppingCartService, IStoreContext storeContext, IStoreService storeService, - IUrlHelperFactory urlHelperFactory, IWorkContext workContext, IWorkflowMessageService workflowMessageService, RewardPointsSettings rewardPointsSettings) { _customerSettings = customerSettings; - _actionContextAccessor = actionContextAccessor; _authenticationService = authenticationService; _customerActivityService = customerActivityService; _customerService = customerService; @@ -88,7 +81,6 @@ namespace Nop.Services.Customers _shoppingCartService = shoppingCartService; _storeContext = storeContext; _storeService = storeService; - _urlHelperFactory = urlHelperFactory; _workContext = workContext; _workflowMessageService = workflowMessageService; _rewardPointsSettings = rewardPointsSettings; @@ -442,10 +434,8 @@ namespace Nop.Services.Customers await _customerActivityService.InsertActivityAsync(customer, "PublicStore.Login", await _localizationService.GetResourceAsync("ActivityLog.PublicStore.Login"), customer); - var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContextAccessor.ActionContext); - //redirect to the return URL if it's specified - if (!string.IsNullOrEmpty(returnUrl) && urlHelper.IsLocalUrl(returnUrl)) + if (!string.IsNullOrEmpty(returnUrl)) return new RedirectResult(returnUrl); return new RedirectToRouteResult("Homepage", null); diff --git a/src/Libraries/Nop.Services/ExportImport/ImportManager.cs b/src/Libraries/Nop.Services/ExportImport/ImportManager.cs index a990fee..fb6bb31 100644 --- a/src/Libraries/Nop.Services/ExportImport/ImportManager.cs +++ b/src/Libraries/Nop.Services/ExportImport/ImportManager.cs @@ -1659,18 +1659,11 @@ namespace Nop.Services.ExportImport //category mappings var categories = isNew || !allProductsCategoryIds.ContainsKey(product.Id) ? Array.Empty() : allProductsCategoryIds[product.Id]; - var storesIds = product.LimitedToStores - ? (await _storeMappingService.GetStoresIdsWithAccessAsync(product)).ToList() - : new List(); - var importedCategories = await categoryList.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) - .Select(categoryName => new CategoryKey(categoryName, storesIds)) + .Select(categoryName => new CategoryKey(categoryName)) .SelectAwait(async categoryKey => { - var rez = (allCategories.ContainsKey(categoryKey) ? allCategories[categoryKey].Id : allCategories.Values.FirstOrDefault(c => c.Name == categoryKey.Key)?.Id) ?? - allCategories.FirstOrDefault(p => - p.Key.Key.Equals(categoryKey.Key, StringComparison.InvariantCultureIgnoreCase)) - .Value?.Id; + var rez = allCategories.ContainsKey(categoryKey) ? allCategories[categoryKey].Id : allCategories.Values.FirstOrDefault(c => c.Name == categoryKey.Key)?.Id; if (!rez.HasValue && int.TryParse(categoryKey.Key, out var id)) rez = id; diff --git a/src/Libraries/Nop.Services/Html/BBCodeHelper.cs b/src/Libraries/Nop.Services/Html/BBCodeHelper.cs index 523f32c..e197f78 100644 --- a/src/Libraries/Nop.Services/Html/BBCodeHelper.cs +++ b/src/Libraries/Nop.Services/Html/BBCodeHelper.cs @@ -29,8 +29,8 @@ namespace Nop.Services.Html private static readonly Regex regexBold = new(@"\[b\](.+?)\[/b\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex regexItalic = new(@"\[i\](.+?)\[/i\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex regexUnderLine = new(@"\[u\](.+?)\[/u\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); - private static readonly Regex regexUrl1 = new(@"\[url\=(https?:.+?)\]([^\]]+)\[/url\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); - private static readonly Regex regexUrl2 = new(@"\[url\](https?:.+?)\[/url\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex regexUrl1 = new(@"\[url\=([^\]]+)\]([^\]]+)\[/url\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex regexUrl2 = new(@"\[url\](.+?)\[/url\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex regexQuote = new(@"\[quote=(.+?)\](.+?)\[/quote\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static readonly Regex regexImg = new(@"\[img\](.+?)\[/img\]", RegexOptions.Compiled | RegexOptions.IgnoreCase); diff --git a/src/Libraries/Nop.Services/Media/RoxyFileman/FileRoxyFilemanService.cs b/src/Libraries/Nop.Services/Media/RoxyFileman/FileRoxyFilemanService.cs index 9d15c51..cb154e1 100644 --- a/src/Libraries/Nop.Services/Media/RoxyFileman/FileRoxyFilemanService.cs +++ b/src/Libraries/Nop.Services/Media/RoxyFileman/FileRoxyFilemanService.cs @@ -543,8 +543,7 @@ namespace Nop.Services.Media.RoxyFileman if (GetFileType(_fileProvider.GetFileExtension(files[i])) == "image") { await using var stream = new FileStream(physicalPath, FileMode.Open); - var skData = SKData.Create(stream); - var image = SKBitmap.DecodeBounds(skData); + var image = SKBitmap.DecodeBounds(stream); width = image.Width; height = image.Height; } diff --git a/src/Libraries/Nop.Services/Orders/CheckoutAttributeFormatter.cs b/src/Libraries/Nop.Services/Orders/CheckoutAttributeFormatter.cs index c5d4cf3..4e0fb5f 100644 --- a/src/Libraries/Nop.Services/Orders/CheckoutAttributeFormatter.cs +++ b/src/Libraries/Nop.Services/Orders/CheckoutAttributeFormatter.cs @@ -84,7 +84,7 @@ namespace Nop.Services.Orders bool allowHyperlinks = true) { var result = new StringBuilder(); - var currentLanguage = await _workContext.GetWorkingLanguageAsync(); + var currentLanguage = _workContext.GetWorkingLanguageAsync(); var attributes = await _checkoutAttributeParser.ParseCheckoutAttributesAsync(attributesXml); for (var i = 0; i < attributes.Count; i++) { diff --git a/src/Libraries/Nop.Services/Orders/IOrderReportService.cs b/src/Libraries/Nop.Services/Orders/IOrderReportService.cs index 4eee870..577af21 100644 --- a/src/Libraries/Nop.Services/Orders/IOrderReportService.cs +++ b/src/Libraries/Nop.Services/Orders/IOrderReportService.cs @@ -74,11 +74,11 @@ namespace Nop.Services.Orders /// /// Get sales summary report /// + /// Store identifier (orders placed in a specific store); 0 to load all records + /// Vendor identifier; 0 to load all records /// Category identifier; 0 to load all records /// Product identifier; 0 to load all records /// Manufacturer identifier; 0 to load all records - /// Store identifier (orders placed in a specific store); 0 to load all records - /// Vendor identifier; 0 to load all records /// Order created date from (UTC); null to load all records /// Order created date to (UTC); null to load all records /// Order status; null to load all records diff --git a/src/Libraries/Nop.Services/Orders/OrderReportService.cs b/src/Libraries/Nop.Services/Orders/OrderReportService.cs index 00b00e4..7506fda 100644 --- a/src/Libraries/Nop.Services/Orders/OrderReportService.cs +++ b/src/Libraries/Nop.Services/Orders/OrderReportService.cs @@ -269,26 +269,18 @@ namespace Nop.Services.Orders query = query.Where(o => o.Id == orderId); if (vendorId > 0) - { query = from o in query join oi in _orderItemRepository.Table on o.Id equals oi.OrderId join p in _productRepository.Table on oi.ProductId equals p.Id where p.VendorId == vendorId select o; - query = query.Distinct(); - } - if (productId > 0) - { query = from o in query join oi in _orderItemRepository.Table on o.Id equals oi.OrderId where oi.ProductId == productId select o; - query = query.Distinct(); - } - if (warehouseId > 0) { var manageStockInventoryMethodId = (int)ManageInventoryMethod.ManageStock; @@ -305,8 +297,6 @@ namespace Nop.Services.Orders //we use standard "warehouse" property ((p.ManageInventoryMethodId != manageStockInventoryMethodId || !p.UseMultipleWarehouses) && p.WarehouseId == warehouseId) select o; - - query = query.Distinct(); } query = from o in query @@ -337,15 +327,11 @@ namespace Nop.Services.Orders query = query.Where(o => endTimeUtc.Value >= o.CreatedOnUtc); if (!string.IsNullOrEmpty(orderNotes)) - { query = from o in query join n in _orderNoteRepository.Table on o.Id equals n.OrderId where n.Note.Contains(orderNotes) select o; - query.Distinct(); - } - var item = await (from oq in query group oq by 1 into result @@ -448,11 +434,11 @@ namespace Nop.Services.Orders /// /// Get sales summary report /// + /// Store identifier (orders placed in a specific store); 0 to load all records + /// Vendor identifier; 0 to load all records /// Category identifier; 0 to load all records /// Product identifier; 0 to load all records /// Manufacturer identifier; 0 to load all records - /// Store identifier (orders placed in a specific store); 0 to load all records - /// Vendor identifier; 0 to load all records /// Order created date from (UTC); null to load all records /// Order created date to (UTC); null to load all records /// Order status; null to load all records @@ -545,15 +531,6 @@ namespace Nop.Services.Orders if (storeId > 0) query = query.Where(o => o.StoreId == storeId); - if (vendorId > 0) - { - query = from o in query - join oi in _orderItemRepository.Table on o.Id equals oi.OrderId - join p in _productRepository.Table on oi.ProductId equals p.Id - where p.VendorId == vendorId - select o; - } - var primaryStoreCurrency = await _currencyService.GetCurrencyByIdAsync(_currencySettings.PrimaryStoreCurrencyId); var items = groupBy switch diff --git a/src/Libraries/Nop.Services/Orders/ShoppingCartService.cs b/src/Libraries/Nop.Services/Orders/ShoppingCartService.cs index 95ad7f8..63e7726 100644 --- a/src/Libraries/Nop.Services/Orders/ShoppingCartService.cs +++ b/src/Libraries/Nop.Services/Orders/ShoppingCartService.cs @@ -264,34 +264,24 @@ namespace Nop.Services.Orders continue; //prepare warning message - var url = urlHelper.RouteUrl(nameof(Product), new { SeName = await _urlRecordService.GetSeNameAsync(requiredProduct) }); var requiredProductName = WebUtility.HtmlEncode(await _localizationService.GetLocalizedAsync(requiredProduct, x => x.Name)); var requiredProductWarning = _catalogSettings.UseLinksInRequiredProductWarnings - ? string.Format(warningLocale, $"{requiredProductName}", requiredProductRequiredQuantity) + ? string.Format(warningLocale, $"{requiredProductName}", requiredProductRequiredQuantity) : string.Format(warningLocale, requiredProductName, requiredProductRequiredQuantity); //add to cart (if possible) if (addRequiredProducts && product.AutomaticallyAddRequiredProducts) { //do not add required products to prevent circular references - var addToCartWarnings = await GetShoppingCartItemWarningsAsync( - customer: customer, - product: requiredProduct, - attributesXml: null, - customerEnteredPrice: decimal.Zero, - shoppingCartType: shoppingCartType, - storeId: storeId, - quantity: quantityToAdd, - addRequiredProducts: true); + var addToCartWarnings = await AddToCartAsync(customer, requiredProduct, shoppingCartType, storeId, + quantity: quantityToAdd, addRequiredProducts: false); //don't display all specific errors only the generic one if (addToCartWarnings.Any()) warnings.Add(requiredProductWarning); } else - { warnings.Add(requiredProductWarning); - } } return warnings; @@ -1565,11 +1555,6 @@ namespace Nop.Services.Orders customerEnteredPrice, rentalStartDate, rentalEndDate, newQuantity, addRequiredProducts, shoppingCartItem.Id)); - if (warnings.Any()) - return warnings; - - await addRequiredProductsToCartAsync(); - if (warnings.Any()) return warnings; @@ -1587,11 +1572,6 @@ namespace Nop.Services.Orders rentalStartDate, rentalEndDate, quantity, addRequiredProducts)); - if (warnings.Any()) - return warnings; - - await addRequiredProductsToCartAsync(); - if (warnings.Any()) return warnings; @@ -1643,43 +1623,6 @@ namespace Nop.Services.Orders } return warnings; - - async Task addRequiredProductsToCartAsync() - { - //get these required products - var requiredProducts = await _productService.GetProductsByIdsAsync(_productService.ParseRequiredProductIds(product)); - if (!requiredProducts.Any()) - return; - - foreach (var requiredProduct in requiredProducts) - { - var productsRequiringRequiredProduct = await GetProductsRequiringProductAsync(cart, requiredProduct); - - //get the required quantity of the required product - var requiredProductRequiredQuantity = quantity + - cart.Where(ci => productsRequiringRequiredProduct.Any(p => p.Id == ci.ProductId)) - .Where(item => item.Id != (shoppingCartItem?.Id ?? 0)) - .Sum(item => item.Quantity); - - //whether required product is already in the cart in the required quantity - var quantityToAdd = requiredProductRequiredQuantity - (cart.FirstOrDefault(item => item.ProductId == requiredProduct.Id)?.Quantity ?? 0); - if (quantityToAdd <= 0) - continue; - - if (addRequiredProducts && product.AutomaticallyAddRequiredProducts) - { - //do not add required products to prevent circular references - var addToCartWarnings = await AddToCartAsync(customer, requiredProduct, shoppingCartType, storeId, - quantity: quantityToAdd, addRequiredProducts: requiredProduct.AutomaticallyAddRequiredProducts); - - if (addToCartWarnings.Any()) - { - warnings.AddRange(addToCartWarnings); - return; - } - } - } - } } /// diff --git a/src/Libraries/Nop.Services/ScheduleTasks/TaskScheduler.cs b/src/Libraries/Nop.Services/ScheduleTasks/TaskScheduler.cs index 23a05a5..431b267 100644 --- a/src/Libraries/Nop.Services/ScheduleTasks/TaskScheduler.cs +++ b/src/Libraries/Nop.Services/ScheduleTasks/TaskScheduler.cs @@ -229,7 +229,7 @@ namespace Nop.Services.ScheduleTasks } finally { - if (!_disposed && _timer != null) + if (!_disposed) { if (RunOnlyOnce) Dispose(); diff --git a/src/Libraries/Nop.Services/Seo/SitemapGenerator.cs b/src/Libraries/Nop.Services/Seo/SitemapGenerator.cs index 1682de2..8165ee7 100644 --- a/src/Libraries/Nop.Services/Seo/SitemapGenerator.cs +++ b/src/Libraries/Nop.Services/Seo/SitemapGenerator.cs @@ -535,11 +535,9 @@ namespace Nop.Services.Seo getRouteParamsAwait != null ? await getRouteParamsAwait(null) : null, await GetHttpProtocolAsync()); - var store = await _storeContext.GetCurrentStoreAsync(); - var updatedOn = dateTimeUpdatedOn ?? DateTime.UtcNow; var languages = _localizationSettings.SeoFriendlyUrlsForLanguagesEnabled - ? await _languageService.GetAllLanguagesAsync(storeId: store.Id) + ? await _languageService.GetAllLanguagesAsync() : null; if (languages == null) diff --git a/src/Libraries/Nop.Services/Shipping/ShipmentService.cs b/src/Libraries/Nop.Services/Shipping/ShipmentService.cs index 0b05054..ad91bcc 100644 --- a/src/Libraries/Nop.Services/Shipping/ShipmentService.cs +++ b/src/Libraries/Nop.Services/Shipping/ShipmentService.cs @@ -410,26 +410,18 @@ namespace Nop.Services.Shipping public virtual async Task GetShipmentTrackerAsync(Shipment shipment) { var order = await _orderRepository.GetByIdAsync(shipment.OrderId, cache => default); - IShipmentTracker shipmentTracker = null; - if (order.PickupInStore) - { - var pickupPointProvider = await _pickupPluginManager - .LoadPluginBySystemNameAsync(order.ShippingRateComputationMethodSystemName); - - if (pickupPointProvider != null) - shipmentTracker = await pickupPointProvider.GetShipmentTrackerAsync(); - } - else + if (!order.PickupInStore) { var shippingRateComputationMethod = await _shippingPluginManager .LoadPluginBySystemNameAsync(order.ShippingRateComputationMethodSystemName); - if (shippingRateComputationMethod != null) - shipmentTracker = await shippingRateComputationMethod.GetShipmentTrackerAsync(); + return await shippingRateComputationMethod?.GetShipmentTrackerAsync(); } - return shipmentTracker; + var pickupPointProvider = await _pickupPluginManager + .LoadPluginBySystemNameAsync(order.ShippingRateComputationMethodSystemName); + return await pickupPointProvider?.GetShipmentTrackerAsync(); } #endregion diff --git a/src/NopCommerce.sln b/src/NopCommerce.sln index c5ca3bd..120da81 100644 --- a/src/NopCommerce.sln +++ b/src/NopCommerce.sln @@ -47,6 +47,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nop.Plugin.Misc.Sendinblue" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nop.Plugin.Tax.Avalara", "Plugins\Nop.Plugin.Tax.Avalara\Nop.Plugin.Tax.Avalara.csproj", "{2426B75D-23D4-41EF-8924-47650C8A18C7}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nop.Plugin.Shipping.ShipStation", "Plugins\Nop.Plugin.Shipping.ShipStation\Nop.Plugin.Shipping.ShipStation.csproj", "{83F05E8D-2D6C-41FC-A95C-9621ED358615}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nop.Plugin.Widgets.FacebookPixel", "Plugins\Nop.Plugin.Widgets.FacebookPixel\Nop.Plugin.Widgets.FacebookPixel.csproj", "{87013DC8-D032-465B-A950-753E4EB15502}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nop.Plugin.MultiFactorAuth.GoogleAuthenticator", "Plugins\Nop.Plugin.MultiFactorAuth.GoogleAuthenticator\Nop.Plugin.MultiFactorAuth.GoogleAuthenticator.csproj", "{F9F61CE0-6FC0-43A5-930A-A27099F4DE44}" @@ -279,6 +281,18 @@ Global {2426B75D-23D4-41EF-8924-47650C8A18C7}.Release|Mixed Platforms.Build.0 = Release|Any CPU {2426B75D-23D4-41EF-8924-47650C8A18C7}.Release|x86.ActiveCfg = Release|Any CPU {2426B75D-23D4-41EF-8924-47650C8A18C7}.Release|x86.Build.0 = Release|Any CPU + {83F05E8D-2D6C-41FC-A95C-9621ED358615}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {83F05E8D-2D6C-41FC-A95C-9621ED358615}.Debug|Any CPU.Build.0 = Debug|Any CPU + {83F05E8D-2D6C-41FC-A95C-9621ED358615}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {83F05E8D-2D6C-41FC-A95C-9621ED358615}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {83F05E8D-2D6C-41FC-A95C-9621ED358615}.Debug|x86.ActiveCfg = Debug|Any CPU + {83F05E8D-2D6C-41FC-A95C-9621ED358615}.Debug|x86.Build.0 = Debug|Any CPU + {83F05E8D-2D6C-41FC-A95C-9621ED358615}.Release|Any CPU.ActiveCfg = Release|Any CPU + {83F05E8D-2D6C-41FC-A95C-9621ED358615}.Release|Any CPU.Build.0 = Release|Any CPU + {83F05E8D-2D6C-41FC-A95C-9621ED358615}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {83F05E8D-2D6C-41FC-A95C-9621ED358615}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {83F05E8D-2D6C-41FC-A95C-9621ED358615}.Release|x86.ActiveCfg = Release|Any CPU + {83F05E8D-2D6C-41FC-A95C-9621ED358615}.Release|x86.Build.0 = Release|Any CPU {87013DC8-D032-465B-A950-753E4EB15502}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {87013DC8-D032-465B-A950-753E4EB15502}.Debug|Any CPU.Build.0 = Debug|Any CPU {87013DC8-D032-465B-A950-753E4EB15502}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -399,6 +413,7 @@ Global {4C0C889A-A3B2-43FA-824A-F529402C1BBF} = {7881B112-7843-4542-B1F7-F99553FB9BB7} {7B08DACB-8E11-416D-9DDF-B0EEF847A8ED} = {7881B112-7843-4542-B1F7-F99553FB9BB7} {2426B75D-23D4-41EF-8924-47650C8A18C7} = {7881B112-7843-4542-B1F7-F99553FB9BB7} + {83F05E8D-2D6C-41FC-A95C-9621ED358615} = {7881B112-7843-4542-B1F7-F99553FB9BB7} {87013DC8-D032-465B-A950-753E4EB15502} = {7881B112-7843-4542-B1F7-F99553FB9BB7} {F9F61CE0-6FC0-43A5-930A-A27099F4DE44} = {7881B112-7843-4542-B1F7-F99553FB9BB7} {FD31C133-B4DE-4BB2-B327-1D0E2F2C95D4} = {E8FC6874-E230-468A-9685-4747354B92FF} diff --git a/src/Plugins/Nop.Plugin.MultiFactorAuth.GoogleAuthenticator/Views/Configure.cshtml b/src/Plugins/Nop.Plugin.MultiFactorAuth.GoogleAuthenticator/Views/Configure.cshtml index 09de209..0386325 100644 --- a/src/Plugins/Nop.Plugin.MultiFactorAuth.GoogleAuthenticator/Views/Configure.cshtml +++ b/src/Plugins/Nop.Plugin.MultiFactorAuth.GoogleAuthenticator/Views/Configure.cshtml @@ -8,7 +8,7 @@
-
+

@@ -102,6 +102,5 @@ })

-
\ No newline at end of file diff --git a/src/Plugins/Nop.Plugin.MultiFactorAuth.GoogleAuthenticator/plugin.json b/src/Plugins/Nop.Plugin.MultiFactorAuth.GoogleAuthenticator/plugin.json index e0417f0..1fb49c3 100644 --- a/src/Plugins/Nop.Plugin.MultiFactorAuth.GoogleAuthenticator/plugin.json +++ b/src/Plugins/Nop.Plugin.MultiFactorAuth.GoogleAuthenticator/plugin.json @@ -2,7 +2,7 @@ "Group": "Multi-factor authentication", "FriendlyName": "Google Authenticator", "SystemName": "MultiFactorAuth.GoogleAuthenticator", - "Version": "1.11", + "Version": "1.10", "SupportedVersions": [ "4.50" ], "Author": "nopCommerce team", "DisplayOrder": 1, diff --git a/src/Plugins/Nop.Plugin.Payments.PayPalCommerce/PayPal/Checkout/VoidRequest.cs b/src/Plugins/Nop.Plugin.Payments.PayPalCommerce/PayPal/Checkout/VoidRequest.cs deleted file mode 100644 index 1b55d89..0000000 --- a/src/Plugins/Nop.Plugin.Payments.PayPalCommerce/PayPal/Checkout/VoidRequest.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Net.Http; - -namespace PayPalCheckoutSdk.Payments -{ - /// - /// Voids, or cancels, an authorized payment, by ID. You cannot void an authorized payment that has been fully captured. - /// - public class VoidRequest : PayPalHttp.HttpRequest - { - public VoidRequest(string authorizationId) : base("/v2/payments/authorizations/{authorization_id}/void?", HttpMethod.Post, typeof(Authorization)) - { - try - { - Path = Path.Replace("{authorization_id}", Uri.EscapeDataString(Convert.ToString(authorizationId))); - } - catch { } - - ContentType = "application/json"; - } - - } -} \ No newline at end of file diff --git a/src/Plugins/Nop.Plugin.Payments.PayPalCommerce/PayPalCommerceDefaults.cs b/src/Plugins/Nop.Plugin.Payments.PayPalCommerce/PayPalCommerceDefaults.cs index 1f8436f..f3ed9c3 100644 --- a/src/Plugins/Nop.Plugin.Payments.PayPalCommerce/PayPalCommerceDefaults.cs +++ b/src/Plugins/Nop.Plugin.Payments.PayPalCommerce/PayPalCommerceDefaults.cs @@ -78,12 +78,6 @@ namespace Nop.Plugin.Payments.PayPalCommerce "PAYMENT.CAPTURE.REFUNDED" }; - /// - /// Gets a list of currencies that do not support decimals. - /// Refer to https://developer.paypal.com/docs/integration/direct/rest/currency-codes/ for more information - /// - public static List CurrenciesWithoutDecimals => new() { "HUF", "JPY", "TWD" }; - /// /// Gets a name of the view component to display payment info in public store /// diff --git a/src/Plugins/Nop.Plugin.Payments.PayPalCommerce/Services/ServiceManager.cs b/src/Plugins/Nop.Plugin.Payments.PayPalCommerce/Services/ServiceManager.cs index 893b575..ab12efa 100644 --- a/src/Plugins/Nop.Plugin.Payments.PayPalCommerce/Services/ServiceManager.cs +++ b/src/Plugins/Nop.Plugin.Payments.PayPalCommerce/Services/ServiceManager.cs @@ -427,12 +427,6 @@ namespace Nop.Plugin.Payments.PayPalCommerce.Services }; } - PayPalCheckoutSdk.Orders.Money prepareMoney(decimal value) => new() - { - CurrencyCode = currency, - Value = value.ToString(PayPalCommerceDefaults.CurrenciesWithoutDecimals.Contains(currency.ToUpperInvariant()) ? "0" : "0.00", CultureInfo.InvariantCulture) - }; - //set order items purchaseUnit.Items = await shoppingCart.SelectAwait(async item => { @@ -448,7 +442,7 @@ namespace Nop.Plugin.Payments.PayPalCommerce.Services Quantity = item.Quantity.ToString(), Category = (product.IsDownload ? ItemCategoryType.Digital_goods : ItemCategoryType.Physical_goods) .ToString().ToUpperInvariant(), - UnitAmount = prepareMoney(itemPrice) + UnitAmount = new PayPalCheckoutSdk.Orders.Money { CurrencyCode = currency, Value = itemPrice.ToString("0.00", CultureInfo.InvariantCulture) } }; }).ToListAsync(); @@ -466,7 +460,7 @@ namespace Nop.Plugin.Payments.PayPalCommerce.Services Name = CommonHelper.EnsureMaximumLength(attribute.Name, 127), Description = CommonHelper.EnsureMaximumLength($"{attribute.Name} - {attributeValue.Name}", 127), Quantity = 1.ToString(), - UnitAmount = prepareMoney(attributePrice) + UnitAmount = new PayPalCheckoutSdk.Orders.Money { CurrencyCode = currency, Value = attributePrice.ToString("0.00", CultureInfo.InvariantCulture) } }); } } @@ -486,13 +480,13 @@ namespace Nop.Plugin.Payments.PayPalCommerce.Services purchaseUnit.AmountWithBreakdown = new AmountWithBreakdown { CurrencyCode = currency, - Value = prepareMoney(orderTotal).Value, + Value = orderTotal.ToString("0.00", CultureInfo.InvariantCulture), AmountBreakdown = new AmountBreakdown { - ItemTotal = prepareMoney(itemTotal), - TaxTotal = prepareMoney(taxTotal), - Shipping = prepareMoney(shippingTotal), - Discount = prepareMoney(discountTotal) + ItemTotal = new PayPalCheckoutSdk.Orders.Money { CurrencyCode = currency, Value = itemTotal.ToString("0.00", CultureInfo.InvariantCulture) }, + TaxTotal = new PayPalCheckoutSdk.Orders.Money { CurrencyCode = currency, Value = taxTotal.ToString("0.00", CultureInfo.InvariantCulture) }, + Shipping = new PayPalCheckoutSdk.Orders.Money { CurrencyCode = currency, Value = shippingTotal.ToString("0.00", CultureInfo.InvariantCulture) }, + Discount = new PayPalCheckoutSdk.Orders.Money { CurrencyCode = currency, Value = discountTotal.ToString("0.00", CultureInfo.InvariantCulture) } } }; @@ -590,9 +584,9 @@ namespace Nop.Plugin.Payments.PayPalCommerce.Services if (!IsConfigured(settings)) throw new NopException("Plugin not configured"); - var request = new VoidRequest(authorizationId); + var request = new AuthorizationsVoidRequest(authorizationId); - return await HandleCheckoutRequestAsync(settings, request); + return await HandleCheckoutRequestAsync(settings, request); }); } @@ -622,7 +616,7 @@ namespace Nop.Plugin.Payments.PayPalCommerce.Services refundRequest.Amount = new PayPalCheckoutSdk.Payments.Money { CurrencyCode = currency, - Value = amount.Value.ToString(PayPalCommerceDefaults.CurrenciesWithoutDecimals.Contains(currency.ToUpperInvariant()) ? "0" : "0.00", CultureInfo.InvariantCulture) + Value = amount.Value.ToString("0.00", CultureInfo.InvariantCulture) }; } var request = new CapturesRefundRequest(captureId).RequestBody(refundRequest); diff --git a/src/Plugins/Nop.Plugin.Payments.PayPalCommerce/plugin.json b/src/Plugins/Nop.Plugin.Payments.PayPalCommerce/plugin.json index cf9d592..ed71e38 100644 --- a/src/Plugins/Nop.Plugin.Payments.PayPalCommerce/plugin.json +++ b/src/Plugins/Nop.Plugin.Payments.PayPalCommerce/plugin.json @@ -2,7 +2,7 @@ "Group": "Payment methods", "FriendlyName": "PayPal Commerce", "SystemName": "Payments.PayPalCommerce", - "Version": "1.10.2", + "Version": "1.10", "SupportedVersions": [ "4.50" ], "Author": "nopCommerce team", "DisplayOrder": -1, diff --git a/src/Plugins/Nop.Plugin.Shipping.EasyPost/Services/EasyPostService.cs b/src/Plugins/Nop.Plugin.Shipping.EasyPost/Services/EasyPostService.cs index 9b41126..a4184ae 100644 --- a/src/Plugins/Nop.Plugin.Shipping.EasyPost/Services/EasyPostService.cs +++ b/src/Plugins/Nop.Plugin.Shipping.EasyPost/Services/EasyPostService.cs @@ -767,10 +767,7 @@ namespace Nop.Plugin.Shipping.EasyPost.Services if (order is null) throw new ArgumentNullException(nameof(order)); - if (order.ShippingRateComputationMethodSystemName != EasyPostDefaults.SystemName) - return false; - - //move the shipment from the customer to the order entry + //move the shipment if from the customer to the order entry var customer = await _customerService.GetCustomerByIdAsync(order.CustomerId); var shipmentId = await _genericAttributeService .GetAttributeAsync(customer, EasyPostDefaults.ShipmentIdAttribute, order.StoreId); diff --git a/src/Plugins/Nop.Plugin.Shipping.EasyPost/plugin.json b/src/Plugins/Nop.Plugin.Shipping.EasyPost/plugin.json index 89c5fff..c610272 100644 --- a/src/Plugins/Nop.Plugin.Shipping.EasyPost/plugin.json +++ b/src/Plugins/Nop.Plugin.Shipping.EasyPost/plugin.json @@ -2,7 +2,7 @@ "Group": "Shipping rate computation", "FriendlyName": "EasyPost", "SystemName": "Shipping.EasyPost", - "Version": "1.13", + "Version": "1.12", "SupportedVersions": [ "4.50" ], "Author": "nopCommerce team", "DisplayOrder": 1, diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/Controllers/ShipStationController.cs b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Controllers/ShipStationController.cs new file mode 100644 index 0000000..91cab0c --- /dev/null +++ b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Controllers/ShipStationController.cs @@ -0,0 +1,163 @@ +using System; +using System.Globalization; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Nop.Core; +using Nop.Plugin.Shipping.ShipStation.Models; +using Nop.Plugin.Shipping.ShipStation.Services; +using Nop.Services; +using Nop.Services.Configuration; +using Nop.Services.Localization; +using Nop.Services.Messages; +using Nop.Web.Framework; +using Nop.Web.Framework.Controllers; +using Nop.Web.Framework.Mvc.Filters; + +namespace Nop.Plugin.Shipping.ShipStation.Controllers +{ + public class ShipStationController : BasePluginController + { + private readonly ILocalizationService _localizationService; + private readonly INotificationService _notificationService; + private readonly ISettingService _settingService; + private readonly IShipStationService _shipStationService; + private readonly IStoreContext _storeContext; + private readonly IWebHelper _webHelper; + + public ShipStationController(ILocalizationService localizationService, + INotificationService notificationService, + ISettingService settingService, + IShipStationService shipStationService, + IStoreContext storeContext, + IWebHelper webHelper) + { + _localizationService = localizationService; + _notificationService = notificationService; + _settingService = settingService; + _shipStationService = shipStationService; + _storeContext = storeContext; + _webHelper = webHelper; + } + + [AuthorizeAdmin] + [Area(AreaNames.Admin)] + public async Task Configure() + { + //load settings for a chosen store scope + var storeScope = await _storeContext.GetActiveStoreScopeConfigurationAsync(); + var shipStationSettings = await _settingService.LoadSettingAsync(storeScope); + + var model = new ShipStationModel + { + ApiKey = shipStationSettings.ApiKey, + ApiSecret = shipStationSettings.ApiSecret, + PackingPackageVolume = shipStationSettings.PackingPackageVolume, + PackingType = Convert.ToInt32(shipStationSettings.PackingType), + PackingTypeValues = await shipStationSettings.PackingType.ToSelectListAsync(), + PassDimensions = shipStationSettings.PassDimensions, + ActiveStoreScopeConfiguration = storeScope, + UserName = shipStationSettings.UserName, + Password = shipStationSettings.Password, + WebhookURL = $"{_webHelper.GetStoreLocation()}Plugins/ShipStation/Webhook" + }; + + if (storeScope <= 0) + return View("~/Plugins/Shipping.ShipStation/Views/Configure.cshtml", model); + + model.ApiKey_OverrideForStore = await _settingService.SettingExistsAsync(shipStationSettings, x => x.ApiKey, storeScope); + model.ApiSecret_OverrideForStore = await _settingService.SettingExistsAsync(shipStationSettings, x => x.ApiSecret, storeScope); + model.PackingPackageVolume_OverrideForStore = await _settingService.SettingExistsAsync(shipStationSettings, x => x.PackingPackageVolume, storeScope); + model.PackingType_OverrideForStore = await _settingService.SettingExistsAsync(shipStationSettings, x => x.PackingType, storeScope); + model.PassDimensions_OverrideForStore = await _settingService.SettingExistsAsync(shipStationSettings, x => x.PassDimensions, storeScope); + model.Password_OverrideForStore = await _settingService.SettingExistsAsync(shipStationSettings, x => x.Password, storeScope); + model.UserName_OverrideForStore = await _settingService.SettingExistsAsync(shipStationSettings, x => x.UserName, storeScope); + + return View("~/Plugins/Shipping.ShipStation/Views/Configure.cshtml", model); + } + + [HttpPost] + [AuthorizeAdmin] + [Area(AreaNames.Admin)] + public async Task Configure(ShipStationModel model) + { + if (!ModelState.IsValid) + return await Configure(); + + //load settings for a chosen store scope + var storeScope = await _storeContext.GetActiveStoreScopeConfigurationAsync(); + var shipStationSettings = await _settingService.LoadSettingAsync(storeScope); + + //save settings + shipStationSettings.ApiKey = model.ApiKey; + shipStationSettings.ApiSecret = model.ApiSecret; + shipStationSettings.PackingPackageVolume = model.PackingPackageVolume; + shipStationSettings.PackingType = (PackingType)model.PackingType; + shipStationSettings.PassDimensions = model.PassDimensions; + shipStationSettings.Password = model.Password; + shipStationSettings.UserName = model.UserName; + + /* We do not clear cache after each setting update. + * This behavior can increase performance because cached settings will not be cleared + * and loaded from database after each update */ + await _settingService.SaveSettingOverridablePerStoreAsync(shipStationSettings, x => x.ApiKey, model.ApiKey_OverrideForStore, storeScope, false); + await _settingService.SaveSettingOverridablePerStoreAsync(shipStationSettings, x => x.ApiSecret, model.ApiSecret_OverrideForStore, storeScope, false); + await _settingService.SaveSettingOverridablePerStoreAsync(shipStationSettings, x => x.PackingPackageVolume, model.PackingPackageVolume_OverrideForStore, storeScope, false); + await _settingService.SaveSettingOverridablePerStoreAsync(shipStationSettings, x => x.PackingType, model.PackingType_OverrideForStore, storeScope, false); + await _settingService.SaveSettingOverridablePerStoreAsync(shipStationSettings, x => x.PassDimensions, model.PassDimensions_OverrideForStore, storeScope, false); + await _settingService.SaveSettingOverridablePerStoreAsync(shipStationSettings, x => x.Password, model.Password_OverrideForStore, storeScope, false); + await _settingService.SaveSettingOverridablePerStoreAsync(shipStationSettings, x => x.UserName, model.UserName_OverrideForStore, storeScope, false); + + //now clear settings cache + await _settingService.ClearCacheAsync(); + + _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Admin.Plugins.Saved")); + + return await Configure(); + } + + public async Task Webhook() + { + var userName = _webHelper.QueryString("SS-UserName"); + var password = _webHelper.QueryString("SS-Password"); + + //load settings for a chosen store scope + var storeScope = await _storeContext.GetActiveStoreScopeConfigurationAsync(); + var shipStationSettings = await _settingService.LoadSettingAsync(storeScope); + + if (!userName.Equals(shipStationSettings.UserName) || !password.Equals(shipStationSettings.Password)) + return Content(string.Empty); + + var action = _webHelper.QueryString("action") ?? string.Empty; + + if (Request.Method == WebRequestMethods.Http.Post && + action.Equals("shipnotify", StringComparison.InvariantCultureIgnoreCase)) + { + var orderNumber = _webHelper.QueryString("order_number"); + var carrier = _webHelper.QueryString("carrier"); + var service = _webHelper.QueryString("service"); + var trackingNumber = _webHelper.QueryString("tracking_number"); + + await _shipStationService.CreateOrUpdateShippingAsync(orderNumber, carrier, service, trackingNumber); + + //nothing should be rendered to visitor + return Content(string.Empty); + } + + if (!action.Equals("export", StringComparison.InvariantCultureIgnoreCase)) + return Content(string.Empty); + + var startDateParam = _webHelper.QueryString("start_date"); + var endDateParam = _webHelper.QueryString("end_date"); + var pageIndex = _webHelper.QueryString("page"); + + if (pageIndex > 0) + pageIndex -= 1; + + var startDate = string.IsNullOrEmpty(startDateParam) ? (DateTime?)null : DateTime.ParseExact(startDateParam, _shipStationService.DateFormat, CultureInfo.InvariantCulture); + var endDate = string.IsNullOrEmpty(endDateParam) ? (DateTime?)null : DateTime.ParseExact(endDateParam, _shipStationService.DateFormat, CultureInfo.InvariantCulture); + + return Content(await _shipStationService.GetXmlOrdersAsync(startDate, endDate, pageIndex, 200), "text/xml"); + } + } +} diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/Infrastructure/NopStartup.cs b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Infrastructure/NopStartup.cs new file mode 100644 index 0000000..0ecd031 --- /dev/null +++ b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Infrastructure/NopStartup.cs @@ -0,0 +1,37 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Nop.Core.Infrastructure; +using Nop.Plugin.Shipping.ShipStation.Services; + +namespace Nop.Plugin.Shipping.ShipStation.Infrastructure +{ + /// + /// Represents object for the configuring services on application startup + /// + public class NopStartup : INopStartup + { + /// + /// Add and configure any of the middleware + /// + /// Collection of service descriptors + /// Configuration of the application + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + services.AddScoped(); + } + + /// + /// Configure the using of added middleware + /// + /// Builder for configuring an application's request pipeline + public void Configure(IApplicationBuilder application) + { + } + + /// + /// Gets order of this startup configuration implementation + /// + public int Order => 3000; + } +} diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/Models/ShipStationModel.cs b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Models/ShipStationModel.cs new file mode 100644 index 0000000..8b5706c --- /dev/null +++ b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Models/ShipStationModel.cs @@ -0,0 +1,44 @@ +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.Rendering; +using Nop.Web.Framework.Models; +using Nop.Web.Framework.Mvc.ModelBinding; + +namespace Nop.Plugin.Shipping.ShipStation.Models +{ + public record ShipStationModel : BaseNopModel + { + public int ActiveStoreScopeConfiguration { get; set; } + + [NopResourceDisplayName("Plugins.Shipping.ShipStation.Fields.ApiKey")] + public string ApiKey { get; set; } + public bool ApiKey_OverrideForStore { get; set; } + + [NopResourceDisplayName("Plugins.Shipping.ShipStation.Fields.ApiSecret")] + public string ApiSecret { get; set; } + public bool ApiSecret_OverrideForStore { get; set; } + + [NopResourceDisplayName("Plugins.Shipping.ShipStation.Fields.PackingType")] + public SelectList PackingTypeValues { get; set; } + public bool PackingType_OverrideForStore { get; set; } + public int PackingType { get; set; } + + [NopResourceDisplayName("Plugins.Shipping.ShipStation.Fields.PackingPackageVolume")] + public int PackingPackageVolume { get; set; } + public bool PackingPackageVolume_OverrideForStore { get; set; } + + [NopResourceDisplayName("Plugins.Shipping.ShipStation.Fields.PassDimensions")] + public bool PassDimensions { get; set; } + public bool PassDimensions_OverrideForStore { get; set; } + + [NopResourceDisplayName("Plugins.Shipping.ShipStation.Fields.UserName")] + public string UserName { get; set; } + public bool UserName_OverrideForStore { get; set; } + + [NopResourceDisplayName("Plugins.Shipping.ShipStation.Fields.Password")] + [DataType(DataType.Password)] + public string Password { get; set; } + public bool Password_OverrideForStore { get; set; } + + public string WebhookURL { get; set; } + } +} diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/Nop.Plugin.Shipping.ShipStation.csproj b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Nop.Plugin.Shipping.ShipStation.csproj new file mode 100644 index 0000000..e82d437 --- /dev/null +++ b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Nop.Plugin.Shipping.ShipStation.csproj @@ -0,0 +1,57 @@ + + + + net6.0 + Copyright © Nop Solutions, Ltd + Nop Solutions, Ltd + Nop Solutions, Ltd + + https://www.nopcommerce.com/ + https://github.com/nopSolutions/nopCommerce + Git + ..\..\Presentation\Nop.Web\Plugins\Shipping.ShipStation + $(OutputPath) + + false + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/Notes.txt b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Notes.txt new file mode 100644 index 0000000..f65afb4 --- /dev/null +++ b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Notes.txt @@ -0,0 +1,32 @@ +Important points when developing plugins + + +- All views (cshtml files) and web.config file should have "Build action" set to "Content" and "Copy to output directory" set to "Copy if newer" + +- When you develop a new plugin from scratch, and when a new class library is added to the solution, open its .csproj file (a main project file) in any text editor and replace its content with the following one + + + + netcoreapp2.1 + ..\..\Presentation\Nop.Web\Plugins\PLUGIN_OUTPUT_DIRECTORY + $(OutputPath) + + false + + + + + + + + + + + + + +Replace “PLUGIN_OUTPUT_DIRECTORY” in the code above with your real plugin output directory name. + +It’s not required. But this way we can use a new ASP.NET approach to add third-party references. It was introduced in .NET Core. Furthermore, references from already referenced libraries will be loaded automatically. It’s very convenient. \ No newline at end of file diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/PackingType.cs b/src/Plugins/Nop.Plugin.Shipping.ShipStation/PackingType.cs new file mode 100644 index 0000000..0a0e9bf --- /dev/null +++ b/src/Plugins/Nop.Plugin.Shipping.ShipStation/PackingType.cs @@ -0,0 +1,18 @@ +namespace Nop.Plugin.Shipping.ShipStation +{ + /// + /// Represents a packing type + /// + public enum PackingType + { + /// + /// Pack by dimensions + /// + PackByDimensions, + + /// + /// Pack by volume + /// + PackByVolume + } +} \ No newline at end of file diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/RouteProvider.cs b/src/Plugins/Nop.Plugin.Shipping.ShipStation/RouteProvider.cs new file mode 100644 index 0000000..656d9be --- /dev/null +++ b/src/Plugins/Nop.Plugin.Shipping.ShipStation/RouteProvider.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Routing; +using Nop.Web.Framework.Mvc.Routing; + +namespace Nop.Plugin.Shipping.ShipStation +{ + public partial class RouteProvider : IRouteProvider + { + /// + /// Register routes + /// + /// Route builder + public void RegisterRoutes(IEndpointRouteBuilder endpointRouteBuilder) + { + //Webhook + endpointRouteBuilder.MapControllerRoute("Plugin.Payments.ShipStation.WebhookHandler", "Plugins/ShipStation/Webhook", + new { controller = "ShipStation", action = "Webhook" }); + } + + public int Priority => 0; + } +} diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/ServiceType.cs b/src/Plugins/Nop.Plugin.Shipping.ShipStation/ServiceType.cs new file mode 100644 index 0000000..18ca860 --- /dev/null +++ b/src/Plugins/Nop.Plugin.Shipping.ShipStation/ServiceType.cs @@ -0,0 +1,20 @@ +namespace Nop.Plugin.Shipping.ShipStation +{ + public enum ServiceType + { + /// + /// All services + /// + All, + + /// + /// Domestic services + /// + Domestic, + + /// + /// International services + /// + International + } +} diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/Services/IShipStationService.cs b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Services/IShipStationService.cs new file mode 100644 index 0000000..9f4e827 --- /dev/null +++ b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Services/IShipStationService.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Nop.Services.Shipping; + +namespace Nop.Plugin.Shipping.ShipStation.Services +{ + public interface IShipStationService + { + /// + /// Gets all rates + /// + /// + /// + /// A task that represents the asynchronous operation + /// The task result contains the + /// + Task> GetAllRatesAsync(GetShippingOptionRequest shippingOptionRequest); + + /// + /// Create or update shipping + /// + /// Order number + /// Carrier + /// Service + /// Tracking number + /// A task that represents the asynchronous operation + Task CreateOrUpdateShippingAsync(string orderNumber, string carrier, string service, string trackingNumber); + + /// + /// Get XML view of orders to sending to the ShipStation service + /// + /// Created date from (UTC); null to load all records + /// Created date to (UTC); null to load all records + /// Page index + /// Page size + /// + /// A task that represents the asynchronous operation + /// The task result contains the xML view of orders + /// + Task GetXmlOrdersAsync(DateTime? startDate, DateTime? endDate, int pageIndex, int pageSize); + + /// + /// Gets a string that defines the required format of date time + /// + string DateFormat { get; } + } +} diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/Services/ShipStationService.cs b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Services/ShipStationService.cs new file mode 100644 index 0000000..c632b2c --- /dev/null +++ b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Services/ShipStationService.cs @@ -0,0 +1,669 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using Newtonsoft.Json; +using Nop.Core; +using Nop.Core.Caching; +using Nop.Core.Domain.Catalog; +using Nop.Core.Domain.Common; +using Nop.Core.Domain.Directory; +using Nop.Core.Domain.Orders; +using Nop.Core.Domain.Shipping; +using Nop.Core.Domain.Tax; +using Nop.Services.Catalog; +using Nop.Services.Common; +using Nop.Services.Customers; +using Nop.Services.Directory; +using Nop.Services.ExportImport; +using Nop.Services.Logging; +using Nop.Services.Orders; +using Nop.Services.Shipping; + +namespace Nop.Plugin.Shipping.ShipStation.Services +{ + public partial class ShipStationService : IShipStationService + { + #region constants + + private const string API_URL = "https://ssapi.shipstation.com/"; + private readonly CacheKey _carriersCacheKey = new("Nop.plugins.shipping.shipstation.carrierscachekey"); + private readonly CacheKey _serviceCacheKey = new("Nop.plugins.shipping.shipstation.servicecachekey.{0}"); + + private const string CONTENT_TYPE = "application/json"; + private const string DATE_FORMAT = "MM/dd/yyyy HH:mm"; + + private const string LIST_CARRIERS_CMD = "carriers"; + private const string LIST_SERVICES_CMD = "carriers/listservices?carrierCode={0}"; + private const string LIST_RATES_CMD = "shipments/getrates"; + + #endregion + + #region Fields + + private readonly IAddressService _addressService; + private readonly ICountryService _countryService; + private readonly ICustomerService _customerService; + private readonly ILogger _logger; + private readonly IMeasureService _measureService; + private readonly IOrderService _orderService; + private readonly IProductAttributeParser _productAttributeParser; + private readonly IProductService _productService; + private readonly IShipmentService _shipmentService; + private readonly IShippingService _shippingService; + private readonly IStateProvinceService _stateProvinceService; + private readonly IStaticCacheManager _staticCacheManager; + private readonly IStoreContext _storeContext; + private readonly ShipStationSettings _shipStationSettings; + + #endregion + + #region Ctor + + public ShipStationService(IAddressService addressService, + ICountryService countryService, + ICustomerService customerService, + ILogger logger, + IMeasureService measureService, + IOrderService orderService, + IProductAttributeParser productAttributeParser, + IProductService productService, + IShipmentService shipmentService, + IShippingService shippingService, + IStateProvinceService stateProvinceService, + IStaticCacheManager staticCacheManager, + IStoreContext storeContext, + ShipStationSettings shipStationSettings) + { + _addressService = addressService; + _countryService = countryService; + _customerService = customerService; + _logger = logger; + _measureService = measureService; + _orderService = orderService; + _productAttributeParser = productAttributeParser; + _productService = productService; + _shipmentService = shipmentService; + _shippingService = shippingService; + _stateProvinceService = stateProvinceService; + _staticCacheManager = staticCacheManager; + _storeContext = storeContext; + _shipStationSettings = shipStationSettings; + } + + #endregion + + #region Utilities + + /// A task that represents the asynchronous operation + protected virtual async Task SendGetRequestAsync(string apiUrl) + { + using var handler = new HttpClientHandler { Credentials = new NetworkCredential(_shipStationSettings.ApiKey, _shipStationSettings.ApiSecret) }; + using var client = new HttpClient(handler); + using var rs = await client.GetStreamAsync(apiUrl); + + if (rs == null) return string.Empty; + using var sr = new StreamReader(rs); + + return await sr.ReadToEndAsync(); + } + + /// A task that represents the asynchronous operation + private async Task ConvertFromPrimaryMeasureDimensionAsync(decimal quantity, MeasureDimension usedMeasureDimension) + { + return Convert.ToInt32(Math.Ceiling(await _measureService.ConvertFromPrimaryMeasureDimensionAsync(quantity, usedMeasureDimension))); + } + + /// A task that represents the asynchronous operation + protected virtual async Task TryGetError(string data) + { + var flag = false; + try + { + var rez = JsonConvert.DeserializeObject>(data); + + if (rez.ContainsKey("message")) + { + flag = true; + + await _logger.ErrorAsync(rez["message"]); + } + } + catch (JsonSerializationException) + { + } + + return flag; + } + + /// A task that represents the asynchronous operation + protected virtual async Task> GetRatesAsync(GetShippingOptionRequest getShippingOptionRequest, string carrierCode) + { + var usedWeight = await _measureService.GetMeasureWeightBySystemKeywordAsync(Weight.Units); + if (usedWeight == null) + throw new NopException("ShipStatio shipping service. Could not load \"{0}\" measure weight", Weight.Units); + + var usedMeasureDimension = await _measureService.GetMeasureDimensionBySystemKeywordAsync(Dimensions.Units); + if (usedMeasureDimension == null) + throw new NopException("ShipStatio shipping service. Could not load \"{0}\" measure dimension", Dimensions.Units); + + var weight = Convert.ToInt32(Math.Ceiling(await _measureService.ConvertFromPrimaryMeasureWeightAsync(await _shippingService.GetTotalWeightAsync(getShippingOptionRequest), usedWeight))); + + var postData = new RatesRequest + { + CarrierCode = carrierCode, + FromPostalCode = getShippingOptionRequest.ZipPostalCodeFrom ?? getShippingOptionRequest.ShippingAddress.ZipPostalCode, + ToState = (await _stateProvinceService.GetStateProvinceByAddressAsync(getShippingOptionRequest.ShippingAddress)).Abbreviation, + ToCountry = (await _countryService.GetCountryByAddressAsync(getShippingOptionRequest.ShippingAddress)).TwoLetterIsoCode, + ToPostalCode = getShippingOptionRequest.ShippingAddress.ZipPostalCode, + ToCity = getShippingOptionRequest.ShippingAddress.City, + Weight = new Weight { Value = weight } + }; + + if (_shipStationSettings.PassDimensions) + { + int length, height, width; + + decimal lengthTmp, widthTmp, heightTmp; + + switch (_shipStationSettings.PackingType) + { + case PackingType.PackByDimensions: + (widthTmp, lengthTmp, heightTmp) = await _shippingService.GetDimensionsAsync(getShippingOptionRequest.Items); + + length = await ConvertFromPrimaryMeasureDimensionAsync(lengthTmp, usedMeasureDimension); + height = await ConvertFromPrimaryMeasureDimensionAsync(heightTmp, usedMeasureDimension); + width = await ConvertFromPrimaryMeasureDimensionAsync(widthTmp, usedMeasureDimension); + break; + case PackingType.PackByVolume: + if (getShippingOptionRequest.Items.Count == 1 && + getShippingOptionRequest.Items[0].GetQuantity() == 1) + { + var sci = getShippingOptionRequest.Items[0].ShoppingCartItem; + var product = getShippingOptionRequest.Items[0].Product; + + (widthTmp, lengthTmp, heightTmp) = await _shippingService.GetDimensionsAsync(new List + { + new GetShippingOptionRequest.PackageItem(sci, product, 1) + }); + + length = await ConvertFromPrimaryMeasureDimensionAsync(lengthTmp, usedMeasureDimension); + height = await ConvertFromPrimaryMeasureDimensionAsync(lengthTmp, usedMeasureDimension); + width = await ConvertFromPrimaryMeasureDimensionAsync(widthTmp, usedMeasureDimension); + } + else + { + decimal totalVolume = 0; + foreach (var item in getShippingOptionRequest.Items) + { + var sci = item.ShoppingCartItem; + var product = item.Product; + + (widthTmp, lengthTmp, heightTmp) = await _shippingService.GetDimensionsAsync(new List + { + new GetShippingOptionRequest.PackageItem(sci, product, 1) + }); + + var productLength = await ConvertFromPrimaryMeasureDimensionAsync(lengthTmp, usedMeasureDimension); + var productHeight = await ConvertFromPrimaryMeasureDimensionAsync(heightTmp, usedMeasureDimension); + var productWidth = await ConvertFromPrimaryMeasureDimensionAsync(widthTmp, usedMeasureDimension); + totalVolume += item.GetQuantity() * (productHeight * productWidth * productLength); + } + + int dimension; + if (totalVolume == 0) + { + dimension = 0; + } + else + { + // cubic inches + var packageVolume = _shipStationSettings.PackingPackageVolume; + if (packageVolume <= 0) + packageVolume = 5184; + + // cube root (floor) + dimension = Convert.ToInt32(Math.Floor(Math.Pow(Convert.ToDouble(packageVolume), + 1.0 / 3.0))); + } + + length = width = height = dimension; + } + + break; + default: + length = height = width = 1; + break; + } + + if (length < 1) + length = 1; + if (height < 1) + height = 1; + if (width < 1) + width = 1; + + postData.Dimensions = new Dimensions + { + Length = length, + Height = height, + Width = width + }; + } + + using var handler = new HttpClientHandler { Credentials = new NetworkCredential(_shipStationSettings.ApiKey, _shipStationSettings.ApiSecret) }; + using var client = new HttpClient(handler); + + client.DefaultRequestHeaders.Add("Content-Type", CONTENT_TYPE); + var responseData = await client.PostAsync($"{API_URL}{LIST_RATES_CMD}", new StringContent(JsonConvert.SerializeObject(postData))); + var data = await responseData.Content.ReadAsStringAsync(); + + return (await TryGetError(data)) ? new List() : JsonConvert.DeserializeObject>(data); + } + + /// A task that represents the asynchronous operation + protected virtual async Task> GetCarriersAsync() + { + var rez = await _staticCacheManager.GetAsync(_staticCacheManager.PrepareKeyForShortTermCache(_carriersCacheKey), async () => + { + var data = await SendGetRequestAsync($"{API_URL}{LIST_CARRIERS_CMD}"); + + return (await TryGetError(data)) ? new List() : JsonConvert.DeserializeObject>(data); + }); + + if (!rez.Any()) + await _staticCacheManager.RemoveAsync(_carriersCacheKey); + + return rez; + } + + /// A task that represents the asynchronous operation + protected virtual async Task> GetServicesAsync() + { + var services = await (await GetCarriersAsync()).SelectManyAwait(async carrier => + { + var cacheKey = _staticCacheManager.PrepareKeyForShortTermCache(_serviceCacheKey, carrier.Code); + + var data = await _staticCacheManager.GetAsync(cacheKey, async () => await SendGetRequestAsync(string.Format($"{API_URL}{LIST_SERVICES_CMD}", carrier.Code))); + + if (!data.Any()) + await _staticCacheManager.RemoveAsync(cacheKey); + + var serviceList = JsonConvert.DeserializeObject>(data); + + return serviceList; + }).ToListAsync(); + + return services.ToList(); + } + + /// A task that represents the asynchronous operation + protected virtual async Task WriteAddressToXmlAsync(XmlWriter writer, bool isBillingAddress, Address address) + { + await writer.WriteElementStringAsync("Name", $"{address.FirstName} {address.LastName}"); + + await writer.WriteElementStringAsync("Company", address.Company); + await writer.WriteElementStringAsync("Phone", address.PhoneNumber); + + if (isBillingAddress) + return; + + await writer.WriteElementStringAsync("Address1", address.Address1); + await writer.WriteElementStringAsync("Address2", address.Address2); + await writer.WriteElementStringAsync("City", address.City); + await writer.WriteElementStringAsync("State", (await _stateProvinceService.GetStateProvinceByAddressAsync(address))?.Name ?? string.Empty); + await writer.WriteElementStringAsync("PostalCode", address.ZipPostalCode); + await writer.WriteElementStringAsync("Country", (await _countryService.GetCountryByAddressAsync(address)).TwoLetterIsoCode); + } + + /// A task that represents the asynchronous operation + protected virtual async Task WriteOrderItemsToXmlAsync(XmlWriter writer, ICollection orderItems) + { + await writer.WriteStartElementAsync("Items"); + + foreach (var orderItem in orderItems) + { + var product = await _productService.GetProductByIdAsync(orderItem.ProductId); + var order = await _orderService.GetOrderByIdAsync(orderItem.OrderId); + + //is shippable + if (!product.IsShipEnabled) + continue; + + await writer.WriteStartElementAsync("Item"); + + var sku = product.Sku; + + if (product.ManageInventoryMethod == ManageInventoryMethod.ManageStockByAttributes) + { + var attributesXml = orderItem.AttributesXml; + + if (!string.IsNullOrEmpty(attributesXml) && product.ManageInventoryMethod == + ManageInventoryMethod.ManageStockByAttributes) + { + var combination = await _productAttributeParser.FindProductAttributeCombinationAsync(product, attributesXml); + if (combination != null && !string.IsNullOrEmpty(combination.Sku)) + sku = combination.Sku; + } + } + + await writer.WriteElementStringAsync("SKU", string.IsNullOrEmpty(sku) ? product.Id.ToString() : sku); + await writer.WriteElementStringAsync("Name", product.Name); + await writer.WriteElementStringAsync("Quantity", orderItem.Quantity.ToString()); + await writer.WriteElementStringAsync("UnitPrice", (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax ? orderItem.UnitPriceInclTax : orderItem.UnitPriceExclTax).ToString(CultureInfo.InvariantCulture)); + + await writer.WriteEndElementAsync(); + await writer.FlushAsync(); + } + + await writer.WriteEndElementAsync(); + await writer.FlushAsync(); + } + + /// A task that represents the asynchronous operation + protected virtual async Task WriteCustomerToXmlAsync(XmlWriter writer, Order order, Core.Domain.Customers.Customer customer) + { + await writer.WriteStartElementAsync("Customer"); + + await writer.WriteElementStringAsync("CustomerCode", customer.Email); + await writer.WriteStartElementAsync("BillTo"); + await WriteAddressToXmlAsync(writer, true, await _addressService.GetAddressByIdAsync(order.BillingAddressId)); + await writer.WriteEndElementAsync(); + await writer.WriteStartElementAsync("ShipTo"); + await WriteAddressToXmlAsync(writer, false, await _addressService.GetAddressByIdAsync(order.ShippingAddressId ?? order.BillingAddressId)); + await writer.WriteEndElementAsync(); + + await writer.WriteEndElementAsync(); + await writer.FlushAsync(); + } + + protected virtual string GetOrderStatus(Order order) + { + return order.OrderStatus switch + { + OrderStatus.Pending => "unpaid", + OrderStatus.Processing => "paid", + OrderStatus.Complete => "shipped", + OrderStatus.Cancelled => "cancelled", + _ => "on_hold", + }; + } + + /// A task that represents the asynchronous operation + protected virtual async Task WriteOrderToXmlAsync(XmlWriter writer, Order order) + { + await writer.WriteStartElementAsync("Order"); + await writer.WriteElementStringAsync("OrderID", order.Id.ToString()); + await writer.WriteElementStringAsync("OrderNumber", order.OrderGuid.ToString()); + await writer.WriteElementStringAsync("OrderDate", order.CreatedOnUtc.ToString(DATE_FORMAT)); + await writer.WriteElementStringAsync("OrderStatus", GetOrderStatus(order)); + await writer.WriteElementStringAsync("LastModified", DateTime.Now.ToString(DATE_FORMAT)); + await writer.WriteElementStringAsync("OrderTotal", order.OrderTotal.ToString(CultureInfo.InvariantCulture)); + await writer.WriteElementStringAsync("ShippingAmount", (order.CustomerTaxDisplayType == TaxDisplayType.IncludingTax ? order.OrderShippingInclTax : order.OrderShippingExclTax).ToString(CultureInfo.InvariantCulture)); + + await WriteCustomerToXmlAsync(writer, order, await _customerService.GetCustomerByIdAsync(order.CustomerId)); + await WriteOrderItemsToXmlAsync(writer, await _orderService.GetOrderItemsAsync(order.Id)); + + await writer.WriteEndElementAsync(); + await writer.FlushAsync(); + } + + #endregion + + #region Methods + + /// + /// Gets all rates + /// + /// + /// + /// A task that represents the asynchronous operation + /// The task result contains the + /// + public virtual async Task> GetAllRatesAsync(GetShippingOptionRequest shippingOptionRequest) + { + var services = await GetServicesAsync(); + + var carrierFilter = services.Select(s => s.CarrierCode).Distinct().ToList(); + var serviceFilter = services.Select(s => s.Code).Distinct().ToList(); + var carriers = (await GetCarriersAsync()).Where(c => carrierFilter.Contains(c.Code)); + + return await carriers.SelectManyAwait(async carrier => + (await GetRatesAsync(shippingOptionRequest, carrier.Code)).Where(r => serviceFilter.Contains(r.ServiceCode))).ToListAsync(); + } + + /// + /// Create or update shipping + /// + /// Order number + /// Carrier + /// Service + /// Tracking number + /// A task that represents the asynchronous operation + public async Task CreateOrUpdateShippingAsync(string orderNumber, string carrier, string service, string trackingNumber) + { + try + { + var order = await _orderService.GetOrderByGuidAsync(Guid.Parse(orderNumber)); + + if (order == null) + return; + + var shipments = await _shipmentService.GetShipmentsByOrderIdAsync(order.Id); + + if (!shipments.Any()) + { + var shipment = new Shipment + { + CreatedOnUtc = DateTime.UtcNow, + ShippedDateUtc = DateTime.UtcNow, + OrderId = order.Id, + TrackingNumber = trackingNumber + }; + + decimal totalWeight = 0; + + await _shipmentService.InsertShipmentAsync(shipment); + + foreach (var orderItem in await _orderService.GetOrderItemsAsync(order.Id)) + { + var product = await _productService.GetProductByIdAsync(orderItem.ProductId); + + //is shippable + if (!product.IsShipEnabled) + continue; + + //ensure that this product can be shipped (have at least one item to ship) + var maxQtyToAdd = await _orderService.GetTotalNumberOfItemsCanBeAddedToShipmentAsync(orderItem); + if (maxQtyToAdd <= 0) + continue; + + var warehouseId = product.WarehouseId; + + //ok. we have at least one item. let's create a shipment (if it does not exist) + + var orderItemTotalWeight = orderItem.ItemWeight * orderItem.Quantity; + if (orderItemTotalWeight.HasValue) + totalWeight += orderItemTotalWeight.Value; + + //create a shipment item + var shipmentItem = new ShipmentItem + { + OrderItemId = orderItem.Id, + Quantity = orderItem.Quantity, + WarehouseId = warehouseId, + ShipmentId = shipment.Id + }; + + await _shipmentService.InsertShipmentItemAsync(shipmentItem); + } + + shipment.TotalWeight = totalWeight; + + await _shipmentService.UpdateShipmentAsync(shipment); + } + else + { + var shipment = shipments.FirstOrDefault(); + + if (shipment == null) + return; + + shipment.TrackingNumber = trackingNumber; + + await _shipmentService.UpdateShipmentAsync(shipment); + } + + order.ShippingStatus = ShippingStatus.Shipped; + order.ShippingMethod = string.IsNullOrEmpty(service) ? carrier : service; + + await _orderService.UpdateOrderAsync(order); + } + catch (Exception e) + { + await _logger.ErrorAsync(e.Message, e); + } + } + + /// + /// Get XML view of orders to sending to the ShipStation service + /// + /// Created date from (UTC); null to load all records + /// Created date to (UTC); null to load all records + /// Page index + /// Page size + /// + /// A task that represents the asynchronous operation + /// The task result contains the xML view of orders + /// + public async Task GetXmlOrdersAsync(DateTime? startDate, DateTime? endDate, int pageIndex, int pageSize) + { + string xml; + + var settings = new XmlWriterSettings + { + Async = true, + Encoding = Encoding.UTF8, + Indent = true, + ConformanceLevel = ConformanceLevel.Auto + }; + + await using var stream = new MemoryStream(); + await using var writer = XmlWriter.Create(stream, settings); + + await writer.WriteStartDocumentAsync(); + await writer.WriteStartElementAsync("Orders"); + + var store = await _storeContext.GetCurrentStoreAsync(); + + foreach (var order in await _orderService.SearchOrdersAsync(createdFromUtc: startDate, createdToUtc: endDate, storeId: store.Id, pageIndex: pageIndex, pageSize: 200)) + { + await WriteOrderToXmlAsync(writer, order); + } + + await writer.WriteEndElementAsync(); + await writer.WriteEndDocumentAsync(); + await writer.FlushAsync(); + + xml = Encoding.UTF8.GetString(stream.ToArray()); + + return xml; + } + + /// + /// Date format + /// + public string DateFormat => DATE_FORMAT; + + #endregion + + #region Nested classes + + protected class Carrier + { + public string Name { get; set; } + + public string Code { get; set; } + } + + protected class Service : IEqualityComparer + { + public string CarrierCode { get; set; } + + public string Code { get; set; } + + public string Name { get; set; } + + public bool Domestic { get; set; } + + public bool International { get; set; } + + /// + /// Determines whether the specified objects are equal + /// + /// The first object of type T to compare + /// The second object of type T to compare + /// true if the specified objects are equal; otherwise, false + public bool Equals(Service first, Service second) + { + if (first == null && second == null) + return true; + + if (first == null) + return false; + + return first.Code.Equals(second?.Code); + } + + public int GetHashCode(Service obj) + { + return Code.GetHashCode(); + } + } + + protected class RatesRequest + { + public string CarrierCode { get; set; } + + public string FromPostalCode { get; set; } + + public string ToState { get; set; } + + public string ToCountry { get; set; } + + public string ToPostalCode { get; set; } + + public string ToCity { get; set; } + + public Weight Weight { get; set; } + + public Dimensions Dimensions { get; set; } + } + + protected class Weight + { + public static string Units => "ounce"; + + public int Value { get; set; } + } + + protected class Dimensions + { + public static string Units => "inches"; + + public int Length { get; set; } + + public int Width { get; set; } + + public int Height { get; set; } + } + + #endregion + } +} diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/ShipStationComputationMethod.cs b/src/Plugins/Nop.Plugin.Shipping.ShipStation/ShipStationComputationMethod.cs new file mode 100644 index 0000000..f7b5432 --- /dev/null +++ b/src/Plugins/Nop.Plugin.Shipping.ShipStation/ShipStationComputationMethod.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Nop.Core; +using Nop.Core.Domain.Shipping; +using Nop.Plugin.Shipping.ShipStation.Services; +using Nop.Services.Common; +using Nop.Services.Configuration; +using Nop.Services.Localization; +using Nop.Services.Plugins; +using Nop.Services.Shipping; +using Nop.Services.Shipping.Tracking; + +namespace Nop.Plugin.Shipping.ShipStation +{ + /// + /// Fixed rate or by weight shipping computation method + /// + public class ShipStationComputationMethod : BasePlugin, IShippingRateComputationMethod, IMiscPlugin + { + #region Fields + + private readonly ILocalizationService _localizationService; + private readonly ISettingService _settingService; + private readonly IShipStationService _shipStationService; + private readonly IWebHelper _webHelper; + + #endregion + + #region Ctor + + public ShipStationComputationMethod(ILocalizationService localizationService, + ISettingService settingService, + IShipStationService shipStationService, + IWebHelper webHelper) + { + _localizationService = localizationService; + _settingService = settingService; + _shipStationService = shipStationService; + _webHelper = webHelper; + } + + #endregion + + #region Methods + + /// + /// Gets available shipping options + /// + /// A request for getting shipping options + /// + /// A task that represents the asynchronous operation + /// The task result contains the represents a response of getting shipping rate options + /// + public async Task GetShippingOptionsAsync(GetShippingOptionRequest getShippingOptionRequest) + { + if (getShippingOptionRequest == null) + throw new ArgumentNullException(nameof(getShippingOptionRequest)); + + var response = new GetShippingOptionResponse(); + + if (getShippingOptionRequest.Items == null) + response.AddError("No shipment items"); + + if (getShippingOptionRequest.ShippingAddress == null) + response.AddError("Shipping address is not set"); + + if ((getShippingOptionRequest.ShippingAddress?.CountryId ?? 0) == 0) + response.AddError("Shipping country is not set"); + + if (!response.Success) + return response; + + try + { + foreach (var rate in await _shipStationService.GetAllRatesAsync(getShippingOptionRequest)) + { + response.ShippingOptions.Add(new ShippingOption + { + Description = rate.ServiceCode, + Name = rate.ServiceName, + Rate = rate.TotalCost + }); + } + } + catch (Exception e) + { + response.Errors.Add(e.Message); + } + + return response; + } + + /// + /// Gets fixed shipping rate (if shipping rate computation method allows it and the rate can be calculated before checkout). + /// + /// A request for getting shipping options + /// + /// A task that represents the asynchronous operation + /// The task result contains the fixed shipping rate; or null in case there's no fixed shipping rate + /// + public Task GetFixedRateAsync(GetShippingOptionRequest getShippingOptionRequest) + { + if (getShippingOptionRequest == null) + throw new ArgumentNullException(nameof(getShippingOptionRequest)); + + return Task.FromResult(null); + } + + /// + /// Get associated shipment tracker + /// + /// + /// A task that represents the asynchronous operation + /// The task result contains the shipment tracker + /// + public Task GetShipmentTrackerAsync() + { + return Task.FromResult(null); + } + + /// + /// Gets a configuration page URL + /// + public override string GetConfigurationPageUrl() + { + return $"{_webHelper.GetStoreLocation()}Admin/ShipStation/Configure"; + } + + /// + /// Install plugin + /// + /// A task that represents the asynchronous operation + public override async Task InstallAsync() + { + //settings + var settings = new ShipStationSettings + { + PackingPackageVolume = 5184 + }; + await _settingService.SaveSettingAsync(settings); + + //locales + await _localizationService.AddOrUpdateLocaleResourceAsync(new Dictionary + { + ["Enums.Nop.Plugin.Shipping.ShipStation.PackingType.PackByDimensions"] = "Pack by dimensions", + ["Enums.Nop.Plugin.Shipping.ShipStation.PackingType.PackByVolume"] = "Pack by volume", + ["Plugins.Shipping.ShipStation.Fields.ApiKey.Hint"] = "Specify ShipStation API key.", + ["Plugins.Shipping.ShipStation.Fields.ApiKey"] = "API key", + ["Plugins.Shipping.ShipStation.Fields.ApiSecret.Hint"] = "Specify ShipStation API secret.", + ["Plugins.Shipping.ShipStation.Fields.ApiSecret"] = "API secret", + ["Plugins.Shipping.ShipStation.Fields.PackingPackageVolume.Hint"] = "Enter your package volume.", + ["Plugins.Shipping.ShipStation.Fields.PackingPackageVolume"] = "Package volume", + ["Plugins.Shipping.ShipStation.Fields.PackingType.Hint"] = "Choose preferred packing type.", + ["Plugins.Shipping.ShipStation.Fields.PackingType"] = "Packing type", + ["Plugins.Shipping.ShipStation.Fields.Password.Hint"] = "Specify ShipStation password", + ["Plugins.Shipping.ShipStation.Fields.Password"] = "Password", + ["Plugins.Shipping.ShipStation.Fields.PassDimensions.Hint"] = "Check if need send dimensions to the ShipStation server", + ["Plugins.Shipping.ShipStation.Fields.PassDimensions"] = "Pass dimensions", + ["Plugins.Shipping.ShipStation.Fields.UserName"] = "User name", + ["Plugins.Shipping.ShipStation.Fields.UserName.Hint"] = "Specify ShipStation user name" + }); + + await base.InstallAsync(); + } + + /// + /// Uninstall plugin + /// + /// A task that represents the asynchronous operation + public override async Task UninstallAsync() + { + //settings + await _settingService.DeleteSettingAsync(); + + //locales + await _localizationService.DeleteLocaleResourcesAsync("Enums.Nop.Plugin.Shipping.ShipStation"); + await _localizationService.DeleteLocaleResourcesAsync("Plugins.Shipping.ShipStation"); + + await base.UninstallAsync(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/ShipStationServiceRate.cs b/src/Plugins/Nop.Plugin.Shipping.ShipStation/ShipStationServiceRate.cs new file mode 100644 index 0000000..21686c4 --- /dev/null +++ b/src/Plugins/Nop.Plugin.Shipping.ShipStation/ShipStationServiceRate.cs @@ -0,0 +1,33 @@ +using System; +using System.Globalization; + +namespace Nop.Plugin.Shipping.ShipStation +{ + public class ShipStationServiceRate + { + /// + /// Service name + /// + public string ServiceName { get; set; } + + /// + /// Service code + /// + public string ServiceCode { get; set; } + + /// + /// Service code + /// + public string ShipmentCost { get; set; } + + /// + /// Other cost + /// + public string OtherCost { get; set; } + + /// + /// Total cost + /// + public decimal TotalCost => Convert.ToDecimal(ShipmentCost, new CultureInfo("en-US")) + Convert.ToDecimal(OtherCost, new CultureInfo("en-US")); + } +} \ No newline at end of file diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/ShipStationSettings.cs b/src/Plugins/Nop.Plugin.Shipping.ShipStation/ShipStationSettings.cs new file mode 100644 index 0000000..aae82f8 --- /dev/null +++ b/src/Plugins/Nop.Plugin.Shipping.ShipStation/ShipStationSettings.cs @@ -0,0 +1,42 @@ +using Nop.Core.Configuration; + +namespace Nop.Plugin.Shipping.ShipStation +{ + public class ShipStationSettings : ISettings + { + /// + /// API key + /// + public string ApiKey { get; set; } + + /// + /// API secret + /// + public string ApiSecret { get; set; } + + /// + /// Set to true if need pass dimensions to the ShipStation server + /// + public bool PassDimensions { get; set; } + + /// + /// Packing type + /// + public PackingType PackingType { get; set; } + + /// + /// Package volume + /// + public int PackingPackageVolume { get; set; } + + /// + /// ShipStation user name + /// + public string UserName { get; set; } + + /// + /// ShipStation password + /// + public string Password { get; set; } + } +} \ No newline at end of file diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/Views/Configure.cshtml b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Views/Configure.cshtml new file mode 100644 index 0000000..a3699cc --- /dev/null +++ b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Views/Configure.cshtml @@ -0,0 +1,147 @@ +@{ + Layout = "_ConfigurePlugin"; +} + +@using Nop.Plugin.Shipping.ShipStation +@model Nop.Plugin.Shipping.ShipStation.Models.ShipStationModel + +@await Component.InvokeAsync("StoreScopeConfiguration") + +
+
+
+
+ +

Setup Instructions

+
    +
  • Register or login on the ShipStation site
  • +
  • Go to the "Settings » API Settings" page and get the API Key and the API Secret and copy them on the plugin settings
  • +
  • Select Selling Channels from the left-hand sidebar, then choose Store Setup.
  • +
  • Click + Connect a Store or Marketplace.
  • +
  • Choose the Custom Store option
  • +
  • Enter the "@Model.WebhookURL" to the URL to Custom XML Page setting
  • +
  • Create a Username and Password, enter them into the settings forms (the ShipStation form and the nopCommerce form). Do not use the ShipStation or nopCommerce user credentials for this.
  • +
  • Don't change the Statuses section
  • +
  • Save the nopCommerce settings by pressing the Save button.
  • +
  • On the ShipStation form press Test your connection using the Test Connection button
  • +
  • Save the settings using the Connect button
  • +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+ +
+
+ + +
+
+ + +
+
+
+
+
+
+   +
+
+ +
+
+ +
+
+
+
+ + diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/Views/_ViewImports.cshtml b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Views/_ViewImports.cshtml new file mode 100644 index 0000000..2d4ad97 --- /dev/null +++ b/src/Plugins/Nop.Plugin.Shipping.ShipStation/Views/_ViewImports.cshtml @@ -0,0 +1,8 @@ +@inherits Nop.Web.Framework.Mvc.Razor.NopRazorPage +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Nop.Web.Framework + +@using Microsoft.AspNetCore.Mvc.ViewFeatures +@using Nop.Web.Framework.UI +@using Nop.Web.Framework.Extensions +@using System.Text.Encodings.Web \ No newline at end of file diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/logo.png b/src/Plugins/Nop.Plugin.Shipping.ShipStation/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0a6f8300a96895814d3016dcd469dac06253fd0a GIT binary patch literal 1648 zcmV-$29NoPP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf1_en(K~z{r?N)nC z(^nK`S>mRfvSdD)i<@Ih_J_$fb;@4Rs1Y-b2|AUbf)+(U9f)Bd51BJYrlRBc|)P)+v#l!MnxjqAH(q_zufPhd+zQ1?m73|THz(c zn5fZ$F;SxhW1>b2{s*G^hxi`HQ1$`PK)?AL+G%)LrjKBW7mw7iKVo#yo4*n4$O6mM zk%}@H46O4cI=Z_cQOMEW-fp&-(AwIHKD}|%PvalRcnXb;jb`>@7>JanNP{^G=HZnI zui}M$jjGHhX`$Iw)e-;FpVsP9%v2ML1JUw?ZTW_Bpg_(kMjTO*8fhd91|!=R`+^FD(Uhr_s3Drc=< z(0+WgYz5=_`Gu@C&|*S~7`;8c=@olAMVPxHt)`Y!*}bL{diK4ct5ue)G!Fu{M@c4XYoBYm9{B-Cri=2qT1RzuwwOU_yh$q z-+7-eq?8I}{96YHELpjdaSJwW^F-n`K9s6FIFm>t6uC+`Zgpq8w4#!@2RggDktLCl zk2kaH^?h^|cA%`P7V3&ih!!nJ(cKg_mMC!nU*S&CN}zbgid>kjSa$1gyS;z3f`PHg*lyxh=BEs>5RYk;Yav#=@OKdm0_l>ExZHw zA@ulhjGr_K<&~8<5E_cVxbZj@aRCk+UFo9N;7WV~Ht*Pt+&mQu)P-2@vIAF>lHo_` z=d{gKNTM!XW}*rT^3kAu2*)iPgq=Lk#xADWBT~2kk(qXgyzw#eDlR~fk%P5Pu53Ip z^*RYOaU+;06)m(kzz-hYUf?BOWdm`%1ROhcl5uWa9FCtp&A3=9#X;J5TKWyx*v^Je zU;uXQ@kT{O6&uf1$VWm?#Vx9QGG#6<#l(_t z4)W<;l9I11Hy?7kd#S>kC@w2U$!*h95EXSEYBjAZ%Z4Z`0~&fF@@P&hMUk731VvsU z6siI!l}h#*F(OeUj zsHmz&YPyj9rifBoEEco9jW|+IVTd#x4jm*?*VAL}nP7aBje?$q_95&R=;`mI=>Q literal 0 HcmV?d00001 diff --git a/src/Plugins/Nop.Plugin.Shipping.ShipStation/plugin.json b/src/Plugins/Nop.Plugin.Shipping.ShipStation/plugin.json new file mode 100644 index 0000000..fb47a78 --- /dev/null +++ b/src/Plugins/Nop.Plugin.Shipping.ShipStation/plugin.json @@ -0,0 +1,11 @@ +{ + "Group": "Shipping rate computation", + "FriendlyName": "ShipStation", + "SystemName": "Shipping.ShipStation", + "Version": "1.23", + "SupportedVersions": [ "4.50" ], + "Author": "nopCommerce team", + "DisplayOrder": 1, + "FileName": "Nop.Plugin.Shipping.ShipStation.dll", + "Description": "This plugin offers shipping rates with ShipStation" +} \ No newline at end of file diff --git a/src/Plugins/Nop.Plugin.Widgets.FacebookPixel/Services/FacebookPixelService.cs b/src/Plugins/Nop.Plugin.Widgets.FacebookPixel/Services/FacebookPixelService.cs index 951b2e7..5647aeb 100644 --- a/src/Plugins/Nop.Plugin.Widgets.FacebookPixel/Services/FacebookPixelService.cs +++ b/src/Plugins/Nop.Plugin.Widgets.FacebookPixel/Services/FacebookPixelService.cs @@ -517,7 +517,7 @@ namespace Nop.Plugin.Widgets.FacebookPixel.Services return await HandleFunctionAsync(async () => { //get the enabled configurations - var store = await _storeContext.GetCurrentStoreAsync(); + var store = _storeContext.GetCurrentStoreAsync(); var configurations = await (await GetConfigurationsAsync(store.Id)).WhereAwait(async configuration => { if (!configuration.Enabled) diff --git a/src/Plugins/Nop.Plugin.Widgets.FacebookPixel/plugin.json b/src/Plugins/Nop.Plugin.Widgets.FacebookPixel/plugin.json index cae88a9..a662c58 100644 --- a/src/Plugins/Nop.Plugin.Widgets.FacebookPixel/plugin.json +++ b/src/Plugins/Nop.Plugin.Widgets.FacebookPixel/plugin.json @@ -2,7 +2,7 @@ "Group": "Widgets", "FriendlyName": "Facebook Pixel", "SystemName": "Widgets.FacebookPixel", - "Version": "1.13.1", + "Version": "1.13", "SupportedVersions": [ "4.50" ], "Author": "nopCommerce team", "DisplayOrder": 1, diff --git a/src/Presentation/Nop.Web.Framework/Mvc/Routing/NopRedirectResultExecutor.cs b/src/Presentation/Nop.Web.Framework/Mvc/Routing/NopRedirectResultExecutor.cs index 77c1b03..46bd1e5 100644 --- a/src/Presentation/Nop.Web.Framework/Mvc/Routing/NopRedirectResultExecutor.cs +++ b/src/Presentation/Nop.Web.Framework/Mvc/Routing/NopRedirectResultExecutor.cs @@ -63,11 +63,7 @@ namespace Nop.Web.Framework.Mvc.Routing var uri = new Uri(isLocalUrl ? $"{_webHelper.GetStoreLocation().TrimEnd('/')}{url}" : url, UriKind.Absolute); - //Allowlist redirect URI schemes to http and https - if (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps) - result.Url = isLocalUrl ? uri.PathAndQuery : $"{uri.GetLeftPart(UriPartial.Query)}{uri.Fragment}"; - else - result.Url = urlHelper.RouteUrl("Homepage"); + result.Url = isLocalUrl ? uri.PathAndQuery : $"{uri.GetLeftPart(UriPartial.Query)}{uri.Fragment}"; } return base.ExecuteAsync(context, result); diff --git a/src/Presentation/Nop.Web.Framework/TagHelpers/Admin/NopNestedSettingTagHelper.cs b/src/Presentation/Nop.Web.Framework/TagHelpers/Admin/NopNestedSettingTagHelper.cs index 06c7d6c..2b9c9dd 100644 --- a/src/Presentation/Nop.Web.Framework/TagHelpers/Admin/NopNestedSettingTagHelper.cs +++ b/src/Presentation/Nop.Web.Framework/TagHelpers/Admin/NopNestedSettingTagHelper.cs @@ -79,7 +79,6 @@ namespace Nop.Web.Framework.TagHelpers.Admin throw new ArgumentNullException(nameof(output)); var parentSettingName = For.Name; - var jsConsistentParentSettingName = parentSettingName.Replace('.', '_'); var random = CommonHelper.GenerateRandomInteger(); var nestedSettingId = $"nestedSetting{random}"; @@ -106,16 +105,16 @@ namespace Nop.Web.Framework.TagHelpers.Admin if (!DisableAutoGeneration) script.InnerHtml.AppendHtml( - $"$('#{jsConsistentParentSettingName}').click(toggle_{jsConsistentParentSettingName});" + - $"toggle_{jsConsistentParentSettingName}();" + $"$('#{parentSettingName}').click(toggle_{parentSettingName});" + + $"toggle_{parentSettingName}();" ); script.InnerHtml.AppendHtml("});"); if (!DisableAutoGeneration) script.InnerHtml.AppendHtml( - $"function toggle_{jsConsistentParentSettingName}() " + "{" + - $"if ({isNot}$('#{jsConsistentParentSettingName}').is(':checked')) " + "{" + + $"function toggle_{parentSettingName}() " + "{" + + $"if ({isNot}$('#{parentSettingName}').is(':checked')) " + "{" + $"$('#{nestedSettingId}').showElement();" + "} else {" + $"$('#{nestedSettingId}').hideElement();" + diff --git a/src/Presentation/Nop.Web/Areas/Admin/Controllers/CommonController.cs b/src/Presentation/Nop.Web/Areas/Admin/Controllers/CommonController.cs index ab7f9ea..69ca0f6 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Controllers/CommonController.cs +++ b/src/Presentation/Nop.Web/Areas/Admin/Controllers/CommonController.cs @@ -262,8 +262,6 @@ namespace Nop.Web.Areas.Admin.Controllers var action = Request.Form["action"]; var fileName = Request.Form["backupFileName"]; - fileName = _fileProvider.GetFileName(_fileProvider.GetAbsolutePath(fileName)); - var backupPath = _maintenanceService.GetBackupPath(fileName); try diff --git a/src/Presentation/Nop.Web/Areas/Admin/Controllers/ReportController.cs b/src/Presentation/Nop.Web/Areas/Admin/Controllers/ReportController.cs index 4cfd78a..ce71221 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Controllers/ReportController.cs +++ b/src/Presentation/Nop.Web/Areas/Admin/Controllers/ReportController.cs @@ -33,7 +33,7 @@ namespace Nop.Web.Areas.Admin.Controllers public virtual async Task SalesSummary() { - if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.SalesSummaryReport)) + if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageOrders)) return AccessDeniedView(); //prepare model diff --git a/src/Presentation/Nop.Web/Areas/Admin/Controllers/VendorController.cs b/src/Presentation/Nop.Web/Areas/Admin/Controllers/VendorController.cs index 5b6e045..8c1aaf0 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Controllers/VendorController.cs +++ b/src/Presentation/Nop.Web/Areas/Admin/Controllers/VendorController.cs @@ -29,7 +29,6 @@ namespace Nop.Web.Areas.Admin.Controllers { #region Fields - private readonly IAddressAttributeParser _addressAttributeParser; private readonly IAddressService _addressService; private readonly ICustomerActivityService _customerActivityService; private readonly ICustomerService _customerService; @@ -49,8 +48,7 @@ namespace Nop.Web.Areas.Admin.Controllers #region Ctor - public VendorController(IAddressAttributeParser addressAttributeParser, - IAddressService addressService, + public VendorController(IAddressService addressService, ICustomerActivityService customerActivityService, ICustomerService customerService, IGenericAttributeService genericAttributeService, @@ -65,7 +63,6 @@ namespace Nop.Web.Areas.Admin.Controllers IVendorModelFactory vendorModelFactory, IVendorService vendorService) { - _addressAttributeParser = addressAttributeParser; _addressService = addressService; _customerActivityService = customerActivityService; _customerService = customerService; @@ -341,14 +338,6 @@ namespace Nop.Web.Areas.Admin.Controllers (await _vendorAttributeParser.GetAttributeWarningsAsync(vendorAttributesXml)).ToList() .ForEach(warning => ModelState.AddModelError(string.Empty, warning)); - //custom address attributes - var customAttributes = await _addressAttributeParser.ParseCustomAddressAttributesAsync(form); - var customAttributeWarnings = await _addressAttributeParser.GetAttributeWarningsAsync(customAttributes); - foreach (var error in customAttributeWarnings) - { - ModelState.AddModelError(string.Empty, error); - } - if (ModelState.IsValid) { var prevPictureId = vendor.PictureId; @@ -371,7 +360,6 @@ namespace Nop.Web.Areas.Admin.Controllers if (address == null) { address = model.Address.ToEntity
(); - address.CustomAttributes = customAttributes; address.CreatedOnUtc = DateTime.UtcNow; //some validation @@ -387,7 +375,6 @@ namespace Nop.Web.Areas.Admin.Controllers else { address = model.Address.ToEntity(address); - address.CustomAttributes = customAttributes; //some validation if (address.CountryId == 0) diff --git a/src/Presentation/Nop.Web/Areas/Admin/Factories/ReportModelFactory.cs b/src/Presentation/Nop.Web/Areas/Admin/Factories/ReportModelFactory.cs index 6ae5cb0..442885c 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Factories/ReportModelFactory.cs +++ b/src/Presentation/Nop.Web/Areas/Admin/Factories/ReportModelFactory.cs @@ -77,9 +77,6 @@ namespace Nop.Web.Areas.Admin.Factories //get parameters to filter orders var orderStatus = searchModel.OrderStatusId > 0 ? (OrderStatus?)searchModel.OrderStatusId : null; var paymentStatus = searchModel.PaymentStatusId > 0 ? (PaymentStatus?)searchModel.PaymentStatusId : null; - - var currentVendor = await _workContext.GetCurrentVendorAsync(); - var startDateValue = !searchModel.StartDate.HasValue ? null : (DateTime?)_dateTimeHelper.ConvertToUtcTime(searchModel.StartDate.Value, await _dateTimeHelper.GetCurrentTimeZoneAsync()); var endDateValue = !searchModel.EndDate.HasValue ? null @@ -96,7 +93,6 @@ namespace Nop.Web.Areas.Admin.Factories categoryId: searchModel.CategoryId, productId: searchModel.ProductId, manufacturerId: searchModel.ManufacturerId, - vendorId: currentVendor?.Id ?? 0, storeId: searchModel.StoreId, pageIndex: searchModel.Page - 1, pageSize: searchModel.PageSize); @@ -177,7 +173,7 @@ namespace Nop.Web.Areas.Admin.Factories //prepare "group by" filter searchModel.GroupByOptions = (await GroupByOptions.Day.ToSelectListAsync()).ToList(); - + //prepare page parameters searchModel.SetGridPageSize(); diff --git a/src/Presentation/Nop.Web/Areas/Admin/Models/Settings/CommonConfigModel.cs b/src/Presentation/Nop.Web/Areas/Admin/Models/Settings/CommonConfigModel.cs index 1ac0c74..c8c8816 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Models/Settings/CommonConfigModel.cs +++ b/src/Presentation/Nop.Web/Areas/Admin/Models/Settings/CommonConfigModel.cs @@ -1,5 +1,4 @@ -using System.ComponentModel.DataAnnotations; -using Nop.Web.Framework.Models; +using Nop.Web.Framework.Models; using Nop.Web.Framework.Mvc.ModelBinding; namespace Nop.Web.Areas.Admin.Models.Settings @@ -27,7 +26,6 @@ namespace Nop.Web.Areas.Admin.Models.Settings public bool MiniProfilerEnabled { get; set; } [NopResourceDisplayName("Admin.Configuration.AppSettings.Common.ScheduleTaskRunTimeout")] - [UIHint("Int32Nullable")] public int? ScheduleTaskRunTimeout { get; set; } [NopResourceDisplayName("Admin.Configuration.AppSettings.Common.StaticFilesCacheControl")] diff --git a/src/Presentation/Nop.Web/Areas/Admin/Models/Settings/DataConfigModel.cs b/src/Presentation/Nop.Web/Areas/Admin/Models/Settings/DataConfigModel.cs index 2752d2a..f2e5767 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Models/Settings/DataConfigModel.cs +++ b/src/Presentation/Nop.Web/Areas/Admin/Models/Settings/DataConfigModel.cs @@ -1,5 +1,4 @@ -using System.ComponentModel.DataAnnotations; -using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.Rendering; using Nop.Web.Framework.Models; using Nop.Web.Framework.Mvc.ModelBinding; @@ -17,7 +16,6 @@ namespace Nop.Web.Areas.Admin.Models.Settings public SelectList DataProviderTypeValues { get; set; } [NopResourceDisplayName("Admin.Configuration.AppSettings.Data.SQLCommandTimeout")] - [UIHint("Int32Nullable")] public int? SQLCommandTimeout { get; set; } #endregion diff --git a/src/Presentation/Nop.Web/Areas/Admin/Views/Setting/AppSettings.cshtml b/src/Presentation/Nop.Web/Areas/Admin/Views/Setting/AppSettings.cshtml index f28f6e8..2e95ef3 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Views/Setting/AppSettings.cshtml +++ b/src/Presentation/Nop.Web/Areas/Admin/Views/Setting/AppSettings.cshtml @@ -60,7 +60,7 @@ @await Html.PartialAsync("_AppSettings.Cache", Model) @await Html.PartialAsync("_AppSettings.DistributedCache", Model) @await Html.PartialAsync("_AppSettings.Hosting", Model) - @await Html.PartialAsync("_AppSettings.AzureBlob", Model) + @await Html.PartialAsync("_AppSettings.AzureBlob", Model.AzureBlobConfigModel) @await Html.PartialAsync("_AppSettings.Installation", Model) @await Html.PartialAsync("_AppSettings.Plugin", Model) @await Html.PartialAsync("_AppSettings.Common", Model) diff --git a/src/Presentation/Nop.Web/Areas/Admin/Views/Setting/GeneralCommon.cshtml b/src/Presentation/Nop.Web/Areas/Admin/Views/Setting/GeneralCommon.cshtml index ee06824..a668096 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Views/Setting/GeneralCommon.cshtml +++ b/src/Presentation/Nop.Web/Areas/Admin/Views/Setting/GeneralCommon.cshtml @@ -78,7 +78,7 @@ @await Html.PartialAsync("_GeneralCommon.FooterItems", Model) @await Html.PartialAsync("_GeneralCommon.SocialMedia", Model) @await Html.PartialAsync("_GeneralCommon.Favicon", Model) - @await Html.PartialAsync("_GeneralCommon.Sitemap", Model) + @await Html.PartialAsync("_GeneralCommon.Sitemap", Model.SitemapSettings) @await Html.PartialAsync("_GeneralCommon.Seo", Model) @await Html.PartialAsync("_GeneralCommon.Minification", Model) @await Html.PartialAsync("_GeneralCommon.Security", Model) diff --git a/src/Presentation/Nop.Web/Areas/Admin/Views/Setting/_AppSettings.AzureBlob.cshtml b/src/Presentation/Nop.Web/Areas/Admin/Views/Setting/_AppSettings.AzureBlob.cshtml index 7e30115..532c2f1 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Views/Setting/_AppSettings.AzureBlob.cshtml +++ b/src/Presentation/Nop.Web/Areas/Admin/Views/Setting/_AppSettings.AzureBlob.cshtml @@ -1,68 +1,68 @@ -@model AppSettingsModel +@model AzureBlobConfigModel
- +
- - + +
- +
- - + +
- +
- - + +
- +
- - + +
- +
- - + +
- -
+ +
- +
- - + +
-
+
- +
- - + +
diff --git a/src/Presentation/Nop.Web/Areas/Admin/Views/Setting/_GeneralCommon.Sitemap.cshtml b/src/Presentation/Nop.Web/Areas/Admin/Views/Setting/_GeneralCommon.Sitemap.cshtml index c3145ee..f4ea3e4 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Views/Setting/_GeneralCommon.Sitemap.cshtml +++ b/src/Presentation/Nop.Web/Areas/Admin/Views/Setting/_GeneralCommon.Sitemap.cshtml @@ -1,96 +1,96 @@ -@model GeneralCommonSettingsModel +@model SitemapSettingsModel
@(Html.Raw(string.Format(T("Admin.Configuration.Settings.GeneralCommon.Sitemap.Instructions").Text, Url.Action("AllSettings", "Setting", new { settingName = "sitemapxml" }))))
- - + +
- - + +
- +
- - + +
- - + +
- - + +
- - + +
- - + +
- - + +
- - + +
- - + +
- - + +
- - + +
- - + +
- - + +
- - + +
- - + +
- - + +
- - + +
diff --git a/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/Decimal.cshtml b/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/Decimal.cshtml index 8e40911..f16b2d4 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/Decimal.cshtml +++ b/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/Decimal.cshtml @@ -16,7 +16,7 @@ max: @decimal.MaxValue.ToString(culture), decimals: 4, restrictDecimals: true, - format: "#.0000 @Html.Raw(postfix)" + format: "#.0000 @postfix" }); }); \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/DecimalNullable.cshtml b/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/DecimalNullable.cshtml index a4b5b69..f50a840 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/DecimalNullable.cshtml +++ b/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/DecimalNullable.cshtml @@ -16,7 +16,7 @@ max: @decimal.MaxValue.ToString(culture), decimals: 4, restrictDecimals: true, - format: "#.0000 @Html.Raw(postfix)" + format: "#.0000 @postfix" }); }); diff --git a/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/Double.cshtml b/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/Double.cshtml index 3e44463..73cbb58 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/Double.cshtml +++ b/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/Double.cshtml @@ -16,7 +16,7 @@ max: @double.MaxValue.ToString(culture), decimals: 4, restrictDecimals: true, - format: "#.0000 @Html.Raw(postfix)" + format: "#.0000 @postfix" }); }); \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/Int32.cshtml b/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/Int32.cshtml index abc33ad..41acf98 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/Int32.cshtml +++ b/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/Int32.cshtml @@ -16,7 +16,7 @@ max: @int.MaxValue.ToString(culture), decimals: 0, restrictDecimals: true, - format: "# @Html.Raw(postfix)" + format: "# @postfix" }); }); \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/Int32Nullable.cshtml b/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/Int32Nullable.cshtml index a5f1b3f..f159af3 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/Int32Nullable.cshtml +++ b/src/Presentation/Nop.Web/Areas/Admin/Views/Shared/EditorTemplates/Int32Nullable.cshtml @@ -16,7 +16,7 @@ max: @int.MaxValue.ToString(culture), decimals: 0, restrictDecimals: true, - format: "# @Html.Raw(postfix)" + format: "# @postfix" }); }); \ No newline at end of file diff --git a/src/Presentation/Nop.Web/Areas/Admin/sitemap.config b/src/Presentation/Nop.Web/Areas/Admin/sitemap.config index 42dc25b..3412a0d 100644 --- a/src/Presentation/Nop.Web/Areas/Admin/sitemap.config +++ b/src/Presentation/Nop.Web/Areas/Admin/sitemap.config @@ -107,7 +107,7 @@ - + @@ -127,3 +127,4 @@ + diff --git a/src/Presentation/Nop.Web/Controllers/CheckoutController.cs b/src/Presentation/Nop.Web/Controllers/CheckoutController.cs index 9f35803..e548490 100644 --- a/src/Presentation/Nop.Web/Controllers/CheckoutController.cs +++ b/src/Presentation/Nop.Web/Controllers/CheckoutController.cs @@ -25,7 +25,6 @@ using Nop.Web.Extensions; using Nop.Web.Factories; using Nop.Web.Framework.Controllers; using Nop.Web.Models.Checkout; -using Nop.Web.Models.Common; namespace Nop.Web.Controllers { @@ -36,8 +35,7 @@ namespace Nop.Web.Controllers private readonly AddressSettings _addressSettings; private readonly CustomerSettings _customerSettings; - private readonly IAddressAttributeParser _addressAttributeParser; - private readonly IAddressModelFactory _addressModelFactory; + private readonly IAddressAttributeParser _addressAttributeParser; private readonly IAddressService _addressService; private readonly ICheckoutModelFactory _checkoutModelFactory; private readonly ICountryService _countryService; @@ -67,7 +65,6 @@ namespace Nop.Web.Controllers public CheckoutController(AddressSettings addressSettings, CustomerSettings customerSettings, IAddressAttributeParser addressAttributeParser, - IAddressModelFactory addressModelFactory, IAddressService addressService, ICheckoutModelFactory checkoutModelFactory, ICountryService countryService, @@ -93,7 +90,6 @@ namespace Nop.Web.Controllers _addressSettings = addressSettings; _customerSettings = customerSettings; _addressAttributeParser = addressAttributeParser; - _addressModelFactory = addressModelFactory; _addressService = addressService; _checkoutModelFactory = checkoutModelFactory; _countryService = countryService; @@ -324,16 +320,7 @@ namespace Nop.Web.Controllers if (address == null) throw new ArgumentNullException(nameof(address)); - var addressModel = new AddressModel(); - - await _addressModelFactory.PrepareAddressModelAsync(addressModel, - address: address, - excludeProperties: false, - addressSettings: _addressSettings, - prePopulateWithCustomerFields: true, - customer: customer); - - var json = JsonConvert.SerializeObject(addressModel, Formatting.Indented, + var json = JsonConvert.SerializeObject(address, Formatting.Indented, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore @@ -348,7 +335,7 @@ namespace Nop.Web.Controllers /// /// /// - public virtual async Task SaveEditAddress(CheckoutBillingAddressModel model, IFormCollection form, bool opc = false) + public virtual async Task SaveEditAddress(CheckoutBillingAddressModel model, bool opc = false) { try { @@ -363,18 +350,7 @@ namespace Nop.Web.Controllers if (address == null) throw new Exception("Address can't be loaded"); - //custom address attributes - var customAttributes = await _addressAttributeParser.ParseCustomAddressAttributesAsync(form); - var customAttributeWarnings = await _addressAttributeParser.GetAttributeWarningsAsync(customAttributes); - - if(customAttributeWarnings.Any()) - { - return Json(new { error = 1, message = customAttributeWarnings }); - } - address = model.BillingNewAddress.ToEntity(address); - address.CustomAttributes = customAttributes; - await _addressService.UpdateAddressAsync(address); customer.BillingAddressId = address.Id; @@ -1776,7 +1752,6 @@ namespace Nop.Web.Controllers } [HttpPost] - [IgnoreAntiforgeryToken] public virtual async Task OpcSavePaymentInfo(IFormCollection form) { try diff --git a/src/Presentation/Nop.Web/Controllers/CustomerController.cs b/src/Presentation/Nop.Web/Controllers/CustomerController.cs index 9e1ac80..23e1fb9 100644 --- a/src/Presentation/Nop.Web/Controllers/CustomerController.cs +++ b/src/Presentation/Nop.Web/Controllers/CustomerController.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -56,9 +55,9 @@ namespace Nop.Web.Controllers private readonly CaptchaSettings _captchaSettings; private readonly CustomerSettings _customerSettings; private readonly DateTimeSettings _dateTimeSettings; + private readonly IDownloadService _downloadService; private readonly ForumSettings _forumSettings; private readonly GdprSettings _gdprSettings; - private readonly HtmlEncoder _htmlEncoder; private readonly IAddressAttributeParser _addressAttributeParser; private readonly IAddressModelFactory _addressModelFactory; private readonly IAddressService _addressService; @@ -71,7 +70,6 @@ namespace Nop.Web.Controllers private readonly ICustomerModelFactory _customerModelFactory; private readonly ICustomerRegistrationService _customerRegistrationService; private readonly ICustomerService _customerService; - private readonly IDownloadService _downloadService; private readonly IEventPublisher _eventPublisher; private readonly IExportManager _exportManager; private readonly IExternalAuthenticationService _externalAuthenticationService; @@ -106,9 +104,9 @@ namespace Nop.Web.Controllers CaptchaSettings captchaSettings, CustomerSettings customerSettings, DateTimeSettings dateTimeSettings, + IDownloadService downloadService, ForumSettings forumSettings, GdprSettings gdprSettings, - HtmlEncoder htmlEncoder, IAddressAttributeParser addressAttributeParser, IAddressModelFactory addressModelFactory, IAddressService addressService, @@ -121,7 +119,6 @@ namespace Nop.Web.Controllers ICustomerModelFactory customerModelFactory, ICustomerRegistrationService customerRegistrationService, ICustomerService customerService, - IDownloadService downloadService, IEventPublisher eventPublisher, IExportManager exportManager, IExternalAuthenticationService externalAuthenticationService, @@ -152,9 +149,9 @@ namespace Nop.Web.Controllers _captchaSettings = captchaSettings; _customerSettings = customerSettings; _dateTimeSettings = dateTimeSettings; + _downloadService = downloadService; _forumSettings = forumSettings; _gdprSettings = gdprSettings; - _htmlEncoder = htmlEncoder; _addressAttributeParser = addressAttributeParser; _addressModelFactory = addressModelFactory; _addressService = addressService; @@ -167,7 +164,6 @@ namespace Nop.Web.Controllers _customerModelFactory = customerModelFactory; _customerRegistrationService = customerRegistrationService; _customerService = customerService; - _downloadService = downloadService; _eventPublisher = eventPublisher; _exportManager = exportManager; _externalAuthenticationService = externalAuthenticationService; @@ -429,7 +425,7 @@ namespace Nop.Web.Controllers { var fullName = await _customerService.GetCustomerFullNameAsync(customer); var message = await _localizationService.GetResourceAsync("Account.Login.AlreadyLogin"); - _notificationService.SuccessNotification(string.Format(message, _htmlEncoder.Encode(fullName))); + _notificationService.SuccessNotification(string.Format(message, fullName)); } return View(model); @@ -1539,14 +1535,14 @@ namespace Nop.Web.Controllers } [HttpPost] - public virtual async Task AddressEdit(CustomerAddressEditModel model, IFormCollection form) + public virtual async Task AddressEdit(CustomerAddressEditModel model, int addressId, IFormCollection form) { var customer = await _workContext.GetCurrentCustomerAsync(); if (!await _customerService.IsRegisteredAsync(customer)) return Challenge(); //find address (ensure that it belongs to the current customer) - var address = await _customerService.GetCustomerAddressAsync(customer.Id, model.Address.Id); + var address = await _customerService.GetCustomerAddressAsync(customer.Id, addressId); if (address == null) //address is not found return RedirectToRoute("CustomerAddresses"); @@ -1648,15 +1644,7 @@ namespace Nop.Web.Controllers if (changePasswordResult.Success) { _notificationService.SuccessNotification(await _localizationService.GetResourceAsync("Account.ChangePassword.Success")); - - if (string.IsNullOrEmpty(returnUrl)) - return View(model); - - //prevent open redirection attack - if (!Url.IsLocalUrl(returnUrl)) - returnUrl = Url.RouteUrl("Homepage"); - - return new RedirectResult(returnUrl); + return string.IsNullOrEmpty(returnUrl) ? View(model) : new RedirectResult(returnUrl); } //errors @@ -1697,11 +1685,6 @@ namespace Nop.Web.Controllers if (!_customerSettings.AllowCustomersToUploadAvatars) return RedirectToRoute("CustomerInfo"); - var contentType = uploadedFile.ContentType.ToLowerInvariant(); - - if (!contentType.Equals("image/jpeg") && !contentType.Equals("image/gif")) - ModelState.AddModelError("", await _localizationService.GetResourceAsync("Account.Avatar.UploadRules")); - if (ModelState.IsValid) { try @@ -1715,9 +1698,9 @@ namespace Nop.Web.Controllers var customerPictureBinary = await _downloadService.GetDownloadBitsAsync(uploadedFile); if (customerAvatar != null) - customerAvatar = await _pictureService.UpdatePictureAsync(customerAvatar.Id, customerPictureBinary, contentType, null); + customerAvatar = await _pictureService.UpdatePictureAsync(customerAvatar.Id, customerPictureBinary, uploadedFile.ContentType, null); else - customerAvatar = await _pictureService.InsertPictureAsync(customerPictureBinary, contentType, null); + customerAvatar = await _pictureService.InsertPictureAsync(customerPictureBinary, uploadedFile.ContentType, null); } var customerAvatarId = 0; diff --git a/src/Presentation/Nop.Web/Controllers/VendorController.cs b/src/Presentation/Nop.Web/Controllers/VendorController.cs index 84d8aa6..03bf95e 100644 --- a/src/Presentation/Nop.Web/Controllers/VendorController.cs +++ b/src/Presentation/Nop.Web/Controllers/VendorController.cs @@ -220,18 +220,12 @@ namespace Nop.Web.Controllers { try { - var contentType = uploadedFile.ContentType.ToLowerInvariant(); + var contentType = uploadedFile.ContentType; + var vendorPictureBinary = await _downloadService.GetDownloadBitsAsync(uploadedFile); + var picture = await _pictureService.InsertPictureAsync(vendorPictureBinary, contentType, null); - if(!contentType.StartsWith("image/")) - ModelState.AddModelError("", await _localizationService.GetResourceAsync("Vendors.ApplyAccount.Picture.ErrorMessage")); - else - { - var vendorPictureBinary = await _downloadService.GetDownloadBitsAsync(uploadedFile); - var picture = await _pictureService.InsertPictureAsync(vendorPictureBinary, contentType, null); - - if (picture != null) - pictureId = picture.Id; - } + if (picture != null) + pictureId = picture.Id; } catch (Exception) { @@ -320,15 +314,9 @@ namespace Nop.Web.Controllers { try { - var contentType = uploadedFile.ContentType.ToLowerInvariant(); - - if (!contentType.StartsWith("image/")) - ModelState.AddModelError("", await _localizationService.GetResourceAsync("Account.VendorInfo.Picture.ErrorMessage")); - else - { - var vendorPictureBinary = await _downloadService.GetDownloadBitsAsync(uploadedFile); - picture = await _pictureService.InsertPictureAsync(vendorPictureBinary, contentType, null); - } + var contentType = uploadedFile.ContentType; + var vendorPictureBinary = await _downloadService.GetDownloadBitsAsync(uploadedFile); + picture = await _pictureService.InsertPictureAsync(vendorPictureBinary, contentType, null); } catch (Exception) { diff --git a/src/Presentation/Nop.Web/Factories/AddressModelFactory.cs b/src/Presentation/Nop.Web/Factories/AddressModelFactory.cs index bae2ab4..aec71a6 100644 --- a/src/Presentation/Nop.Web/Factories/AddressModelFactory.cs +++ b/src/Presentation/Nop.Web/Factories/AddressModelFactory.cs @@ -77,7 +77,6 @@ namespace Nop.Web.Factories var attributeModel = new AddressAttributeModel { Id = attribute.Id, - ControlId = string.Format(NopCommonDefaults.AddressAttributeControlName, attribute.Id), Name = await _localizationService.GetLocalizedAsync(attribute, x => x.Name), IsRequired = attribute.IsRequired, AttributeControlType = attribute.AttributeControlType, diff --git a/src/Presentation/Nop.Web/Factories/ProductModelFactory.cs b/src/Presentation/Nop.Web/Factories/ProductModelFactory.cs index cdf6839..d7b31e5 100644 --- a/src/Presentation/Nop.Web/Factories/ProductModelFactory.cs +++ b/src/Presentation/Nop.Web/Factories/ProductModelFactory.cs @@ -470,7 +470,7 @@ namespace Nop.Web.Factories { //calculate price for the maximum quantity if we have tier prices, and choose minimal tmpMinPossiblePrice = Math.Min(tmpMinPossiblePrice, - (await _priceCalculationService.GetFinalPriceAsync(associatedProduct, customer, quantity: int.MaxValue)).finalPrice); + (await _priceCalculationService.GetFinalPriceAsync(associatedProduct, customer, quantity: int.MaxValue)).priceWithoutDiscounts); } if (minPossiblePrice.HasValue && tmpMinPossiblePrice >= minPossiblePrice.Value) @@ -1065,7 +1065,7 @@ namespace Nop.Web.Factories { var priceBase = (await _taxService.GetProductPriceAsync(product, (await _priceCalculationService.GetFinalPriceAsync(product, customer, decimal.Zero, _catalogSettings.DisplayTierPricesWithDiscounts, - tierPrice.Quantity)).finalPrice)).price; + tierPrice.Quantity)).priceWithoutDiscounts)).price; var price = await _currencyService.ConvertFromPrimaryStoreCurrencyAsync(priceBase, await _workContext.GetWorkingCurrencyAsync()); diff --git a/src/Presentation/Nop.Web/Models/Common/AddressAttributeModel.cs b/src/Presentation/Nop.Web/Models/Common/AddressAttributeModel.cs index 14cc937..7cb6626 100644 --- a/src/Presentation/Nop.Web/Models/Common/AddressAttributeModel.cs +++ b/src/Presentation/Nop.Web/Models/Common/AddressAttributeModel.cs @@ -11,8 +11,6 @@ namespace Nop.Web.Models.Common Values = new List(); } - public string ControlId { get; set; } - public string Name { get; set; } public bool IsRequired { get; set; } diff --git a/src/Presentation/Nop.Web/Nop.Web.csproj b/src/Presentation/Nop.Web/Nop.Web.csproj index 5a94acc..236d31a 100644 --- a/src/Presentation/Nop.Web/Nop.Web.csproj +++ b/src/Presentation/Nop.Web/Nop.Web.csproj @@ -11,8 +11,6 @@ https://www.nopcommerce.com/ https://github.com/nopSolutions/nopCommerce Git - - true true InProcess diff --git a/src/Presentation/Nop.Web/Program.cs b/src/Presentation/Nop.Web/Program.cs index 808ed9a..86e3a29 100644 --- a/src/Presentation/Nop.Web/Program.cs +++ b/src/Presentation/Nop.Web/Program.cs @@ -8,11 +8,6 @@ var builder = WebApplication.CreateBuilder(args); builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); builder.Configuration.AddJsonFile(NopConfigurationDefaults.AppSettingsFilePath, true, true); -if (!string.IsNullOrEmpty(builder.Environment?.EnvironmentName)) -{ - var path = string.Format(NopConfigurationDefaults.AppSettingsEnvironmentFilePath, builder.Environment.EnvironmentName); - builder.Configuration.AddJsonFile(path, true, true); -} builder.Configuration.AddEnvironmentVariables(); //Add services to the application and configure service provider diff --git a/src/Presentation/Nop.Web/Themes/DefaultClean/Content/css/styles.css b/src/Presentation/Nop.Web/Themes/DefaultClean/Content/css/styles.css index 46a03f1..ea20cbe 100644 --- a/src/Presentation/Nop.Web/Themes/DefaultClean/Content/css/styles.css +++ b/src/Presentation/Nop.Web/Themes/DefaultClean/Content/css/styles.css @@ -6467,6 +6467,12 @@ label, label + * { /* BB codes */ +pre { + white-space: pre-wrap; + white-space: normal; + word-wrap: break-word; +} + .csharpcode { margin: 10px 0; border: 1px dashed #ccc; @@ -6474,9 +6480,6 @@ label, label + * { padding: 10px; font-family: "Courier New", Courier, monospace; color: #000; - - white-space: pre-wrap; - word-wrap: break-word; } .csharpcode .rem { diff --git a/src/Presentation/Nop.Web/Themes/DefaultClean/Content/css/styles.rtl.css b/src/Presentation/Nop.Web/Themes/DefaultClean/Content/css/styles.rtl.css index 82aead1..2b7cae1 100644 --- a/src/Presentation/Nop.Web/Themes/DefaultClean/Content/css/styles.rtl.css +++ b/src/Presentation/Nop.Web/Themes/DefaultClean/Content/css/styles.rtl.css @@ -6490,6 +6490,12 @@ label, label + * { /* BB codes */ +pre { + white-space: pre-wrap; + white-space: normal; + word-wrap: break-word; +} + .csharpcode { margin: 10px 0; border: 1px dashed #ccc; @@ -6497,9 +6503,6 @@ label, label + * { padding: 10px; font-family: "Courier New", Courier, monospace; color: #000; - - white-space: pre-wrap; - word-wrap: break-word; } .csharpcode .rem { diff --git a/src/Presentation/Nop.Web/Views/Boards/Forum.cshtml b/src/Presentation/Nop.Web/Views/Boards/Forum.cshtml index 4147c52..2bafa92 100644 --- a/src/Presentation/Nop.Web/Views/Boards/Forum.cshtml +++ b/src/Presentation/Nop.Web/Views/Boards/Forum.cshtml @@ -14,6 +14,7 @@ @await Component.InvokeAsync("ForumBreadcrumb", new { forumId = Model.Id }) @await Html.PartialAsync("_ForumHeader") @await Component.InvokeAsync("Widget", new { widgetZone = PublicWidgetZones.BoardsForumAfterHeader, additionalData = Model }) +
@if (Model.ForumFeedsEnabled) diff --git a/src/Presentation/Nop.Web/Views/Boards/Topic.cshtml b/src/Presentation/Nop.Web/Views/Boards/Topic.cshtml index cf2aff7..006a523 100644 --- a/src/Presentation/Nop.Web/Views/Boards/Topic.cshtml +++ b/src/Presentation/Nop.Web/Views/Boards/Topic.cshtml @@ -13,6 +13,7 @@ @await Component.InvokeAsync("ForumBreadcrumb", new { forumTopicId = Model.Id }) @await Html.PartialAsync("_ForumHeader") @await Component.InvokeAsync("Widget", new { widgetZone = PublicWidgetZones.BoardsTopicAfterHeader, additionalData = Model }) +

@Model.Subject

diff --git a/src/Presentation/Nop.Web/Views/Customer/Addresses.cshtml b/src/Presentation/Nop.Web/Views/Customer/Addresses.cshtml index 329d673..2a2e51e 100644 --- a/src/Presentation/Nop.Web/Views/Customer/Addresses.cshtml +++ b/src/Presentation/Nop.Web/Views/Customer/Addresses.cshtml @@ -17,6 +17,7 @@

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

+ @if (Model.Addresses.Count > 0) {
diff --git a/src/Presentation/Nop.Web/Views/Customer/Avatar.cshtml b/src/Presentation/Nop.Web/Views/Customer/Avatar.cshtml index d3d7923..0cd4344 100644 --- a/src/Presentation/Nop.Web/Views/Customer/Avatar.cshtml +++ b/src/Presentation/Nop.Web/Views/Customer/Avatar.cshtml @@ -26,7 +26,7 @@ avatar
} - +
diff --git a/src/Presentation/Nop.Web/Views/Product/_ProductEstimateShipping.cshtml b/src/Presentation/Nop.Web/Views/Product/_ProductEstimateShipping.cshtml index a4da3ea..d3ebbd8 100644 --- a/src/Presentation/Nop.Web/Views/Product/_ProductEstimateShipping.cshtml +++ b/src/Presentation/Nop.Web/Views/Product/_ProductEstimateShipping.cshtml @@ -31,10 +31,10 @@ cityEl: '#@Html.IdFor(model => model.City)', requestDelay: @Model.RequestDelay, localizedData: { - noShippingOptionsMessage: '@JavaScriptEncoder.Default.Encode(T("Shipping.EstimateShippingPopUp.NoShippingOptions").Text)', - countryErrorMessage: '@JavaScriptEncoder.Default.Encode(T("Shipping.EstimateShipping.Country.Required").Text)', - zipPostalCodeErrorMessage: '@JavaScriptEncoder.Default.Encode(T("Shipping.EstimateShipping.ZipPostalCode.Required").Text)', - cityErrorMessage: '@JavaScriptEncoder.Default.Encode(T("Shipping.EstimateShipping.City.Required").Text)', + noShippingOptionsMessage: '@T("Shipping.EstimateShippingPopUp.NoShippingOptions")', + countryErrorMessage: '@T("Shipping.EstimateShipping.Country.Required")', + zipPostalCodeErrorMessage: '@T("Shipping.EstimateShipping.ZipPostalCode.Required")', + cityErrorMessage: '@T("Shipping.EstimateShipping.City.Required")', }, urlFactory: function (address) { var params = $.param({ @@ -62,7 +62,7 @@ load: function () { if (!$.magnificPopup.instance.isOpen) { var shippingTitle = $('
').addClass('shipping-title') - .append($('').addClass('shipping-price-title').text('@JavaScriptEncoder.Default.Encode(T("Products.EstimateShipping.PriceTitle").Text)')) + .append($('').addClass('shipping-price-title').text('@T("Products.EstimateShipping.PriceTitle")')) .append($('').addClass('shipping-loading')); $('#open-estimate-shipping-popup-@Model.ProductId').html(shippingTitle); } @@ -83,22 +83,22 @@ var shippingContent = $('#open-estimate-shipping-popup-@Model.ProductId'); var shippingTitle = $('
').addClass('shipping-title') - .append($('').addClass('shipping-price-title').text('@JavaScriptEncoder.Default.Encode(T("Products.EstimateShipping.PriceTitle").Text)')) + .append($('').addClass('shipping-price-title').text('@T("Products.EstimateShipping.PriceTitle")')) .append($('').addClass('shipping-price').text(option.price)); shippingContent.html(shippingTitle); var estimatedDelivery = $('
').addClass('estimated-delivery') .append($('
').addClass('shipping-address') - .append($('').text('@JavaScriptEncoder.Default.Encode(T("Products.EstimateShipping.ToAddress").Text) ' + option.address.countryName + ', ' + (option.address.stateProvinceName ? option.address.stateProvinceName + ', ' : '') + (popUp.settings.useCity ? option.address.city : option.address.zipPostalCode) + ' @JavaScriptEncoder.Default.Encode(T("Products.EstimateShipping.ViaProvider").Text) ' + option.provider)) + .append($('').text('@T("Products.EstimateShipping.ToAddress") ' + option.address.countryName + ', ' + (option.address.stateProvinceName ? option.address.stateProvinceName + ', ' : '') + (popUp.settings.useCity ? option.address.city : option.address.zipPostalCode) + ' @T("Products.EstimateShipping.ViaProvider") ' + option.provider)) .append($('').addClass('arrow-down'))); if (option.deliveryDate && option.deliveryDate !== '-') - estimatedDelivery.append($('
').addClass('shipping-date').text('@JavaScriptEncoder.Default.Encode(T("Products.EstimateShipping.EstimatedDeliveryPrefix").Text) ' + option.deliveryDate)); + estimatedDelivery.append($('
').addClass('shipping-date').text('@T("Products.EstimateShipping.EstimatedDeliveryPrefix") ' + option.deliveryDate)); shippingContent.append(estimatedDelivery); } else { $('#open-estimate-shipping-popup-@Model.ProductId') - .html($('').text('@JavaScriptEncoder.Default.Encode(T("Products.EstimateShipping.NoSelectedShippingOption").Text)')) + .html($('').text('@T("Products.EstimateShipping.NoSelectedShippingOption")')) .append($('').addClass('arrow-down')); } } diff --git a/src/Presentation/Nop.Web/Views/Shared/Components/EuCookieLaw/Default.cshtml b/src/Presentation/Nop.Web/Views/Shared/Components/EuCookieLaw/Default.cshtml index 12092fb..cb7711c 100644 --- a/src/Presentation/Nop.Web/Views/Shared/Components/EuCookieLaw/Default.cshtml +++ b/src/Presentation/Nop.Web/Views/Shared/Components/EuCookieLaw/Default.cshtml @@ -1,4 +1,5 @@ -