diff --git a/src/.idea/.idea.NopCommerce/.idea/.gitignore b/src/.idea/.idea.NopCommerce/.idea/.gitignore new file mode 100644 index 00000000000..4eba5f6ca8c --- /dev/null +++ b/src/.idea/.idea.NopCommerce/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/projectSettingsUpdater.xml +/contentModel.xml +/modules.xml +/.idea.NopCommerce.iml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/src/.idea/.idea.NopCommerce/.idea/.name b/src/.idea/.idea.NopCommerce/.idea/.name new file mode 100644 index 00000000000..be061eeea83 --- /dev/null +++ b/src/.idea/.idea.NopCommerce/.idea/.name @@ -0,0 +1 @@ +NopCommerce \ No newline at end of file diff --git a/src/.idea/.idea.NopCommerce/.idea/encodings.xml b/src/.idea/.idea.NopCommerce/.idea/encodings.xml new file mode 100644 index 00000000000..df87cf951fb --- /dev/null +++ b/src/.idea/.idea.NopCommerce/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/.idea/.idea.NopCommerce/.idea/indexLayout.xml b/src/.idea/.idea.NopCommerce/.idea/indexLayout.xml new file mode 100644 index 00000000000..7b08163cebc --- /dev/null +++ b/src/.idea/.idea.NopCommerce/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/.idea/.idea.NopCommerce/.idea/material_theme_project_new.xml b/src/.idea/.idea.NopCommerce/.idea/material_theme_project_new.xml new file mode 100644 index 00000000000..09b9ee06266 --- /dev/null +++ b/src/.idea/.idea.NopCommerce/.idea/material_theme_project_new.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/src/.idea/.idea.NopCommerce/.idea/vcs.xml b/src/.idea/.idea.NopCommerce/.idea/vcs.xml new file mode 100644 index 00000000000..6c0b8635858 --- /dev/null +++ b/src/.idea/.idea.NopCommerce/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Libraries/Nop.Core/Http/NopRouteNames.cs b/src/Libraries/Nop.Core/Http/NopRouteNames.cs index 0f5cd7896ba..0cb82d95a3a 100644 --- a/src/Libraries/Nop.Core/Http/NopRouteNames.cs +++ b/src/Libraries/Nop.Core/Http/NopRouteNames.cs @@ -48,7 +48,7 @@ public static partial class General /// /// Gets the product search route name /// - public const string SEARCH = "ProductSearch"; + public const string SEARCH = "ProductSearch"; /// /// Gets the compare products route name @@ -703,6 +703,11 @@ public static partial class Ajax /// public const string DELETE_CUSTOM_WISHLIST = "DeleteCustomWishlist"; + /// + /// Gets the edit custom wishlist route name + /// + public const string RENAME_CUSTOM_WISHLIST = "RenameCustomWishlist"; + /// /// Gets the estimate shipping route name /// diff --git a/src/Libraries/Nop.Services/Orders/CustomWishlistService.cs b/src/Libraries/Nop.Services/Orders/CustomWishlistService.cs index 7d5191dd883..2db92b7c7c6 100644 --- a/src/Libraries/Nop.Services/Orders/CustomWishlistService.cs +++ b/src/Libraries/Nop.Services/Orders/CustomWishlistService.cs @@ -17,7 +17,7 @@ public partial class CustomWishlistService : ICustomWishlistService #region Ctor - public CustomWishlistService(IRepository customWishlistRepository, + public CustomWishlistService(IRepository customWishlistRepository, ShoppingCartSettings shoppingCartSettings) { _customWishlistRepository = customWishlistRepository; @@ -56,6 +56,26 @@ public virtual async Task AddCustomWishlistAsync(CustomWishlist item) await _customWishlistRepository.InsertAsync(item); } + public virtual async Task EditCustomWishlistAsync(int wishlistId, string newName, int customerId) + { + var wishlist = await _customWishlistRepository.GetByIdAsync(wishlistId); + if (wishlist == null) + throw new ArgumentException($"Custom wishlist with ID {wishlistId} not found."); + + // Verify ownership + if (wishlist.CustomerId != customerId) + throw new UnauthorizedAccessException("You are not allowed to edit this wishlist."); + + // Validate new name + if (string.IsNullOrWhiteSpace(newName)) + throw new ArgumentException("Wishlist name cannot be empty."); + + wishlist.Name = newName.Trim(); + + await _customWishlistRepository.UpdateAsync(wishlist); + } + + /// /// Removes a custom wishlist item with the specified identifier. /// @@ -69,6 +89,7 @@ public virtual async Task RemoveCustomWishlistAsync(int itemId) } } + /// /// Retrieves a custom wishlist by its unique identifier. /// @@ -81,4 +102,4 @@ public virtual async Task GetCustomWishlistByIdAsync(int itemId) } #endregion -} +} \ No newline at end of file diff --git a/src/Libraries/Nop.Services/Orders/ICustomWishlistService.cs b/src/Libraries/Nop.Services/Orders/ICustomWishlistService.cs index a597e1b2e36..e20d775fe9c 100644 --- a/src/Libraries/Nop.Services/Orders/ICustomWishlistService.cs +++ b/src/Libraries/Nop.Services/Orders/ICustomWishlistService.cs @@ -27,6 +27,7 @@ public partial interface ICustomWishlistService /// /// The unique identifier of the custom wishlist item to remove. Must be a valid identifier of an existing item. Task RemoveCustomWishlistAsync(int itemId); + Task EditCustomWishlistAsync(int wishlistId, string newName, int customer); /// /// Retrieves a custom wishlist by its unique identifier. @@ -35,4 +36,4 @@ public partial interface ICustomWishlistService /// A object representing the custom wishlist with the specified identifier. Returns /// null if no wishlist is found with the given identifier. Task GetCustomWishlistByIdAsync(int itemId); -} +} \ No newline at end of file diff --git a/src/Presentation/Nop.Web.Framework/Mvc/Routing/SlugRouteTransformer.cs b/src/Presentation/Nop.Web.Framework/Mvc/Routing/SlugRouteTransformer.cs index ce36ca6515f..e511b91289c 100644 --- a/src/Presentation/Nop.Web.Framework/Mvc/Routing/SlugRouteTransformer.cs +++ b/src/Presentation/Nop.Web.Framework/Mvc/Routing/SlugRouteTransformer.cs @@ -92,14 +92,12 @@ protected virtual async Task SingleSlugRoutingAsync(HttpContext httpContext, Rou var store = await _storeContext.GetCurrentStoreAsync(); var languages = await _languageService.GetAllLanguagesAsync(storeId: store.Id); var language = languages - .FirstOrDefault(lang => lang.Published && lang.UniqueSeoCode.Equals(langValue?.ToString(), StringComparison.InvariantCultureIgnoreCase)) - ?? languages.FirstOrDefault(); + .FirstOrDefault(lang => lang.UniqueSeoCode.Equals(langValue?.ToString(), StringComparison.InvariantCultureIgnoreCase)) + ?? languages.FirstOrDefault(); - var slugLocalized = await _urlRecordService.GetActiveSlugAsync(urlRecord.EntityId, urlRecord.EntityName, language.Id); + var slugLocalized = await _urlRecordService.GetSeNameAsync(urlRecord.EntityId, urlRecord.EntityName, language.Id, true, false); if (!string.IsNullOrEmpty(slugLocalized) && !slugLocalized.Equals(slug, StringComparison.InvariantCultureIgnoreCase)) { - //we should make validation above because some entities does not have SeName for standard (Id = 0) language (e.g. news, blog posts) - //redirect to the page for current language InternalRedirect(httpContext, values, $"/{language.UniqueSeoCode}/{slugLocalized}", false); return; @@ -208,11 +206,11 @@ protected virtual async Task TryProductCatalogRoutingAsync(HttpContext htt var store = await _storeContext.GetCurrentStoreAsync(); var languages = await _languageService.GetAllLanguagesAsync(storeId: store.Id); var language = languages - .FirstOrDefault(lang => lang.Published && lang.UniqueSeoCode.Equals(langValue?.ToString(), StringComparison.InvariantCultureIgnoreCase)) - ?? languages.FirstOrDefault(); + .FirstOrDefault(lang => lang.UniqueSeoCode.Equals(langValue?.ToString(), StringComparison.InvariantCultureIgnoreCase)) + ?? languages.FirstOrDefault(); - var slugLocalized = await _urlRecordService.GetActiveSlugAsync(urlRecord.EntityId, urlRecord.EntityName, language.Id); - var catalogSlugLocalized = await _urlRecordService.GetActiveSlugAsync(catalogUrlRecord.EntityId, catalogUrlRecord.EntityName, language.Id); + var slugLocalized = await _urlRecordService.GetSeNameAsync(urlRecord.EntityId, urlRecord.EntityName, language.Id, true, false); + var catalogSlugLocalized = await _urlRecordService.GetSeNameAsync(catalogUrlRecord.EntityId, catalogUrlRecord.EntityName, language.Id, true, false); if ((!string.IsNullOrEmpty(slugLocalized) && !slugLocalized.Equals(slug, StringComparison.InvariantCultureIgnoreCase)) || (!string.IsNullOrEmpty(catalogSlugLocalized) && !catalogSlugLocalized.Equals(catalogUrlRecord.Slug, StringComparison.InvariantCultureIgnoreCase))) { diff --git a/src/Presentation/Nop.Web/Controllers/ShoppingCartController.cs b/src/Presentation/Nop.Web/Controllers/ShoppingCartController.cs index bdd1bab2ba4..bbc652d44f2 100644 --- a/src/Presentation/Nop.Web/Controllers/ShoppingCartController.cs +++ b/src/Presentation/Nop.Web/Controllers/ShoppingCartController.cs @@ -1888,6 +1888,51 @@ public virtual async Task MoveToCustomWishlist(int shoppingCartIt }); } + [HttpPost] + public virtual async Task RenameWishlist(int wishlistId, string name) + { + var customer = await _workContext.GetCurrentCustomerAsync(); + var isGuest = await _customerService.IsGuestAsync(customer); + + if (isGuest) + { + return Json(new + { + success = false, + message = await _localizationService.GetResourceAsync("Wishlist.MultipleWishlistNotForGuest") + }); + } + + if (!_shoppingCartSettings.AllowMultipleWishlist) + { + return Json(new + { + success = false, + message = await _localizationService.GetResourceAsync("Wishlist.NotAllowMultipleWishlist") + }); + } + + try + { + await _customWishlistService.EditCustomWishlistAsync(wishlistId, name, customer.Id); + + return Json(new + { + success = true, + message = await _localizationService.GetResourceAsync("Wishlist.Rename.Success") + }); + } + catch (Exception ex) + { + return Json(new + { + success = false, + message = ex.Message + }); + } + } + + [HttpPost] public virtual async Task DeleteWishlist(int wishlistId) { diff --git a/src/Presentation/Nop.Web/Infrastructure/RouteProvider.cs b/src/Presentation/Nop.Web/Infrastructure/RouteProvider.cs index 8a5f3dc388e..ee873b06bdc 100644 --- a/src/Presentation/Nop.Web/Infrastructure/RouteProvider.cs +++ b/src/Presentation/Nop.Web/Infrastructure/RouteProvider.cs @@ -226,6 +226,10 @@ public virtual void RegisterRoutes(IEndpointRouteBuilder endpointRouteBuilder) pattern: $"addcustomwishlist", defaults: new { controller = "ShoppingCart", action = "AddWishlist" }); + endpointRouteBuilder.MapControllerRoute(name: NopRouteNames.Ajax.RENAME_CUSTOM_WISHLIST, + pattern: $"renamecustomwishlist", + defaults: new { controller = "ShoppingCart", action = "RenameWishlist" }); + //comparing products (AJAX) endpointRouteBuilder.MapControllerRoute(name: NopRouteNames.Ajax.ADD_PRODUCT_TO_COMPARE, pattern: $"compareproducts/add/{{productId:min(0)}}", @@ -508,12 +512,12 @@ public virtual void RegisterRoutes(IEndpointRouteBuilder endpointRouteBuilder) pattern: $"{lang}/customer/gdpr", defaults: new { controller = "Customer", action = "GdprTools" }); - //customer check gift card balance + //customer check gift card balance endpointRouteBuilder.MapControllerRoute(name: NopRouteNames.General.CHECK_GIFT_CARD_BALANCE, pattern: $"{lang}/customer/checkgiftcardbalance", defaults: new { controller = "Customer", action = "CheckGiftCardBalance" }); - //customer multi-factor authentication settings + //customer multi-factor authentication settings endpointRouteBuilder.MapControllerRoute(name: NopRouteNames.Standard.MULTI_FACTOR_AUTHENTICATION_SETTINGS, pattern: $"{lang}/customer/multifactorauthentication", defaults: new { controller = "Customer", action = "MultiFactorAuthentication" });