From 3c8f4f3928c7c9e6c50089abb2be3ef4abe3eee4 Mon Sep 17 00:00:00 2001 From: KrzysztofPajak Date: Sun, 5 Jul 2026 10:48:11 +0200 Subject: [PATCH] Lock system-wide settings in store panel & add "All stores" scope in admin Store panel (Grand.Web.Store): - Store owners can no longer change password storage formats (CustomerSettings.DefaultPasswordFormat, HashedPasswordFormat) and LoyaltyPointsSettings.PointsAccumulatedForAllStores - the values are preserved server-side on save and the inputs are removed from the views. These are administrator-managed settings; a store owner able to enable cross-store points accumulation or downgrade password hashing would impact other stores. Admin panel (Grand.Web.Admin): - Store scope selector now offers an "All stores" option so the administrator can manage the global settings record directly; new stores inherit it automatically instead of requiring per-store updates. - GetActiveStore() returns the global scope ("") when no store is selected instead of falling back to the first store. - Removed dead IsStoreManager branches - store managers have no access to the admin panel. Co-Authored-By: Claude Fable 5 --- .../Grand.Web.Admin/Components/StoreScope.cs | 22 +++++++++---------- .../Controllers/BaseAdminController.cs | 11 ++++------ .../Customer.TabCustomerSecurity.cshtml | 18 --------------- .../Partials/Sales.TabLoyaltyPoints.cshtml | 12 ---------- .../Controllers/SettingController.cs | 13 +++++++++++ 5 files changed, 28 insertions(+), 48 deletions(-) diff --git a/src/Web/Grand.Web.Admin/Components/StoreScope.cs b/src/Web/Grand.Web.Admin/Components/StoreScope.cs index 340819741..200b5ae38 100644 --- a/src/Web/Grand.Web.Admin/Components/StoreScope.cs +++ b/src/Web/Grand.Web.Admin/Components/StoreScope.cs @@ -1,6 +1,5 @@ -using Grand.Business.Core.Interfaces.Common.Directory; +using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Common.Stores; -using Grand.Infrastructure; using Grand.Web.AdminShared.Models.Settings; using Grand.Web.Common.Components; using Grand.Web.Common.Helpers; @@ -15,9 +14,8 @@ public class StoreScopeViewComponent : BaseAdminViewComponent #region Fields private readonly IStoreService _storeService; - private readonly IContextAccessor _contextAccessor; - private readonly IGroupService _groupService; private readonly IAdminStoreService _adminStoreService; + private readonly ITranslationService _translationService; #endregion @@ -26,13 +24,11 @@ public class StoreScopeViewComponent : BaseAdminViewComponent public StoreScopeViewComponent( IStoreService storeService, IAdminStoreService adminStoreService, - IGroupService groupService, - IContextAccessor contextAccessor) + ITranslationService translationService) { _adminStoreService = adminStoreService; _storeService = storeService; - _contextAccessor = contextAccessor; - _groupService = groupService; + _translationService = translationService; } #endregion @@ -45,10 +41,14 @@ public async Task InvokeAsync() if (allStores.Count < 2) return Content(""); - if (await _groupService.IsStoreManager(_contextAccessor.WorkContext.CurrentCustomer)) - allStores = allStores.Where(x => x.Id == _contextAccessor.WorkContext.CurrentCustomer.StaffStoreId).ToList(); - var model = new StoreScopeModel(); + + //global scope (all stores) + model.Stores.Add(new StoreModel { + Id = "", + Name = _translationService.GetResource("Admin.Settings.StoreScope.AllStores") + }); + foreach (var s in allStores) model.Stores.Add(new StoreModel { Id = s.Id, diff --git a/src/Web/Grand.Web.Admin/Controllers/BaseAdminController.cs b/src/Web/Grand.Web.Admin/Controllers/BaseAdminController.cs index 49774c2b5..786c7e428 100644 --- a/src/Web/Grand.Web.Admin/Controllers/BaseAdminController.cs +++ b/src/Web/Grand.Web.Admin/Controllers/BaseAdminController.cs @@ -1,5 +1,4 @@ -using Grand.Business.Core.Interfaces.Common.Directory; -using Grand.Business.Core.Interfaces.Common.Stores; +using Grand.Business.Core.Interfaces.Common.Stores; using Grand.Domain.Common; using Grand.Domain.Customers; using Grand.Infrastructure; @@ -24,23 +23,21 @@ protected async Task GetActiveStore() { var storeService = HttpContext.RequestServices.GetRequiredService(); var workContext = HttpContext.RequestServices.GetRequiredService().WorkContext; - var groupService = HttpContext.RequestServices.GetRequiredService(); var stores = await storeService.GetAllStores(); if (stores.Count < 2) return stores.FirstOrDefault()?.Id; - if (await groupService.IsStoreManager(workContext.CurrentCustomer)) return workContext.CurrentCustomer.StaffStoreId; - var storeId = workContext.CurrentCustomer.GetUserFieldFromEntity(SystemCustomerFieldNames .AdminAreaStoreScopeConfiguration); + //empty scope means "all stores" - settings are loaded from/saved to the global scope if (string.IsNullOrEmpty(storeId)) { - return stores.First()?.Id; + return ""; } var store = await storeService.GetStoreById(storeId); - return store != null ? store.Id : stores.First()?.Id; + return store != null ? store.Id : ""; } } \ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Customer.TabCustomerSecurity.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Customer.TabCustomerSecurity.cshtml index 1d6321c51..6e7c142c6 100644 --- a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Customer.TabCustomerSecurity.cshtml +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Customer.TabCustomerSecurity.cshtml @@ -40,24 +40,6 @@ -
-
- -
-
- - -
-
-
-
- -
-
- - -
-
diff --git a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Sales.TabLoyaltyPoints.cshtml b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Sales.TabLoyaltyPoints.cshtml index c71b24ede..c64162cc9 100644 --- a/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Sales.TabLoyaltyPoints.cshtml +++ b/src/Web/Grand.Web.Store/Areas/Store/Views/Setting/Partials/Sales.TabLoyaltyPoints.cshtml @@ -103,18 +103,6 @@
-
-
- -
-
- - -
-
\ No newline at end of file diff --git a/src/Web/Grand.Web.Store/Controllers/SettingController.cs b/src/Web/Grand.Web.Store/Controllers/SettingController.cs index 11aa64f11..aa716bb02 100644 --- a/src/Web/Grand.Web.Store/Controllers/SettingController.cs +++ b/src/Web/Grand.Web.Store/Controllers/SettingController.cs @@ -285,7 +285,13 @@ public async Task Sales(SalesSettingsModel model, if (ModelState.IsValid) { var loyaltyPointsSettings = await settingService.LoadSetting(storeScope); + + // Cross-store accumulation is managed by the administrator - store owners cannot change it + var pointsAccumulatedForAllStores = loyaltyPointsSettings.PointsAccumulatedForAllStores; + loyaltyPointsSettings = model.LoyaltyPointsSettings.ToEntity(loyaltyPointsSettings); + loyaltyPointsSettings.PointsAccumulatedForAllStores = pointsAccumulatedForAllStores; + await settingService.SaveSetting(loyaltyPointsSettings, storeScope); var shoppingCartSettings = await settingService.LoadSetting(storeScope); @@ -625,7 +631,14 @@ public async Task Customer(CustomerSettingsModel model) var customerSettings = await settingService.LoadSetting(storeScope); var addressSettings = await settingService.LoadSetting(storeScope); + // Password storage formats are managed by the administrator - store owners cannot change them + var defaultPasswordFormat = customerSettings.DefaultPasswordFormat; + var hashedPasswordFormat = customerSettings.HashedPasswordFormat; + customerSettings = model.CustomerSettings.ToEntity(customerSettings); + customerSettings.DefaultPasswordFormat = defaultPasswordFormat; + customerSettings.HashedPasswordFormat = hashedPasswordFormat; + await settingService.SaveSetting(customerSettings, storeScope); addressSettings = model.AddressSettings.ToEntity(addressSettings);