This commit is contained in:
l.gabrysiak 2024-08-21 16:29:02 +02:00
parent 6c30c84067
commit a61f230d0a
96 changed files with 1799 additions and 642 deletions

View File

@ -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 .

View File

@ -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<T>(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<T>(json);
_perRequestCache.Set(key.Key, item);
using var _ = _locker.Lock();
_keys.Add(key.Key);
return (true, item);
}

View File

@ -9,11 +9,5 @@
/// Gets the path to file that contains app settings
/// </summary>
public static string AppSettingsFilePath => "App_Data/appsettings.json";
/// <summary>
/// Gets the path to file that contains app settings for specific hosting environment
/// </summary>
/// <remarks>0 - Environment name</remarks>
public static string AppSettingsEnvironmentFilePath => "App_Data/appsettings.{0}.json";
}
}

View File

@ -13,7 +13,7 @@
/// <summary>
/// Gets the minor store version
/// </summary>
public const string MINOR_VERSION = "4";
public const string MINOR_VERSION = "0";
/// <summary>
/// Gets the full store version

View File

@ -42,20 +42,19 @@ namespace Nop.Data.Migrations
#endregion
#region Utils
/// <summary>
/// Returns the instances for found types implementing FluentMigrator.IMigration which ready to Up process
/// Returns the instances for found types implementing FluentMigrator.IMigration
/// </summary>
/// <param name="assembly">Assembly to find migrations</param>
/// <param name="migrationProcessType">Type of migration process; pass MigrationProcessType.NoMatter to load all migrations</param>
/// <param name="migrationProcessType">Type of migration process; pass null to load all migrations</param>
/// <returns>The instances for found types implementing FluentMigrator.IMigration</returns>
protected virtual IEnumerable<IMigrationInfo> GetUpMigrations(Assembly assembly, MigrationProcessType migrationProcessType = MigrationProcessType.NoMatter)
protected virtual IEnumerable<IMigrationInfo> GetMigrations(Assembly assembly, MigrationProcessType migrationProcessType = MigrationProcessType.NoMatter)
{
var migrations = _filteringMigrationSource
.GetMigrations(t =>
{
var migrationAttribute = t.GetCustomAttribute<NopMigrationAttribute>();
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<IMigration>();
return migrations
.Select(m => _migrationRunnerConventions.GetMigrationInfoForMigration(m))
.OrderBy(migration => migration.Version);
}
/// <summary>
/// Returns the instances for found types implementing FluentMigrator.IMigration which ready to Down process
/// </summary>
/// <param name="assembly">Assembly to find migrations</param>
/// <param name="migrationProcessType">Type of migration process; pass MigrationProcessType.NoMatter to load all migrations</param>
/// <returns>The instances for found types implementing FluentMigrator.IMigration</returns>
protected virtual IEnumerable<IMigrationInfo> GetDownMigrations(Assembly assembly, MigrationProcessType migrationProcessType = MigrationProcessType.NoMatter)
{
var migrations = _filteringMigrationSource
.GetMigrations(t =>
{
var migrationAttribute = t.GetCustomAttribute<NopMigrationAttribute>();
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<IMigration>();
return migrations
.Select(m => _migrationRunnerConventions.GetMigrationInfoForMigration(m))
//.OrderBy(m => m.Migration.GetType().GetCustomAttribute<NopMigrationAttribute>().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
}
/// <summary>
/// Executes all found (and applied) migrations
/// Executes all found (and unapplied) migrations
/// </summary>
/// <param name="assembly">Assembly to find the migration</param>
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);

View File

@ -18,7 +18,7 @@
<PackageReference Include="FluentMigrator.Runner" Version="3.3.1" />
<PackageReference Include="linq2db" Version="3.6.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="4.0.0" />
<PackageReference Include="MySql.Data" Version="8.0.31" />
<PackageReference Include="MySql.Data" Version="8.0.27" />
<PackageReference Include="Npgsql" Version="6.0.1" />
<PackageReference Include="System.Net.NameResolution" Version="4.3.0" />
</ItemGroup>

View File

@ -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<ExternalAuthenticationRecord> _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<ExternalAuthenticationRecord> 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
/// <returns>Result of an authentication</returns>
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);

View File

@ -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<IAclService>();
var customerRoleIds = await aclService.GetCustomerRoleIdsWithAccessAsync(product);
foreach (var id in customerRoleIds)
await aclService.InsertAclRecordAsync(productCopy, id);
//tier prices
await CopyTierPricesAsync(product, productCopy);

View File

@ -407,7 +407,7 @@ namespace Nop.Services.Catalog
/// <summary>
/// Gets a key pattern to clear cache
/// </summary>
public static string FilterableSpecificationAttributeOptionsPrefix => "Nop.specificationattributeoption";
public static string FilterableSpecificationAttributeOptionsPrefix => "Nop.filterablespecificationattributeoptions";
/// <summary>
/// Gets a key for specification attribute groups caching by product id

View File

@ -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:

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -1659,18 +1659,11 @@ namespace Nop.Services.ExportImport
//category mappings
var categories = isNew || !allProductsCategoryIds.ContainsKey(product.Id) ? Array.Empty<int>() : allProductsCategoryIds[product.Id];
var storesIds = product.LimitedToStores
? (await _storeMappingService.GetStoresIdsWithAccessAsync(product)).ToList()
: new List<int>();
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;

View File

@ -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);

View File

@ -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;
}

View File

@ -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++)
{

View File

@ -74,11 +74,11 @@ namespace Nop.Services.Orders
/// <summary>
/// Get sales summary report
/// </summary>
/// <param name="storeId">Store identifier (orders placed in a specific store); 0 to load all records</param>
/// <param name="vendorId">Vendor identifier; 0 to load all records</param>
/// <param name="categoryId">Category identifier; 0 to load all records</param>
/// <param name="productId">Product identifier; 0 to load all records</param>
/// <param name="manufacturerId">Manufacturer identifier; 0 to load all records</param>
/// <param name="storeId">Store identifier (orders placed in a specific store); 0 to load all records</param>
/// <param name="vendorId">Vendor identifier; 0 to load all records</param>
/// <param name="createdFromUtc">Order created date from (UTC); null to load all records</param>
/// <param name="createdToUtc">Order created date to (UTC); null to load all records</param>
/// <param name="os">Order status; null to load all records</param>

View File

@ -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
/// <summary>
/// Get sales summary report
/// </summary>
/// <param name="storeId">Store identifier (orders placed in a specific store); 0 to load all records</param>
/// <param name="vendorId">Vendor identifier; 0 to load all records</param>
/// <param name="categoryId">Category identifier; 0 to load all records</param>
/// <param name="productId">Product identifier; 0 to load all records</param>
/// <param name="manufacturerId">Manufacturer identifier; 0 to load all records</param>
/// <param name="storeId">Store identifier (orders placed in a specific store); 0 to load all records</param>
/// <param name="vendorId">Vendor identifier; 0 to load all records</param>
/// <param name="createdFromUtc">Order created date from (UTC); null to load all records</param>
/// <param name="createdToUtc">Order created date to (UTC); null to load all records</param>
/// <param name="os">Order status; null to load all records</param>
@ -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

View File

@ -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, $"<a href=\"{url}\">{requiredProductName}</a>", requiredProductRequiredQuantity)
? string.Format(warningLocale, $"<a href=\"{urlHelper.RouteUrl(nameof(Product), new { SeName = await _urlRecordService.GetSeNameAsync(requiredProduct) })}\">{requiredProductName}</a>", 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;
}
}
}
}
}
/// <summary>

View File

@ -229,7 +229,7 @@ namespace Nop.Services.ScheduleTasks
}
finally
{
if (!_disposed && _timer != null)
if (!_disposed)
{
if (RunOnlyOnce)
Dispose();

View File

@ -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)

View File

@ -410,26 +410,18 @@ namespace Nop.Services.Shipping
public virtual async Task<IShipmentTracker> 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

View File

@ -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}

View File

@ -8,7 +8,7 @@
<form asp-controller="GoogleAuthenticator" asp-action="Configure" method="post">
<selection class="content">
<div class="container-fluid">
<dv class="container-fluid">
<div class="card card-default">
<div class="card-body">
<p>
@ -102,6 +102,5 @@
})
</div>
</div>
</div>
</selection>
</form>

View File

@ -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,

View File

@ -1,23 +0,0 @@
using System;
using System.Net.Http;
namespace PayPalCheckoutSdk.Payments
{
/// <summary>
/// Voids, or cancels, an authorized payment, by ID. You cannot void an authorized payment that has been fully captured.
/// </summary>
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";
}
}
}

View File

@ -78,12 +78,6 @@ namespace Nop.Plugin.Payments.PayPalCommerce
"PAYMENT.CAPTURE.REFUNDED"
};
/// <summary>
/// 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
/// </summary>
public static List<string> CurrenciesWithoutDecimals => new() { "HUF", "JPY", "TWD" };
/// <summary>
/// Gets a name of the view component to display payment info in public store
/// </summary>

View File

@ -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<VoidRequest, PayPalCheckoutSdk.Payments.Authorization>(settings, request);
return await HandleCheckoutRequestAsync<AuthorizationsVoidRequest, object>(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);

View File

@ -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,

View File

@ -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<string>(customer, EasyPostDefaults.ShipmentIdAttribute, order.StoreId);

View File

@ -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,

View File

@ -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<IActionResult> Configure()
{
//load settings for a chosen store scope
var storeScope = await _storeContext.GetActiveStoreScopeConfigurationAsync();
var shipStationSettings = await _settingService.LoadSettingAsync<ShipStationSettings>(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<IActionResult> 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<ShipStationSettings>(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<IActionResult> Webhook()
{
var userName = _webHelper.QueryString<string>("SS-UserName");
var password = _webHelper.QueryString<string>("SS-Password");
//load settings for a chosen store scope
var storeScope = await _storeContext.GetActiveStoreScopeConfigurationAsync();
var shipStationSettings = await _settingService.LoadSettingAsync<ShipStationSettings>(storeScope);
if (!userName.Equals(shipStationSettings.UserName) || !password.Equals(shipStationSettings.Password))
return Content(string.Empty);
var action = _webHelper.QueryString<string>("action") ?? string.Empty;
if (Request.Method == WebRequestMethods.Http.Post &&
action.Equals("shipnotify", StringComparison.InvariantCultureIgnoreCase))
{
var orderNumber = _webHelper.QueryString<string>("order_number");
var carrier = _webHelper.QueryString<string>("carrier");
var service = _webHelper.QueryString<string>("service");
var trackingNumber = _webHelper.QueryString<string>("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<string>("start_date");
var endDateParam = _webHelper.QueryString<string>("end_date");
var pageIndex = _webHelper.QueryString<int>("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");
}
}
}

View File

@ -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
{
/// <summary>
/// Represents object for the configuring services on application startup
/// </summary>
public class NopStartup : INopStartup
{
/// <summary>
/// Add and configure any of the middleware
/// </summary>
/// <param name="services">Collection of service descriptors</param>
/// <param name="configuration">Configuration of the application</param>
public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
services.AddScoped<IShipStationService, ShipStationService>();
}
/// <summary>
/// Configure the using of added middleware
/// </summary>
/// <param name="application">Builder for configuring an application's request pipeline</param>
public void Configure(IApplicationBuilder application)
{
}
/// <summary>
/// Gets order of this startup configuration implementation
/// </summary>
public int Order => 3000;
}
}

View File

@ -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; }
}
}

View File

@ -0,0 +1,57 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Copyright>Copyright © Nop Solutions, Ltd</Copyright>
<Company>Nop Solutions, Ltd</Company>
<Authors>Nop Solutions, Ltd</Authors>
<PackageLicenseUrl></PackageLicenseUrl>
<PackageProjectUrl>https://www.nopcommerce.com/</PackageProjectUrl>
<RepositoryUrl>https://github.com/nopSolutions/nopCommerce</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<OutputPath>..\..\Presentation\Nop.Web\Plugins\Shipping.ShipStation</OutputPath>
<OutDir>$(OutputPath)</OutDir>
<!--Set this parameter to true to get the dlls copied from the NuGet cache to the output of your project.
You need to set this parameter to true if your plugin has a nuget package
to ensure that the dlls copied from the NuGet cache to the output of your project-->
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<None Remove="logo.jpg" />
<None Remove="plugin.json" />
<None Remove="Views\Configure.cshtml" />
<None Remove="Views\_ViewImports.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="plugin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Views\Configure.cshtml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Views\_ViewImports.cshtml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Presentation\Nop.Web\Nop.Web.csproj" />
<ClearPluginAssemblies Include="$(MSBuildProjectDirectory)\..\..\Build\ClearPluginAssemblies.proj" />
</ItemGroup>
<ItemGroup>
<WCFMetadata Include="Connected Services" />
</ItemGroup>
<!-- This target execute after "Build" target -->
<Target Name="NopTarget" AfterTargets="Build">
<!-- Delete unnecessary libraries from plugins path -->
<MSBuild Projects="@(ClearPluginAssemblies)" Properties="PluginPath=$(MSBuildProjectDirectory)\$(OutDir)" Targets="NopClear" />
</Target>
</Project>

View File

@ -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
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<OutputPath>..\..\Presentation\Nop.Web\Plugins\PLUGIN_OUTPUT_DIRECTORY</OutputPath>
<OutDir>$(OutputPath)</OutDir>
<!--Set this parameter to true to get the dlls copied from the NuGet cache to the output of your project.
You need to set this parameter to true if your plugin has a nuget package
to ensure that the dlls copied from the NuGet cache to the output of your project-->
<CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ClearPluginAssemblies Include="$(MSBuildProjectDirectory)\..\..\Build\ClearPluginAssemblies.proj" />
</ItemGroup>
<!-- This target execute after "Build" target -->
<Target Name="NopTarget" AfterTargets="Build">
<!-- Delete unnecessary libraries from plugins path -->
<MSBuild Projects="@(ClearPluginAssemblies)" Properties="PluginPath=$(MSBuildProjectDirectory)\$(OutDir)" Targets="NopClear" />
</Target>
</Project>
Replace “PLUGIN_OUTPUT_DIRECTORY” in the code above with your real plugin output directory name.
Its 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. Its very convenient.

View File

@ -0,0 +1,18 @@
namespace Nop.Plugin.Shipping.ShipStation
{
/// <summary>
/// Represents a packing type
/// </summary>
public enum PackingType
{
/// <summary>
/// Pack by dimensions
/// </summary>
PackByDimensions,
/// <summary>
/// Pack by volume
/// </summary>
PackByVolume
}
}

View File

@ -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
{
/// <summary>
/// Register routes
/// </summary>
/// <param name="endpointRouteBuilder">Route builder</param>
public void RegisterRoutes(IEndpointRouteBuilder endpointRouteBuilder)
{
//Webhook
endpointRouteBuilder.MapControllerRoute("Plugin.Payments.ShipStation.WebhookHandler", "Plugins/ShipStation/Webhook",
new { controller = "ShipStation", action = "Webhook" });
}
public int Priority => 0;
}
}

View File

@ -0,0 +1,20 @@
namespace Nop.Plugin.Shipping.ShipStation
{
public enum ServiceType
{
/// <summary>
/// All services
/// </summary>
All,
/// <summary>
/// Domestic services
/// </summary>
Domestic,
/// <summary>
/// International services
/// </summary>
International
}
}

View File

@ -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
{
/// <summary>
/// Gets all rates
/// </summary>
/// <param name="shippingOptionRequest"></param>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the
/// </returns>
Task<IList<ShipStationServiceRate>> GetAllRatesAsync(GetShippingOptionRequest shippingOptionRequest);
/// <summary>
/// Create or update shipping
/// </summary>
/// <param name="orderNumber">Order number</param>
/// <param name="carrier">Carrier</param>
/// <param name="service">Service</param>
/// <param name="trackingNumber">Tracking number</param>
/// <returns>A task that represents the asynchronous operation</returns>
Task CreateOrUpdateShippingAsync(string orderNumber, string carrier, string service, string trackingNumber);
/// <summary>
/// Get XML view of orders to sending to the ShipStation service
/// </summary>
/// <param name="startDate">Created date from (UTC); null to load all records</param>
/// <param name="endDate">Created date to (UTC); null to load all records</param>
/// <param name="pageIndex">Page index</param>
/// <param name="pageSize">Page size</param>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the xML view of orders
/// </returns>
Task<string> GetXmlOrdersAsync(DateTime? startDate, DateTime? endDate, int pageIndex, int pageSize);
/// <summary>
/// Gets a string that defines the required format of date time
/// </summary>
string DateFormat { get; }
}
}

View File

@ -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
/// <returns>A task that represents the asynchronous operation</returns>
protected virtual async Task<string> 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();
}
/// <returns>A task that represents the asynchronous operation</returns>
private async Task<int> ConvertFromPrimaryMeasureDimensionAsync(decimal quantity, MeasureDimension usedMeasureDimension)
{
return Convert.ToInt32(Math.Ceiling(await _measureService.ConvertFromPrimaryMeasureDimensionAsync(quantity, usedMeasureDimension)));
}
/// <returns>A task that represents the asynchronous operation</returns>
protected virtual async Task<bool> TryGetError(string data)
{
var flag = false;
try
{
var rez = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
if (rez.ContainsKey("message"))
{
flag = true;
await _logger.ErrorAsync(rez["message"]);
}
}
catch (JsonSerializationException)
{
}
return flag;
}
/// <returns>A task that represents the asynchronous operation</returns>
protected virtual async Task<IList<ShipStationServiceRate>> 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<GetShippingOptionRequest.PackageItem>
{
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<GetShippingOptionRequest.PackageItem>
{
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<ShipStationServiceRate>() : JsonConvert.DeserializeObject<List<ShipStationServiceRate>>(data);
}
/// <returns>A task that represents the asynchronous operation</returns>
protected virtual async Task<IList<Carrier>> 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<Carrier>() : JsonConvert.DeserializeObject<List<Carrier>>(data);
});
if (!rez.Any())
await _staticCacheManager.RemoveAsync(_carriersCacheKey);
return rez;
}
/// <returns>A task that represents the asynchronous operation</returns>
protected virtual async Task<IList<Service>> 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<IList<Service>>(data);
return serviceList;
}).ToListAsync();
return services.ToList();
}
/// <returns>A task that represents the asynchronous operation</returns>
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);
}
/// <returns>A task that represents the asynchronous operation</returns>
protected virtual async Task WriteOrderItemsToXmlAsync(XmlWriter writer, ICollection<OrderItem> 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();
}
/// <returns>A task that represents the asynchronous operation</returns>
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",
};
}
/// <returns>A task that represents the asynchronous operation</returns>
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
/// <summary>
/// Gets all rates
/// </summary>
/// <param name="shippingOptionRequest"></param>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the
/// </returns>
public virtual async Task<IList<ShipStationServiceRate>> 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();
}
/// <summary>
/// Create or update shipping
/// </summary>
/// <param name="orderNumber">Order number</param>
/// <param name="carrier">Carrier</param>
/// <param name="service">Service</param>
/// <param name="trackingNumber">Tracking number</param>
/// <returns>A task that represents the asynchronous operation</returns>
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);
}
}
/// <summary>
/// Get XML view of orders to sending to the ShipStation service
/// </summary>
/// <param name="startDate">Created date from (UTC); null to load all records</param>
/// <param name="endDate">Created date to (UTC); null to load all records</param>
/// <param name="pageIndex">Page index</param>
/// <param name="pageSize">Page size</param>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the xML view of orders
/// </returns>
public async Task<string> 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;
}
/// <summary>
/// Date format
/// </summary>
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<Service>
{
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; }
/// <summary>
/// Determines whether the specified objects are equal
/// </summary>
/// <param name="first">The first object of type T to compare</param>
/// <param name="second">The second object of type T to compare</param>
/// <returns>true if the specified objects are equal; otherwise, false</returns>
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
}
}

View File

@ -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
{
/// <summary>
/// Fixed rate or by weight shipping computation method
/// </summary>
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
/// <summary>
/// Gets available shipping options
/// </summary>
/// <param name="getShippingOptionRequest">A request for getting shipping options</param>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the represents a response of getting shipping rate options
/// </returns>
public async Task<GetShippingOptionResponse> 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;
}
/// <summary>
/// Gets fixed shipping rate (if shipping rate computation method allows it and the rate can be calculated before checkout).
/// </summary>
/// <param name="getShippingOptionRequest">A request for getting shipping options</param>
/// <returns>
/// 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
/// </returns>
public Task<decimal?> GetFixedRateAsync(GetShippingOptionRequest getShippingOptionRequest)
{
if (getShippingOptionRequest == null)
throw new ArgumentNullException(nameof(getShippingOptionRequest));
return Task.FromResult<decimal?>(null);
}
/// <summary>
/// Get associated shipment tracker
/// </summary>
/// <returns>
/// A task that represents the asynchronous operation
/// The task result contains the shipment tracker
/// </returns>
public Task<IShipmentTracker> GetShipmentTrackerAsync()
{
return Task.FromResult<IShipmentTracker>(null);
}
/// <summary>
/// Gets a configuration page URL
/// </summary>
public override string GetConfigurationPageUrl()
{
return $"{_webHelper.GetStoreLocation()}Admin/ShipStation/Configure";
}
/// <summary>
/// Install plugin
/// </summary>
/// <returns>A task that represents the asynchronous operation</returns>
public override async Task InstallAsync()
{
//settings
var settings = new ShipStationSettings
{
PackingPackageVolume = 5184
};
await _settingService.SaveSettingAsync(settings);
//locales
await _localizationService.AddOrUpdateLocaleResourceAsync(new Dictionary<string, string>
{
["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();
}
/// <summary>
/// Uninstall plugin
/// </summary>
/// <returns>A task that represents the asynchronous operation</returns>
public override async Task UninstallAsync()
{
//settings
await _settingService.DeleteSettingAsync<ShipStationSettings>();
//locales
await _localizationService.DeleteLocaleResourcesAsync("Enums.Nop.Plugin.Shipping.ShipStation");
await _localizationService.DeleteLocaleResourcesAsync("Plugins.Shipping.ShipStation");
await base.UninstallAsync();
}
#endregion
}
}

View File

@ -0,0 +1,33 @@
using System;
using System.Globalization;
namespace Nop.Plugin.Shipping.ShipStation
{
public class ShipStationServiceRate
{
/// <summary>
/// Service name
/// </summary>
public string ServiceName { get; set; }
/// <summary>
/// Service code
/// </summary>
public string ServiceCode { get; set; }
/// <summary>
/// Service code
/// </summary>
public string ShipmentCost { get; set; }
/// <summary>
/// Other cost
/// </summary>
public string OtherCost { get; set; }
/// <summary>
/// Total cost
/// </summary>
public decimal TotalCost => Convert.ToDecimal(ShipmentCost, new CultureInfo("en-US")) + Convert.ToDecimal(OtherCost, new CultureInfo("en-US"));
}
}

View File

@ -0,0 +1,42 @@
using Nop.Core.Configuration;
namespace Nop.Plugin.Shipping.ShipStation
{
public class ShipStationSettings : ISettings
{
/// <summary>
/// API key
/// </summary>
public string ApiKey { get; set; }
/// <summary>
/// API secret
/// </summary>
public string ApiSecret { get; set; }
/// <summary>
/// Set to true if need pass dimensions to the ShipStation server
/// </summary>
public bool PassDimensions { get; set; }
/// <summary>
/// Packing type
/// </summary>
public PackingType PackingType { get; set; }
/// <summary>
/// Package volume
/// </summary>
public int PackingPackageVolume { get; set; }
/// <summary>
/// ShipStation user name
/// </summary>
public string UserName { get; set; }
/// <summary>
/// ShipStation password
/// </summary>
public string Password { get; set; }
}
}

View File

@ -0,0 +1,147 @@
@{
Layout = "_ConfigurePlugin";
}
@using Nop.Plugin.Shipping.ShipStation
@model Nop.Plugin.Shipping.ShipStation.Models.ShipStationModel
@await Component.InvokeAsync("StoreScopeConfiguration")
<form asp-controller="ShipStation" asp-action="Configure" method="post">
<div class="cards-group">
<div class="card card-default">
<div class="card-body">
<h3>Setup Instructions</h3>
<ul>
<li>Register or login on the <strong><a href="https://www.shipstation.com/?ref=partner-nopcommerce&utm_campaign=partner-referrals&utm_source=nopcommerce&utm_medium=partner-referral" target="_blank">ShipStation</a></strong> site</li>
<li>Go to the "Settings » API Settings" page and get the <strong>API Key</strong> and the <strong>API Secret</strong> and copy them on the plugin settings</li>
<li>Select <strong>Selling Channels</strong> from the left-hand sidebar, then choose <strong>Store Setup</strong>.</li>
<li>Click <strong>+ Connect a Store or Marketplace</strong>.</li>
<li>Choose the <strong>Custom Store</strong> option</li>
<li>Enter the "@Model.WebhookURL" to the <strong>URL to Custom XML Page</strong> setting</li>
<li>Create a <strong>Username</strong> and <strong>Password</strong>, enter them into the settings forms (the ShipStation form and the nopCommerce form). <em style="color: red">Do not use the ShipStation or nopCommerce user credentials for this</em>.</li>
<li>Don't change the <strong>Statuses</strong> section</li>
<li>Save the nopCommerce settings by pressing the <strong>Save</strong> button.</li>
<li>On the ShipStation form press Test your connection using the <strong>Test Connection</strong> button</li>
<li>Save the settings using the <strong>Connect</strong> button</li>
</ul>
<hr />
<div class="form-group row">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="ApiKey_OverrideForStore" asp-input="ApiKey" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="ApiKey" />
</div>
<div class="col-md-9">
<nop-editor asp-for="ApiKey" />
<span asp-validation-for="ApiKey"></span>
</div>
</div>
<div class="form-group row">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="ApiSecret_OverrideForStore" asp-input="ApiSecret" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="ApiSecret" />
</div>
<div class="col-md-9">
<nop-editor asp-for="ApiSecret" />
<span asp-validation-for="ApiSecret"></span>
</div>
</div>
<div class="form-group row">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="UserName_OverrideForStore" asp-input="UserName" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="UserName" />
</div>
<div class="col-md-9">
<nop-editor asp-for="UserName" />
<span asp-validation-for="UserName"></span>
</div>
</div>
<div class="form-group row">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="Password_OverrideForStore" asp-input="Password" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="Password" />
</div>
<div class="col-md-9">
<nop-editor asp-for="Password" html-attributes="@(new { value = Model.Password })" />
<span asp-validation-for="Password"></span>
</div>
</div>
<div class="form-group row">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="PassDimensions_OverrideForStore" asp-input="PassDimensions" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="PassDimensions" />
</div>
<div class="col-md-9">
<nop-editor asp-for="PassDimensions" />
<span asp-validation-for="PassDimensions"></span>
</div>
</div>
<nop-nested-setting asp-for="PassDimensions" disable-auto-generation="true">
<div class="form-group row" id="pnlPackingType">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="PackingType_OverrideForStore" asp-input="PackingType" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="PackingTypeValues" />
</div>
<div class="col-md-9">
<nop-select asp-for="PackingType" asp-items="Model.PackingTypeValues" />
</div>
</div>
<nop-nested-setting asp-for="PackingTypeValues" disable-auto-generation="true">
<div class="form-group row" id="pnlPackingPackageVolume">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="PackingPackageVolume_OverrideForStore" asp-input="PackingPackageVolume" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="PackingPackageVolume" />
</div>
<div class="col-md-9">
<nop-editor asp-for="PackingPackageVolume" />
<span asp-validation-for="PackingPackageVolume"></span>
</div>
</div>
</nop-nested-setting>
</nop-nested-setting>
<div class="form-group row">
<div class="col-md-3">
&nbsp;
</div>
<div class="col-md-9">
<button type="submit" name="save" class="btn btn-primary">@T("Admin.Common.Save")</button>
</div>
</div>
</div>
</div>
</div>
</form>
<script>
$(document).ready(function() {
$("#PackingType").change(togglePackingType);
$(@Html.IdFor(model => model.PassDimensions)).change(togglePassDimensions);
togglePassDimensions();
});
function togglePackingType() {
var selectedPackingTypeId = $("#PackingType").val();
if (selectedPackingTypeId == @(((int) PackingType.PackByDimensions).ToString())) {
$('#pnlPackingPackageVolume').hideElement();
} else if (selectedPackingTypeId == @(((int) PackingType.PackByVolume).ToString())) {
$('#pnlPackingPackageVolume').showElement();
}
}
function togglePassDimensions() {
togglePackingType();
if ($(@Html.IdFor(model => model.PassDimensions)).is(':checked')) {
$('#pnlPackingType').showElement();
} else {
$('#pnlPackingType').hideElement();
$('#pnlPackingPackageVolume').hideElement();
}
}
</script>

View File

@ -0,0 +1,8 @@
@inherits Nop.Web.Framework.Mvc.Razor.NopRazorPage<TModel>
@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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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"
}

View File

@ -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)

View File

@ -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,

View File

@ -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);

View File

@ -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();" +

View File

@ -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

View File

@ -33,7 +33,7 @@ namespace Nop.Web.Areas.Admin.Controllers
public virtual async Task<IActionResult> SalesSummary()
{
if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.SalesSummaryReport))
if (!await _permissionService.AuthorizeAsync(StandardPermissionProvider.ManageOrders))
return AccessDeniedView();
//prepare model

View File

@ -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>();
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)

View File

@ -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();

View File

@ -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")]

View File

@ -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

View File

@ -60,7 +60,7 @@
<nop-card asp-name="app-settings-cache" asp-icon="fas fa-cogs" asp-title="@T("Admin.Configuration.AppSettings.Cache")" asp-hide-block-attribute-name="@hideCacheBlockAttributeName" asp-hide="@hideCacheBlock" asp-advanced="false">@await Html.PartialAsync("_AppSettings.Cache", Model)</nop-card>
<nop-card asp-name="app-settings-distributed-cache" asp-icon="fas fa-cogs" asp-title="@T("Admin.Configuration.AppSettings.DistributedCache")" asp-hide-block-attribute-name="@hideDistributedCacheBlockAttributeName" asp-hide="@hideDistributedCacheBlock" asp-advanced="false">@await Html.PartialAsync("_AppSettings.DistributedCache", Model)</nop-card>
<nop-card asp-name="app-settings-hosting" asp-icon="fas fa-cogs" asp-title="@T("Admin.Configuration.AppSettings.Hosting")" asp-hide-block-attribute-name="@hideHostingBlockAttributeName" asp-hide="@hideHostingBlock" asp-advanced="false">@await Html.PartialAsync("_AppSettings.Hosting", Model)</nop-card>
<nop-card asp-name="app-settings-azure-blob" asp-icon="fas fa-cogs" asp-title="@T("Admin.Configuration.AppSettings.AzureBlob")" asp-hide-block-attribute-name="@hideAzureBlobBlockAttributeName" asp-hide="@hideAzureBlobBlock" asp-advanced="false">@await Html.PartialAsync("_AppSettings.AzureBlob", Model)</nop-card>
<nop-card asp-name="app-settings-azure-blob" asp-icon="fas fa-cogs" asp-title="@T("Admin.Configuration.AppSettings.AzureBlob")" asp-hide-block-attribute-name="@hideAzureBlobBlockAttributeName" asp-hide="@hideAzureBlobBlock" asp-advanced="false">@await Html.PartialAsync("_AppSettings.AzureBlob", Model.AzureBlobConfigModel)</nop-card>
<nop-card asp-name="app-settings-installation" asp-icon="fas fa-cogs" asp-title="@T("Admin.Configuration.AppSettings.Installation")" asp-hide-block-attribute-name="@hideInstallationBlockAttributeName" asp-hide="@hideInstallationBlock" asp-advanced="true">@await Html.PartialAsync("_AppSettings.Installation", Model)</nop-card>
<nop-card asp-name="app-settings-plugin" asp-icon="fas fa-cogs" asp-title="@T("Admin.Configuration.AppSettings.Plugin")" asp-hide-block-attribute-name="@hidePluginBlockAttributeName" asp-hide="@hidePluginBlock" asp-advanced="true">@await Html.PartialAsync("_AppSettings.Plugin", Model)</nop-card>
<nop-card asp-name="app-settings-common" asp-icon="fas fa-cogs" asp-title="@T("Admin.Configuration.AppSettings.Common")" asp-hide-block-attribute-name="@hideCommonBlockAttributeName" asp-hide="@hideCommonBlock" asp-advanced="false">@await Html.PartialAsync("_AppSettings.Common", Model)</nop-card>

View File

@ -78,7 +78,7 @@
<nop-card asp-name="generalcommon-footeritems" asp-icon="fas fa-level-down-alt" asp-title="@T("Admin.Configuration.Settings.GeneralCommon.BlockTitle.FooterItems")" asp-hide-block-attribute-name="@hideFooterItemsBlockAttributeName" asp-hide="@hideFooterItemsBlock" asp-advanced="true">@await Html.PartialAsync("_GeneralCommon.FooterItems", Model)</nop-card>
<nop-card asp-name="generalcommon-socialmedia" asp-icon="fas fa-share-square" asp-title="@T("Admin.Configuration.Settings.GeneralCommon.BlockTitle.SocialMedia")" asp-hide-block-attribute-name="@hideSocialMediaBlockAttributeName" asp-hide="@hideSocialMediaBlock" asp-advanced="false">@await Html.PartialAsync("_GeneralCommon.SocialMedia", Model)</nop-card>
<nop-card asp-name="generalcommon-favicon" asp-icon="far fa-images" asp-title="@T("Admin.Configuration.Settings.GeneralCommon.BlockTitle.FaviconAndAppIcons")" asp-hide-block-attribute-name="@hideFaviconBlockAttributeName" asp-hide="@hideFaviconBlock" asp-advanced="false">@await Html.PartialAsync("_GeneralCommon.Favicon", Model)</nop-card>
<nop-card asp-name="generalcommon-sitemap" asp-icon="fas fa-sitemap" asp-title="@T("Admin.Configuration.Settings.GeneralCommon.BlockTitle.Sitemap")" asp-hide-block-attribute-name="@hideSitemapBlockAttributeName" asp-hide="@hideSitemapBlock" asp-advanced="true">@await Html.PartialAsync("_GeneralCommon.Sitemap", Model)</nop-card>
<nop-card asp-name="generalcommon-sitemap" asp-icon="fas fa-sitemap" asp-title="@T("Admin.Configuration.Settings.GeneralCommon.BlockTitle.Sitemap")" asp-hide-block-attribute-name="@hideSitemapBlockAttributeName" asp-hide="@hideSitemapBlock" asp-advanced="true">@await Html.PartialAsync("_GeneralCommon.Sitemap", Model.SitemapSettings)</nop-card>
<nop-card asp-name="generalcommon-seo" asp-icon="fas fa-search-plus" asp-title="@T("Admin.Configuration.Settings.GeneralCommon.BlockTitle.SEO")" asp-hide-block-attribute-name="@hideSEOBlockAttributeName" asp-hide="@hideSEOBlock" asp-advanced="false">@await Html.PartialAsync("_GeneralCommon.Seo", Model)</nop-card>
<nop-card asp-name="generalcommon-minification" asp-icon="fas fa-compress" asp-title="@T("Admin.Configuration.Settings.GeneralCommon.BlockTitle.Minification")" asp-hide-block-attribute-name="@hideMinificationBlockAttributeName" asp-hide="@hideMinificationBlock" asp-advanced="true">@await Html.PartialAsync("_GeneralCommon.Minification", Model)</nop-card>
<nop-card asp-name="generalcommon-security" asp-icon="fas fa-shield-alt" asp-title="@T("Admin.Configuration.Settings.GeneralCommon.BlockTitle.Security")" asp-hide-block-attribute-name="@hideSecurityBlockAttributeName" asp-hide="@hideSecurityBlock" asp-advanced="true">@await Html.PartialAsync("_GeneralCommon.Security", Model)</nop-card>

View File

@ -1,68 +1,68 @@
@model AppSettingsModel
@model AzureBlobConfigModel
<div class="card-body">
<div class="form-group row">
<div class="col-md-3">
<nop-label asp-for="AzureBlobConfigModel.ConnectionString" />
<nop-label asp-for="ConnectionString" />
</div>
<div class="col-md-9">
<nop-editor asp-for="AzureBlobConfigModel.ConnectionString" />
<span asp-validation-for="AzureBlobConfigModel.ConnectionString"></span>
<nop-editor asp-for="ConnectionString" />
<span asp-validation-for="ConnectionString"></span>
</div>
</div>
<div class="form-group row">
<div class="col-md-3">
<nop-label asp-for="AzureBlobConfigModel.ContainerName" />
<nop-label asp-for="ContainerName" />
</div>
<div class="col-md-9">
<nop-editor asp-for="AzureBlobConfigModel.ContainerName" />
<span asp-validation-for="AzureBlobConfigModel.ContainerName"></span>
<nop-editor asp-for="ContainerName" />
<span asp-validation-for="ContainerName"></span>
</div>
</div>
<div class="form-group row">
<div class="col-md-3">
<nop-label asp-for="AzureBlobConfigModel.EndPoint" />
<nop-label asp-for="EndPoint" />
</div>
<div class="col-md-9">
<nop-editor asp-for="AzureBlobConfigModel.EndPoint" />
<span asp-validation-for="AzureBlobConfigModel.EndPoint"></span>
<nop-editor asp-for="EndPoint" />
<span asp-validation-for="EndPoint"></span>
</div>
</div>
<div class="form-group row">
<div class="col-md-3">
<nop-label asp-for="AzureBlobConfigModel.AppendContainerName" />
<nop-label asp-for="AppendContainerName" />
</div>
<div class="col-md-9">
<nop-editor asp-for="AzureBlobConfigModel.AppendContainerName" />
<span asp-validation-for="AzureBlobConfigModel.AppendContainerName"></span>
<nop-editor asp-for="AppendContainerName" />
<span asp-validation-for="AppendContainerName"></span>
</div>
</div>
<div class="form-group row">
<div class="col-md-3">
<nop-label asp-for="AzureBlobConfigModel.StoreDataProtectionKeys" />
<nop-label asp-for="StoreDataProtectionKeys" />
</div>
<div class="col-md-9">
<nop-editor asp-for="AzureBlobConfigModel.StoreDataProtectionKeys" />
<span asp-validation-for="AzureBlobConfigModel.StoreDataProtectionKeys"></span>
<nop-editor asp-for="StoreDataProtectionKeys" />
<span asp-validation-for="StoreDataProtectionKeys"></span>
</div>
</div>
<nop-nested-setting asp-for="AzureBlobConfigModel.StoreDataProtectionKeys">
<div class="form-group row">
<nop-nested-setting asp-for="StoreDataProtectionKeys">
<div class="form-group row" id="azure-data-protection-container">
<div class="col-md-3">
<nop-label asp-for="AzureBlobConfigModel.DataProtectionKeysContainerName" />
<nop-label asp-for="DataProtectionKeysContainerName" />
</div>
<div class="col-md-9">
<nop-editor asp-for="AzureBlobConfigModel.DataProtectionKeysContainerName" />
<span asp-validation-for="AzureBlobConfigModel.DataProtectionKeysContainerName"></span>
<nop-editor asp-for="DataProtectionKeysContainerName" />
<span asp-validation-for="DataProtectionKeysContainerName"></span>
</div>
</div>
<div class="form-group row">
<div class="form-group row" id="azure-data-protection-vault">
<div class="col-md-3">
<nop-label asp-for="AzureBlobConfigModel.DataProtectionKeysVaultId" />
<nop-label asp-for="DataProtectionKeysVaultId" />
</div>
<div class="col-md-9">
<nop-editor asp-for="AzureBlobConfigModel.DataProtectionKeysVaultId" />
<span asp-validation-for="AzureBlobConfigModel.DataProtectionKeysVaultId"></span>
<nop-editor asp-for="DataProtectionKeysVaultId" />
<span asp-validation-for="DataProtectionKeysVaultId"></span>
</div>
</div>
</nop-nested-setting>

View File

@ -1,96 +1,96 @@
@model GeneralCommonSettingsModel
@model SitemapSettingsModel
<div class="card-body">
@(Html.Raw(string.Format(T("Admin.Configuration.Settings.GeneralCommon.Sitemap.Instructions").Text, Url.Action("AllSettings", "Setting", new { settingName = "sitemapxml" }))))
<div class="form-group row">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="SitemapSettings.SitemapEnabled_OverrideForStore" asp-input="SitemapSettings.SitemapEnabled" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapSettings.SitemapEnabled" />
<nop-override-store-checkbox asp-for="SitemapEnabled_OverrideForStore" asp-input="SitemapEnabled" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapEnabled" />
</div>
<div class="col-md-9">
<nop-editor asp-for="SitemapSettings.SitemapEnabled" />
<span asp-validation-for="SitemapSettings.SitemapEnabled"></span>
<nop-editor asp-for="SitemapEnabled" />
<span asp-validation-for="SitemapEnabled"></span>
</div>
</div>
<nop-nested-setting asp-for="SitemapSettings.SitemapEnabled">
<nop-nested-setting asp-for="SitemapEnabled">
<div class="form-group row advanced-setting">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="SitemapSettings.SitemapPageSize_OverrideForStore" asp-input="SitemapSettings.SitemapPageSize" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapSettings.SitemapPageSize" />
<nop-override-store-checkbox asp-for="SitemapPageSize_OverrideForStore" asp-input="SitemapPageSize" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapPageSize" />
</div>
<div class="col-md-9">
<nop-editor asp-for="SitemapSettings.SitemapPageSize" />
<span asp-validation-for="SitemapSettings.SitemapPageSize"></span>
<nop-editor asp-for="SitemapPageSize" />
<span asp-validation-for="SitemapPageSize"></span>
</div>
</div>
<div class="form-group row advanced-setting">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="SitemapSettings.SitemapIncludeCategories_OverrideForStore" asp-input="SitemapSettings.SitemapIncludeCategories" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapSettings.SitemapIncludeCategories" />
<nop-override-store-checkbox asp-for="SitemapIncludeCategories_OverrideForStore" asp-input="SitemapIncludeCategories" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapIncludeCategories" />
</div>
<div class="col-md-9">
<nop-editor asp-for="SitemapSettings.SitemapIncludeCategories" />
<span asp-validation-for="SitemapSettings.SitemapIncludeCategories"></span>
<nop-editor asp-for="SitemapIncludeCategories" />
<span asp-validation-for="SitemapIncludeCategories"></span>
</div>
</div>
<div class="form-group row advanced-setting">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="SitemapSettings.SitemapIncludeManufacturers_OverrideForStore" asp-input="SitemapSettings.SitemapIncludeManufacturers" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapSettings.SitemapIncludeManufacturers" />
<nop-override-store-checkbox asp-for="SitemapIncludeManufacturers_OverrideForStore" asp-input="SitemapIncludeManufacturers" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapIncludeManufacturers" />
</div>
<div class="col-md-9">
<nop-editor asp-for="SitemapSettings.SitemapIncludeManufacturers" />
<span asp-validation-for="SitemapSettings.SitemapIncludeManufacturers"></span>
<nop-editor asp-for="SitemapIncludeManufacturers" />
<span asp-validation-for="SitemapIncludeManufacturers"></span>
</div>
</div>
<div class="form-group row advanced-setting">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="SitemapSettings.SitemapIncludeProducts_OverrideForStore" asp-input="SitemapSettings.SitemapIncludeProducts" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapSettings.SitemapIncludeProducts" />
<nop-override-store-checkbox asp-for="SitemapIncludeProducts_OverrideForStore" asp-input="SitemapIncludeProducts" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapIncludeProducts" />
</div>
<div class="col-md-9">
<nop-editor asp-for="SitemapSettings.SitemapIncludeProducts" />
<span asp-validation-for="SitemapSettings.SitemapIncludeProducts"></span>
<nop-editor asp-for="SitemapIncludeProducts" />
<span asp-validation-for="SitemapIncludeProducts"></span>
</div>
</div>
<div class="form-group row advanced-setting">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="SitemapSettings.SitemapIncludeProductTags_OverrideForStore" asp-input="SitemapSettings.SitemapIncludeProductTags" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapSettings.SitemapIncludeProductTags" />
<nop-override-store-checkbox asp-for="SitemapIncludeProductTags_OverrideForStore" asp-input="SitemapIncludeProductTags" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapIncludeProductTags" />
</div>
<div class="col-md-9">
<nop-editor asp-for="SitemapSettings.SitemapIncludeProductTags" />
<span asp-validation-for="SitemapSettings.SitemapIncludeProductTags"></span>
<nop-editor asp-for="SitemapIncludeProductTags" />
<span asp-validation-for="SitemapIncludeProductTags"></span>
</div>
</div>
<div class="form-group row advanced-setting">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="SitemapSettings.SitemapIncludeTopics_OverrideForStore" asp-input="SitemapSettings.SitemapIncludeTopics" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapSettings.SitemapIncludeTopics" />
<nop-override-store-checkbox asp-for="SitemapIncludeTopics_OverrideForStore" asp-input="SitemapIncludeTopics" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapIncludeTopics" />
</div>
<div class="col-md-9">
<nop-editor asp-for="SitemapSettings.SitemapIncludeTopics" />
<span asp-validation-for="SitemapSettings.SitemapIncludeTopics"></span>
<nop-editor asp-for="SitemapIncludeTopics" />
<span asp-validation-for="SitemapIncludeTopics"></span>
</div>
</div>
<div class="form-group row advanced-setting">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="SitemapSettings.SitemapIncludeBlogPosts_OverrideForStore" asp-input="SitemapSettings.SitemapIncludeBlogPosts" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapSettings.SitemapIncludeBlogPosts" />
<nop-override-store-checkbox asp-for="SitemapIncludeBlogPosts_OverrideForStore" asp-input="SitemapIncludeBlogPosts" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapIncludeBlogPosts" />
</div>
<div class="col-md-9">
<nop-editor asp-for="SitemapSettings.SitemapIncludeBlogPosts" />
<span asp-validation-for="SitemapSettings.SitemapIncludeBlogPosts"></span>
<nop-editor asp-for="SitemapIncludeBlogPosts" />
<span asp-validation-for="SitemapIncludeBlogPosts"></span>
</div>
</div>
<div class="form-group row advanced-setting">
<div class="col-md-3">
<nop-override-store-checkbox asp-for="SitemapSettings.SitemapIncludeNews_OverrideForStore" asp-input="SitemapSettings.SitemapIncludeNews" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapSettings.SitemapIncludeNews" />
<nop-override-store-checkbox asp-for="SitemapIncludeNews_OverrideForStore" asp-input="SitemapIncludeNews" asp-store-scope="@Model.ActiveStoreScopeConfiguration" />
<nop-label asp-for="SitemapIncludeNews" />
</div>
<div class="col-md-9">
<nop-editor asp-for="SitemapSettings.SitemapIncludeNews" />
<span asp-validation-for="SitemapSettings.SitemapIncludeNews"></span>
<nop-editor asp-for="SitemapIncludeNews" />
<span asp-validation-for="SitemapIncludeNews"></span>
</div>
</div>
</nop-nested-setting>

View File

@ -16,7 +16,7 @@
max: @decimal.MaxValue.ToString(culture),
decimals: 4,
restrictDecimals: true,
format: "#.0000 @Html.Raw(postfix)"
format: "#.0000 @postfix"
});
});
</script>

View File

@ -16,7 +16,7 @@
max: @decimal.MaxValue.ToString(culture),
decimals: 4,
restrictDecimals: true,
format: "#.0000 @Html.Raw(postfix)"
format: "#.0000 @postfix"
});
});
</script>

View File

@ -16,7 +16,7 @@
max: @double.MaxValue.ToString(culture),
decimals: 4,
restrictDecimals: true,
format: "#.0000 @Html.Raw(postfix)"
format: "#.0000 @postfix"
});
});
</script>

View File

@ -16,7 +16,7 @@
max: @int.MaxValue.ToString(culture),
decimals: 0,
restrictDecimals: true,
format: "# @Html.Raw(postfix)"
format: "# @postfix"
});
});
</script>

View File

@ -16,7 +16,7 @@
max: @int.MaxValue.ToString(culture),
decimals: 0,
restrictDecimals: true,
format: "# @Html.Raw(postfix)"
format: "# @postfix"
});
});
</script>

View File

@ -107,7 +107,7 @@
<siteMapNode SystemName="Templates" nopResource="Admin.System.Templates" PermissionNames="ManageMaintenance" controller="Template" action="List" IconClass="far fa-dot-circle"/>
</siteMapNode>
<siteMapNode SystemName="Reports" nopResource="Admin.Reports" PermissionNames="ManageProducts,ManageOrders,OrderCountryReport,ManageCustomers" IconClass="fas fa-chart-line">
<siteMapNode SystemName="Sales summary" nopResource="Admin.Reports.SalesSummary" PermissionNames="SalesSummaryReport" controller="Report" action="SalesSummary" IconClass="far fa-dot-circle"/>
<siteMapNode SystemName="Sales summary" nopResource="Admin.Reports.SalesSummary" PermissionNames="ManageOrders" controller="Report" action="SalesSummary" IconClass="far fa-dot-circle"/>
<siteMapNode SystemName="Low stock" nopResource="Admin.Reports.LowStock" PermissionNames="ManageProducts" controller="Report" action="LowStock" IconClass="far fa-dot-circle"/>
<siteMapNode SystemName="Bestsellers" nopResource="Admin.Reports.Sales.Bestsellers" PermissionNames="ManageOrders" controller="Report" action="Bestsellers" IconClass="far fa-dot-circle"/>
<siteMapNode SystemName="Products never purchased" nopResource="Admin.Reports.Sales.NeverSold" PermissionNames="ManageOrders" controller="Report" action="NeverSold" IconClass="far fa-dot-circle"/>
@ -127,3 +127,4 @@
<siteMapNode SystemName="Third party plugins" nopResource="Admin.Plugins" IconClass="fas fa-bars" />
</siteMapNode>
</siteMap>

View File

@ -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
/// <param name="model"></param>
/// <param name="opc"></param>
/// <returns></returns>
public virtual async Task<IActionResult> SaveEditAddress(CheckoutBillingAddressModel model, IFormCollection form, bool opc = false)
public virtual async Task<IActionResult> 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<IActionResult> OpcSavePaymentInfo(IFormCollection form)
{
try

View File

@ -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<IActionResult> AddressEdit(CustomerAddressEditModel model, IFormCollection form)
public virtual async Task<IActionResult> 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;

View File

@ -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)
{

View File

@ -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,

View File

@ -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());

View File

@ -11,8 +11,6 @@ namespace Nop.Web.Models.Common
Values = new List<AddressAttributeValueModel>();
}
public string ControlId { get; set; }
public string Name { get; set; }
public bool IsRequired { get; set; }

View File

@ -11,8 +11,6 @@
<PackageProjectUrl>https://www.nopcommerce.com/</PackageProjectUrl>
<RepositoryUrl>https://github.com/nopSolutions/nopCommerce</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<!--Starting with the .NET 6 SDK, the [Appname].runtimesettings.dev.json file is no longer generated by default at compile time. If you still want this file to be generated, set the GenerateRuntimeConfigDevFile property to true.-->
<GenerateRuntimeConfigDevFile>true</GenerateRuntimeConfigDevFile>
<!--Set this parameter to true to get the dlls copied from the NuGet cache to the output of your project-->
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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 })
<nop-antiforgery-token />
<div class="forum-info">
<div class="forum-name">
@if (Model.ForumFeedsEnabled)

View File

@ -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 })
<nop-antiforgery-token />
<div class="topic-name">
<h1>@Model.Subject</h1>
</div>

View File

@ -17,6 +17,7 @@
<h1>@T("Account.MyAccount") - @T("Account.CustomerAddresses")</h1>
</div>
<div class="page-body">
<nop-antiforgery-token />
@if (Model.Addresses.Count > 0)
{
<div class="address-list">

View File

@ -26,7 +26,7 @@
<img src="@(Model.AvatarUrl)" alt="avatar" />
</div>
}
<input name="uploadedFile" accept="image/jpeg, image/gif" type="file" />
<input name="uploadedFile" type="file" />
</div>
<div class="buttons">
<button type="submit" name="upload-avatar" class="button-1 upload-avatar-button">@T("Common.Upload")</button>

View File

@ -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 = $('<div/>').addClass('shipping-title')
.append($('<span/>').addClass('shipping-price-title').text('@JavaScriptEncoder.Default.Encode(T("Products.EstimateShipping.PriceTitle").Text)'))
.append($('<span/>').addClass('shipping-price-title').text('@T("Products.EstimateShipping.PriceTitle")'))
.append($('<span/>').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 = $('<div/>').addClass('shipping-title')
.append($('<span/>').addClass('shipping-price-title').text('@JavaScriptEncoder.Default.Encode(T("Products.EstimateShipping.PriceTitle").Text)'))
.append($('<span/>').addClass('shipping-price-title').text('@T("Products.EstimateShipping.PriceTitle")'))
.append($('<span/>').addClass('shipping-price').text(option.price));
shippingContent.html(shippingTitle);
var estimatedDelivery = $('<div/>').addClass('estimated-delivery')
.append($('<div/>').addClass('shipping-address')
.append($('<span/>').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($('<span/>').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($('<i/>').addClass('arrow-down')));
if (option.deliveryDate && option.deliveryDate !== '-')
estimatedDelivery.append($('<div/>').addClass('shipping-date').text('@JavaScriptEncoder.Default.Encode(T("Products.EstimateShipping.EstimatedDeliveryPrefix").Text) ' + option.deliveryDate));
estimatedDelivery.append($('<div/>').addClass('shipping-date').text('@T("Products.EstimateShipping.EstimatedDeliveryPrefix") ' + option.deliveryDate));
shippingContent.append(estimatedDelivery);
} else {
$('#open-estimate-shipping-popup-@Model.ProductId')
.html($('<span/>').text('@JavaScriptEncoder.Default.Encode(T("Products.EstimateShipping.NoSelectedShippingOption").Text)'))
.html($('<span/>').text('@T("Products.EstimateShipping.NoSelectedShippingOption")'))
.append($('<i/>').addClass('arrow-down'));
}
}

View File

@ -1,4 +1,5 @@
<script asp-location="Footer">
<nop-antiforgery-token />
<script asp-location="Footer">
$(document).ready(function () {
$('#eu-cookie-bar-notification').show();

View File

@ -29,6 +29,7 @@
</div>
</div>
<div class="newsletter-result" id="newsletter-result-block"></div>
<nop-antiforgery-token />
<script asp-location="Footer">
function newsletter_subscribe(subscribe) {
var subscribeProgress = $("#subscribe-loading-progress");

View File

@ -25,10 +25,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({

View File

@ -2,7 +2,7 @@
@using Nop.Core.Domain.Catalog;
@foreach (var attribute in Model)
{
var controlId = attribute.ControlId;
var controlId = $"address_attribute_{attribute.Id}";
var textPrompt = attribute.Name;
<div class="inputs custom-attributes">

View File

@ -30,6 +30,7 @@
</div>
<div class="poll-vote-error" id="block-poll-vote-error-@(Model.Id)" style="display:none">
</div>
<nop-antiforgery-token />
<script asp-location="Footer">
$(document).ready(function () {
$('#vote-poll-@(Model.Id)').on('click', function () {

View File

@ -70,7 +70,6 @@
<!--Powered by nopCommerce - https://www.nopCommerce.com-->
</head>
<body>
<nop-antiforgery-token />
@RenderBody()
@NopHtml.GenerateScripts(ResourceLocation.Footer)

View File

@ -44,7 +44,7 @@
</div>
<div class="inputs">
<label>@T("Vendors.ApplyAccount.Picture"):</label>
<input name="uploadedFile" type="file" accept="image/*"/>
<input name="uploadedFile" type="file"/>
</div>
@await Html.PartialAsync("_VendorAttributes", Model.VendorAttributes)
@if (Model.DisplayCaptcha)

View File

@ -41,7 +41,7 @@
</div>
<div class="inputs">
<label asp-for="PictureUrl" asp-postfix=":"></label>
<input name="uploadedFile" type="file" accept="image/*" />
<input name="uploadedFile" type="file" />
@if (!string.IsNullOrEmpty(Model.PictureUrl))
{
<div class="vendor-picture">

View File

@ -20,27 +20,7 @@
success: function(data, textStatus, jqXHR) {
$.each(data,
function(id, value) {
if (id.indexOf("CustomAddressAttributes") >= 0 && Array.isArray(value)) {
$.each(value, function (i, customAttribute) {
if (customAttribute.DefaultValue) {
$(`#${customAttribute.ControlId}`).val(
customAttribute.DefaultValue
);
} else {
$.each(customAttribute.Values, function (j, attributeValue) {
if (attributeValue.IsPreSelected) {
$(`#${customAttribute.ControlId}`).val(attributeValue.Id);
$(
`#${customAttribute.ControlId}_${attributeValue.Id}`
).prop("checked", attributeValue.Id);
}
});
}
});
return;
}
//console.log("id:" + id + "\nvalue:" + value);
if (value !== null) {
var val = $(`#${prefix}${id}`).val(value);
if (id.indexOf('CountryId') >= 0) {

View File

@ -210,50 +210,30 @@ var Billing = {
type: "GET",
url: url,
data: {
addressId: selectedItem,
"addressId": selectedItem
},
success: function (data, textStatus, jqXHR) {
$.each(data, function (id, value) {
if (value === null)
return;
if (id.indexOf("CustomAddressAttributes") >= 0 && Array.isArray(value)) {
$.each(value, function (i, customAttribute) {
if (customAttribute.DefaultValue) {
$(`#${customAttribute.ControlId}`).val(
customAttribute.DefaultValue
);
} else {
$.each(customAttribute.Values, function (j, attributeValue) {
if (attributeValue.IsPreSelected) {
$(`#${customAttribute.ControlId}`).val(attributeValue.Id);
$(
`#${customAttribute.ControlId}_${attributeValue.Id}`
).prop("checked", attributeValue.Id);
}
});
success: function(data, textStatus, jqXHR) {
$.each(data,
function(id, value) {
//console.log("id:" + id + "\nvalue:" + value);
if (value !== null) {
var val = $(`#${prefix}${id}`).val(value);
if (id.indexOf('CountryId') >= 0) {
val.trigger('change');
}
});
return;
}
var val = $(`#${prefix}${id}`).val(value);
if (id.indexOf("CountryId") >= 0) {
val.trigger("change");
}
if (id.indexOf("StateProvinceId") >= 0) {
Billing.setSelectedStateId(value);
}
});
if (id.indexOf('StateProvinceId') >= 0) {
Billing.setSelectedStateId(value);
}
}
});
},
complete: function (jqXHR, textStatus) {
$("#billing-new-address-form").show();
$("#edit-address-button").hide();
$("#delete-address-button").hide();
$("#save-address-button").show();
complete: function(jqXHR, textStatus) {
$('#billing-new-address-form').show();
$('#edit-address-button').hide();
$('#delete-address-button').hide();
$('#save-address-button').show();
},
error: Checkout.ajaxFailure,
error: Checkout.ajaxFailure
});
},

View File

@ -20,7 +20,6 @@ using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
@ -259,10 +258,6 @@ namespace Nop.Tests
services.AddSingleton<IStaticCacheManager, MemoryCacheManager>();
services.AddSingleton<ILocker, MemoryCacheManager>();
var distributedCache = new Mock<IDistributedCache>();
services.AddSingleton(distributedCache.Object);
services.AddSingleton<DistributedCacheManager>();
//services
services.AddTransient<IBackInStockSubscriptionService, BackInStockSubscriptionService>();
services.AddTransient<ICategoryService, CategoryService>();

View File

@ -1,53 +0,0 @@
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Distributed;
using Moq;
using Nop.Core.Caching;
using NUnit.Framework;
namespace Nop.Tests.Nop.Core.Tests.Caching
{
[TestFixture]
public class DistributedCacheManagerTests : BaseNopTest
{
private DistributedCacheManager _staticCacheManager;
private Mock<IDistributedCache> _distributedCache;
[OneTimeSetUp]
public void Setup()
{
_staticCacheManager = GetService<DistributedCacheManager>();
_distributedCache = Mock.Get(GetService<IDistributedCache>());
}
[Test]
public async Task CanSetObjectInCacheAndWillTrackIfRemoved()
{
await _staticCacheManager.SetAsync(new CacheKey("some_key_1"), 1);
_distributedCache.Verify(x=> x.SetAsync("some_key_1", It.IsAny<byte[]>(), It.IsAny<DistributedCacheEntryOptions>(), It.IsAny<CancellationToken>()), Times.Once);
await _staticCacheManager.RemoveByPrefixAsync("some_key_1");
_distributedCache.Verify(x=> x.RemoveAsync("some_key_1", It.IsAny<CancellationToken>()), Times.Once);
}
[Test]
public async Task CanGetAsyncFromCacheAndWillTrackInPerRequestCacheIfRemoved()
{
_distributedCache.Setup(x => x.GetAsync("some_key_2", It.IsAny<CancellationToken>())).ReturnsAsync(Encoding.UTF8.GetBytes("2"));
await _staticCacheManager.GetAsync(new CacheKey("some_key_2"),()=> 2);
await _staticCacheManager.RemoveByPrefixAsync("some_key_2");
_distributedCache.Verify(x=> x.RemoveAsync("some_key_2", It.IsAny<CancellationToken>()), Times.Once);
}
[Test]
public async Task CanGetFromCacheAndWillTrackInPerRequestCacheIfRemoved()
{
_distributedCache.Setup(x => x.Get("some_key_3")).Returns(Encoding.UTF8.GetBytes("3"));
_staticCacheManager.Get(new CacheKey("some_key_3"),()=> 3);
await _staticCacheManager.RemoveByPrefixAsync("some_key_3");
_distributedCache.Verify(x=> x.RemoveAsync("some_key_3", It.IsAny<CancellationToken>()), Times.Once);
}
}
}