diff --git a/.ai/knowledge/scoping.md b/.ai/knowledge/scoping.md index f58444cf9a..19e88f54d7 100644 --- a/.ai/knowledge/scoping.md +++ b/.ai/knowledge/scoping.md @@ -85,6 +85,15 @@ Prices are stored in the store's primary currency and converted for display. Do Code in those paths must receive store, customer, or language **explicitly as parameters**. Reaching for `IWorkContext` there yields null or a stale context. +### Catalog reads take a `CatalogAccess` + +The catalog read services (`IBrandService`, `ICategoryService`, `ICollectionService`, `IProductService` and the product mapping services) take a `CatalogAccess` instead of a customer, a store id and a `showHidden` flag: + +- `CatalogAccess.For(customer, storeId)`: the published records the customer's groups may see in the store. The storefront gets it from the request with `IContextAccessor.CatalogAccess()` (`Grand.Web.Common.Extensions`). +- `CatalogAccess.IncludingHidden(storeId = "")`: every record, unpublished and group-limited ones included, for the admin panels, import, export and the sitemap. A store id still leaves out the records limited to other stores. + +A store id limits the records whenever it is set. Pass `IncludingHidden()` without a store when a back-office screen must see every store's records. + ## Review checklist - [ ] Every list query filters by store, and by vendor when the caller is a vendor. diff --git a/src/Business/Grand.Business.Catalog/Queries/Handlers/GetPersonalizedProductsQueryHandler.cs b/src/Business/Grand.Business.Catalog/Queries/Handlers/GetPersonalizedProductsQueryHandler.cs index 8090984005..c3913254d1 100644 --- a/src/Business/Grand.Business.Catalog/Queries/Handlers/GetPersonalizedProductsQueryHandler.cs +++ b/src/Business/Grand.Business.Catalog/Queries/Handlers/GetPersonalizedProductsQueryHandler.cs @@ -1,5 +1,6 @@ using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Queries.Catalog; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Domain.Catalog; using Grand.Domain.Customers; @@ -28,17 +29,21 @@ public GetPersonalizedProductsQueryHandler( public async Task> Handle(GetPersonalizedProductsQuery request, CancellationToken cancellationToken) { - return await _cacheBase.GetAsync(string.Format(CacheKey.PRODUCTS_CUSTOMER_PERSONAL_KEY, request.CustomerId), + //the result is cached per key shared by many customers, so a query without one must not fill it + ArgumentNullException.ThrowIfNull(request.Customer); + + return await _cacheBase.GetAsync(string.Format(CacheKey.PRODUCTS_CUSTOMER_PERSONAL_KEY, request.Customer.Id), async () => { var query = from cr in _customerProductRepository.Table - where cr.CustomerId == request.CustomerId + where cr.CustomerId == request.Customer.Id orderby cr.DisplayOrder select cr.ProductId; var productIds = query.Take(request.ProductsNumber).ToList(); - var ids = await _productService.GetProductsByIds(productIds.Distinct().ToArray()); + var ids = await _productService.GetProductsByIds(productIds.Distinct().ToArray(), + access: CatalogAccess.For(request.Customer, request.StoreId)); return ids.Where(product => product.Published).ToList(); }); diff --git a/src/Business/Grand.Business.Catalog/Queries/Handlers/GetRecommendedProductsQueryHandler.cs b/src/Business/Grand.Business.Catalog/Queries/Handlers/GetRecommendedProductsQueryHandler.cs index e9b9eb426c..918e6bdb34 100644 --- a/src/Business/Grand.Business.Catalog/Queries/Handlers/GetRecommendedProductsQueryHandler.cs +++ b/src/Business/Grand.Business.Catalog/Queries/Handlers/GetRecommendedProductsQueryHandler.cs @@ -1,5 +1,6 @@ using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Queries.Catalog; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Domain.Catalog; using Grand.Domain.Customers; @@ -28,18 +29,24 @@ public GetRecommendedProductsQueryHandler( public async Task> Handle(GetRecommendedProductsQuery request, CancellationToken cancellationToken) { + //the result is cached per key shared by many customers, so a query without one must not fill it + ArgumentNullException.ThrowIfNull(request.Customer); + + var customerGroupIds = request.Customer.GetCustomerGroupIds(); + return await _cacheBase.GetAsync( - string.Format(CacheKey.PRODUCTS_CUSTOMER_GROUP, string.Join(",", request.CustomerGroupIds), + string.Format(CacheKey.PRODUCTS_CUSTOMER_GROUP, string.Join(",", customerGroupIds), request.StoreId), async () => { var query = from cr in _customerGroupProductRepository.Table - where request.CustomerGroupIds.Contains(cr.CustomerGroupId) + where customerGroupIds.Contains(cr.CustomerGroupId) orderby cr.DisplayOrder select cr.ProductId; var productIds = query.ToList(); - var ids = await _productService.GetProductsByIds(productIds.Distinct().ToArray()); + var ids = await _productService.GetProductsByIds(productIds.Distinct().ToArray(), + access: CatalogAccess.For(request.Customer, request.StoreId)); return ids.Where(product => product.Published).ToList(); }); diff --git a/src/Business/Grand.Business.Catalog/Queries/Handlers/GetSearchProductsQueryHandler.cs b/src/Business/Grand.Business.Catalog/Queries/Handlers/GetSearchProductsQueryHandler.cs index ac9f7efd5d..7cda042dfb 100644 --- a/src/Business/Grand.Business.Catalog/Queries/Handlers/GetSearchProductsQueryHandler.cs +++ b/src/Business/Grand.Business.Catalog/Queries/Handlers/GetSearchProductsQueryHandler.cs @@ -34,8 +34,11 @@ public GetSearchProductsQueryHandler( // Clean up request parameters CleanupRequestParameters(request); - // Access control list. Allowed customer groups - var allowedCustomerGroupsIds = request.CustomerGroupIds?.ToArray() ?? request.Customer.GetCustomerGroupIds(); + // Access control list. Allowed customer groups; the customer is read only when the filter applies + var allowedCustomerGroupsIds = request.CustomerGroupIds?.ToArray() + ?? (request.ShowHidden || _accessControlConfig.IgnoreAcl + ? [] + : request.Customer.GetCustomerGroupIds()); // Build base query var query = _productRepository.Table.AsQueryable(); diff --git a/src/Business/Grand.Business.Catalog/Queries/Handlers/GetSuggestedProductsQueryHandler.cs b/src/Business/Grand.Business.Catalog/Queries/Handlers/GetSuggestedProductsQueryHandler.cs index 3aa1bebb5f..cc544694dd 100644 --- a/src/Business/Grand.Business.Catalog/Queries/Handlers/GetSuggestedProductsQueryHandler.cs +++ b/src/Business/Grand.Business.Catalog/Queries/Handlers/GetSuggestedProductsQueryHandler.cs @@ -1,5 +1,6 @@ using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Queries.Catalog; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Domain.Catalog; using Grand.Domain.Customers; @@ -23,17 +24,23 @@ public GetSuggestedProductsQueryHandler( public async Task> Handle(GetSuggestedProductsQuery request, CancellationToken cancellationToken) { + //the result is cached per key shared by many customers, so a query without one must not fill it + ArgumentNullException.ThrowIfNull(request.Customer); + + var customerTagIds = request.Customer.CustomerTags.ToArray(); + return await _cacheBase.GetAsync( - string.Format(CacheKey.PRODUCTS_CUSTOMER_TAG, string.Join(",", request.CustomerTagIds)), async () => + string.Format(CacheKey.PRODUCTS_CUSTOMER_TAG, string.Join(",", customerTagIds)), async () => { var query = from cr in _customerTagProductRepository.Table - where request.CustomerTagIds.Contains(cr.CustomerTagId) + where customerTagIds.Contains(cr.CustomerTagId) orderby cr.DisplayOrder select cr.ProductId; var productIds = query.Take(request.ProductsNumber).ToList(); - var ids = await _productService.GetProductsByIds(productIds.Distinct().ToArray()); + var ids = await _productService.GetProductsByIds(productIds.Distinct().ToArray(), + access: CatalogAccess.For(request.Customer, request.StoreId)); return ids.Where(product => product.Published).ToList(); }); diff --git a/src/Business/Grand.Business.Catalog/Services/Brands/BrandService.cs b/src/Business/Grand.Business.Catalog/Services/Brands/BrandService.cs index 0065024f06..0870eb7346 100644 --- a/src/Business/Grand.Business.Catalog/Services/Brands/BrandService.cs +++ b/src/Business/Grand.Business.Catalog/Services/Brands/BrandService.cs @@ -1,9 +1,9 @@ using Grand.Business.Core.Interfaces.Catalog.Brands; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Domain; using Grand.Domain.Catalog; using Grand.Domain.Customers; -using Grand.Infrastructure; using Grand.Infrastructure.Caching; using Grand.Infrastructure.Caching.Constants; using Grand.Infrastructure.Configuration; @@ -24,12 +24,10 @@ public class BrandService : IBrandService /// public BrandService(ICacheBase cacheBase, IRepository brandRepository, - IContextAccessor contextAccessor, IMediator mediator, AccessControlConfig accessControlConfig) { _cacheBase = cacheBase; _brandRepository = brandRepository; - _contextAccessor = contextAccessor; _mediator = mediator; _accessControlConfig = accessControlConfig; } @@ -39,7 +37,6 @@ public BrandService(ICacheBase cacheBase, #region Fields private readonly IRepository _brandRepository; - private readonly IContextAccessor _contextAccessor; private readonly IMediator _mediator; private readonly ICacheBase _cacheBase; private readonly AccessControlConfig _accessControlConfig; @@ -52,41 +49,41 @@ public BrandService(ICacheBase cacheBase, /// Gets all brands /// /// Brand name - /// Store ident + /// Whose view of the brands to return, and the store whose limitations apply /// Page index /// Page size - /// A value that indicates if it should shows hidden records /// Brands public virtual async Task> GetAllBrands(string brandName, - string storeId, + CatalogAccess access, int pageIndex = 0, - int pageSize = int.MaxValue, - bool showHidden = false) + int pageSize = int.MaxValue) { + ArgumentNullException.ThrowIfNull(access); + var query = from m in _brandRepository.Table select m; - if (!showHidden) + if (!access.ShowHidden) query = query.Where(m => m.Published); if (!string.IsNullOrWhiteSpace(brandName)) query = query.Where(m => m.Name != null && m.Name.ToLower().Contains(brandName.ToLower())); if (!_accessControlConfig.IgnoreAcl || - (!string.IsNullOrEmpty(storeId) && !_accessControlConfig.IgnoreStoreLimitations)) + (!string.IsNullOrEmpty(access.StoreId) && !_accessControlConfig.IgnoreStoreLimitations)) { - if (!showHidden && !_accessControlConfig.IgnoreAcl) + if (!access.ShowHidden && !_accessControlConfig.IgnoreAcl) { //Limited to customer groups rules - var allowedCustomerGroupsIds = _contextAccessor.WorkContext.CurrentCustomer.GetCustomerGroupIds(); + var allowedCustomerGroupsIds = access.Customer.GetCustomerGroupIds(); query = from p in query where !p.LimitedToGroups || allowedCustomerGroupsIds.Any(x => p.CustomerGroups.Contains(x)) select p; } - if (!string.IsNullOrEmpty(storeId) && !_accessControlConfig.IgnoreStoreLimitations) + if (!string.IsNullOrEmpty(access.StoreId) && !_accessControlConfig.IgnoreStoreLimitations) //Limited to stores rules query = from p in query - where !p.LimitedToStores || p.Stores.Contains(storeId) + where !p.LimitedToStores || p.Stores.Contains(access.StoreId) select p; } diff --git a/src/Business/Grand.Business.Catalog/Services/Categories/CategoryService.cs b/src/Business/Grand.Business.Catalog/Services/Categories/CategoryService.cs index 744e3bed10..800df78688 100644 --- a/src/Business/Grand.Business.Catalog/Services/Categories/CategoryService.cs +++ b/src/Business/Grand.Business.Catalog/Services/Categories/CategoryService.cs @@ -1,12 +1,11 @@ using Grand.Business.Core.Extensions; using Grand.Business.Core.Interfaces.Catalog.Categories; using Grand.Business.Core.Interfaces.Common.Security; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Domain; using Grand.Domain.Catalog; using Grand.Domain.Customers; -using Grand.Domain.Stores; -using Grand.Infrastructure; using Grand.Infrastructure.Caching; using Grand.Infrastructure.Caching.Constants; using Grand.Infrastructure.Configuration; @@ -27,20 +26,17 @@ public class CategoryService : ICategoryService /// /// Cache manager /// Category repository - /// Work context /// Mediator /// ACL service /// public CategoryService(ICacheBase cacheBase, IRepository categoryRepository, - IContextAccessor contextAccessor, IMediator mediator, IAclService aclService, AccessControlConfig accessControlConfig) { _cacheBase = cacheBase; _categoryRepository = categoryRepository; - _contextAccessor = contextAccessor; _mediator = mediator; _aclService = aclService; _accessControlConfig = accessControlConfig; @@ -51,15 +47,11 @@ public CategoryService(ICacheBase cacheBase, #region Fields private readonly IRepository _categoryRepository; - private readonly IContextAccessor _contextAccessor; private readonly IMediator _mediator; private readonly ICacheBase _cacheBase; private readonly IAclService _aclService; private readonly AccessControlConfig _accessControlConfig; - private Customer CurrentCustomer => _contextAccessor.WorkContext.CurrentCustomer; - private Store CurrentStore => _contextAccessor.StoreContext.CurrentStore; - #endregion #region Methods @@ -69,19 +61,19 @@ public CategoryService(ICacheBase cacheBase, /// /// Parent Id /// Category name - /// Store ident + /// Whose view of the categories to return, and the store whose limitations apply /// Page index /// Page size - /// A value that indicates if it should shows hidden records /// Categories public virtual async Task> GetAllCategories(string parentId, string categoryName, - string storeId, - int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false) + CatalogAccess access, int pageIndex = 0, int pageSize = int.MaxValue) { + ArgumentNullException.ThrowIfNull(access); + var query = from c in _categoryRepository.Table select c; - if (!showHidden) + if (!access.ShowHidden) query = query.Where(c => c.Published); if (!string.IsNullOrWhiteSpace(categoryName)) query = query.Where(m => m.Name.ToLowerInvariant().Contains(categoryName.ToLowerInvariant())); @@ -89,25 +81,7 @@ public virtual async Task> GetAllCategories(string parentId if (parentId != null) query = query.Where(m => m.ParentCategoryId == parentId); - if (!_accessControlConfig.IgnoreAcl || - (!string.IsNullOrEmpty(storeId) && !_accessControlConfig.IgnoreStoreLimitations)) - { - if (!showHidden && !_accessControlConfig.IgnoreAcl) - { - //Limited to customer group (access control list) - var allowedCustomerGroupsIds = CurrentCustomer.GetCustomerGroupIds(); - query = from p in query - where !p.LimitedToGroups || allowedCustomerGroupsIds.Any(x => p.CustomerGroups.Contains(x)) - select p; - } - - if (!string.IsNullOrEmpty(storeId) && !_accessControlConfig.IgnoreStoreLimitations) - //Limited to stores rule - query = from p in query - where !p.LimitedToStores || p.Stores.Contains(storeId) - select p; - } - + query = ApplyAccess(query, access); query = query.OrderBy(c => c.DisplayOrder).ThenBy(c => c.Name); //pagination @@ -117,49 +91,32 @@ public virtual async Task> GetAllCategories(string parentId /// /// Gets categories for menu /// + /// Customer whose groups limit the result + /// Store whose limitations apply /// Categories - public virtual async Task> GetMenuCategories() + public virtual async Task> GetMenuCategories(Customer customer, string storeId) { var query = from c in _categoryRepository.Table select c; query = query.Where(c => c.Published && c.IncludeInMenu); - - switch (_accessControlConfig.IgnoreAcl) - { - case true when - string.IsNullOrEmpty(CurrentStore.Id) || _accessControlConfig.IgnoreStoreLimitations: - return await _categoryRepository.ToListAsync(query); - case false: - { - //Limited to customer group (access control list) - var allowedCustomerGroupsIds = CurrentCustomer.GetCustomerGroupIds(); - query = from p in query - where !p.LimitedToGroups || allowedCustomerGroupsIds.Any(x => p.CustomerGroups.Contains(x)) - select p; - break; - } - } - - if (!string.IsNullOrEmpty(CurrentStore.Id) && !_accessControlConfig.IgnoreStoreLimitations) - //Limited to stores rule - query = from p in query - where !p.LimitedToStores || p.Stores.Contains(CurrentStore.Id) - select p; + query = ApplyAccess(query, CatalogAccess.For(customer, storeId)); return await _categoryRepository.ToListAsync(query); } /// /// Gets all categories filtered by parent category identifier /// + /// Whose view of the categories to return, and the store whose limitations apply /// Parent category identifier - /// A value that indicates if it should shows hidden records /// A value that indicates if we should load all child levels /// Categories - public virtual async Task> GetAllCategoriesByParentCategoryId(string parentCategoryId = "", - bool showHidden = false, bool includeAllLevels = false) + public virtual async Task> GetAllCategoriesByParentCategoryId(CatalogAccess access, + string parentCategoryId = "", bool includeAllLevels = false) { - var categories = await GetChildCategories(parentCategoryId, showHidden); + ArgumentNullException.ThrowIfNull(access); + + var categories = await GetChildCategories(parentCategoryId, access); if (!includeAllLevels) return categories; //walk the tree outside the cache: every level resolves through its own cache entry, so no @@ -167,42 +124,26 @@ public virtual async Task> GetAllCategoriesByParentCategoryId(st //once per ancestor var allLevels = new List(categories); foreach (var category in categories) - allLevels.AddRange(await GetAllCategoriesByParentCategoryId(category.Id, showHidden, true)); + allLevels.AddRange(await GetAllCategoriesByParentCategoryId(access, category.Id, true)); return allLevels; } /// - /// Gets the direct children of a category, filtered by the current customer's groups and store + /// Gets the direct children of a category, as the given access sees them /// - private async Task> GetChildCategories(string parentCategoryId, bool showHidden) + private async Task> GetChildCategories(string parentCategoryId, CatalogAccess access) { - var key = string.Format(CacheKey.CATEGORIES_BY_PARENT_CATEGORY_ID_KEY, parentCategoryId, showHidden, - string.Join(",", CurrentCustomer.GetCustomerGroupIds()), CurrentStore.Id); + //hidden records are not filtered by customer groups, so the key does not depend on them + var key = string.Format(CacheKey.CATEGORIES_BY_PARENT_CATEGORY_ID_KEY, parentCategoryId, access.ShowHidden, + access.ShowHidden ? "" : string.Join(",", access.Customer.GetCustomerGroupIds()), access.StoreId); return await _cacheBase.GetAsync(key, () => { var query = _categoryRepository.Table.Where(c => c.ParentCategoryId == parentCategoryId); - if (!showHidden) + if (!access.ShowHidden) query = query.Where(c => c.Published); - if (!showHidden && (!_accessControlConfig.IgnoreAcl || !_accessControlConfig.IgnoreStoreLimitations)) - { - if (!_accessControlConfig.IgnoreAcl) - { - //Limited to customer groups rules - var allowedCustomerGroupsIds = CurrentCustomer.GetCustomerGroupIds(); - query = from p in query - where !p.LimitedToGroups || allowedCustomerGroupsIds.Any(x => p.CustomerGroups.Contains(x)) - select p; - } - - if (!_accessControlConfig.IgnoreStoreLimitations) - //Limited to stores rules - query = from p in query - where !p.LimitedToStores || p.Stores.Contains(CurrentStore.Id) - select p; - } - + query = ApplyAccess(query, access); return Task.FromResult>(query.OrderBy(x => x.DisplayOrder).ToList()); }); } @@ -210,79 +151,66 @@ private async Task> GetChildCategories(string parentCategoryId, /// /// Gets all categories that should be displayed on the home page /// - /// A value that indicates if it should shows hidden records + /// Whose view of the categories to return, and the store whose limitations apply /// Categories - public virtual async Task> GetAllCategoriesDisplayedOnHomePage(bool showHidden = false) + public virtual async Task> GetAllCategoriesDisplayedOnHomePage(CatalogAccess access) { + ArgumentNullException.ThrowIfNull(access); + var query = _categoryRepository.Table .Where(x => x.Published && x.ShowOnHomePage) .OrderBy(x => x.DisplayOrder); - var categories = await _categoryRepository.ToListAsync(query); - if (!showHidden) - categories = categories - .Where(c => _aclService.Authorize(c, CurrentCustomer) && - _aclService.Authorize(c, CurrentStore.Id)) - .ToList(); - - return categories; + return (await _categoryRepository.ToListAsync(query)).Where(c => IsVisible(c, access)).ToList(); } /// /// Gets all featured products from categories that should be displayed on the home page /// - /// A value that indicates if it should shows hidden records + /// Whose view of the categories to return, and the store whose limitations apply /// Categories - public virtual async Task> GetAllCategoriesFeaturedProductsOnHomePage(bool showHidden = false) + public virtual async Task> GetAllCategoriesFeaturedProductsOnHomePage(CatalogAccess access) { + ArgumentNullException.ThrowIfNull(access); + var query = _categoryRepository.Table .Where(x => x.Published && x.FeaturedProductsOnHomePage) .OrderBy(x => x.DisplayOrder); - - var categories = await _categoryRepository.ToListAsync(query); - if (!showHidden) - categories = categories - .Where(c => _aclService.Authorize(c, CurrentCustomer) && - _aclService.Authorize(c, CurrentStore.Id)) - .ToList(); - return categories; + return (await _categoryRepository.ToListAsync(query)).Where(c => IsVisible(c, access)).ToList(); } /// /// Gets all categories displayed in the search box /// + /// Customer whose groups limit the result + /// Store whose limitations apply /// Categories - public virtual async Task> GetAllCategoriesSearchBox() + public virtual async Task> GetAllCategoriesSearchBox(Customer customer, string storeId) { + var access = CatalogAccess.For(customer, storeId); + var query = _categoryRepository.Table .Where(x => x.Published && x.ShowOnSearchBox) .OrderBy(x => x.SearchBoxDisplayOrder); - - var categories = (await _categoryRepository.ToListAsync(query)) - .Where(c => _aclService.Authorize(c, CurrentCustomer) && - _aclService.Authorize(c, CurrentStore.Id)) - .ToList(); - - return categories; + return (await _categoryRepository.ToListAsync(query)).Where(c => IsVisible(c, access)).ToList(); } /// /// Get category breadcrumb /// /// Category - /// A value that indicates if it should shows hidden records + /// Whose view of the categories to return, and the store whose limitations apply /// Category breadcrumb - public virtual async Task> GetCategoryBreadCrumb(Category category, bool showHidden = false) + public virtual async Task> GetCategoryBreadCrumb(Category category, CatalogAccess access) { + ArgumentNullException.ThrowIfNull(access); + var result = new List(); //used to avoid circular references var alreadyProcessedCategoryIds = new List(); - while (category != null && //not null - (showHidden || category.Published) && //published - (showHidden || - _aclService.Authorize(category, CurrentCustomer)) && //limited to customer groups - (showHidden || _aclService.Authorize(category, CurrentStore.Id)) && //limited to store + while (category != null && //not null + IsVisible(category, access) && //published, limited to customer groups and store !alreadyProcessedCategoryIds.Contains(category.Id)) { result.Add(category); @@ -301,21 +229,20 @@ public virtual async Task> GetCategoryBreadCrumb(Category catego /// /// Category /// All categories - /// A value that indicates if it should shows hidden records + /// Whose view of the categories to return, and the store whose limitations apply /// Category breadcrumb public virtual IList GetCategoryBreadCrumb(Category category, IList allCategories, - bool showHidden = false) + CatalogAccess access) { + ArgumentNullException.ThrowIfNull(access); + var result = new List(); //used to avoid circular references var alreadyProcessedCategoryIds = new List(); - while (category != null && //not null - (showHidden || category.Published) && //published - (showHidden || - _aclService.Authorize(category, CurrentCustomer)) && //limited to customer groups - (showHidden || _aclService.Authorize(category, CurrentStore.Id)) && //limited to store + while (category != null && //not null + IsVisible(category, access) && //published, limited to customer groups and store !alreadyProcessedCategoryIds.Contains(category.Id)) //avoid circular references { result.Add(category); @@ -344,7 +271,7 @@ public virtual async Task GetFormattedBreadCrumb(Category category, stri { var result = string.Empty; - var breadcrumb = await GetCategoryBreadCrumb(category, true); + var breadcrumb = await GetCategoryBreadCrumb(category, CatalogAccess.IncludingHidden()); for (var i = 0; i <= breadcrumb.Count - 1; i++) { var categoryName = breadcrumb[i].GetTranslation(x => x.Name, languageId); @@ -370,7 +297,7 @@ public virtual string GetFormattedBreadCrumb(Category category, { var result = string.Empty; - var breadcrumb = GetCategoryBreadCrumb(category, allCategories, true); + var breadcrumb = GetCategoryBreadCrumb(category, allCategories, CatalogAccess.IncludingHidden()); for (var i = 0; i <= breadcrumb.Count - 1; i++) { var categoryName = breadcrumb[i].GetTranslation(x => x.Name, languageId); @@ -468,7 +395,9 @@ public virtual async Task DeleteCategory(Category category) ArgumentNullException.ThrowIfNull(category); //reset a "Parent category" property of all child subcategories - var subcategories = await GetAllCategoriesByParentCategoryId(category.Id, true); + var subcategories = await _categoryRepository.ToListAsync(_categoryRepository.Table + .Where(c => c.ParentCategoryId == category.Id) + .OrderBy(c => c.DisplayOrder)); foreach (var subcategory in subcategories) { subcategory.ParentCategoryId = ""; @@ -484,5 +413,37 @@ public virtual async Task DeleteCategory(Category category) await _mediator.EntityDeleted(category); } + /// + /// Limits the query to the categories the access may see + /// + private IQueryable ApplyAccess(IQueryable query, CatalogAccess access) + { + if (!access.ShowHidden && !_accessControlConfig.IgnoreAcl) + { + //Limited to customer group (access control list) + var allowedCustomerGroupsIds = access.Customer.GetCustomerGroupIds(); + query = from p in query + where !p.LimitedToGroups || allowedCustomerGroupsIds.Any(x => p.CustomerGroups.Contains(x)) + select p; + } + + if (!string.IsNullOrEmpty(access.StoreId) && !_accessControlConfig.IgnoreStoreLimitations) + //Limited to stores rule + query = from p in query + where !p.LimitedToStores || p.Stores.Contains(access.StoreId) + select p; + + return query; + } + + /// + /// Gets a value indicating whether the access may see the category + /// + private bool IsVisible(Category category, CatalogAccess access) + { + return (access.ShowHidden || (category.Published && _aclService.Authorize(category, access.Customer))) + && _aclService.Authorize(category, access.StoreId); + } + #endregion } \ No newline at end of file diff --git a/src/Business/Grand.Business.Catalog/Services/Categories/ProductCategoryService.cs b/src/Business/Grand.Business.Catalog/Services/Categories/ProductCategoryService.cs index e29adae7e2..6dba399179 100644 --- a/src/Business/Grand.Business.Catalog/Services/Categories/ProductCategoryService.cs +++ b/src/Business/Grand.Business.Catalog/Services/Categories/ProductCategoryService.cs @@ -4,7 +4,6 @@ using Grand.Domain; using Grand.Domain.Catalog; using Grand.Domain.Customers; -using Grand.Infrastructure; using Grand.Infrastructure.Caching; using Grand.Infrastructure.Caching.Constants; using Grand.Infrastructure.Configuration; @@ -19,17 +18,14 @@ public class ProductCategoryService : IProductCategoryService private readonly ICacheBase _cacheBase; private readonly IMediator _mediator; private readonly IRepository _productRepository; - private readonly IContextAccessor _contextAccessor; public ProductCategoryService( IRepository productRepository, ICacheBase cacheBase, - IContextAccessor contextAccessor, IMediator mediator, AccessControlConfig accessControlConfig) { _productRepository = productRepository; _cacheBase = cacheBase; - _contextAccessor = contextAccessor; _mediator = mediator; _accessControlConfig = accessControlConfig; } @@ -38,44 +34,41 @@ public ProductCategoryService( /// Gets product category mapping collection /// /// Category identifier + /// Whose view of the products to return, and the store whose limitations apply /// Page index /// Page size - /// A value that indicates if it should shows hidden records /// Product a category mapping collection public virtual async Task> GetProductCategoriesByCategoryId(string categoryId, - int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false) + CatalogAccess access, int pageIndex = 0, int pageSize = int.MaxValue) { + ArgumentNullException.ThrowIfNull(access); + if (string.IsNullOrEmpty(categoryId)) return new PagedList(new List(), pageIndex, pageSize); - var key = string.Format(CacheKey.PRODUCTCATEGORIES_ALLBYCATEGORYID_KEY, showHidden, categoryId, pageIndex, - pageSize, string.Join(",", _contextAccessor.WorkContext.CurrentCustomer.GetCustomerGroupIds()), - _contextAccessor.StoreContext.CurrentStore.Id); + //hidden records are not filtered by customer groups, so the key does not depend on them + var key = string.Format(CacheKey.PRODUCTCATEGORIES_ALLBYCATEGORYID_KEY, access.ShowHidden, categoryId, + pageIndex, pageSize, access.ShowHidden ? "" : string.Join(",", access.Customer.GetCustomerGroupIds()), + access.StoreId); return await _cacheBase.GetAsync(key, async () => { var query = _productRepository.Table.Where(x => x.ProductCategories.Any(y => y.CategoryId == categoryId)); - if (!showHidden && (!_accessControlConfig.IgnoreAcl || !_accessControlConfig.IgnoreStoreLimitations)) + if (!access.ShowHidden && !_accessControlConfig.IgnoreAcl) { - if (!_accessControlConfig.IgnoreAcl) - { - //Limited to customer groups - var allowedCustomerGroupsIds = _contextAccessor.WorkContext.CurrentCustomer.GetCustomerGroupIds(); - query = from p in query - where !p.LimitedToGroups || allowedCustomerGroupsIds.Any(x => p.CustomerGroups.Contains(x)) - select p; - } - - if (!_accessControlConfig.IgnoreStoreLimitations) - { - //Limited to stores - var currentStoreId = _contextAccessor.StoreContext.CurrentStore.Id; - query = from p in query - where !p.LimitedToStores || p.Stores.Contains(currentStoreId) - select p; - } + //Limited to customer groups + var allowedCustomerGroupsIds = access.Customer.GetCustomerGroupIds(); + query = from p in query + where !p.LimitedToGroups || allowedCustomerGroupsIds.Any(x => p.CustomerGroups.Contains(x)) + select p; } + if (!string.IsNullOrEmpty(access.StoreId) && !_accessControlConfig.IgnoreStoreLimitations) + //Limited to stores + query = from p in query + where !p.LimitedToStores || p.Stores.Contains(access.StoreId) + select p; + var queryProductCategories = from prod in query from pc in prod.ProductCategories select new ProductsCategory { diff --git a/src/Business/Grand.Business.Catalog/Services/Collections/CollectionService.cs b/src/Business/Grand.Business.Catalog/Services/Collections/CollectionService.cs index 88721efdae..68e98408c8 100644 --- a/src/Business/Grand.Business.Catalog/Services/Collections/CollectionService.cs +++ b/src/Business/Grand.Business.Catalog/Services/Collections/CollectionService.cs @@ -1,10 +1,10 @@ using Grand.Business.Core.Interfaces.Catalog.Collections; using Grand.Business.Core.Interfaces.Common.Security; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Domain; using Grand.Domain.Catalog; using Grand.Domain.Customers; -using Grand.Infrastructure; using Grand.Infrastructure.Caching; using Grand.Infrastructure.Caching.Constants; using Grand.Infrastructure.Configuration; @@ -25,13 +25,11 @@ public class CollectionService : ICollectionService /// public CollectionService(ICacheBase cacheBase, IRepository collectionRepository, - IContextAccessor contextAccessor, IMediator mediator, IAclService aclService, AccessControlConfig accessControlConfig) { _cacheBase = cacheBase; _collectionRepository = collectionRepository; - _contextAccessor = contextAccessor; _mediator = mediator; _aclService = aclService; _accessControlConfig = accessControlConfig; @@ -42,7 +40,6 @@ public CollectionService(ICacheBase cacheBase, #region Fields private readonly IRepository _collectionRepository; - private readonly IContextAccessor _contextAccessor; private readonly IMediator _mediator; private readonly ICacheBase _cacheBase; private readonly IAclService _aclService; @@ -56,41 +53,41 @@ public CollectionService(ICacheBase cacheBase, /// Gets all collections /// /// Collection name - /// Store ident + /// Whose view of the collections to return, and the store whose limitations apply /// Page index /// Page size - /// A value that indicates if it should shows hidden records /// Collections public virtual async Task> GetAllCollections(string collectionName, - string storeId, + CatalogAccess access, int pageIndex = 0, - int pageSize = int.MaxValue, - bool showHidden = false) + int pageSize = int.MaxValue) { + ArgumentNullException.ThrowIfNull(access); + var query = from m in _collectionRepository.Table select m; - if (!showHidden) + if (!access.ShowHidden) query = query.Where(m => m.Published); if (!string.IsNullOrWhiteSpace(collectionName)) query = query.Where(m => m.Name != null && m.Name.ToLower().Contains(collectionName.ToLower())); if (!_accessControlConfig.IgnoreAcl || - (!string.IsNullOrEmpty(storeId) && !_accessControlConfig.IgnoreStoreLimitations)) + (!string.IsNullOrEmpty(access.StoreId) && !_accessControlConfig.IgnoreStoreLimitations)) { - if (!showHidden && !_accessControlConfig.IgnoreAcl) + if (!access.ShowHidden && !_accessControlConfig.IgnoreAcl) { //Limited to customer groups rules - var allowedCustomerGroupsIds = _contextAccessor.WorkContext.CurrentCustomer.GetCustomerGroupIds(); + var allowedCustomerGroupsIds = access.Customer.GetCustomerGroupIds(); query = from p in query where !p.LimitedToGroups || allowedCustomerGroupsIds.Any(x => p.CustomerGroups.Contains(x)) select p; } - if (!string.IsNullOrEmpty(storeId) && !_accessControlConfig.IgnoreStoreLimitations) + if (!string.IsNullOrEmpty(access.StoreId) && !_accessControlConfig.IgnoreStoreLimitations) //Limited to stores rules query = from p in query - where !p.LimitedToStores || p.Stores.Contains(storeId) + where !p.LimitedToStores || p.Stores.Contains(access.StoreId) select p; } @@ -102,20 +99,19 @@ public virtual async Task> GetAllCollections(string colle /// /// Gets all featured products for collections displayed on the home page /// - /// A value that indicates if it should shows hidden records + /// Whose view of the collections to return, and the store whose limitations apply /// Collections - public virtual async Task> GetAllCollectionFeaturedProductsOnHomePage(bool showHidden = false) + public virtual async Task> GetAllCollectionFeaturedProductsOnHomePage(CatalogAccess access) { + ArgumentNullException.ThrowIfNull(access); + var query = _collectionRepository.Table.Where(x => x.Published && x.FeaturedProductsOnHomePage) .OrderBy(x => x.DisplayOrder); - var collections = await _collectionRepository.ToListAsync(query); - if (!showHidden) - collections = collections - .Where(c => _aclService.Authorize(c, _contextAccessor.WorkContext.CurrentCustomer) && - _aclService.Authorize(c, _contextAccessor.StoreContext.CurrentStore.Id)) - .ToList(); - return collections; + return (await _collectionRepository.ToListAsync(query)) + .Where(c => (access.ShowHidden || _aclService.Authorize(c, access.Customer)) && + _aclService.Authorize(c, access.StoreId)) + .ToList(); } diff --git a/src/Business/Grand.Business.Catalog/Services/Collections/ProductCollectionService.cs b/src/Business/Grand.Business.Catalog/Services/Collections/ProductCollectionService.cs index 3f88c0651a..9a2f8bac02 100644 --- a/src/Business/Grand.Business.Catalog/Services/Collections/ProductCollectionService.cs +++ b/src/Business/Grand.Business.Catalog/Services/Collections/ProductCollectionService.cs @@ -4,7 +4,6 @@ using Grand.Domain; using Grand.Domain.Catalog; using Grand.Domain.Customers; -using Grand.Infrastructure; using Grand.Infrastructure.Caching; using Grand.Infrastructure.Caching.Constants; using Grand.Infrastructure.Configuration; @@ -19,12 +18,10 @@ public class ProductCollectionService : IProductCollectionService public ProductCollectionService(ICacheBase cacheBase, IRepository productRepository, - IContextAccessor contextAccessor, IMediator mediator, AccessControlConfig accessControlConfig) { _cacheBase = cacheBase; _productRepository = productRepository; - _contextAccessor = contextAccessor; _mediator = mediator; _accessControlConfig = accessControlConfig; } @@ -35,40 +32,39 @@ public ProductCollectionService(ICacheBase cacheBase, /// Gets product collection by collection id /// /// Collection id - /// Store ident + /// Whose view of the products to return, and the store whose limitations apply /// Page index /// Page size - /// A value that indicates if it should shows hidden records /// Product collection collection public virtual async Task> GetProductCollectionsByCollectionId(string collectionId, - string storeId, - int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false) + CatalogAccess access, int pageIndex = 0, int pageSize = int.MaxValue) { - var key = string.Format(CacheKey.PRODUCTCOLLECTIONS_ALLBYCOLLECTIONID_KEY, showHidden, collectionId, pageIndex, - pageSize, string.Join(",", _contextAccessor.WorkContext.CurrentCustomer.GetCustomerGroupIds()), storeId); + ArgumentNullException.ThrowIfNull(access); + + //hidden records are not filtered by customer groups, so the key does not depend on them + var key = string.Format(CacheKey.PRODUCTCOLLECTIONS_ALLBYCOLLECTIONID_KEY, access.ShowHidden, collectionId, + pageIndex, pageSize, access.ShowHidden ? "" : string.Join(",", access.Customer.GetCustomerGroupIds()), + access.StoreId); return await _cacheBase.GetAsync(key, async () => { var query = _productRepository.Table.Where(x => x.ProductCollections.Any(y => y.CollectionId == collectionId)); - if (!showHidden && (!_accessControlConfig.IgnoreAcl || !_accessControlConfig.IgnoreStoreLimitations)) + if (!access.ShowHidden && !_accessControlConfig.IgnoreAcl) { - if (!_accessControlConfig.IgnoreAcl) - { - //ACL (access control list) - var allowedCustomerGroupsIds = _contextAccessor.WorkContext.CurrentCustomer.GetCustomerGroupIds(); - query = from p in query - where !p.LimitedToGroups || allowedCustomerGroupsIds.Any(x => p.CustomerGroups.Contains(x)) - select p; - } - - if (!_accessControlConfig.IgnoreStoreLimitations && !string.IsNullOrEmpty(storeId)) - //Store acl - query = from p in query - where !p.LimitedToStores || p.Stores.Contains(storeId) - select p; + //Limited to customer groups + var allowedCustomerGroupsIds = access.Customer.GetCustomerGroupIds(); + query = from p in query + where !p.LimitedToGroups || allowedCustomerGroupsIds.Any(x => p.CustomerGroups.Contains(x)) + select p; } + if (!string.IsNullOrEmpty(access.StoreId) && !_accessControlConfig.IgnoreStoreLimitations) + //Limited to stores + query = from p in query + where !p.LimitedToStores || p.Stores.Contains(access.StoreId) + select p; + var queryProductCollection = from prod in query from pm in prod.ProductCollections select new ProductsCollection { @@ -149,7 +145,6 @@ public virtual async Task DeleteProductCollection(ProductCollection productColle #region Fields private readonly IRepository _productRepository; - private readonly IContextAccessor _contextAccessor; private readonly IMediator _mediator; private readonly ICacheBase _cacheBase; private readonly AccessControlConfig _accessControlConfig; diff --git a/src/Business/Grand.Business.Catalog/Services/ExportImport/BrandImportDataObject.cs b/src/Business/Grand.Business.Catalog/Services/ExportImport/BrandImportDataObject.cs index 62c8f6105d..a2fc29e120 100644 --- a/src/Business/Grand.Business.Catalog/Services/ExportImport/BrandImportDataObject.cs +++ b/src/Business/Grand.Business.Catalog/Services/ExportImport/BrandImportDataObject.cs @@ -3,6 +3,7 @@ using Grand.Business.Core.Interfaces.Common.Seo; using Grand.Business.Core.Interfaces.ExportImport; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Business.Core.Utilities.System; using Grand.Domain.Catalog; using Grand.Domain.Seo; @@ -148,7 +149,7 @@ private static string Key(BrandDto dto) if (string.IsNullOrEmpty(dto.Name)) return (null, false, null); - var candidates = await _brandService.GetAllBrands(dto.Name, "", showHidden: true); + var candidates = await _brandService.GetAllBrands(dto.Name, CatalogAccess.IncludingHidden()); var (found, error) = ImportRows.SingleByName(candidates, b => b.Name, dto.Name, "brand"); return (found, found != null, error); } diff --git a/src/Business/Grand.Business.Catalog/Services/ExportImport/CategoryImportDataObject.cs b/src/Business/Grand.Business.Catalog/Services/ExportImport/CategoryImportDataObject.cs index eb54d94e05..c034c8cbe8 100644 --- a/src/Business/Grand.Business.Catalog/Services/ExportImport/CategoryImportDataObject.cs +++ b/src/Business/Grand.Business.Catalog/Services/ExportImport/CategoryImportDataObject.cs @@ -3,6 +3,7 @@ using Grand.Business.Core.Interfaces.Common.Seo; using Grand.Business.Core.Interfaces.ExportImport; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Business.Core.Utilities.System; using Grand.Domain.Catalog; using Grand.Domain.Seo; @@ -155,7 +156,7 @@ private static string Key(CategoryDto dto) return (null, false, null); var parentId = string.IsNullOrEmpty(dto.ParentCategoryId) ? null : dto.ParentCategoryId; - var candidates = await _categoryService.GetAllCategories(parentId, dto.Name, "", showHidden: true); + var candidates = await _categoryService.GetAllCategories(parentId, dto.Name, CatalogAccess.IncludingHidden()); var (found, error) = ImportRows.SingleByName(candidates, c => c.Name, dto.Name, "category"); return (found, found != null, error); } diff --git a/src/Business/Grand.Business.Catalog/Services/ExportImport/CollectionImportDataObject.cs b/src/Business/Grand.Business.Catalog/Services/ExportImport/CollectionImportDataObject.cs index 13439ab62f..ebc7dfd26e 100644 --- a/src/Business/Grand.Business.Catalog/Services/ExportImport/CollectionImportDataObject.cs +++ b/src/Business/Grand.Business.Catalog/Services/ExportImport/CollectionImportDataObject.cs @@ -3,6 +3,7 @@ using Grand.Business.Core.Interfaces.Common.Seo; using Grand.Business.Core.Interfaces.ExportImport; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Business.Core.Utilities.System; using Grand.Domain.Catalog; using Grand.Domain.Seo; @@ -149,7 +150,7 @@ private static string Key(CollectionDto dto) if (string.IsNullOrEmpty(dto.Name)) return (null, false, null); - var candidates = await _collectionService.GetAllCollections(dto.Name, "", showHidden: true); + var candidates = await _collectionService.GetAllCollections(dto.Name, CatalogAccess.IncludingHidden()); var (found, error) = ImportRows.SingleByName(candidates, c => c.Name, dto.Name, "collection"); return (found, found != null, error); } diff --git a/src/Business/Grand.Business.Catalog/Services/Products/ProductService.cs b/src/Business/Grand.Business.Catalog/Services/Products/ProductService.cs index 71b5a49915..6170d7a7bf 100644 --- a/src/Business/Grand.Business.Catalog/Services/Products/ProductService.cs +++ b/src/Business/Grand.Business.Catalog/Services/Products/ProductService.cs @@ -2,12 +2,12 @@ using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Interfaces.Common.Security; using Grand.Business.Core.Queries.Catalog; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Domain; using Grand.Domain.Catalog; using Grand.Domain.Customers; using Grand.Domain.Orders; -using Grand.Infrastructure; using Grand.Infrastructure.Caching; using Grand.Infrastructure.Caching.Constants; using Grand.Infrastructure.Extensions; @@ -28,14 +28,12 @@ public class ProductService : IProductService /// public ProductService(ICacheBase cacheBase, IRepository productRepository, - IContextAccessor contextAccessor, IMediator mediator, IAclService aclService ) { _cacheBase = cacheBase; _productRepository = productRepository; - _contextAccessor = contextAccessor; _mediator = mediator; _aclService = aclService; } @@ -46,7 +44,6 @@ IAclService aclService private readonly IRepository _productRepository; private readonly ICacheBase _cacheBase; - private readonly IContextAccessor _contextAccessor; private readonly IAclService _aclService; private readonly IMediator _mediator; @@ -119,10 +116,12 @@ public virtual async Task GetProductByIdIncludeArch(string productId) /// Get products by identifiers /// /// Product identifiers - /// Show hidden + /// Whose view of the products to return, and the store whose limitations apply /// Products - public virtual async Task> GetProductsByIds(string[] productIds, bool showHidden = false) + public virtual async Task> GetProductsByIds(string[] productIds, CatalogAccess access) { + ArgumentNullException.ThrowIfNull(access); + if (productIds == null || productIds.Length == 0) return new List(); @@ -130,9 +129,9 @@ public virtual async Task> GetProductsByIds(string[] productIds, foreach (var id in productIds) { var product = await GetProductById(id); - if (product != null && (showHidden || (_aclService.Authorize(product, _contextAccessor.WorkContext.CurrentCustomer) && - _aclService.Authorize(product, _contextAccessor.StoreContext.CurrentStore.Id) && - product.IsAvailable()))) + if (product != null && + (access.ShowHidden || (_aclService.Authorize(product, access.Customer) && product.IsAvailable())) && + _aclService.Authorize(product, access.StoreId)) products.Add(product); } @@ -451,6 +450,7 @@ public virtual int GetCategoryProductNumber(Customer customer, IList cat /// /// Search products /// + /// Whose view of the products to search, and the store whose limitations apply /// /// A value indicating whether we should load the specification /// attribute option identifiers applied to loaded products (all pages) @@ -460,7 +460,6 @@ public virtual int GetCategoryProductNumber(Customer customer, IList cat /// Category identifiers /// Brand ident /// Collection identifier; "" to load all records - /// Store identifier; "" to load all records /// Vendor identifier; "" to load all records /// Warehouse identifier; "" to load all records /// Product type; "" to load all records @@ -485,9 +484,8 @@ public virtual int GetCategoryProductNumber(Customer customer, IList cat /// Filtered product specification identifiers /// Specification options identifiers /// Order by - /// A value indicating whether to show hidden records /// - /// null - process "Published" property according to "showHidden" parameter + /// null - process "Published" property according to "access" /// true - load only "Published" products /// false - load only "Unpublished" products /// @@ -495,14 +493,14 @@ public virtual int GetCategoryProductNumber(Customer customer, IList cat /// Products public virtual async Task<(IPagedList products, IList filterableSpecificationAttributeOptionIds)> SearchProducts( + CatalogAccess access, bool loadFilterableSpecificationAttributeOptionIds = false, int pageIndex = 0, int pageSize = int.MaxValue, IList categoryIds = null, string brandId = "", string collectionId = "", - string storeId = "", - string vendorId = "", + string vendorId = "", string warehouseId = "", ProductType? productType = null, bool visibleIndividuallyOnly = false, @@ -520,18 +518,19 @@ public virtual int GetCategoryProductNumber(Customer customer, IList cat IList filteredSpecs = null, IList specificationOptions = null, ProductSortingEnum orderBy = ProductSortingEnum.Position, - bool showHidden = false, - bool? overridePublished = null) + bool? overridePublished = null) { + ArgumentNullException.ThrowIfNull(access); + var model = await _mediator.Send(new GetSearchProductsQuery { - Customer = _contextAccessor.WorkContext.CurrentCustomer, + Customer = access.Customer, LoadFilterableSpecificationAttributeOptionIds = loadFilterableSpecificationAttributeOptionIds, PageIndex = pageIndex, PageSize = pageSize, CategoryIds = categoryIds, BrandId = brandId, CollectionId = collectionId, - StoreId = storeId, + StoreId = access.StoreId, VendorId = vendorId, WarehouseId = warehouseId, ProductType = productType, @@ -550,7 +549,7 @@ public virtual int GetCategoryProductNumber(Customer customer, IList cat FilteredSpecs = filteredSpecs, SpecificationOptions = specificationOptions, OrderBy = orderBy, - ShowHidden = showHidden, + ShowHidden = access.ShowHidden, OverridePublished = overridePublished }); @@ -584,20 +583,21 @@ public virtual async Task> GetProductsByProductAttributeId(s /// Gets associated products /// /// Parent product identifier (used with grouped products) - /// Store identifier; "" to load all records + /// Whose view of the products to return, and the store whose limitations apply /// Vendor identifier; "" to load all records - /// Show hidden /// Products - public virtual async Task> GetAssociatedProducts(string parentGroupedProductId, - string storeId = "", string vendorId = "", bool showHidden = false) + public virtual async Task> GetAssociatedProducts(string parentGroupedProductId, CatalogAccess access, + string vendorId = "") { + ArgumentNullException.ThrowIfNull(access); + var query = from p in _productRepository.Table select p; query = query.Where(p => p.ParentGroupedProductId == parentGroupedProductId); - if (!showHidden) query = query.Where(p => p.Published); - if (!showHidden) + if (!access.ShowHidden) query = query.Where(p => p.Published); + if (!access.ShowHidden) { var nowUtc = DateTime.UtcNow; //available dates @@ -612,11 +612,10 @@ public virtual async Task> GetAssociatedProducts(string parentGro var products = query.OrderBy(x => x.DisplayOrder).ToList(); //ACL mapping - if (!showHidden) - products = products.Where(x => _aclService.Authorize(x, _contextAccessor.WorkContext.CurrentCustomer)).ToList(); + if (!access.ShowHidden) + products = products.Where(x => _aclService.Authorize(x, access.Customer)).ToList(); //Store acl - if (!showHidden && !string.IsNullOrEmpty(storeId)) - products = products.Where(x => _aclService.Authorize(x, storeId)).ToList(); + products = products.Where(x => _aclService.Authorize(x, access.StoreId)).ToList(); return await Task.FromResult(products); } @@ -863,10 +862,15 @@ public virtual async Task DeleteCrossSellProduct(CrossSellProduct crossSellProdu /// /// Shopping cart /// Number of products to return + /// Customer whose groups limit the result + /// Store whose limitations apply /// Cross-sells public virtual async Task> GetCrossSellProductsByShoppingCart(IList cart, - int numberOfProducts) + int numberOfProducts, Customer customer, string storeId) { + //without a customer the ACL check rejects every record, so fail instead of returning nothing + ArgumentNullException.ThrowIfNull(customer); + var result = new List(); if (numberOfProducts == 0) @@ -895,8 +899,8 @@ public virtual async Task> GetCrossSellProductsByShoppingCart(ILi var productToAdd = await GetProductById(crossSell); //validate product if (productToAdd is not { Published: true } - || !_aclService.Authorize(productToAdd, _contextAccessor.WorkContext.CurrentCustomer) || - !_aclService.Authorize(productToAdd, _contextAccessor.StoreContext.CurrentStore.Id) + || !_aclService.Authorize(productToAdd, customer) || + !_aclService.Authorize(productToAdd, storeId) || !productToAdd.IsAvailable()) continue; diff --git a/src/Business/Grand.Business.Catalog/Services/Products/RecentlyViewedProductsService.cs b/src/Business/Grand.Business.Catalog/Services/Products/RecentlyViewedProductsService.cs index 8b7f73b9a6..696b6c118f 100644 --- a/src/Business/Grand.Business.Catalog/Services/Products/RecentlyViewedProductsService.cs +++ b/src/Business/Grand.Business.Catalog/Services/Products/RecentlyViewedProductsService.cs @@ -1,6 +1,8 @@ using Grand.Business.Core.Interfaces.Catalog.Products; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Domain.Catalog; +using Grand.Domain.Customers; using Grand.Infrastructure.Caching; using Grand.Infrastructure.Caching.Constants; @@ -81,13 +83,15 @@ orderby p.CreatedOnUtc descending /// /// Gets a "recently viewed products" list /// - /// Customer ident + /// Customer whose list is loaded and whose groups limit it + /// Store whose limitations apply /// Number of products to load /// "recently viewed products" list - public virtual async Task> GetRecentlyViewedProducts(string customerId, int number) + public virtual async Task> GetRecentlyViewedProducts(Customer customer, string storeId, int number) { - var productIds = await GetRecentlyViewedProductsIds(customerId, number); - return (await _productService.GetProductsByIds(productIds.ToArray())).Where(product => product.Published) + var productIds = await GetRecentlyViewedProductsIds(customer.Id, number); + return (await _productService.GetProductsByIds(productIds.ToArray(), CatalogAccess.For(customer, storeId))) + .Where(product => product.Published) .ToList(); } diff --git a/src/Business/Grand.Business.Core/Interfaces/Catalog/Brands/IBrandService.cs b/src/Business/Grand.Business.Core/Interfaces/Catalog/Brands/IBrandService.cs index 11e09608f4..b19bb291a1 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Catalog/Brands/IBrandService.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Catalog/Brands/IBrandService.cs @@ -1,3 +1,4 @@ +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain; using Grand.Domain.Catalog; @@ -12,16 +13,14 @@ public interface IBrandService /// Gets all brands /// /// Brand name - /// Store ident + /// Whose view of the brands to return, and the store whose limitations apply /// Page index /// Page size - /// A value that indicates if it should shows hidden records /// Brands Task> GetAllBrands(string brandName, - string storeId, + CatalogAccess access, int pageIndex = 0, - int pageSize = int.MaxValue, - bool showHidden = false); + int pageSize = int.MaxValue); /// /// Gets an existing brand by id diff --git a/src/Business/Grand.Business.Core/Interfaces/Catalog/Categories/ICategoryService.cs b/src/Business/Grand.Business.Core/Interfaces/Catalog/Categories/ICategoryService.cs index 5015edb1a3..fbb1760ea0 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Catalog/Categories/ICategoryService.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Catalog/Categories/ICategoryService.cs @@ -1,5 +1,7 @@ +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain; using Grand.Domain.Catalog; +using Grand.Domain.Customers; namespace Grand.Business.Core.Interfaces.Catalog.Categories; @@ -10,65 +12,68 @@ public interface ICategoryService /// /// /// Category name - /// Store id + /// Whose view of the categories to return, and the store whose limitations apply /// Page index /// Page size - /// A value that indicates if it should shows hidden records /// Categories - Task> GetAllCategories(string parentId, string categoryName, string storeId, - int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false); + Task> GetAllCategories(string parentId, string categoryName, CatalogAccess access, + int pageIndex = 0, int pageSize = int.MaxValue); /// /// Gets all categories /// + /// Customer whose groups limit the result + /// Store whose limitations apply /// Categories - Task> GetMenuCategories(); + Task> GetMenuCategories(Customer customer, string storeId); /// /// Gets all categories by parent category /// + /// Whose view of the categories to return, and the store whose limitations apply /// Parent category id - /// A value that indicates if it should shows hidden records /// A value indicates if we should load all child levels /// Categories - Task> GetAllCategoriesByParentCategoryId(string parentCategoryId = "", - bool showHidden = false, bool includeAllLevels = false); + Task> GetAllCategoriesByParentCategoryId(CatalogAccess access, string parentCategoryId = "", + bool includeAllLevels = false); /// /// Gets all categories displayed on the home page /// - /// A value that indicates if it should shows hidden records + /// Whose view of the categories to return, and the store whose limitations apply /// Categories - Task> GetAllCategoriesDisplayedOnHomePage(bool showHidden = false); + Task> GetAllCategoriesDisplayedOnHomePage(CatalogAccess access); /// /// Gets all categories displayed that should be visible on the home page, in featured products section /// - /// A value that indicates if it should shows hidden records - Task> GetAllCategoriesFeaturedProductsOnHomePage(bool showHidden = false); + /// Whose view of the categories to return, and the store whose limitations apply + Task> GetAllCategoriesFeaturedProductsOnHomePage(CatalogAccess access); /// /// Gets all categories that should be displayed in search box /// + /// Customer whose groups limit the result + /// Store whose limitations apply /// Categories - Task> GetAllCategoriesSearchBox(); + Task> GetAllCategoriesSearchBox(Customer customer, string storeId); /// /// Get category breadcrumb /// /// Category - /// A value that indicates if it should shows hidden records + /// Whose view of the categories to return, and the store whose limitations apply /// Category breadcrumb - Task> GetCategoryBreadCrumb(Category category, bool showHidden = false); + Task> GetCategoryBreadCrumb(Category category, CatalogAccess access); /// /// Get category breadcrumb /// /// Category /// All categories - /// A value that indicates if it should shows hidden records + /// Whose view of the categories to return, and the store whose limitations apply /// Category breadcrumb - IList GetCategoryBreadCrumb(Category category, IList allCategories, bool showHidden = false); + IList GetCategoryBreadCrumb(Category category, IList allCategories, CatalogAccess access); /// /// Get formatted category breadcrumb diff --git a/src/Business/Grand.Business.Core/Interfaces/Catalog/Categories/IProductCategoryService.cs b/src/Business/Grand.Business.Core/Interfaces/Catalog/Categories/IProductCategoryService.cs index f0a495d082..c8e57b5ac7 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Catalog/Categories/IProductCategoryService.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Catalog/Categories/IProductCategoryService.cs @@ -10,12 +10,12 @@ public interface IProductCategoryService /// Gets product category mapping collection /// /// Category id + /// Whose view of the products to return, and the store whose limitations apply /// Page index /// Page size - /// A value that indicates if it should shows hidden records /// Product a category mapping collection - Task> GetProductCategoriesByCategoryId(string categoryId, - int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false); + Task> GetProductCategoriesByCategoryId(string categoryId, CatalogAccess access, + int pageIndex = 0, int pageSize = int.MaxValue); /// /// Inserts a mapping for product category diff --git a/src/Business/Grand.Business.Core/Interfaces/Catalog/Collections/ICollectionService.cs b/src/Business/Grand.Business.Core/Interfaces/Catalog/Collections/ICollectionService.cs index fa1c18d637..e21220c815 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Catalog/Collections/ICollectionService.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Catalog/Collections/ICollectionService.cs @@ -1,3 +1,4 @@ +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain; using Grand.Domain.Catalog; @@ -12,23 +13,21 @@ public interface ICollectionService /// Gets all collections /// /// Collection name - /// Store ident + /// Whose view of the collections to return, and the store whose limitations apply /// Page index /// Page size - /// A value that indicates if it should shows hidden records /// Collections Task> GetAllCollections(string collectionName, - string storeId, + CatalogAccess access, int pageIndex = 0, - int pageSize = int.MaxValue, - bool showHidden = false); + int pageSize = int.MaxValue); /// /// Gets all collections products displayed on the home page /// - /// A value that indicates if it should shows hidden records + /// Whose view of the collections to return, and the store whose limitations apply /// Collections - Task> GetAllCollectionFeaturedProductsOnHomePage(bool showHidden = false); + Task> GetAllCollectionFeaturedProductsOnHomePage(CatalogAccess access); /// /// Gets an existing collection by id diff --git a/src/Business/Grand.Business.Core/Interfaces/Catalog/Collections/IProductCollectionService.cs b/src/Business/Grand.Business.Core/Interfaces/Catalog/Collections/IProductCollectionService.cs index 2f8ecb7071..415e513ddb 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Catalog/Collections/IProductCollectionService.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Catalog/Collections/IProductCollectionService.cs @@ -10,13 +10,12 @@ public interface IProductCollectionService /// Gets product collection by collection id /// /// Collection id - /// Store id + /// Whose view of the products to return, and the store whose limitations apply /// Page index /// Page size - /// A value that indicates if it should shows hidden records /// Product collection collection - Task> GetProductCollectionsByCollectionId(string collectionId, string storeId, - int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false); + Task> GetProductCollectionsByCollectionId(string collectionId, CatalogAccess access, + int pageIndex = 0, int pageSize = int.MaxValue); /// diff --git a/src/Business/Grand.Business.Core/Interfaces/Catalog/Products/IProductService.cs b/src/Business/Grand.Business.Core/Interfaces/Catalog/Products/IProductService.cs index d76f1c8f14..2235b54ecf 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Catalog/Products/IProductService.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Catalog/Products/IProductService.cs @@ -1,3 +1,4 @@ +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain; using Grand.Domain.Catalog; using Grand.Domain.Customers; @@ -44,9 +45,9 @@ public interface IProductService /// Gets products by identifier /// /// Product identifiers - /// Show hidden + /// Whose view of the products to return, and the store whose limitations apply /// Products - Task> GetProductsByIds(string[] productIds, bool showHidden = false); + Task> GetProductsByIds(string[] productIds, CatalogAccess access); /// /// Gets products by discount @@ -115,6 +116,7 @@ int GetCategoryProductNumber(Customer customer, IList categoryIds = null /// /// Search products /// + /// Whose view of the products to search, and the store whose limitations apply /// /// A value indicating whether we should load the specification /// attribute option identifiers applied to loaded products (all pages) @@ -124,7 +126,6 @@ int GetCategoryProductNumber(Customer customer, IList categoryIds = null /// Category identifiers /// Brand ident /// Collection identifier; "" to load all records - /// Store identifier; "" to load all records /// Vendor identifier; "" to load all records /// Warehouse identifier; "" to load all records /// Product type; "" to load all records @@ -150,22 +151,21 @@ int GetCategoryProductNumber(Customer customer, IList categoryIds = null /// Filtered product specification identifiers /// Specification options identifiers /// Order by - /// A value indicating whether to show hidden records /// - /// null - process "Published" property according to "showHidden" parameter + /// null - process "Published" property according to "access" /// true - load only "Published" products /// false - load only "Unpublished" products /// /// Products Task<(IPagedList products, IList filterableSpecificationAttributeOptionIds)> SearchProducts( + CatalogAccess access, bool loadFilterableSpecificationAttributeOptionIds = false, int pageIndex = 0, int pageSize = int.MaxValue, IList categoryIds = null, string brandId = "", string collectionId = "", - string storeId = "", - string vendorId = "", + string vendorId = "", string warehouseId = "", ProductType? productType = null, bool visibleIndividuallyOnly = false, @@ -183,8 +183,7 @@ int GetCategoryProductNumber(Customer customer, IList categoryIds = null IList filteredSpecs = null, IList specificationOptions = null, ProductSortingEnum orderBy = ProductSortingEnum.Position, - bool showHidden = false, - bool? overridePublished = null); + bool? overridePublished = null); /// /// Gets products by product attribute @@ -201,12 +200,11 @@ Task> GetProductsByProductAttributeId(string productAttribut /// Gets associated products /// /// Parent product identifier (used with grouped products) - /// Store identifier; "" to load all records + /// Whose view of the products to return, and the store whose limitations apply /// Vendor identifier; "" to load all records - /// A value indicating whether to show hidden records /// Products - Task> GetAssociatedProducts(string parentGroupedProductId, - string storeId = "", string vendorId = "", bool showHidden = false); + Task> GetAssociatedProducts(string parentGroupedProductId, CatalogAccess access, + string vendorId = ""); /// /// Update product associated @@ -289,8 +287,11 @@ Task> GetAssociatedProducts(string parentGroupedProductId, /// /// Shopping cart /// Number of products to return + /// Customer whose groups limit the result + /// Store whose limitations apply /// Cross-sells - Task> GetCrossSellProductsByShoppingCart(IList cart, int numberOfProducts); + Task> GetCrossSellProductsByShoppingCart(IList cart, int numberOfProducts, + Customer customer, string storeId); #endregion diff --git a/src/Business/Grand.Business.Core/Interfaces/Catalog/Products/IRecentlyViewedProductsService.cs b/src/Business/Grand.Business.Core/Interfaces/Catalog/Products/IRecentlyViewedProductsService.cs index 7510508cc7..7d3285aa8a 100644 --- a/src/Business/Grand.Business.Core/Interfaces/Catalog/Products/IRecentlyViewedProductsService.cs +++ b/src/Business/Grand.Business.Core/Interfaces/Catalog/Products/IRecentlyViewedProductsService.cs @@ -1,4 +1,5 @@ using Grand.Domain.Catalog; +using Grand.Domain.Customers; namespace Grand.Business.Core.Interfaces.Catalog.Products; @@ -10,10 +11,11 @@ public interface IRecentlyViewedProductsService /// /// Gets a "recently viewed products" list /// - /// Customer ident + /// Customer whose list is loaded and whose groups limit it + /// Store whose limitations apply /// Number of products to load /// "recently viewed products" list - Task> GetRecentlyViewedProducts(string customerId, int number); + Task> GetRecentlyViewedProducts(Customer customer, string storeId, int number); /// /// Adds a product to a recently viewed products list diff --git a/src/Business/Grand.Business.Core/Queries/Catalog/GetPersonalizedProductsQuery.cs b/src/Business/Grand.Business.Core/Queries/Catalog/GetPersonalizedProductsQuery.cs index 4d01c5bd65..2cf03864ca 100644 --- a/src/Business/Grand.Business.Core/Queries/Catalog/GetPersonalizedProductsQuery.cs +++ b/src/Business/Grand.Business.Core/Queries/Catalog/GetPersonalizedProductsQuery.cs @@ -1,10 +1,12 @@ using Grand.Domain.Catalog; +using Grand.Domain.Customers; using Grand.Mediator; namespace Grand.Business.Core.Queries.Catalog; public class GetPersonalizedProductsQuery : IRequest> { - public string CustomerId { get; set; } public int ProductsNumber { get; set; } + public Customer Customer { get; set; } + public string StoreId { get; set; } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Core/Queries/Catalog/GetRecommendedProductsQuery.cs b/src/Business/Grand.Business.Core/Queries/Catalog/GetRecommendedProductsQuery.cs index 3439150c70..1fb839a05c 100644 --- a/src/Business/Grand.Business.Core/Queries/Catalog/GetRecommendedProductsQuery.cs +++ b/src/Business/Grand.Business.Core/Queries/Catalog/GetRecommendedProductsQuery.cs @@ -1,10 +1,11 @@ using Grand.Domain.Catalog; +using Grand.Domain.Customers; using Grand.Mediator; namespace Grand.Business.Core.Queries.Catalog; public class GetRecommendedProductsQuery : IRequest> { - public string[] CustomerGroupIds { get; set; } public string StoreId { get; set; } + public Customer Customer { get; set; } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Core/Queries/Catalog/GetSuggestedProductsQuery.cs b/src/Business/Grand.Business.Core/Queries/Catalog/GetSuggestedProductsQuery.cs index 9cfc0382a1..a0eb80aaf5 100644 --- a/src/Business/Grand.Business.Core/Queries/Catalog/GetSuggestedProductsQuery.cs +++ b/src/Business/Grand.Business.Core/Queries/Catalog/GetSuggestedProductsQuery.cs @@ -1,10 +1,12 @@ using Grand.Domain.Catalog; +using Grand.Domain.Customers; using Grand.Mediator; namespace Grand.Business.Core.Queries.Catalog; public class GetSuggestedProductsQuery : IRequest> { - public string[] CustomerTagIds { get; set; } public int ProductsNumber { get; set; } + public Customer Customer { get; set; } + public string StoreId { get; set; } } \ No newline at end of file diff --git a/src/Business/Grand.Business.Core/Utilities/Catalog/CatalogAccess.cs b/src/Business/Grand.Business.Core/Utilities/Catalog/CatalogAccess.cs new file mode 100644 index 0000000000..9c4e93fe66 --- /dev/null +++ b/src/Business/Grand.Business.Core/Utilities/Catalog/CatalogAccess.cs @@ -0,0 +1,50 @@ +using Grand.Domain.Customers; + +namespace Grand.Business.Core.Utilities.Catalog; + +/// +/// Whose view of the catalog a read returns: the published records a customer's groups may see, or every +/// record, hidden and group-limited ones included, for the back office, import and the sitemap. A store id +/// keeps out the records limited to other stores in both cases. Two accesses are equal when they hold the same +/// customer and store. +/// +public sealed record CatalogAccess +{ + private CatalogAccess(Customer customer, string storeId) + { + Customer = customer; + StoreId = storeId ?? ""; + } + + /// + /// Gets the customer whose groups limit the records; null when hidden records are included + /// + public Customer Customer { get; } + + /// + /// Gets the store whose limitations apply; empty when no store limits the records + /// + public string StoreId { get; } + + /// + /// Gets a value indicating whether unpublished and group-limited records are included + /// + public bool ShowHidden => Customer == null; + + /// + /// The published records the customer's groups may see in the store + /// + public static CatalogAccess For(Customer customer, string storeId) + { + ArgumentNullException.ThrowIfNull(customer); + return new CatalogAccess(customer, storeId); + } + + /// + /// Every record, hidden and group-limited ones included; a store id leaves out those limited to other stores + /// + public static CatalogAccess IncludingHidden(string storeId = "") + { + return new CatalogAccess(null, storeId); + } +} diff --git a/src/Business/Grand.Business.Messages/Commands/Handlers/Common/GetSitemapXMLCommandHandler.cs b/src/Business/Grand.Business.Messages/Commands/Handlers/Common/GetSitemapXMLCommandHandler.cs index 746e1751a8..f83dd5bb8b 100644 --- a/src/Business/Grand.Business.Messages/Commands/Handlers/Common/GetSitemapXMLCommandHandler.cs +++ b/src/Business/Grand.Business.Messages/Commands/Handlers/Common/GetSitemapXMLCommandHandler.cs @@ -6,6 +6,7 @@ using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Business.Core.Interfaces.Storage; using Grand.Business.Core.Queries.Catalog; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Blogs; using Grand.Domain.Catalog; using Grand.Domain.Common; @@ -196,7 +197,8 @@ private async Task> GenerateUrls(Language language, Store stor //categories if (_commonSettings.SitemapIncludeCategories) { - var categories = (await _categoryService.GetAllCategories(null, "", store.Id, showHidden: true)) + var categories = (await _categoryService.GetAllCategories(null, "", + CatalogAccess.IncludingHidden(store.Id))) .Where(c => c.Published && IsVisibleToGuests(c)) .ToLookup(c => c.ParentCategoryId ?? ""); sitemapUrls.AddRange(await GetCategoryUrls("", categories, language)); @@ -259,7 +261,7 @@ private async Task> GetCategoryUrls(string parentCategor private async Task> GetBrandUrls(Language language, Store store) { - var brands = (await _brandService.GetAllBrands(brandName: "", storeId: store.Id, showHidden: true)) + var brands = (await _brandService.GetAllBrands(brandName: "", access: CatalogAccess.IncludingHidden(store.Id))) .Where(b => b.Published && IsVisibleToGuests(b)); var brandUrls = new List(); var storeLocation = GetStoreLocation(); diff --git a/src/Plugins/DiscountRules.Standard/Controllers/DiscountRulesHasAllProductsController.cs b/src/Plugins/DiscountRules.Standard/Controllers/DiscountRulesHasAllProductsController.cs index 16c754fec8..aaef90d616 100644 --- a/src/Plugins/DiscountRules.Standard/Controllers/DiscountRulesHasAllProductsController.cs +++ b/src/Plugins/DiscountRules.Standard/Controllers/DiscountRulesHasAllProductsController.cs @@ -5,6 +5,7 @@ using Grand.Business.Core.Interfaces.Common.Security; using Grand.Business.Core.Interfaces.Common.Stores; using Grand.Business.Core.Interfaces.Customers; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Discounts; using Grand.Domain.Permissions; @@ -174,16 +175,14 @@ public async Task ProductAddPopupList(DataSourceRequest command, searchCategoryIds.Add(model.SearchCategoryId); var products = (await _productService.SearchProducts( + access: CatalogAccess.IncludingHidden(model.SearchStoreId), categoryIds: searchCategoryIds, collectionId: model.SearchCollectionId, - storeId: model.SearchStoreId, vendorId: model.SearchVendorId, productType: model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null, keywords: model.SearchProductName, pageIndex: command.Page - 1, - pageSize: command.PageSize, - showHidden: true - )).products; + pageSize: command.PageSize)).products; var gridModel = new DataSourceResult { Data = products.Select(x => new RequirementAllProductsModel.ProductModel { Id = x.Id, @@ -229,7 +228,7 @@ public async Task LoadProductFriendlyNames(string productIds) ids.Add(str2); } - var products = await _productService.GetProductsByIds(ids.ToArray(), true); + var products = await _productService.GetProductsByIds(ids.ToArray(), access: CatalogAccess.IncludingHidden()); for (var i = 0; i <= products.Count - 1; i++) { result += products[i].Name; diff --git a/src/Plugins/DiscountRules.Standard/Controllers/DiscountRulesHasOneProductController.cs b/src/Plugins/DiscountRules.Standard/Controllers/DiscountRulesHasOneProductController.cs index b214e08ad7..ec0de48df5 100644 --- a/src/Plugins/DiscountRules.Standard/Controllers/DiscountRulesHasOneProductController.cs +++ b/src/Plugins/DiscountRules.Standard/Controllers/DiscountRulesHasOneProductController.cs @@ -5,6 +5,7 @@ using Grand.Business.Core.Interfaces.Common.Security; using Grand.Business.Core.Interfaces.Common.Stores; using Grand.Business.Core.Interfaces.Customers; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Discounts; using Grand.Domain.Permissions; @@ -186,16 +187,14 @@ public async Task ProductAddPopupList(DataSourceRequest command, searchCategoryIds.Add(model.SearchCategoryId); var products = (await _productService.SearchProducts( + access: CatalogAccess.IncludingHidden(model.SearchStoreId), categoryIds: searchCategoryIds, collectionId: model.SearchCollectionId, - storeId: model.SearchStoreId, vendorId: model.SearchVendorId, productType: model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null, keywords: model.SearchProductName, pageIndex: command.Page - 1, - pageSize: command.PageSize, - showHidden: true - )).products; + pageSize: command.PageSize)).products; var gridModel = new DataSourceResult { Data = products.Select(x => new RequirementOneProductModel.ProductModel { Id = x.Id, @@ -223,7 +222,7 @@ public async Task LoadProductFriendlyNames(string productIds) .Select(x => x.Split(':')[0].Trim()) .ToList(); - var products = await _productService.GetProductsByIds(ids.ToArray(), true); + var products = await _productService.GetProductsByIds(ids.ToArray(), access: CatalogAccess.IncludingHidden()); result = string.Join(", ", products.Select(p => p.Name)); return new JsonResult(new { Text = result }); diff --git a/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetPersonalizedProductsQueryHandlerTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetPersonalizedProductsQueryHandlerTests.cs index 3443522058..b208304959 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetPersonalizedProductsQueryHandlerTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetPersonalizedProductsQueryHandlerTests.cs @@ -1,10 +1,12 @@ using Grand.Business.Catalog.Queries.Handlers; using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Queries.Catalog; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Domain.Catalog; using Grand.Domain.Customers; using Grand.Infrastructure.Caching; +using Grand.Infrastructure.Caching.Constants; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -33,8 +35,43 @@ public void Init() [TestMethod] public async Task HandleTest() { - await handler.Handle(new GetPersonalizedProductsQuery(), CancellationToken.None); + await handler.Handle(new GetPersonalizedProductsQuery { Customer = new Customer() }, CancellationToken.None); _casheManagerMock.Verify(c => c.GetAsync(It.IsAny(), It.IsAny>>>()), Times.Once); } + + [TestMethod] + public async Task Handle_WithoutCustomer_ThrowsBeforeCaching() + { + await Assert.ThrowsExactlyAsync(() => + handler.Handle(new GetPersonalizedProductsQuery { ProductsNumber = 1 }, + CancellationToken.None)); + _casheManagerMock.Verify(c => c.GetAsync(It.IsAny(), It.IsAny>>>()), + Times.Never); + } + + [TestMethod] + public async Task Handle_KeysAndLoadsByIdOfQueryCustomer() + { + //Arrange + var customer = new Customer { Id = "c1" }; + _casheManagerMock.Setup(c => c.GetAsync(It.IsAny(), It.IsAny>>>())) + .Returns((string _, Func>> acquire) => acquire()); + _customerProductRepositoryMock.Setup(r => r.Table) + .Returns(new List { + new() { CustomerId = "c1", ProductId = "p1" }, new() { CustomerId = "c2", ProductId = "p2" } + }.AsQueryable()); + _productServiceMock.Setup(p => p.GetProductsByIds(It.IsAny(), CatalogAccess.For(customer, "s1"))) + .ReturnsAsync(new List()); + + //Act + await handler.Handle(new GetPersonalizedProductsQuery { Customer = customer, StoreId = "s1", ProductsNumber = 10 }, + CancellationToken.None); + + //Assert + _casheManagerMock.Verify(c => c.GetAsync(string.Format(CacheKey.PRODUCTS_CUSTOMER_PERSONAL_KEY, "c1"), + It.IsAny>>>()), Times.Once); + _productServiceMock.Verify(p => p.GetProductsByIds(new[] { "p1" }, + CatalogAccess.For(customer, "s1")), Times.Once); + } } \ No newline at end of file diff --git a/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetRecommendedProductsQueryHandlerTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetRecommendedProductsQueryHandlerTests.cs index 42d2efc3e5..45e2ac3e7e 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetRecommendedProductsQueryHandlerTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetRecommendedProductsQueryHandlerTests.cs @@ -1,10 +1,12 @@ using Grand.Business.Catalog.Queries.Handlers; using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Queries.Catalog; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Domain.Catalog; using Grand.Domain.Customers; using Grand.Infrastructure.Caching; +using Grand.Infrastructure.Caching.Constants; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -34,11 +36,47 @@ public void Init() public async Task HandleTest() { var getRecommendedProductsQuery = new GetRecommendedProductsQuery { - CustomerGroupIds = ["1"], - StoreId = "1" + StoreId = "1", + Customer = new Customer() }; await handler.Handle(getRecommendedProductsQuery, CancellationToken.None); _casheManagerMock.Verify(c => c.GetAsync(It.IsAny(), It.IsAny>>>()), Times.Once); } + + [TestMethod] + public async Task Handle_WithoutCustomer_ThrowsBeforeCaching() + { + await Assert.ThrowsExactlyAsync(() => + handler.Handle(new GetRecommendedProductsQuery { StoreId = "1" }, + CancellationToken.None)); + _casheManagerMock.Verify(c => c.GetAsync(It.IsAny(), It.IsAny>>>()), + Times.Never); + } + + [TestMethod] + public async Task Handle_AuthorizesProductsAgainstCustomerAndStoreOfQuery() + { + //Arrange + var customer = new Customer(); + customer.Groups.Add("1"); + _casheManagerMock.Setup(c => c.GetAsync(It.IsAny(), It.IsAny>>>())) + .Returns((string _, Func>> acquire) => acquire()); + _customerGroupProductRepositoryMock.Setup(r => r.Table) + .Returns(new List { new() { CustomerGroupId = "1", ProductId = "p1" } } + .AsQueryable()); + _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "p1" }, CatalogAccess.For(customer, "1"))) + .ReturnsAsync(new List()); + + //Act + await handler.Handle(new GetRecommendedProductsQuery { + StoreId = "1", Customer = customer + }, CancellationToken.None); + + //Assert + _casheManagerMock.Verify(c => c.GetAsync(string.Format(CacheKey.PRODUCTS_CUSTOMER_GROUP, "1", "1"), It.IsAny>>>()), + Times.Once); + _productServiceMock.Verify(p => p.GetProductsByIds(new[] { "p1" }, + CatalogAccess.For(customer, "1")), Times.Once); + } } \ No newline at end of file diff --git a/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetSearchProductsQueryHandlerTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetSearchProductsQueryHandlerTests.cs index c2f20dff86..f16d663a46 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetSearchProductsQueryHandlerTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetSearchProductsQueryHandlerTests.cs @@ -61,4 +61,20 @@ await _repository.InsertAsync(new Product { //Assert CollectionAssert.AreEquivalent(new[] { "open", "guests" }, result.products.Select(p => p.Id).ToList()); } + + [TestMethod] + public async Task Handle_WhenShowHiddenWithoutCustomer_DoesNotFilterByGroups() + { + //Arrange + await _repository.InsertAsync(new Product { Id = "open", Published = true, VisibleIndividually = true }); + await _repository.InsertAsync(new Product { + Id = "admins", Published = true, VisibleIndividually = true, LimitedToGroups = true, + CustomerGroups = ["admins-group"] + }); + var searchProductsQuery = new GetSearchProductsQuery { ShowHidden = true }; + //Act + var result = await handler.Handle(searchProductsQuery, CancellationToken.None); + //Assert + CollectionAssert.AreEquivalent(new[] { "open", "admins" }, result.products.Select(p => p.Id).ToList()); + } } \ No newline at end of file diff --git a/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetSuggestedProductsQueryHandlerTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetSuggestedProductsQueryHandlerTests.cs index 6d76a5202b..ccd9094c96 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetSuggestedProductsQueryHandlerTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Queries/Handlers/GetSuggestedProductsQueryHandlerTests.cs @@ -1,10 +1,12 @@ using Grand.Business.Catalog.Queries.Handlers; using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Queries.Catalog; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Domain.Catalog; using Grand.Domain.Customers; using Grand.Infrastructure.Caching; +using Grand.Infrastructure.Caching.Constants; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -34,11 +36,47 @@ public void Init() public async Task HandleTest() { var suggestedProductsQuery = new GetSuggestedProductsQuery { - CustomerTagIds = ["1"], - ProductsNumber = 10 + ProductsNumber = 10, + Customer = new Customer() }; await handler.Handle(suggestedProductsQuery, CancellationToken.None); _casheManagerMock.Verify(c => c.GetAsync(It.IsAny(), It.IsAny>>>()), Times.Once); } + + [TestMethod] + public async Task Handle_WithoutCustomer_ThrowsBeforeCaching() + { + await Assert.ThrowsExactlyAsync(() => + handler.Handle(new GetSuggestedProductsQuery { ProductsNumber = 1 }, + CancellationToken.None)); + _casheManagerMock.Verify(c => c.GetAsync(It.IsAny(), It.IsAny>>>()), + Times.Never); + } + + [TestMethod] + public async Task Handle_KeysAndFiltersByTagsOfQueryCustomer() + { + //Arrange + var customer = new Customer(); + customer.CustomerTags.Add("t1"); + _casheManagerMock.Setup(c => c.GetAsync(It.IsAny(), It.IsAny>>>())) + .Returns((string _, Func>> acquire) => acquire()); + _customerGroupProductRepositoryMock.Setup(r => r.Table) + .Returns(new List { + new() { CustomerTagId = "t1", ProductId = "p1" }, new() { CustomerTagId = "t2", ProductId = "p2" } + }.AsQueryable()); + _productServiceMock.Setup(p => p.GetProductsByIds(It.IsAny(), CatalogAccess.For(customer, "s1"))) + .ReturnsAsync(new List()); + + //Act + await handler.Handle(new GetSuggestedProductsQuery { Customer = customer, StoreId = "s1", ProductsNumber = 10 }, + CancellationToken.None); + + //Assert + _casheManagerMock.Verify(c => c.GetAsync(string.Format(CacheKey.PRODUCTS_CUSTOMER_TAG, "t1"), + It.IsAny>>>()), Times.Once); + _productServiceMock.Verify(p => p.GetProductsByIds(new[] { "p1" }, + CatalogAccess.For(customer, "s1")), Times.Once); + } } \ No newline at end of file diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/Brands/BrandServiceTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/Brands/BrandServiceTests.cs index a648d49e93..e789b10b62 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Services/Brands/BrandServiceTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Services/Brands/BrandServiceTests.cs @@ -1,10 +1,9 @@ using Grand.Business.Catalog.Services.Brands; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Data.Tests.MongoDb; using Grand.Domain.Catalog; using Grand.Domain.Customers; -using Grand.Domain.Stores; -using Grand.Infrastructure; using Grand.Infrastructure.Caching; using Grand.Infrastructure.Configuration; using Grand.Infrastructure.Tests.Caching; @@ -21,19 +20,15 @@ public class BrandServiceTests private MemoryCacheBase _cacheBase; private Mock _mediatorMock; private IRepository _repository; - private Mock _workContextMock; [TestInitialize] public void InitializeTests() { _repository = new MongoDBRepositoryTest(); - _workContextMock = new Mock(); - _workContextMock.Setup(c => c.StoreContext.CurrentStore).Returns(() => new Store { Id = "" }); - _workContextMock.Setup(c => c.WorkContext.CurrentCustomer).Returns(() => new Customer()); _mediatorMock = new Mock(); _cacheBase = new MemoryCacheBase(MemoryCacheTest.Get(), _mediatorMock.Object, new CacheConfig { DefaultCacheTimeMinutes = 1 }); - _brandService = new BrandService(_cacheBase, _repository, _workContextMock.Object, _mediatorMock.Object, + _brandService = new BrandService(_cacheBase, _repository, _mediatorMock.Object, new AccessControlConfig()); } @@ -47,12 +42,33 @@ public async Task GetAllBrandsTest() await _brandService.InsertBrand(new Brand { Published = true }); //Act - var brand = await _brandService.GetAllBrands(brandName: "", storeId: ""); + var brand = await _brandService.GetAllBrands(brandName: "", access: CatalogAccess.For(new Customer(), "")); //Assert Assert.HasCount(3, brand); } + [TestMethod] + public async Task GetAllBrands_FiltersByGroupsOfGivenCustomer() + { + //Arrange + await _repository.InsertAsync(new Brand { Id = "open", Name = "open", Published = true }); + await _repository.InsertAsync(new Brand { + Id = "limited", Name = "limited", Published = true, LimitedToGroups = true, + CustomerGroups = new List { "vip" } + }); + var vipCustomer = new Customer(); + vipCustomer.Groups.Add("vip"); + + //Act + var guest = await _brandService.GetAllBrands("", CatalogAccess.For(new Customer(), "")); + var vip = await _brandService.GetAllBrands("", CatalogAccess.For(vipCustomer, "")); + + //Assert + Assert.HasCount(1, guest); + Assert.HasCount(2, vip); + } + [TestMethod] public async Task GetBrandByIdTest() { diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/CategoryServiceCacheKeyTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/CategoryServiceCacheKeyTests.cs index 8a8ab6ecf7..8760991d54 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/CategoryServiceCacheKeyTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/CategoryServiceCacheKeyTests.cs @@ -1,5 +1,6 @@ using Grand.Business.Catalog.Services.Categories; using Grand.Business.Common.Services.Security; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Data.Tests.MongoDb; using Grand.Domain.Catalog; @@ -28,20 +29,15 @@ public class CategoryServiceCacheKeyTests private MemoryCacheBase _cacheBase; private IRepository _categoryRepository; private CategoryService _categoryService; - private Customer _currentCustomer; [TestInitialize] public void InitializeTests() { _categoryRepository = new MongoDBRepositoryTest(); - _currentCustomer = new Customer(); - var contextAccessor = new Mock(); - contextAccessor.Setup(c => c.StoreContext.CurrentStore).Returns(() => new Store { Id = "store" }); - contextAccessor.Setup(c => c.WorkContext.CurrentCustomer).Returns(() => _currentCustomer); var mediator = new Mock(); _cacheBase = new MemoryCacheBase(MemoryCacheTest.Get(), mediator.Object, new CacheConfig { DefaultCacheTimeMinutes = 1 }); - _categoryService = new CategoryService(_cacheBase, _categoryRepository, contextAccessor.Object, + _categoryService = new CategoryService(_cacheBase, _categoryRepository, mediator.Object, new AclService(new AccessControlConfig()), new AccessControlConfig()); } @@ -58,11 +54,11 @@ public async Task TwoCustomersOfTheSameGroup_ShareTheCachedTree() { await _categoryService.InsertCategory(new Category { ParentCategoryId = "root", Published = true }); - _currentCustomer = WithGroups("customer-1", "group-a"); - var first = await _categoryService.GetAllCategoriesByParentCategoryId("root"); + var first = await _categoryService.GetAllCategoriesByParentCategoryId( + CatalogAccess.For(WithGroups("customer-1", "group-a"), "store"), "root"); - _currentCustomer = WithGroups("customer-2", "group-a"); - var second = await _categoryService.GetAllCategoriesByParentCategoryId("root"); + var second = await _categoryService.GetAllCategoriesByParentCategoryId( + CatalogAccess.For(WithGroups("customer-2", "group-a"), "store"), "root"); Assert.AreSame(first, second, "same groups must resolve to the same cache entry"); } @@ -72,11 +68,11 @@ public async Task CustomersOfDifferentGroups_DoNotShareTheCachedTree() { await _categoryService.InsertCategory(new Category { ParentCategoryId = "root", Published = true }); - _currentCustomer = WithGroups("customer-1", "group-a"); - var first = await _categoryService.GetAllCategoriesByParentCategoryId("root"); + var first = await _categoryService.GetAllCategoriesByParentCategoryId( + CatalogAccess.For(WithGroups("customer-1", "group-a"), "store"), "root"); - _currentCustomer = WithGroups("customer-2", "group-b"); - var second = await _categoryService.GetAllCategoriesByParentCategoryId("root"); + var second = await _categoryService.GetAllCategoriesByParentCategoryId( + CatalogAccess.For(WithGroups("customer-2", "group-b"), "store"), "root"); Assert.AreNotSame(first, second, "the ACL filter differs per group, so the entries must differ"); } diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/CategoryServiceDbTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/CategoryServiceDbTests.cs index 03583a4025..af7a47e690 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/CategoryServiceDbTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/CategoryServiceDbTests.cs @@ -1,6 +1,7 @@ using Grand.Business.Catalog.Services.Categories; using Grand.Business.Common.Services.Security; using Grand.Business.Core.Interfaces.Common.Security; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Data.Tests.MongoDb; using Grand.Domain.Catalog; @@ -25,24 +26,92 @@ public class CategoryServiceDbTests private CategoryService _categoryService; private Mock _mediatorMock; private CatalogSettings _settings; - private Mock _workContextMock; [TestInitialize] public void InitializeTests() { _categoryRepository = new MongoDBRepositoryTest(); - _workContextMock = new Mock(); - _workContextMock.Setup(c => c.StoreContext.CurrentStore).Returns(() => new Store { Id = "" }); - _workContextMock.Setup(c => c.WorkContext.CurrentCustomer).Returns(() => new Customer()); _mediatorMock = new Mock(); _aclServiceMock = new AclService(new AccessControlConfig()); _settings = new CatalogSettings(); _cacheBase = new MemoryCacheBase(MemoryCacheTest.Get(), _mediatorMock.Object, new CacheConfig { DefaultCacheTimeMinutes = 1 }); - _categoryService = new CategoryService(_cacheBase, _categoryRepository, _workContextMock.Object, + _categoryService = new CategoryService(_cacheBase, _categoryRepository, _mediatorMock.Object, _aclServiceMock, new AccessControlConfig()); } + [TestMethod] + public async Task GetAllCategoriesByParentCategoryId_ShowHiddenWithoutCustomer_ReturnsHiddenChildren() + { + //Arrange + await _categoryRepository.InsertAsync(new Category { + Id = "child", ParentCategoryId = "parent", Published = false, + LimitedToGroups = true, CustomerGroups = new List { "vip" } + }); + + //Act + var children = await _categoryService.GetAllCategoriesByParentCategoryId( + access: CatalogAccess.IncludingHidden(), + parentCategoryId: "parent"); + + //Assert + Assert.HasCount(1, children); + } + + [TestMethod] + public async Task GetAllCategoriesSearchBox_WithoutCustomer_Throws() + { + await Assert.ThrowsExactlyAsync(() => + _categoryService.GetAllCategoriesSearchBox(null, "store1")); + } + + [TestMethod] + public async Task GetMenuCategories_UsesGivenCustomerAndStore() + { + //Arrange + await _categoryRepository.InsertAsync(new Category { Id = "open", Published = true, IncludeInMenu = true }); + await _categoryRepository.InsertAsync(new Category { + Id = "vip", Published = true, IncludeInMenu = true, + LimitedToGroups = true, CustomerGroups = new List { "vip" } + }); + await _categoryRepository.InsertAsync(new Category { + Id = "s2", Published = true, IncludeInMenu = true, + LimitedToStores = true, Stores = new List { "store2" } + }); + var vipCustomer = new Customer(); + vipCustomer.Groups.Add("vip"); + + //Act + var guestStore1 = await _categoryService.GetMenuCategories(new Customer(), "store1"); + var vipStore2 = await _categoryService.GetMenuCategories(vipCustomer, "store2"); + + //Assert + CollectionAssert.AreEquivalent(new[] { "open" }, guestStore1.Select(c => c.Id).ToArray()); + CollectionAssert.AreEquivalent(new[] { "open", "vip", "s2" }, vipStore2.Select(c => c.Id).ToArray()); + } + + [TestMethod] + public async Task GetCategoryBreadCrumb_StopsAtCategoryHiddenFromGivenCustomer() + { + //Arrange + var parent = new Category { + Id = "p", Published = true, LimitedToGroups = true, CustomerGroups = new List { "vip" } + }; + var child = new Category { Id = "c", Published = true, ParentCategoryId = "p" }; + await _categoryRepository.InsertAsync(parent); + await _categoryRepository.InsertAsync(child); + var vipCustomer = new Customer(); + vipCustomer.Groups.Add("vip"); + + //Act + var guest = await _categoryService.GetCategoryBreadCrumb(child, CatalogAccess.For(new Customer(), "")); + var vip = await _categoryService.GetCategoryBreadCrumb(child, CatalogAccess.For(vipCustomer, "")); + + //Assert + Assert.HasCount(1, guest); + Assert.HasCount(2, vip); + } + [TestMethod] public async Task InsertCategory() { @@ -65,6 +134,23 @@ public async Task UpdateCategory() Assert.IsNotNull(_categoryRepository.Table.FirstOrDefault(x => x.Name == "test2")); } + [TestMethod] + public async Task DeleteCategory_DetachesItsChildCategories() + { + //Arrange + var parent = new Category { Id = "p", Published = true }; + await _categoryService.InsertCategory(parent); + await _categoryService.InsertCategory(new Category { Id = "c1", ParentCategoryId = "p" }); + await _categoryService.InsertCategory(new Category { Id = "c2", ParentCategoryId = "p", Published = true }); + await _categoryService.InsertCategory(new Category { Id = "other", ParentCategoryId = "x" }); + //Act + await _categoryService.DeleteCategory(parent); + //Assert + Assert.AreEqual("", _categoryRepository.Table.First(x => x.Id == "c1").ParentCategoryId); + Assert.AreEqual("", _categoryRepository.Table.First(x => x.Id == "c2").ParentCategoryId); + Assert.AreEqual("x", _categoryRepository.Table.First(x => x.Id == "other").ParentCategoryId); + } + [TestMethod] public async Task DeleteCategory() { @@ -85,7 +171,7 @@ public async Task GetCategoryBreadCrumb() allCategory.ToList().ForEach(x => _categoryService.InsertCategory(x).GetAwaiter().GetResult()); var category = new Category { Id = "6", ParentCategoryId = "3", Published = true }; await _categoryService.InsertCategory(category); - var result = await _categoryService.GetCategoryBreadCrumb(category); + var result = await _categoryService.GetCategoryBreadCrumb(category, CatalogAccess.For(new Customer(), "")); Assert.HasCount(2, result); Assert.IsTrue(result.Any(c => c.Id.Equals("6"))); Assert.IsTrue(result.Any(c => c.Id.Equals("3"))); @@ -97,7 +183,8 @@ public void GetCategoryBreadCrumb_AllCategory() var allCategory = GetMockCategoryList(); allCategory.ToList().ForEach(x => _categoryService.InsertCategory(x).GetAwaiter().GetResult()); var category = new Category { ParentCategoryId = "3" }; - var result = _categoryService.GetCategoryBreadCrumb(category, allCategory); + var result = _categoryService.GetCategoryBreadCrumb(category, allCategory, + CatalogAccess.For(new Customer(), "")); Assert.IsEmpty(result); } @@ -106,7 +193,8 @@ public async Task GetAllCategories() { var allCategory = GetMockCategoryList(); allCategory.ToList().ForEach(x => _categoryService.InsertCategory(x).GetAwaiter().GetResult()); - var result = await _categoryService.GetAllCategories(parentId: null, categoryName: "", storeId: ""); + var result = await _categoryService.GetAllCategories(parentId: null, categoryName: "", + access: CatalogAccess.For(new Customer(), "")); Assert.HasCount(5, result); } @@ -115,7 +203,7 @@ public async Task GetMenuCategories() { var allCategory = GetMockCategoryList(); allCategory.ToList().ForEach(x => _categoryService.InsertCategory(x).GetAwaiter().GetResult()); - var result = await _categoryService.GetMenuCategories(); + var result = await _categoryService.GetMenuCategories(new Customer(), ""); Assert.HasCount(1, result); } @@ -124,7 +212,8 @@ public async Task GetAllCategoriesByParentCategoryId() { var allCategory = GetMockCategoryList(); allCategory.ToList().ForEach(x => _categoryService.InsertCategory(x).GetAwaiter().GetResult()); - var result = await _categoryService.GetAllCategoriesByParentCategoryId("5"); + var result = await _categoryService.GetAllCategoriesByParentCategoryId( + CatalogAccess.For(new Customer(), ""), "5"); Assert.HasCount(1, result); } @@ -133,7 +222,7 @@ public async Task GetAllCategoriesDisplayedOnHomePage() { var allCategory = GetMockCategoryList(); allCategory.ToList().ForEach(x => _categoryService.InsertCategory(x).GetAwaiter().GetResult()); - var result = await _categoryService.GetAllCategoriesDisplayedOnHomePage(); + var result = await _categoryService.GetAllCategoriesDisplayedOnHomePage(CatalogAccess.For(new Customer(), "")); Assert.HasCount(2, result); } @@ -142,7 +231,8 @@ public async Task GetAllCategoriesFeaturedProductsOnHomePage() { var allCategory = GetMockCategoryList(); allCategory.ToList().ForEach(x => _categoryService.InsertCategory(x).GetAwaiter().GetResult()); - var result = await _categoryService.GetAllCategoriesFeaturedProductsOnHomePage(); + var result = await _categoryService.GetAllCategoriesFeaturedProductsOnHomePage( + CatalogAccess.For(new Customer(), "")); Assert.HasCount(2, result); } @@ -151,7 +241,7 @@ public async Task GetAllCategoriesSearchBox() { var allCategory = GetMockCategoryList(); allCategory.ToList().ForEach(x => _categoryService.InsertCategory(x).GetAwaiter().GetResult()); - var result = await _categoryService.GetAllCategoriesSearchBox(); + var result = await _categoryService.GetAllCategoriesSearchBox(new Customer(), ""); Assert.HasCount(2, result); } @@ -180,7 +270,8 @@ public async Task GetCategoryBreadCrumb_ShouldReturnTwoElement() allCategory.ToList().ForEach(x => _categoryService.InsertCategory(x).GetAwaiter().GetResult()); var category = new Category { Id = "6", ParentCategoryId = "3", Published = true }; await _categoryService.InsertCategory(category); - var result = _categoryService.GetCategoryBreadCrumb(category, allCategory); + var result = _categoryService.GetCategoryBreadCrumb(category, allCategory, + CatalogAccess.For(new Customer(), "")); Assert.HasCount(2, result); Assert.IsTrue(result.Any(c => c.Id.Equals("6"))); Assert.IsTrue(result.Any(c => c.Id.Equals("3"))); @@ -192,7 +283,8 @@ public void GetCategoryBreadCrumb_ShouldReturnThreeElement() var allCategory = GetMockCategoryList(); allCategory.ToList().ForEach(x => _categoryService.InsertCategory(x).GetAwaiter().GetResult()); var category = new Category { Id = "6", ParentCategoryId = "1", Published = true }; - var result = _categoryService.GetCategoryBreadCrumb(category, allCategory); + var result = _categoryService.GetCategoryBreadCrumb(category, allCategory, + CatalogAccess.For(new Customer(), "")); Assert.HasCount(3, result); Assert.IsTrue(result.Any(c => c.Id.Equals("6"))); Assert.IsTrue(result.Any(c => c.Id.Equals("1"))); diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/CategoryServiceTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/CategoryServiceTests.cs index 109237f306..c15c82ac32 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/CategoryServiceTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/CategoryServiceTests.cs @@ -1,5 +1,6 @@ using Grand.Business.Catalog.Services.Categories; using Grand.Business.Core.Interfaces.Common.Security; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Data.Mongo; using Grand.Domain.Catalog; @@ -27,7 +28,6 @@ public class CategoryServiceTests private ProductCategoryService _productCategoryService; private Mock> _productRepositoryMock; private CatalogSettings _settings; - private Mock _workContextMock; [TestInitialize] public void Init() @@ -37,15 +37,13 @@ public void Init() _casheManagerMock = new Mock(); _categoryRepositoryMock = new Mock>(); _productRepositoryMock = new Mock>(Mock.Of()); - _workContextMock = new Mock(); _mediatorMock = new Mock(); _aclServiceMock = new Mock(); _settings = new CatalogSettings(); _categoryService = new CategoryService(_casheManagerMock.Object, _categoryRepositoryMock.Object, - _workContextMock.Object, _mediatorMock.Object, _aclServiceMock.Object, new AccessControlConfig()); _productCategoryService = new ProductCategoryService(_productRepositoryMock.Object, _casheManagerMock.Object, - _workContextMock.Object, _mediatorMock.Object, new AccessControlConfig()); + _mediatorMock.Object, new AccessControlConfig()); } [TestMethod] @@ -88,7 +86,8 @@ public void GetCategoryBreadCrumb_ShouldReturnEmptyList() var category = new Category { ParentCategoryId = "3" }; _aclServiceMock.Setup(a => a.Authorize(It.IsAny(), It.IsAny())).Returns(() => true); _aclServiceMock.Setup(a => a.Authorize(It.IsAny(), It.IsAny())).Returns(() => true); - var result = _categoryService.GetCategoryBreadCrumb(category, allCategory); + var result = _categoryService.GetCategoryBreadCrumb(category, allCategory, + CatalogAccess.For(new Customer { Id = "" }, "")); Assert.IsEmpty(result); } @@ -97,11 +96,10 @@ public void GetCategoryBreadCrumb_ShouldReturnTwoElement() { var allCategory = GetMockCategoryList(); var category = new Category { Id = "6", ParentCategoryId = "3", Published = true }; - _workContextMock.Setup(c => c.StoreContext.CurrentStore).Returns(() => new Store { Id = "" }); - _workContextMock.Setup(c => c.WorkContext.CurrentCustomer).Returns(() => new Customer { Id = "" }); _aclServiceMock.Setup(a => a.Authorize(It.IsAny(), It.IsAny())).Returns(() => true); _aclServiceMock.Setup(a => a.Authorize(It.IsAny(), It.IsAny())).Returns(() => true); - var result = _categoryService.GetCategoryBreadCrumb(category, allCategory); + var result = _categoryService.GetCategoryBreadCrumb(category, allCategory, + CatalogAccess.For(new Customer { Id = "" }, "")); Assert.HasCount(2, result); Assert.IsTrue(result.Any(c => c.Id.Equals("6"))); Assert.IsTrue(result.Any(c => c.Id.Equals("3"))); @@ -112,11 +110,10 @@ public void GetCategoryBreadCrumb_ShouldReturnThreeElement() { var allCategory = GetMockCategoryList(); var category = new Category { Id = "6", ParentCategoryId = "1", Published = true }; - _workContextMock.Setup(c => c.StoreContext.CurrentStore).Returns(() => new Store { Id = "" }); - _workContextMock.Setup(c => c.WorkContext.CurrentCustomer).Returns(() => new Customer { Id = "" }); _aclServiceMock.Setup(a => a.Authorize(It.IsAny(), It.IsAny())).Returns(() => true); _aclServiceMock.Setup(a => a.Authorize(It.IsAny(), It.IsAny())).Returns(() => true); - var result = _categoryService.GetCategoryBreadCrumb(category, allCategory); + var result = _categoryService.GetCategoryBreadCrumb(category, allCategory, + CatalogAccess.For(new Customer { Id = "" }, "")); Assert.HasCount(3, result); Assert.IsTrue(result.Any(c => c.Id.Equals("6"))); Assert.IsTrue(result.Any(c => c.Id.Equals("1"))); diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/CategoryServiceTreeTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/CategoryServiceTreeTests.cs index 49c552eaf2..12499369ce 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/CategoryServiceTreeTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/CategoryServiceTreeTests.cs @@ -1,5 +1,6 @@ using Grand.Business.Catalog.Services.Categories; using Grand.Business.Common.Services.Security; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Data.Tests.MongoDb; using Grand.Domain.Catalog; @@ -26,20 +27,19 @@ public class CategoryServiceTreeTests private CategoryService _categoryService; private IRepository _categoryRepository; + private Customer _customer; + [TestInitialize] public void InitializeTests() { _categoryRepository = new MongoDBRepositoryTest(); - //one customer for the whole fixture: a fresh instance per access would give every call its + //one customer for the whole fixture: a fresh instance per call would give every call its //own id, hence its own cache key, and the caching under test would never be exercised - var customer = new Customer { Id = "customer" }; - var contextAccessor = new Mock(); - contextAccessor.Setup(c => c.StoreContext.CurrentStore).Returns(() => new Store { Id = "store" }); - contextAccessor.Setup(c => c.WorkContext.CurrentCustomer).Returns(() => customer); + _customer = new Customer { Id = "customer" }; var mediator = new Mock(); var cacheBase = new MemoryCacheBase(MemoryCacheTest.Get(), mediator.Object, new CacheConfig { DefaultCacheTimeMinutes = 1 }); - _categoryService = new CategoryService(cacheBase, _categoryRepository, contextAccessor.Object, + _categoryService = new CategoryService(cacheBase, _categoryRepository, mediator.Object, new AclService(new AccessControlConfig()), new AccessControlConfig()); } @@ -59,7 +59,8 @@ public async Task AllLevels_KeepsTheWholeLevelBeforeEachSubtree() { await GivenATwoLevelTree(); - var result = await _categoryService.GetAllCategoriesByParentCategoryId("root", includeAllLevels: true); + var result = await _categoryService.GetAllCategoriesByParentCategoryId( + CatalogAccess.For(_customer, "store"), "root", includeAllLevels: true); CollectionAssert.AreEqual(new[] { "a", "b", "a1", "b1" }, result.Select(x => x.Id).ToArray()); } @@ -69,7 +70,8 @@ public async Task WithoutAllLevels_ReturnsOnlyTheDirectChildren() { await GivenATwoLevelTree(); - var result = await _categoryService.GetAllCategoriesByParentCategoryId("root"); + var result = await _categoryService.GetAllCategoriesByParentCategoryId( + CatalogAccess.For(_customer, "store"), "root"); CollectionAssert.AreEqual(new[] { "a", "b" }, result.Select(x => x.Id).ToArray()); } @@ -83,9 +85,11 @@ public async Task AllLevels_HandsBackAListTheCallerOwns() { await GivenATwoLevelTree(); - var first = await _categoryService.GetAllCategoriesByParentCategoryId("root", includeAllLevels: true); + var first = await _categoryService.GetAllCategoriesByParentCategoryId( + CatalogAccess.For(_customer, "store"), "root", includeAllLevels: true); first.Clear(); - var second = await _categoryService.GetAllCategoriesByParentCategoryId("root", includeAllLevels: true); + var second = await _categoryService.GetAllCategoriesByParentCategoryId( + CatalogAccess.For(_customer, "store"), "root", includeAllLevels: true); Assert.HasCount(4, second); } diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/ProductCategoryServiceTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/ProductCategoryServiceTests.cs index 4be5c2083d..096b738585 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/ProductCategoryServiceTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Services/Categories/ProductCategoryServiceTests.cs @@ -1,4 +1,5 @@ using Grand.Business.Catalog.Services.Categories; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Data.Tests.MongoDb; using Grand.Domain.Catalog; @@ -21,23 +22,57 @@ public class ProductCategoryServiceTests private Mock _mediatorMock; private ProductCategoryService _productCategoryService; private IRepository _repository; - private Mock _workContextMock; [TestInitialize] public void InitializeTests() { _repository = new MongoDBRepositoryTest(); - _workContextMock = new Mock(); - _workContextMock.Setup(c => c.StoreContext.CurrentStore).Returns(() => new Store { Id = "" }); - _workContextMock.Setup(c => c.WorkContext.CurrentCustomer).Returns(() => new Customer()); _mediatorMock = new Mock(); _cacheBase = new MemoryCacheBase(MemoryCacheTest.Get(), _mediatorMock.Object, new CacheConfig { DefaultCacheTimeMinutes = 1 }); - _productCategoryService = new ProductCategoryService(_repository, _cacheBase, _workContextMock.Object, + _productCategoryService = new ProductCategoryService(_repository, _cacheBase, _mediatorMock.Object, new AccessControlConfig()); } + [TestMethod] + public async Task GetProductCategoriesByCategoryId_FiltersByGivenStore() + { + //Arrange + var product = new Product { LimitedToStores = true, Stores = new List { "store1" } }; + product.ProductCategories.Add(new ProductCategory { CategoryId = "cat" }); + await _repository.InsertAsync(product); + + //Act + var store1 = await _productCategoryService.GetProductCategoriesByCategoryId("cat", + CatalogAccess.For(new Customer(), "store1")); + var store2 = await _productCategoryService.GetProductCategoriesByCategoryId("cat", + CatalogAccess.For(new Customer(), "store2")); + + //Assert + Assert.HasCount(1, store1); + Assert.IsEmpty(store2); + } + + [TestMethod] + public async Task GetProductCategoriesByCategoryId_ShowHiddenWithoutCustomer_ReturnsLimitedProducts() + { + //Arrange + var product = new Product { + LimitedToStores = true, Stores = new List { "store1" }, + LimitedToGroups = true, CustomerGroups = new List { "vip" } + }; + product.ProductCategories.Add(new ProductCategory { CategoryId = "cat" }); + await _repository.InsertAsync(product); + + //Act + var mappings = await _productCategoryService.GetProductCategoriesByCategoryId("cat", + access: CatalogAccess.IncludingHidden()); + + //Assert + Assert.HasCount(1, mappings); + } + [TestMethod] public async Task GetProductCategoriesByCategoryIdTest() { @@ -52,8 +87,10 @@ public async Task GetProductCategoriesByCategoryIdTest() await _repository.InsertAsync(p2); //Act - var pc1 = await _productCategoryService.GetProductCategoriesByCategoryId("1"); - var pc2 = await _productCategoryService.GetProductCategoriesByCategoryId("2"); + var pc1 = await _productCategoryService.GetProductCategoriesByCategoryId("1", + CatalogAccess.For(new Customer(), "")); + var pc2 = await _productCategoryService.GetProductCategoriesByCategoryId("2", + CatalogAccess.For(new Customer(), "")); //Assert Assert.HasCount(1, pc1); @@ -74,7 +111,8 @@ await _productCategoryService.InsertProductCategory(new ProductCategory { Catego p1.Id); await _productCategoryService.InsertProductCategory(new ProductCategory { CategoryId = "2" }, p1.Id); - var pc1 = await _productCategoryService.GetProductCategoriesByCategoryId("1"); + var pc1 = await _productCategoryService.GetProductCategoriesByCategoryId("1", + CatalogAccess.For(new Customer(), "")); //Assert Assert.HasCount(1, pc1); @@ -99,7 +137,8 @@ public async Task UpdateProductCategoryTest() pc.DisplayOrder = 5; await _productCategoryService.UpdateProductCategory(pc, p1.Id); - var pc1 = await _productCategoryService.GetProductCategoriesByCategoryId("10"); + var pc1 = await _productCategoryService.GetProductCategoriesByCategoryId("10", + CatalogAccess.For(new Customer(), "")); //Assert Assert.HasCount(1, pc1); @@ -122,7 +161,8 @@ public async Task DeleteProductCategoryTest() await _productCategoryService.DeleteProductCategory(pc, p1.Id); - var pc1 = await _productCategoryService.GetProductCategoriesByCategoryId("1"); + var pc1 = await _productCategoryService.GetProductCategoriesByCategoryId("1", + CatalogAccess.For(new Customer(), "")); //Assert Assert.IsEmpty(pc1); diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/Collections/CollectionServiceTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/Collections/CollectionServiceTests.cs index 352fc287ef..2157e0b0a4 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Services/Collections/CollectionServiceTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Services/Collections/CollectionServiceTests.cs @@ -1,9 +1,10 @@ using Grand.Business.Catalog.Services.Collections; using Grand.Business.Core.Interfaces.Catalog.Collections; using Grand.Business.Core.Interfaces.Common.Security; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Domain.Catalog; -using Grand.Infrastructure; +using Grand.Domain.Customers; using Grand.Infrastructure.Caching; using Grand.Infrastructure.Configuration; using Grand.Infrastructure.Events; @@ -22,22 +23,42 @@ public class CollectionServiceTests private Mock _mediatorMock; private Mock> _repositoryMock; private CatalogSettings _settings; - private Mock _workContextMock; [TestInitialize] public void Init() { _cacheMock = new Mock(); _repositoryMock = new Mock>(); - _workContextMock = new Mock(); _mediatorMock = new Mock(); _aclMock = new Mock(); _settings = new CatalogSettings(); - _collectionService = new CollectionService(_cacheMock.Object, _repositoryMock.Object, _workContextMock.Object - , _mediatorMock.Object, _aclMock.Object, new AccessControlConfig()); + _collectionService = new CollectionService(_cacheMock.Object, _repositoryMock.Object, _mediatorMock.Object, _aclMock.Object, new AccessControlConfig()); } + [TestMethod] + public async Task GetAllCollectionFeaturedProductsOnHomePage_AuthorizesAgainstGivenCustomerAndStore() + { + //Arrange + var collection = new Collection { Id = "a", Published = true, FeaturedProductsOnHomePage = true }; + var customer = new Customer(); + _repositoryMock.Setup(r => r.Table).Returns(new List { collection }.AsQueryable()); + _repositoryMock.Setup(r => r.ToListAsync(It.IsAny>(), It.IsAny())) + .ReturnsAsync((IQueryable q, CancellationToken _) => q.ToList()); + _aclMock.Setup(a => a.Authorize(collection, customer)).Returns(true); + _aclMock.Setup(a => a.Authorize(collection, "store1")).Returns(true); + + //Act + var inStore1 = await _collectionService.GetAllCollectionFeaturedProductsOnHomePage( + CatalogAccess.For(customer, "store1")); + var inStore2 = await _collectionService.GetAllCollectionFeaturedProductsOnHomePage( + CatalogAccess.For(customer, "store2")); + + //Assert + Assert.HasCount(1, inStore1); + Assert.IsEmpty(inStore2); + } + [TestMethod] public async Task DeleteCollection_ValidArguments_InvokeMethods() { diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/Collections/ProductCollectionServiceTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/Collections/ProductCollectionServiceTests.cs index 0880d34358..0e130f3d8b 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Services/Collections/ProductCollectionServiceTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Services/Collections/ProductCollectionServiceTests.cs @@ -1,4 +1,5 @@ using Grand.Business.Catalog.Services.Collections; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Data.Tests.MongoDb; using Grand.Domain.Catalog; @@ -21,23 +22,40 @@ public class ProductCollectionServiceTests private Mock _mediatorMock; private ProductCollectionService _productCollectionService; private IRepository _repository; - private Mock _workContextMock; [TestInitialize] public void InitializeTests() { _repository = new MongoDBRepositoryTest(); - _workContextMock = new Mock(); - _workContextMock.Setup(c => c.StoreContext.CurrentStore).Returns(() => new Store { Id = "" }); - _workContextMock.Setup(c => c.WorkContext.CurrentCustomer).Returns(() => new Customer()); _mediatorMock = new Mock(); _cacheBase = new MemoryCacheBase(MemoryCacheTest.Get(), _mediatorMock.Object, new CacheConfig { DefaultCacheTimeMinutes = 1 }); - _productCollectionService = new ProductCollectionService(_cacheBase, _repository, _workContextMock.Object, + _productCollectionService = new ProductCollectionService(_cacheBase, _repository, _mediatorMock.Object, new AccessControlConfig()); } + [TestMethod] + public async Task GetProductCollectionsByCollectionId_FiltersByGroupsOfGivenCustomer() + { + //Arrange + var product = new Product { LimitedToGroups = true, CustomerGroups = new List { "vip" } }; + product.ProductCollections.Add(new ProductCollection { CollectionId = "col" }); + await _repository.InsertAsync(product); + var vipCustomer = new Customer(); + vipCustomer.Groups.Add("vip"); + + //Act + var guest = await _productCollectionService.GetProductCollectionsByCollectionId("col", + CatalogAccess.For(new Customer(), "")); + var vip = await _productCollectionService.GetProductCollectionsByCollectionId("col", + CatalogAccess.For(vipCustomer, "")); + + //Assert + Assert.IsEmpty(guest); + Assert.HasCount(1, vip); + } + [TestMethod] public async Task GetProductCollectionsByCollectionIdTest() { @@ -52,8 +70,10 @@ public async Task GetProductCollectionsByCollectionIdTest() await _repository.InsertAsync(p2); //Act - var pc1 = await _productCollectionService.GetProductCollectionsByCollectionId("1", ""); - var pc2 = await _productCollectionService.GetProductCollectionsByCollectionId("2", ""); + var pc1 = await _productCollectionService.GetProductCollectionsByCollectionId("1", + CatalogAccess.For(new Customer(), "")); + var pc2 = await _productCollectionService.GetProductCollectionsByCollectionId("2", + CatalogAccess.For(new Customer(), "")); //Assert Assert.HasCount(1, pc1); @@ -74,7 +94,8 @@ await _productCollectionService.InsertProductCollection( new ProductCollection { CollectionId = "1", DisplayOrder = 10 }, p1.Id); await _productCollectionService.InsertProductCollection(new ProductCollection { CollectionId = "2" }, p1.Id); - var pc1 = await _productCollectionService.GetProductCollectionsByCollectionId("1", ""); + var pc1 = await _productCollectionService.GetProductCollectionsByCollectionId("1", + CatalogAccess.For(new Customer(), "")); //Assert Assert.HasCount(1, pc1); @@ -99,7 +120,8 @@ public async Task UpdateProductCollectionTest() pc.DisplayOrder = 5; await _productCollectionService.UpdateProductCollection(pc, p1.Id); - var pc1 = await _productCollectionService.GetProductCollectionsByCollectionId("10", ""); + var pc1 = await _productCollectionService.GetProductCollectionsByCollectionId("10", + CatalogAccess.For(new Customer(), "")); //Assert Assert.HasCount(1, pc1); @@ -122,7 +144,8 @@ public async Task DeleteProductCollectionTest() await _productCollectionService.DeleteProductCollection(pc, p1.Id); - var pc1 = await _productCollectionService.GetProductCollectionsByCollectionId("1", ""); + var pc1 = await _productCollectionService.GetProductCollectionsByCollectionId("1", + CatalogAccess.For(new Customer(), "")); //Assert Assert.IsEmpty(pc1); diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/ExportImport/BrandImportDataObjectTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/ExportImport/BrandImportDataObjectTests.cs index dc3c93e257..5ea53bfc09 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Services/ExportImport/BrandImportDataObjectTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Services/ExportImport/BrandImportDataObjectTests.cs @@ -43,7 +43,6 @@ public class BrandImportDataObjectTests private IRepository _repository; private Mock _slugServiceMock; - private Mock _workContextMock; private ISeNameService _seNameService; private SecurityConfig _securityConfig; private Mock> _loggerMock; @@ -59,14 +58,11 @@ public void Init() _brandLayoutServiceMock = new Mock(); _slugServiceMock = new Mock(); _languageServiceMock = new Mock(); - _workContextMock = new Mock(); - _workContextMock.Setup(c => c.StoreContext.CurrentStore).Returns(() => new Store { Id = "" }); - _workContextMock.Setup(c => c.WorkContext.CurrentCustomer).Returns(() => new Customer()); _mediatorMock = new Mock(); _cacheBase = new MemoryCacheBase(MemoryCacheTest.Get(), _mediatorMock.Object, new CacheConfig { DefaultCacheTimeMinutes = 1 }); - _brandService = new BrandService(_cacheBase, _repository, _workContextMock.Object, _mediatorMock.Object, + _brandService = new BrandService(_cacheBase, _repository, _mediatorMock.Object, new AccessControlConfig()); _seNameService = new SeNameService(_slugServiceMock.Object, _languageServiceMock.Object, new SeoSettings()); _brandImportDataObject = new BrandImportDataObject(_brandService, _pictureServiceMock.Object, diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/ExportImport/CategoryImportDataObjectTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/ExportImport/CategoryImportDataObjectTests.cs index 43fb3a9929..819a7bf06a 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Services/ExportImport/CategoryImportDataObjectTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Services/ExportImport/CategoryImportDataObjectTests.cs @@ -44,7 +44,6 @@ public class CategoryImportDataObjectTests private IRepository _repository; private Mock _slugServiceMock; - private Mock _workContextMock; private ISeNameService _seNameService; private SecurityConfig _securityConfig; private Mock> _loggerMock; @@ -60,14 +59,11 @@ public void Init() _categoryLayoutServiceMock = new Mock(); _slugServiceMock = new Mock(); _languageServiceMock = new Mock(); - _workContextMock = new Mock(); - _workContextMock.Setup(c => c.StoreContext.CurrentStore).Returns(() => new Store { Id = "" }); - _workContextMock.Setup(c => c.WorkContext.CurrentCustomer).Returns(() => new Customer()); _mediatorMock = new Mock(); _cacheBase = new MemoryCacheBase(MemoryCacheTest.Get(), _mediatorMock.Object, new CacheConfig { DefaultCacheTimeMinutes = 1 }); - _categoryService = new CategoryService(_cacheBase, _repository, _workContextMock.Object, _mediatorMock.Object, + _categoryService = new CategoryService(_cacheBase, _repository, _mediatorMock.Object, new AclService(new AccessControlConfig()), new AccessControlConfig()); _seNameService = new SeNameService(_slugServiceMock.Object, _languageServiceMock.Object, new SeoSettings()); _categoryImportDataObject = new CategoryImportDataObject(_categoryService, _pictureServiceMock.Object, diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/ExportImport/CollectionImportDataObjectTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/ExportImport/CollectionImportDataObjectTests.cs index 4fb9eef77b..abbc677751 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Services/ExportImport/CollectionImportDataObjectTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Services/ExportImport/CollectionImportDataObjectTests.cs @@ -44,7 +44,6 @@ public class CollectionImportDataObjectTests private IRepository _repository; private Mock _slugServiceMock; - private Mock _workContextMock; private ISeNameService _seNameService; private SecurityConfig _securityConfig; private Mock> _loggerMock; @@ -60,14 +59,11 @@ public void Init() _collectionLayoutServiceMock = new Mock(); _slugServiceMock = new Mock(); _languageServiceMock = new Mock(); - _workContextMock = new Mock(); - _workContextMock.Setup(c => c.StoreContext.CurrentStore).Returns(() => new Store { Id = "" }); - _workContextMock.Setup(c => c.WorkContext.CurrentCustomer).Returns(() => new Customer()); _mediatorMock = new Mock(); _cacheBase = new MemoryCacheBase(MemoryCacheTest.Get(), _mediatorMock.Object, new CacheConfig { DefaultCacheTimeMinutes = 1 }); - _collectionService = new CollectionService(_cacheBase, _repository, _workContextMock.Object, + _collectionService = new CollectionService(_cacheBase, _repository, _mediatorMock.Object, new AclService(new AccessControlConfig()), new AccessControlConfig()); _seNameService = new SeNameService(_slugServiceMock.Object, _languageServiceMock.Object, new SeoSettings()); _collectionImportDataObject = new CollectionImportDataObject(_collectionService, _pictureServiceMock.Object, diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/ExportImport/ProductImportDataObjectTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/ExportImport/ProductImportDataObjectTests.cs index 1b092edb23..c50526dcbc 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Services/ExportImport/ProductImportDataObjectTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Services/ExportImport/ProductImportDataObjectTests.cs @@ -66,7 +66,6 @@ public class ProductImportDataObjectTests private Mock _slugServiceMock; private Mock _taxServiceMock; private Mock _warehouseServiceMock; - private Mock _workContextMock; private ISeNameService _seNameService; private SecurityConfig _securityConfig; private TaxSettings _taxSettings; @@ -100,14 +99,10 @@ public void Init() _productCategoryServiceMock = new Mock(); _productCollectionServiceMock = new Mock(); - _workContextMock = new Mock(); - _workContextMock.Setup(c => c.StoreContext.CurrentStore).Returns(() => new Store { Id = "" }); - _workContextMock.Setup(c => c.WorkContext.CurrentCustomer).Returns(() => new Customer()); - _mediatorMock = new Mock(); _cacheBase = new MemoryCacheBase(MemoryCacheTest.Get(), _mediatorMock.Object, new CacheConfig { DefaultCacheTimeMinutes = 1 }); - _productService = new ProductService(_cacheBase, _repository, _workContextMock.Object, _mediatorMock.Object, + _productService = new ProductService(_cacheBase, _repository, _mediatorMock.Object, new AclService(new AccessControlConfig())); _seNameService = new SeNameService(_slugServiceMock.Object, _languageServiceMock.Object, new SeoSettings()); _productImportDataObject = new ProductImportDataObject diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/Products/ProductServiceTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/Products/ProductServiceTests.cs index c134491da4..e9b006affb 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Services/Products/ProductServiceTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Services/Products/ProductServiceTests.cs @@ -2,8 +2,10 @@ using Grand.Business.Common.Services.Security; using Grand.Business.Core.Interfaces.Common.Security; using Grand.Business.Core.Queries.Catalog; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Data.Tests.MongoDb; +using Grand.Domain; using Grand.Domain.Catalog; using Grand.Domain.Customers; using Grand.Domain.Orders; @@ -26,15 +28,11 @@ public class ProductServiceTests private Mock _mediatorMock; private IRepository _productRepository; private ProductService _productService; - private Mock _workContextMock; [TestInitialize] public void InitializeTests() { _productRepository = new MongoDBRepositoryTest(); - _workContextMock = new Mock(); - _workContextMock.Setup(c => c.StoreContext.CurrentStore).Returns(() => new Store { Id = "" }); - _workContextMock.Setup(c => c.WorkContext.CurrentCustomer).Returns(() => new Customer()); _mediatorMock = new Mock(); _mediatorMock.Setup(x => x.Send(It.IsAny(), default)) @@ -43,10 +41,65 @@ public void InitializeTests() _aclServiceMock = new AclService(new AccessControlConfig()); _cacheBase = new MemoryCacheBase(MemoryCacheTest.Get(), _mediatorMock.Object, new CacheConfig { DefaultCacheTimeMinutes = 1 }); - _productService = new ProductService(_cacheBase, _productRepository, _workContextMock.Object, + _productService = new ProductService(_cacheBase, _productRepository, _mediatorMock.Object, _aclServiceMock); } + [TestMethod] + public async Task GetProductsByIds_AuthorizesAgainstGivenCustomerAndStore() + { + //Arrange + await _productRepository.InsertAsync(new Product { + Id = "p1", Published = true, LimitedToStores = true, Stores = new List { "store1" } + }); + + //Act + var store1 = await _productService.GetProductsByIds(["p1"], CatalogAccess.For(new Customer(), "store1")); + var store2 = await _productService.GetProductsByIds(["p1"], CatalogAccess.For(new Customer(), "store2")); + + //Assert + Assert.HasCount(1, store1); + Assert.IsEmpty(store2); + } + + [TestMethod] + public async Task GetProductsByIds_WithoutAccess_Throws() + { + await Assert.ThrowsExactlyAsync(() => _productService.GetProductsByIds(["p1"], null)); + } + + [TestMethod] + public async Task GetProductsByIds_IncludingHidden_ReturnsUnpublishedAndGroupLimitedProducts() + { + //Arrange + await _productRepository.InsertAsync(new Product { + Id = "p1", Published = false, LimitedToGroups = true, CustomerGroups = new List { "vip" } + }); + + //Act + var products = await _productService.GetProductsByIds(["p1"], CatalogAccess.IncludingHidden()); + + //Assert + Assert.HasCount(1, products); + } + + [TestMethod] + public async Task SearchProducts_SendsGivenCustomerInQuery() + { + //Arrange + var customer = new Customer { Id = "c1" }; + _mediatorMock.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync((new PagedList(new List(), 0, 1), (IList)new List())); + + //Act + await _productService.SearchProducts(CatalogAccess.For(customer, "s1")); + + //Assert + _mediatorMock.Verify(m => m.Send( + It.Is(q => q.Customer == customer && q.StoreId == "s1"), + It.IsAny()), Times.Once); + } + private async Task insertSampleProducts() { await _productRepository.InsertAsync(new Product @@ -140,7 +193,7 @@ public async Task GetProductsByIdsTest() { await insertSampleProducts(); //Act - var result = await _productService.GetProductsByIds(["1"]); + var result = await _productService.GetProductsByIds(["1"], CatalogAccess.For(new Customer(), "")); //Assert Assert.IsNotEmpty(result); @@ -257,7 +310,7 @@ public async Task GetAssociatedProductsTest() //Arrange await insertSampleProducts(); //Act - var result = await _productService.GetAssociatedProducts("1"); + var result = await _productService.GetAssociatedProducts("1", CatalogAccess.For(new Customer(), "")); //Assert Assert.IsNotEmpty(result); @@ -558,7 +611,7 @@ await _productService.InsertCrossSellProduct(new CrossSellProduct { var result = await _productService.GetCrossSellProductsByShoppingCart( - new List { new() { ProductId = product.Id } }, 2); + new List { new() { ProductId = product.Id } }, 2, new Customer(), ""); //Assert Assert.IsNotNull(result); diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/Products/RecentlyViewedProductsServiceTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/Products/RecentlyViewedProductsServiceTests.cs index 89e1febfe9..10c4a711fb 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Services/Products/RecentlyViewedProductsServiceTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Services/Products/RecentlyViewedProductsServiceTests.cs @@ -1,8 +1,10 @@ using Grand.Business.Catalog.Services.Products; using Grand.Business.Core.Interfaces.Catalog.Products; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Data.Tests.MongoDb; using Grand.Domain.Catalog; +using Grand.Domain.Customers; using Grand.Infrastructure.Caching; using Grand.Infrastructure.Configuration; using Grand.Infrastructure.Tests.Caching; @@ -27,7 +29,8 @@ public void Init() _repository = new MongoDBRepositoryTest(); _mediatorMock = new Mock(); _productServiceMock = new Mock(); - _productServiceMock.Setup(a => a.GetProductsByIds(It.IsAny(), false)).Returns(() => + _productServiceMock.Setup(a => a.GetProductsByIds(It.IsAny(), + It.IsAny())).Returns(() => Task.FromResult((IList)new List { new() { Id = "1", Published = true }, new() { Id = "2", Published = true }, new() { Id = "3", Published = true } @@ -50,7 +53,7 @@ public async Task GetRecentlyViewedProductsTest() await _service.AddProductToRecentlyViewedList("2", "1"); //Act - var result = await _service.GetRecentlyViewedProducts("1", 10); + var result = await _service.GetRecentlyViewedProducts(new Customer { Id = "1" }, "", 10); //Assert Assert.HasCount(3, result); diff --git a/src/Tests/Grand.Business.Catalog.Tests/Services/Products/SpecificationAttributeServiceTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Services/Products/SpecificationAttributeServiceTests.cs index 7d9ef088f8..e83da43448 100644 --- a/src/Tests/Grand.Business.Catalog.Tests/Services/Products/SpecificationAttributeServiceTests.cs +++ b/src/Tests/Grand.Business.Catalog.Tests/Services/Products/SpecificationAttributeServiceTests.cs @@ -1,8 +1,10 @@ using Grand.Business.Catalog.Services.Products; using Grand.Business.Core.Interfaces.Catalog.Products; +using Grand.Business.Core.Utilities.Catalog; using Grand.Data; using Grand.Data.Tests.MongoDb; using Grand.Domain.Catalog; +using Grand.Domain.Customers; using Grand.Infrastructure.Caching; using Grand.Infrastructure.Configuration; using Grand.Infrastructure.Tests.Caching; @@ -30,7 +32,8 @@ public void Init() _repositoryProduct = new MongoDBRepositoryTest(); _mediatorMock = new Mock(); _productServiceMock = new Mock(); - _productServiceMock.Setup(a => a.GetProductsByIds(It.IsAny(), false)).Returns(() => + _productServiceMock.Setup(a => a.GetProductsByIds(It.IsAny(), + It.IsAny())).Returns(() => Task.FromResult((IList)new List { new() { Id = "1", Published = true }, new() { Id = "2", Published = true }, new() { Id = "3", Published = true } diff --git a/src/Tests/Grand.Business.Catalog.Tests/Utilities/CatalogAccessTests.cs b/src/Tests/Grand.Business.Catalog.Tests/Utilities/CatalogAccessTests.cs new file mode 100644 index 0000000000..17259b7ec4 --- /dev/null +++ b/src/Tests/Grand.Business.Catalog.Tests/Utilities/CatalogAccessTests.cs @@ -0,0 +1,54 @@ +using Grand.Business.Core.Utilities.Catalog; +using Grand.Domain.Customers; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace Grand.Business.Catalog.Tests.Utilities; + +[TestClass] +public class CatalogAccessTests +{ + [TestMethod] + public void For_WithoutCustomer_Throws() + { + Assert.ThrowsExactly(() => CatalogAccess.For(null, "s1")); + } + + [TestMethod] + public void For_LimitsToCustomerAndStore() + { + var customer = new Customer(); + + var access = CatalogAccess.For(customer, "s1"); + + Assert.AreSame(customer, access.Customer); + Assert.AreEqual("s1", access.StoreId); + Assert.IsFalse(access.ShowHidden); + } + + [TestMethod] + public void IncludingHidden_ShowsHiddenRecordsOfAnyStoreByDefault() + { + var access = CatalogAccess.IncludingHidden(); + + Assert.IsNull(access.Customer); + Assert.AreEqual("", access.StoreId); + Assert.IsTrue(access.ShowHidden); + } + + [TestMethod] + public void IncludingHidden_TreatsNullStoreAsNoStore() + { + Assert.AreEqual(CatalogAccess.IncludingHidden(), CatalogAccess.IncludingHidden(null)); + } + + [TestMethod] + public void Equals_ComparesCustomerAndStore() + { + var customer = new Customer(); + + Assert.AreEqual(CatalogAccess.For(customer, "s1"), CatalogAccess.For(customer, "s1")); + Assert.AreNotEqual(CatalogAccess.For(customer, "s1"), CatalogAccess.For(customer, "s2")); + Assert.AreNotEqual(CatalogAccess.For(customer, "s1"), CatalogAccess.For(new Customer(), "s1")); + Assert.AreNotEqual(CatalogAccess.IncludingHidden("s1"), CatalogAccess.IncludingHidden()); + } +} diff --git a/src/Tests/Grand.Business.Messages.Tests/Commands/GetSitemapXmlCommandHandlerTests.cs b/src/Tests/Grand.Business.Messages.Tests/Commands/GetSitemapXmlCommandHandlerTests.cs index 7a5cfac00a..11e093ea89 100644 --- a/src/Tests/Grand.Business.Messages.Tests/Commands/GetSitemapXmlCommandHandlerTests.cs +++ b/src/Tests/Grand.Business.Messages.Tests/Commands/GetSitemapXmlCommandHandlerTests.cs @@ -5,6 +5,7 @@ using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Business.Core.Interfaces.Storage; using Grand.Business.Core.Queries.Catalog; +using Grand.Business.Core.Utilities.Catalog; using Grand.Business.Messages.Commands.Handlers.Common; using Grand.Domain; using Grand.Domain.Blogs; @@ -60,9 +61,10 @@ public void Init() groupServiceMock.Setup(x => x.GetCustomerGroupBySystemName(SystemCustomerGroupNames.Guests)) .ReturnsAsync(new CustomerGroup { Id = GuestsGroupId, SystemName = SystemCustomerGroupNames.Guests }); - _categoryServiceMock.Setup(x => x.GetAllCategories(null, "", StoreId, 0, int.MaxValue, true)) + _categoryServiceMock.Setup(x => x.GetAllCategories(null, "", + CatalogAccess.IncludingHidden(StoreId), 0, int.MaxValue)) .ReturnsAsync(new PagedList()); - _brandServiceMock.Setup(x => x.GetAllBrands("", StoreId, 0, int.MaxValue, true)) + _brandServiceMock.Setup(x => x.GetAllBrands("", CatalogAccess.IncludingHidden(StoreId), 0, int.MaxValue)) .ReturnsAsync(new PagedList()); _pageServiceMock.Setup(x => x.GetAllPages(StoreId, true)).ReturnsAsync(new List()); _knowledgebaseServiceMock.Setup(x => x.GetPublicKnowledgebaseArticles(StoreId, @@ -124,8 +126,10 @@ public async Task Handle_WithoutWorkContext_UsesOnlyExplicitlyScopedQueries() //Assert Assert.IsFalse(Locations(xml).Any(string.IsNullOrEmpty), "sitemap contains an empty "); - _categoryServiceMock.Verify(x => x.GetAllCategories(null, "", StoreId, 0, int.MaxValue, true), Times.Once); - _brandServiceMock.Verify(x => x.GetAllBrands("", StoreId, 0, int.MaxValue, true), Times.Once); + _categoryServiceMock.Verify(x => x.GetAllCategories(null, "", + CatalogAccess.IncludingHidden(StoreId), 0, int.MaxValue), Times.Once); + _brandServiceMock.Verify(x => x.GetAllBrands("", + CatalogAccess.IncludingHidden(StoreId), 0, int.MaxValue), Times.Once); _pageServiceMock.Verify(x => x.GetAllPages(StoreId, true), Times.Once); _mediatorMock.Verify(x => x.Send(It.Is(q => q.Customer == null && q.StoreId == StoreId && q.VisibleIndividuallyOnly && @@ -148,7 +152,8 @@ public async Task Handle_ListsOnlyWhatAGuestCanSee() }, new() { Id = "4", SystemName = "Draft", SeName = "draft-page", Published = false, IncludeInSitemap = true } }); - _categoryServiceMock.Setup(x => x.GetAllCategories(null, "", StoreId, 0, int.MaxValue, true)) + _categoryServiceMock.Setup(x => x.GetAllCategories(null, "", + CatalogAccess.IncludingHidden(StoreId), 0, int.MaxValue)) .ReturnsAsync(new PagedList(new List { new() { Id = "c1", ParentCategoryId = "", SeName = "open-category", Published = true }, new() { Id = "c2", ParentCategoryId = "c1", SeName = "child-category", Published = true }, @@ -159,7 +164,7 @@ public async Task Handle_ListsOnlyWhatAGuestCanSee() new() { Id = "c4", ParentCategoryId = "c3", SeName = "child-of-hidden-category", Published = true }, new() { Id = "c5", ParentCategoryId = "", SeName = "unpublished-category", Published = false } }, 0, int.MaxValue)); - _brandServiceMock.Setup(x => x.GetAllBrands("", StoreId, 0, int.MaxValue, true)) + _brandServiceMock.Setup(x => x.GetAllBrands("", CatalogAccess.IncludingHidden(StoreId), 0, int.MaxValue)) .ReturnsAsync(new PagedList(new List { new() { Id = "b1", SeName = "open-brand", Published = true }, new() { diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseBrandControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseBrandControllerTests.cs index 24ac187d59..6f08b301f4 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseBrandControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseBrandControllerTests.cs @@ -1,6 +1,7 @@ using Grand.Business.Core.Interfaces.Catalog.Brands; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Common.Stores; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain; using Grand.Domain.Catalog; using Grand.Domain.Stores; @@ -122,7 +123,7 @@ public async Task ListPost_ForcesScopeDefaultStoreIdOntoSearchModel() { _scopeMock.Setup(s => s.DefaultStoreId).Returns("store-1"); _brandServiceMock - .Setup(b => b.GetAllBrands(It.IsAny(), "store-1", 0, 10, true)) + .Setup(b => b.GetAllBrands(It.IsAny(), CatalogAccess.IncludingHidden("store-1"), 0, 10)) .ReturnsAsync(new PagedList(new List(), 0, 10)); var model = new BrandListModel { SearchStoreId = "attacker-supplied-store" }; @@ -136,7 +137,8 @@ public async Task ListPost_GlobalScope_LeavesSubmittedSearchStoreIdUntouched() { _scopeMock.Setup(s => s.DefaultStoreId).Returns((string)null); _brandServiceMock - .Setup(b => b.GetAllBrands(It.IsAny(), "admin-submitted-store", 0, 10, true)) + .Setup(b => b.GetAllBrands(It.IsAny(), + CatalogAccess.IncludingHidden("admin-submitted-store"), 0, 10)) .ReturnsAsync(new PagedList(new List(), 0, 10)); var model = new BrandListModel { SearchStoreId = "admin-submitted-store" }; @@ -377,7 +379,7 @@ public async Task PicturePopupPost_ValidRequest_CallsUpdatePicture() public async Task ExportXlsx_Success_ReturnsXlsxFile() { _brandServiceMock - .Setup(b => b.GetAllBrands("", "", 0, int.MaxValue, true)) + .Setup(b => b.GetAllBrands("", CatalogAccess.IncludingHidden(), 0, int.MaxValue)) .ReturnsAsync(new PagedList(new List { new() { Id = "b1" } }, 0, int.MaxValue)); var exportManagerMock = new Mock>(); exportManagerMock.Setup(e => e.Export(It.IsAny>(), It.IsAny())).ReturnsAsync([1, 2, 3]); @@ -394,7 +396,7 @@ public async Task ExportXlsx_StoreScoped_ScopesExportToDefaultStoreId() { _scopeMock.Setup(s => s.DefaultStoreId).Returns("store-1"); _brandServiceMock - .Setup(b => b.GetAllBrands("", "store-1", 0, int.MaxValue, true)) + .Setup(b => b.GetAllBrands("", CatalogAccess.IncludingHidden("store-1"), 0, int.MaxValue)) .ReturnsAsync(new PagedList(new List { new() { Id = "b1" } }, 0, int.MaxValue)); var exportManagerMock = new Mock>(); exportManagerMock.Setup(e => e.Export(It.IsAny>(), It.IsAny())).ReturnsAsync([1, 2, 3]); @@ -403,15 +405,17 @@ public async Task ExportXlsx_StoreScoped_ScopesExportToDefaultStoreId() var file = result as FileContentResult; Assert.IsNotNull(file); - _brandServiceMock.Verify(b => b.GetAllBrands("", "store-1", 0, int.MaxValue, true), Times.Once); - _brandServiceMock.Verify(b => b.GetAllBrands("", "", 0, int.MaxValue, true), Times.Never); + _brandServiceMock.Verify(b => b.GetAllBrands("", + CatalogAccess.IncludingHidden("store-1"), 0, int.MaxValue), Times.Once); + _brandServiceMock.Verify(b => b.GetAllBrands("", + CatalogAccess.IncludingHidden(), 0, int.MaxValue), Times.Never); } [TestMethod] public async Task ExportXlsx_ExportThrows_ShowsErrorAndRedirectsToList() { _brandServiceMock - .Setup(b => b.GetAllBrands("", "", 0, int.MaxValue, true)) + .Setup(b => b.GetAllBrands("", CatalogAccess.IncludingHidden(), 0, int.MaxValue)) .ThrowsAsync(new InvalidOperationException("boom")); var exportManagerMock = new Mock>(); @@ -495,7 +499,7 @@ await Assert.ThrowsExactlyAsync(() => public async Task ExportXlsx_CancelledWithoutAbortedRequest_ShowsErrorAndRedirectsToList() { _brandServiceMock - .Setup(b => b.GetAllBrands("", "", 0, int.MaxValue, true)) + .Setup(b => b.GetAllBrands("", CatalogAccess.IncludingHidden(), 0, int.MaxValue)) .ThrowsAsync(new OperationCanceledException()); var exportManagerMock = new Mock>(); diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseCollectionControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseCollectionControllerTests.cs index 6aba71c60a..3f67b8c6fd 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseCollectionControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseCollectionControllerTests.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Common.Stores; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Stores; using Grand.Infrastructure.Mapper; @@ -123,7 +124,7 @@ public async Task ListPost_ForcesScopeDefaultStoreIdOntoSearchModel() { _scopeMock.Setup(s => s.DefaultStoreId).Returns("store-1"); _collectionServiceMock - .Setup(c => c.GetAllCollections(It.IsAny(), "store-1", 0, 10, true)) + .Setup(c => c.GetAllCollections(It.IsAny(), CatalogAccess.IncludingHidden("store-1"), 0, 10)) .ReturnsAsync(new Grand.Domain.PagedList(new List(), 0, 10)); var model = new CollectionListModel { SearchStoreId = "attacker-supplied-store" }; @@ -137,7 +138,8 @@ public async Task ListPost_GlobalScope_LeavesSubmittedSearchStoreIdUntouched() { _scopeMock.Setup(s => s.DefaultStoreId).Returns((string)null); _collectionServiceMock - .Setup(c => c.GetAllCollections(It.IsAny(), "admin-submitted-store", 0, 10, true)) + .Setup(c => c.GetAllCollections(It.IsAny(), + CatalogAccess.IncludingHidden("admin-submitted-store"), 0, 10)) .ReturnsAsync(new Grand.Domain.PagedList(new List(), 0, 10)); var model = new CollectionListModel { SearchStoreId = "admin-submitted-store" }; diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseDiscountControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseDiscountControllerTests.cs index 7322533f2e..5e8a751517 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseDiscountControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseDiscountControllerTests.cs @@ -8,6 +8,7 @@ using Grand.Business.Core.Interfaces.Common.Directory; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Queries.Catalog; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain; using Grand.Domain.Catalog; using Grand.Domain.Discounts; @@ -449,13 +450,13 @@ public async Task CategoryAddPopupList_GlobalScope_PassesEmptyStoreId() { _scope.Setup(x => x.GetDefaultStoreIdAsync()).ReturnsAsync((string?)null); var categoryService = new Mock(); - categoryService.Setup(x => x.GetAllCategories(null, null, "", 0, 10, true)) + categoryService.Setup(x => x.GetAllCategories(null, null, CatalogAccess.IncludingHidden(), 0, 10)) .ReturnsAsync(new PagedList(new List(), 0, 10, 0)); await _sut.CategoryAddPopupList(new DataSourceRequest { Page = 1, PageSize = 10 }, new DiscountModel.AddCategoryToDiscountModel(), categoryService.Object); - categoryService.Verify(x => x.GetAllCategories(null, null, "", 0, 10, true), Times.Once); + categoryService.Verify(x => x.GetAllCategories(null, null, CatalogAccess.IncludingHidden(), 0, 10), Times.Once); } [TestMethod] @@ -463,13 +464,14 @@ public async Task CategoryAddPopupList_StoreScope_PassesScopedStoreId() { _scope.Setup(x => x.GetDefaultStoreIdAsync()).ReturnsAsync("store-a"); var categoryService = new Mock(); - categoryService.Setup(x => x.GetAllCategories(null, null, "store-a", 0, 10, true)) + categoryService.Setup(x => x.GetAllCategories(null, null, CatalogAccess.IncludingHidden("store-a"), 0, 10)) .ReturnsAsync(new PagedList(new List(), 0, 10, 0)); await _sut.CategoryAddPopupList(new DataSourceRequest { Page = 1, PageSize = 10 }, new DiscountModel.AddCategoryToDiscountModel(), categoryService.Object); - categoryService.Verify(x => x.GetAllCategories(null, null, "store-a", 0, 10, true), Times.Once); + categoryService.Verify(x => x.GetAllCategories(null, null, + CatalogAccess.IncludingHidden("store-a"), 0, 10), Times.Once); } [TestMethod] @@ -531,13 +533,13 @@ public async Task BrandAddPopupList_GlobalScope_PassesEmptyStoreId() { _scope.Setup(x => x.GetDefaultStoreIdAsync()).ReturnsAsync((string?)null); var brandService = new Mock(); - brandService.Setup(x => x.GetAllBrands(null, "", 0, 10, true)) + brandService.Setup(x => x.GetAllBrands(null, CatalogAccess.IncludingHidden(), 0, 10)) .ReturnsAsync(new PagedList(new List(), 0, 10, 0)); await _sut.BrandAddPopupList(new DataSourceRequest { Page = 1, PageSize = 10 }, new DiscountModel.AddBrandToDiscountModel(), brandService.Object); - brandService.Verify(x => x.GetAllBrands(null, "", 0, 10, true), Times.Once); + brandService.Verify(x => x.GetAllBrands(null, CatalogAccess.IncludingHidden(), 0, 10), Times.Once); } [TestMethod] @@ -545,13 +547,13 @@ public async Task BrandAddPopupList_StoreScope_PassesScopedStoreId() { _scope.Setup(x => x.GetDefaultStoreIdAsync()).ReturnsAsync("store-a"); var brandService = new Mock(); - brandService.Setup(x => x.GetAllBrands(null, "store-a", 0, 10, true)) + brandService.Setup(x => x.GetAllBrands(null, CatalogAccess.IncludingHidden("store-a"), 0, 10)) .ReturnsAsync(new PagedList(new List(), 0, 10, 0)); await _sut.BrandAddPopupList(new DataSourceRequest { Page = 1, PageSize = 10 }, new DiscountModel.AddBrandToDiscountModel(), brandService.Object); - brandService.Verify(x => x.GetAllBrands(null, "store-a", 0, 10, true), Times.Once); + brandService.Verify(x => x.GetAllBrands(null, CatalogAccess.IncludingHidden("store-a"), 0, 10), Times.Once); } [TestMethod] @@ -613,13 +615,13 @@ public async Task CollectionAddPopupList_GlobalScope_PassesEmptyStoreId() { _scope.Setup(x => x.GetDefaultStoreIdAsync()).ReturnsAsync((string?)null); var collectionService = new Mock(); - collectionService.Setup(x => x.GetAllCollections(null, "", 0, 10, true)) + collectionService.Setup(x => x.GetAllCollections(null, CatalogAccess.IncludingHidden(), 0, 10)) .ReturnsAsync(new PagedList(new List(), 0, 10, 0)); await _sut.CollectionAddPopupList(new DataSourceRequest { Page = 1, PageSize = 10 }, new DiscountModel.AddCollectionToDiscountModel(), collectionService.Object); - collectionService.Verify(x => x.GetAllCollections(null, "", 0, 10, true), Times.Once); + collectionService.Verify(x => x.GetAllCollections(null, CatalogAccess.IncludingHidden(), 0, 10), Times.Once); } [TestMethod] @@ -627,13 +629,14 @@ public async Task CollectionAddPopupList_StoreScope_PassesScopedStoreId() { _scope.Setup(x => x.GetDefaultStoreIdAsync()).ReturnsAsync("store-a"); var collectionService = new Mock(); - collectionService.Setup(x => x.GetAllCollections(null, "store-a", 0, 10, true)) + collectionService.Setup(x => x.GetAllCollections(null, CatalogAccess.IncludingHidden("store-a"), 0, 10)) .ReturnsAsync(new PagedList(new List(), 0, 10, 0)); await _sut.CollectionAddPopupList(new DataSourceRequest { Page = 1, PageSize = 10 }, new DiscountModel.AddCollectionToDiscountModel(), collectionService.Object); - collectionService.Verify(x => x.GetAllCollections(null, "store-a", 0, 10, true), Times.Once); + collectionService.Verify(x => x.GetAllCollections(null, + CatalogAccess.IncludingHidden("store-a"), 0, 10), Times.Once); } [TestMethod] diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseOrderControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseOrderControllerTests.cs index a84197dbb3..988bc2e7f7 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseOrderControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseOrderControllerTests.cs @@ -1,6 +1,7 @@ using Grand.Business.Core.Interfaces.Checkout.Orders; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Common.Pdf; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Localization; using Grand.Domain.Orders; using Grand.Infrastructure; @@ -264,23 +265,25 @@ public async Task ProductSearchAutoComplete_VendorScope_ForcesVendorIdIntoSearch new Grand.Domain.PagedList(new List(), 0, 15); productServiceMock .Setup(p => p.SearchProducts( - It.IsAny(), It.IsAny(), 15, It.IsAny>(), It.IsAny(), It.IsAny(), - null, "vendor-A", It.IsAny(), It.IsAny(), It.IsAny(), + CatalogAccess.IncludingHidden(), It.IsAny(), It.IsAny(), 15, It.IsAny>(), + It.IsAny(), It.IsAny(), + "vendor-A", It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), "abc", It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny(), - true, It.IsAny())) + It.IsAny())) .ReturnsAsync((pagedProducts, (IList)new List())); await _controller.ProductSearchAutoComplete("abc", productServiceMock.Object); productServiceMock.Verify(p => p.SearchProducts( - It.IsAny(), It.IsAny(), 15, It.IsAny>(), It.IsAny(), It.IsAny(), - null, "vendor-A", It.IsAny(), It.IsAny(), It.IsAny(), + CatalogAccess.IncludingHidden(), It.IsAny(), It.IsAny(), 15, It.IsAny>(), + It.IsAny(), It.IsAny(), + "vendor-A", It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), "abc", It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny(), - true, It.IsAny()), Times.Once); + It.IsAny()), Times.Once); } [TestMethod] diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseProductControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseProductControllerTests.cs index 73df439b8d..26a1fe9d63 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseProductControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseProductControllerTests.cs @@ -5,6 +5,7 @@ using Grand.Business.Core.Interfaces.Common.Security; using Grand.Business.Core.Interfaces.ExportImport; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain; using Grand.Domain.Catalog; using Grand.Domain.Media; @@ -411,7 +412,8 @@ public async Task DeleteSelected_FiltersOutProductsScopeDenies() // bulk-delete endpoint (any staff could delete any product id in the system). var owned = new Product { Id = "owned" }; var foreign = new Product { Id = "foreign" }; - _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "owned", "foreign" }, true)) + _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "owned", "foreign" }, + CatalogAccess.IncludingHidden())) .ReturnsAsync(new List { owned, foreign }); _scopeMock.Setup(s => s.HasAccess(owned)).ReturnsAsync(true); _scopeMock.Setup(s => s.HasAccess(foreign)).ReturnsAsync(false); @@ -426,7 +428,7 @@ public async Task DeleteSelected_FiltersOutProductsScopeDenies() public async Task DeleteSelected_AllProductsScopeDenies_DoesNotCallDeleteSelected() { var foreign = new Product { Id = "foreign" }; - _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "foreign" }, true)) + _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "foreign" }, CatalogAccess.IncludingHidden())) .ReturnsAsync(new List { foreign }); _scopeMock.Setup(s => s.HasAccess(foreign)).ReturnsAsync(false); @@ -522,7 +524,8 @@ public async Task LoadProductFriendlyNames_EmptyInput_ReturnsEmptyText() var json = result as JsonResult; Assert.IsNotNull(json); Assert.AreEqual("", GetTextProperty(json.Value)); - _productServiceMock.Verify(s => s.GetProductsByIds(It.IsAny(), It.IsAny()), Times.Never); + _productServiceMock.Verify(s => s.GetProductsByIds(It.IsAny(), + It.IsAny()), Times.Never); } [TestMethod] @@ -530,7 +533,7 @@ public async Task LoadProductFriendlyNames_ScopeDeniesAccess_SkipsProduct() { var allowed = new Product { Id = "p1", Name = "Allowed" }; var denied = new Product { Id = "p2", Name = "Denied" }; - _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "p1", "p2" }, true)) + _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "p1", "p2" }, CatalogAccess.IncludingHidden())) .ReturnsAsync(new List { allowed, denied }); _scopeMock.Setup(s => s.HasAccess(allowed)).ReturnsAsync(true); _scopeMock.Setup(s => s.HasAccess(denied)).ReturnsAsync(false); @@ -551,7 +554,7 @@ public async Task LoadProductFriendlyNames_ScopeGrantsAccess_IncludesAllProducts { var p1 = new Product { Id = "p1", Name = "First" }; var p2 = new Product { Id = "p2", Name = "Second" }; - _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "p1", "p2" }, true)) + _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "p1", "p2" }, CatalogAccess.IncludingHidden())) .ReturnsAsync(new List { p1, p2 }); _scopeMock.Setup(s => s.HasAccess(It.IsAny())).ReturnsAsync(true); @@ -1979,7 +1982,7 @@ public async Task AssociatedProductList_ScopeGrantsAccess_ReturnsGrid() var product = new Product { Id = "p1" }; _productServiceMock.Setup(p => p.GetProductById("p1", false)).ReturnsAsync(product); _scopeMock.Setup(s => s.CanView(product)).ReturnsAsync(true); - _productServiceMock.Setup(p => p.GetAssociatedProducts("p1", "", "", true)) + _productServiceMock.Setup(p => p.GetAssociatedProducts("p1", CatalogAccess.IncludingHidden(), "")) .ReturnsAsync(new List { new() { Id = "a1", Name = "Assoc", DisplayOrder = 1 } }); var result = await _controller.AssociatedProductList(new Grand.Web.Common.DataSource.DataSourceRequest(), "p1"); @@ -2920,7 +2923,8 @@ public async Task ExportExcelSelected_NullSelectedIds_ExportsEmptyList() var file = result as FileContentResult; Assert.IsNotNull(file); - _productServiceMock.Verify(p => p.GetProductsByIds(It.IsAny(), true), Times.Never); + _productServiceMock.Verify(p => p.GetProductsByIds(It.IsAny(), + It.IsAny()), Times.Never); } [TestMethod] @@ -2931,7 +2935,8 @@ public async Task ExportExcelSelected_FiltersOutProductsScopeDenies() // Admin (where the original had no check at all) gets the same protection. var owned = new Product { Id = "owned" }; var foreign = new Product { Id = "foreign" }; - _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "owned", "foreign" }, true)) + _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "owned", "foreign" }, + CatalogAccess.IncludingHidden())) .ReturnsAsync([owned, foreign]); _scopeMock.Setup(s => s.HasAccess(owned)).ReturnsAsync(true); _scopeMock.Setup(s => s.HasAccess(foreign)).ReturnsAsync(false); @@ -3133,7 +3138,8 @@ public async Task BulkEditUpdate_FiltersOutProductsScopeDenies() // that per-item gate uniformly. var owned = new Product { Id = "owned" }; var foreign = new Product { Id = "foreign" }; - _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "owned", "foreign" }, true)) + _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "owned", "foreign" }, + CatalogAccess.IncludingHidden())) .ReturnsAsync(new List { owned, foreign }); _scopeMock.Setup(s => s.HasAccess(owned)).ReturnsAsync(true); _scopeMock.Setup(s => s.HasAccess(foreign)).ReturnsAsync(false); @@ -3152,7 +3158,7 @@ public async Task BulkEditUpdate_FiltersOutProductsScopeDenies() public async Task BulkEditUpdate_AllProductsScopeDenies_DoesNotCallUpdateBulkEdit() { var foreign = new Product { Id = "foreign" }; - _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "foreign" }, true)) + _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "foreign" }, CatalogAccess.IncludingHidden())) .ReturnsAsync(new List { foreign }); _scopeMock.Setup(s => s.HasAccess(foreign)).ReturnsAsync(false); @@ -3177,7 +3183,8 @@ public async Task BulkEditDelete_FiltersOutProductsScopeDenies() { var owned = new Product { Id = "owned" }; var foreign = new Product { Id = "foreign" }; - _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "owned", "foreign" }, true)) + _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "owned", "foreign" }, + CatalogAccess.IncludingHidden())) .ReturnsAsync(new List { owned, foreign }); _scopeMock.Setup(s => s.HasAccess(owned)).ReturnsAsync(true); _scopeMock.Setup(s => s.HasAccess(foreign)).ReturnsAsync(false); @@ -3195,7 +3202,7 @@ await _controller.BulkEditDelete(new List { public async Task BulkEditDelete_AllProductsScopeDenies_DoesNotCallDeleteBulkEdit() { var foreign = new Product { Id = "foreign" }; - _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "foreign" }, true)) + _productServiceMock.Setup(p => p.GetProductsByIds(new[] { "foreign" }, CatalogAccess.IncludingHidden())) .ReturnsAsync(new List { foreign }); _scopeMock.Setup(s => s.HasAccess(foreign)).ReturnsAsync(false); @@ -3211,7 +3218,8 @@ public async Task BulkEditUpdate_ProductsWithEmptyIds_AreIgnoredWithoutServiceCa var result = await _controller.BulkEditUpdate(new List { new() { Id = "" } }); Assert.IsInstanceOfType(result, typeof(JsonResult)); - _productServiceMock.Verify(p => p.GetProductsByIds(It.IsAny(), It.IsAny()), Times.Never); + _productServiceMock.Verify(p => p.GetProductsByIds(It.IsAny(), + It.IsAny()), Times.Never); _productViewModelServiceMock.Verify( s => s.UpdateBulkEdit(It.IsAny>()), Times.Never); } diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseProductReviewControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseProductReviewControllerTests.cs index 30338f4f87..d80d4997b6 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseProductReviewControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseProductReviewControllerTests.cs @@ -1,5 +1,6 @@ using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Web.AdminShared.Controllers; using Grand.Web.AdminShared.Interfaces; @@ -320,13 +321,13 @@ public async Task ProductSearchAutoComplete_TermTooShort_ReturnsEmptyContent() Assert.IsNotNull(result); Assert.AreEqual("", result.Content); productService.Verify(s => s.SearchProducts( + access: It.IsAny(), loadFilterableSpecificationAttributeOptionIds: It.IsAny(), pageIndex: It.IsAny(), pageSize: It.IsAny(), categoryIds: It.IsAny>(), brandId: It.IsAny(), collectionId: It.IsAny(), - storeId: It.IsAny(), vendorId: It.IsAny(), warehouseId: It.IsAny(), productType: It.IsAny(), @@ -345,7 +346,6 @@ public async Task ProductSearchAutoComplete_TermTooShort_ReturnsEmptyContent() filteredSpecs: It.IsAny>(), specificationOptions: It.IsAny>(), orderBy: It.IsAny(), - showHidden: It.IsAny(), overridePublished: It.IsAny()), Times.Never); } @@ -355,13 +355,13 @@ public async Task ProductSearchAutoComplete_Store_PassesDefaultStoreId() _scope.Setup(s => s.DefaultStoreId).Returns("store-1"); var productService = new Mock(); productService.Setup(s => s.SearchProducts( + access: CatalogAccess.IncludingHidden("store-1"), loadFilterableSpecificationAttributeOptionIds: false, pageIndex: 0, pageSize: 15, categoryIds: null, brandId: "", collectionId: "", - storeId: "store-1", vendorId: "", warehouseId: "", productType: null, @@ -380,7 +380,6 @@ public async Task ProductSearchAutoComplete_Store_PassesDefaultStoreId() filteredSpecs: null, specificationOptions: null, orderBy: Grand.Domain.Catalog.ProductSortingEnum.Position, - showHidden: true, overridePublished: null)) .ReturnsAsync((new Grand.Domain.PagedList(new List(), 0, 15), new List())); diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseSearchControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseSearchControllerTests.cs index 56c5f6fa05..c88ab33958 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseSearchControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseSearchControllerTests.cs @@ -1,9 +1,12 @@ using Grand.Business.Core.Interfaces.Catalog.Brands; using Grand.Business.Core.Interfaces.Catalog.Categories; using Grand.Business.Core.Interfaces.Catalog.Collections; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain; using Grand.Domain.Admin; using Grand.Domain.Catalog; +using Grand.Domain.Customers; +using Grand.Infrastructure; using Grand.Web.AdminShared.Controllers; using Grand.Web.Common.DataSource; using Microsoft.AspNetCore.Mvc; @@ -24,8 +27,9 @@ private class TestSearchController( IBrandService brandService, ICollectionService collectionService, AdminSearchSettings adminSearchSettings, + IContextAccessor contextAccessor, string pickerStoreId) - : BaseSearchController(categoryService, brandService, collectionService, adminSearchSettings) + : BaseSearchController(categoryService, brandService, collectionService, adminSearchSettings, contextAccessor) { protected override string PickerStoreId => pickerStoreId; } @@ -34,6 +38,8 @@ private class TestSearchController( private Mock _brandServiceMock = null!; private Mock _collectionServiceMock = null!; private AdminSearchSettings _settings = null!; + private Customer _customer = null!; + private Mock _contextAccessorMock = null!; [TestInitialize] public void Setup() @@ -41,6 +47,9 @@ public void Setup() _categoryServiceMock = new Mock(); _brandServiceMock = new Mock(); _collectionServiceMock = new Mock(); + _customer = new Customer(); + _contextAccessorMock = new Mock(); + _contextAccessorMock.Setup(c => c.WorkContext.CurrentCustomer).Returns(_customer); _settings = new AdminSearchSettings { CategorySizeLimit = 10, CollectionSizeLimit = 10, @@ -49,7 +58,7 @@ public void Setup() } private TestSearchController CreateController(string pickerStoreId) => - new(_categoryServiceMock.Object, _brandServiceMock.Object, _collectionServiceMock.Object, _settings, pickerStoreId); + new(_categoryServiceMock.Object, _brandServiceMock.Object, _collectionServiceMock.Object, _settings, _contextAccessorMock.Object, pickerStoreId); private static DataSourceRequestFilter EmptyFilter() => new() { Filters = [] }; @@ -75,7 +84,7 @@ public async Task Category_DefaultPickerStoreId_PassesEmptyStoreIdToService() { var controller = CreateController(""); _categoryServiceMock - .Setup(s => s.GetAllCategories(null, null, "", 0, 10, false)) + .Setup(s => s.GetAllCategories(null, null, CatalogAccess.For(_customer, ""), 0, 10)) .ReturnsAsync(new PagedList([new Category { Id = "cat-1", Name = "Books" }], 0, 10)); _categoryServiceMock.Setup(s => s.GetFormattedBreadCrumb(It.IsAny(), ">>", "")) .ReturnsAsync("Books"); @@ -85,7 +94,8 @@ public async Task Category_DefaultPickerStoreId_PassesEmptyStoreIdToService() var rows = ReadRows(result); Assert.AreEqual(1, rows.Length); Assert.AreEqual("cat-1", rows[0].Id); - _categoryServiceMock.Verify(s => s.GetAllCategories(null, null, "", 0, 10, false), Times.Once); + _categoryServiceMock.Verify(s => s.GetAllCategories(null, null, + CatalogAccess.For(_customer, ""), 0, 10), Times.Once); } [TestMethod] @@ -93,12 +103,13 @@ public async Task Category_OverriddenPickerStoreId_PassesStoreIdToService() { var controller = CreateController("store-1"); _categoryServiceMock - .Setup(s => s.GetAllCategories(null, null, "store-1", 0, 10, false)) + .Setup(s => s.GetAllCategories(null, null, CatalogAccess.For(_customer, "store-1"), 0, 10)) .ReturnsAsync(new PagedList([], 0, 10)); await controller.Category(null, EmptyFilter()); - _categoryServiceMock.Verify(s => s.GetAllCategories(null, null, "store-1", 0, 10, false), Times.Once); + _categoryServiceMock.Verify(s => s.GetAllCategories(null, null, + CatalogAccess.For(_customer, "store-1"), 0, 10), Times.Once); } [TestMethod] @@ -106,7 +117,7 @@ public async Task Collection_DefaultPickerStoreId_PassesEmptyStoreIdToService() { var controller = CreateController(""); _collectionServiceMock - .Setup(s => s.GetAllCollections(null, "", 0, 10, false)) + .Setup(s => s.GetAllCollections(null, CatalogAccess.For(_customer, ""), 0, 10)) .ReturnsAsync(new PagedList([new Collection { Id = "col-1", Name = "Summer" }], 0, 10)); var result = await controller.Collection(null, EmptyFilter()); @@ -114,7 +125,8 @@ public async Task Collection_DefaultPickerStoreId_PassesEmptyStoreIdToService() var rows = ReadRows(result); Assert.AreEqual(1, rows.Length); Assert.AreEqual("Summer", rows[0].Name); - _collectionServiceMock.Verify(s => s.GetAllCollections(null, "", 0, 10, false), Times.Once); + _collectionServiceMock.Verify(s => s.GetAllCollections(null, + CatalogAccess.For(_customer, ""), 0, 10), Times.Once); } [TestMethod] @@ -122,12 +134,13 @@ public async Task Collection_OverriddenPickerStoreId_PassesStoreIdToService() { var controller = CreateController("store-1"); _collectionServiceMock - .Setup(s => s.GetAllCollections(null, "store-1", 0, 10, false)) + .Setup(s => s.GetAllCollections(null, CatalogAccess.For(_customer, "store-1"), 0, 10)) .ReturnsAsync(new PagedList([], 0, 10)); await controller.Collection(null, EmptyFilter()); - _collectionServiceMock.Verify(s => s.GetAllCollections(null, "store-1", 0, 10, false), Times.Once); + _collectionServiceMock.Verify(s => s.GetAllCollections(null, + CatalogAccess.For(_customer, "store-1"), 0, 10), Times.Once); } [TestMethod] @@ -135,7 +148,7 @@ public async Task Brand_DefaultPickerStoreId_PassesEmptyStoreIdToService() { var controller = CreateController(""); _brandServiceMock - .Setup(s => s.GetAllBrands(null, "", 0, 10, false)) + .Setup(s => s.GetAllBrands(null, CatalogAccess.For(_customer, ""), 0, 10)) .ReturnsAsync(new PagedList([new Brand { Id = "brand-1", Name = "Acme" }], 0, 10)); var result = await controller.Brand(null, EmptyFilter()); @@ -143,7 +156,7 @@ public async Task Brand_DefaultPickerStoreId_PassesEmptyStoreIdToService() var rows = ReadRows(result); Assert.AreEqual(1, rows.Length); Assert.AreEqual("Acme", rows[0].Name); - _brandServiceMock.Verify(s => s.GetAllBrands(null, "", 0, 10, false), Times.Once); + _brandServiceMock.Verify(s => s.GetAllBrands(null, CatalogAccess.For(_customer, ""), 0, 10), Times.Once); } [TestMethod] @@ -151,11 +164,11 @@ public async Task Brand_OverriddenPickerStoreId_PassesStoreIdToService() { var controller = CreateController("store-1"); _brandServiceMock - .Setup(s => s.GetAllBrands(null, "store-1", 0, 10, false)) + .Setup(s => s.GetAllBrands(null, CatalogAccess.For(_customer, "store-1"), 0, 10)) .ReturnsAsync(new PagedList([], 0, 10)); await controller.Brand(null, EmptyFilter()); - _brandServiceMock.Verify(s => s.GetAllBrands(null, "store-1", 0, 10, false), Times.Once); + _brandServiceMock.Verify(s => s.GetAllBrands(null, CatalogAccess.For(_customer, "store-1"), 0, 10), Times.Once); } } diff --git a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseSpecificationAttributeControllerTests.cs b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseSpecificationAttributeControllerTests.cs index 4ead67a87f..8e763df5b1 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseSpecificationAttributeControllerTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Controllers/BaseSpecificationAttributeControllerTests.cs @@ -1,5 +1,6 @@ using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain; using Grand.Domain.Catalog; using Grand.Domain.Seo; @@ -84,13 +85,13 @@ public async Task UsedByProducts_PassesScopeStoreIdToSearchProducts() _service.Setup(x => x.GetSpecificationAttributeById("s1")).ReturnsAsync(spec); _scope.Setup(x => x.CanView(spec)).ReturnsAsync(true); _productService.Setup(x => x.SearchProducts( + access: CatalogAccess.IncludingHidden("store-a"), loadFilterableSpecificationAttributeOptionIds: false, pageIndex: 0, pageSize: 10, categoryIds: null, brandId: "", collectionId: "", - storeId: "store-a", vendorId: "", warehouseId: "", productType: null, @@ -109,20 +110,19 @@ public async Task UsedByProducts_PassesScopeStoreIdToSearchProducts() filteredSpecs: null, specificationOptions: It.Is>(l => l.Contains("o1")), orderBy: It.IsAny(), - showHidden: true, overridePublished: null)) .ReturnsAsync((new PagedList(new List(), 0, 10, 0), (IList)new List())); await _sut.UsedByProducts(new DataSourceRequest { Page = 1, PageSize = 10 }, "s1"); _productService.Verify(x => x.SearchProducts( + access: CatalogAccess.IncludingHidden("store-a"), loadFilterableSpecificationAttributeOptionIds: false, pageIndex: 0, pageSize: 10, categoryIds: null, brandId: "", collectionId: "", - storeId: "store-a", vendorId: "", warehouseId: "", productType: null, @@ -141,7 +141,6 @@ public async Task UsedByProducts_PassesScopeStoreIdToSearchProducts() filteredSpecs: null, specificationOptions: It.IsAny>(), orderBy: It.IsAny(), - showHidden: true, overridePublished: null), Times.Once); } } diff --git a/src/Tests/Grand.Web.Admin.Tests/Services/ProductViewModelServiceTests.cs b/src/Tests/Grand.Web.Admin.Tests/Services/ProductViewModelServiceTests.cs index 0b58f2b6a8..bbca32b343 100644 --- a/src/Tests/Grand.Web.Admin.Tests/Services/ProductViewModelServiceTests.cs +++ b/src/Tests/Grand.Web.Admin.Tests/Services/ProductViewModelServiceTests.cs @@ -13,6 +13,7 @@ using Grand.Business.Core.Interfaces.Common.Stores; using Grand.Business.Core.Interfaces.Customers; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain; using Grand.Domain.Catalog; using Grand.Domain.Customers; @@ -306,24 +307,24 @@ public async Task PrepareTierPriceModel_GlobalScope_IncludesStoreDropdown() private void SetupSearchProducts() { _productServiceMock.Setup(p => p.SearchProducts( - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), It.IsAny())) + It.IsAny(), It.IsAny())) .ReturnsAsync((new PagedList(), new List())); } private void VerifySearchProductsCalledWithVendorId(string expectedVendorId) { _productServiceMock.Verify(p => p.SearchProducts( - It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), - It.IsAny(), It.IsAny(), It.IsAny(), expectedVendorId, It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), + It.IsAny(), It.IsAny(), expectedVendorId, It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny>(), - It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + It.IsAny(), It.IsAny()), Times.Once); } [TestMethod] diff --git a/src/Tests/Grand.Web.Common.Tests/Extensions/CatalogAccessExtensionsTests.cs b/src/Tests/Grand.Web.Common.Tests/Extensions/CatalogAccessExtensionsTests.cs new file mode 100644 index 0000000000..f2bf011765 --- /dev/null +++ b/src/Tests/Grand.Web.Common.Tests/Extensions/CatalogAccessExtensionsTests.cs @@ -0,0 +1,30 @@ +using Grand.Domain.Customers; +using Grand.Domain.Stores; +using Grand.Infrastructure; +using Grand.Web.Common.Extensions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace Grand.Web.Common.Tests.Extensions; + +[TestClass] +public class CatalogAccessExtensionsTests +{ + [TestMethod] + public void CatalogAccess_TakesCustomerAndStoreOfTheRequest() + { + //Arrange + var customer = new Customer(); + var accessor = new Mock(); + accessor.Setup(a => a.WorkContext.CurrentCustomer).Returns(customer); + accessor.Setup(a => a.StoreContext.CurrentStore).Returns(new Store { Id = "s1" }); + + //Act + var access = accessor.Object.CatalogAccess(); + + //Assert + Assert.AreSame(customer, access.Customer); + Assert.AreEqual("s1", access.StoreId); + Assert.IsFalse(access.ShowHidden); + } +} diff --git a/src/Tests/Grand.Web.Store.Tests/Controllers/SearchControllerAttributeTests.cs b/src/Tests/Grand.Web.Store.Tests/Controllers/SearchControllerAttributeTests.cs index 3b18c46ba1..987d19122b 100644 --- a/src/Tests/Grand.Web.Store.Tests/Controllers/SearchControllerAttributeTests.cs +++ b/src/Tests/Grand.Web.Store.Tests/Controllers/SearchControllerAttributeTests.cs @@ -1,4 +1,5 @@ using System.Linq; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Admin; using Grand.Domain.Customers; using Grand.Business.Core.Interfaces.Catalog.Brands; @@ -60,13 +61,14 @@ public void HasAuthorizeMenuAttribute() [TestMethod] public async System.Threading.Tasks.Task PickerStoreId_ReflectsCurrentCustomerStaffStoreId() { + var customer = new Customer { StaffStoreId = "store-42" }; var categoryServiceMock = new Mock(); categoryServiceMock - .Setup(s => s.GetAllCategories(null, null, "store-42", 0, It.IsAny(), false)) + .Setup(s => s.GetAllCategories(null, null, CatalogAccess.For(customer, "store-42"), 0, It.IsAny())) .ReturnsAsync(new Grand.Domain.PagedList([], 0, 10)); var workContextMock = new Mock(); - workContextMock.Setup(w => w.CurrentCustomer).Returns(new Customer { StaffStoreId = "store-42" }); + workContextMock.Setup(w => w.CurrentCustomer).Returns(customer); var contextAccessorMock = new Mock(); contextAccessorMock.Setup(c => c.WorkContext).Returns(workContextMock.Object); @@ -79,6 +81,7 @@ public async System.Threading.Tasks.Task PickerStoreId_ReflectsCurrentCustomerSt await controller.Category(null, new Grand.Web.Common.DataSource.DataSourceRequestFilter { Filters = [] }); - categoryServiceMock.Verify(s => s.GetAllCategories(null, null, "store-42", 0, 10, false), Times.Once); + categoryServiceMock.Verify(s => s.GetAllCategories(null, null, + CatalogAccess.For(customer, "store-42"), 0, 10), Times.Once); } } diff --git a/src/Web/Grand.Web.Admin/Controllers/ProductTagsController.cs b/src/Web/Grand.Web.Admin/Controllers/ProductTagsController.cs index 5deff039cd..1572e71d95 100644 --- a/src/Web/Grand.Web.Admin/Controllers/ProductTagsController.cs +++ b/src/Web/Grand.Web.Admin/Controllers/ProductTagsController.cs @@ -1,9 +1,11 @@ using Grand.Business.Core.Extensions; using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Permissions; using Grand.Domain.Catalog; using Grand.Domain.Seo; +using Grand.Infrastructure; using Grand.Web.AdminShared.Models.Catalog; using Grand.Web.Common.DataSource; using Grand.Web.Common.Extensions; @@ -21,19 +23,22 @@ public class ProductTagsController : BaseAdminController private readonly SeoSettings _seoSettings; private readonly ITranslationService _translationService; + private readonly IContextAccessor _contextAccessor; public ProductTagsController( IProductTagService productTagService, IProductService productService, ILanguageService languageService, ITranslationService translationService, - SeoSettings seoSettings) + SeoSettings seoSettings, + IContextAccessor contextAccessor) { _productTagService = productTagService; _productService = productService; _languageService = languageService; _translationService = translationService; _seoSettings = seoSettings; + _contextAccessor = contextAccessor; } public IActionResult Index() @@ -76,7 +81,9 @@ public async Task Products(string tagId, DataSourceRequest comman { var tag = await _productTagService.GetProductTagById(tagId); - var products = (await _productService.SearchProducts(pageIndex: command.Page - 1, pageSize: command.PageSize, + var products = (await _productService.SearchProducts( + access: CatalogAccess.For(_contextAccessor.WorkContext.CurrentCustomer, ""), + pageIndex: command.Page - 1, pageSize: command.PageSize, productTag: tag.Name, orderBy: ProductSortingEnum.NameAsc)).products; var gridModel = new DataSourceResult { Data = products.Select(x => new { diff --git a/src/Web/Grand.Web.Admin/Controllers/SearchController.cs b/src/Web/Grand.Web.Admin/Controllers/SearchController.cs index b3a4b6b15b..9b212a3b04 100644 --- a/src/Web/Grand.Web.Admin/Controllers/SearchController.cs +++ b/src/Web/Grand.Web.Admin/Controllers/SearchController.cs @@ -8,6 +8,7 @@ using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Common.Stores; using Grand.Business.Core.Interfaces.Customers; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain; using Grand.Domain.Admin; using Grand.Domain.Customers; @@ -50,7 +51,7 @@ public SearchController(IProductService productService, ICategoryService categor AdminSearchSettings adminSearchSettings, ITranslationService translationService, IContextAccessor contextAccessor, IGroupService groupService, IStoreService storeService, IVendorService vendorService) - : base(categoryService, brandService, collectionService, adminSearchSettings) + : base(categoryService, brandService, collectionService, adminSearchSettings, contextAccessor) { _productService = productService; _categoryService = categoryService; @@ -82,8 +83,9 @@ public async Task Index(string searchTerm, FoundMenuItem[] foundM { if (result.Count < _adminSearchSettings.MaxSearchResultsCount && _adminSearchSettings.SearchInProducts) { - var products = (await _productService.SearchProducts(keywords: searchTerm, - pageSize: _adminSearchSettings.MaxSearchResultsCount - result.Count, showHidden: true)).products; + var products = (await _productService.SearchProducts( + access: CatalogAccess.IncludingHidden(), keywords: searchTerm, + pageSize: _adminSearchSettings.MaxSearchResultsCount - result.Count)).products; foreach (var product in products) result.Add(new Tuple(new { @@ -95,9 +97,12 @@ public async Task Index(string searchTerm, FoundMenuItem[] foundM if (result.Count < _adminSearchSettings.MaxSearchResultsCount && _adminSearchSettings.SearchInCategories) { - var categories = await _categoryService.GetAllCategories(parentId: null, categoryName: searchTerm, storeId: "", - pageSize: _adminSearchSettings.MaxSearchResultsCount - result.Count, showHidden: - await _groupService.IsAdmin(_contextAccessor.WorkContext.CurrentCustomer)); + var customer = _contextAccessor.WorkContext.CurrentCustomer; + var access = await _groupService.IsAdmin(customer) + ? CatalogAccess.IncludingHidden() + : CatalogAccess.For(customer, ""); + var categories = await _categoryService.GetAllCategories(parentId: null, categoryName: searchTerm, + access: access, pageSize: _adminSearchSettings.MaxSearchResultsCount - result.Count); foreach (var category in categories) result.Add(new Tuple(new { @@ -109,8 +114,9 @@ public async Task Index(string searchTerm, FoundMenuItem[] foundM if (result.Count < _adminSearchSettings.MaxSearchResultsCount && _adminSearchSettings.SearchInCollections) { - var collections = await _collectionService.GetAllCollections(searchTerm, storeId: "", - pageSize: _adminSearchSettings.MaxSearchResultsCount - result.Count, showHidden: true); + var collections = await _collectionService.GetAllCollections(searchTerm, + access: CatalogAccess.IncludingHidden(), + pageSize: _adminSearchSettings.MaxSearchResultsCount - result.Count); foreach (var collection in collections) result.Add(new Tuple(new { diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseBrandController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseBrandController.cs index ec77e7092d..324fa0cb14 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseBrandController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseBrandController.cs @@ -4,6 +4,7 @@ using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Common.Stores; using Grand.Business.Core.Interfaces.ExportImport; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Permissions; using Grand.Web.AdminShared.Extensions.Mapping; @@ -75,7 +76,7 @@ public async Task List(DataSourceRequest command, BrandListModel { if (scope.DefaultStoreId is not null) model.SearchStoreId = scope.DefaultStoreId; var brands = await brandService.GetAllBrands(model.SearchBrandName, - model.SearchStoreId, command.Page - 1, command.PageSize, true); + CatalogAccess.IncludingHidden(model.SearchStoreId), command.Page - 1, command.PageSize); var gridModel = new DataSourceResult { Data = brands.Select(x => x.ToModel()), Total = brands.TotalCount @@ -251,7 +252,8 @@ public async Task ExportXlsx([FromServices] IExportManager { try { - var bytes = await exportManager.Export(await brandService.GetAllBrands(brandName: "", storeId: scope.DefaultStoreId ?? "", showHidden: true), + var bytes = await exportManager.Export(await brandService.GetAllBrands(brandName: "", + access: CatalogAccess.IncludingHidden(scope.DefaultStoreId ?? "")), HttpContext.RequestAborted); return File(bytes, "text/xls", "brands.xlsx"); } diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseCategoryController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseCategoryController.cs index a8c2da8a9b..ddb196c258 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseCategoryController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseCategoryController.cs @@ -4,6 +4,7 @@ using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.ExportImport; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Permissions; using Grand.Web.AdminShared.Extensions; @@ -219,7 +220,8 @@ public async Task ExportXlsx([FromServices] IExportManager List(DataSourceRequest command, CollectionListM { if (scope.DefaultStoreId is not null) model.SearchStoreId = scope.DefaultStoreId; var collections = await collectionService.GetAllCollections(model.SearchCollectionName, - model.SearchStoreId, command.Page - 1, command.PageSize, true); + CatalogAccess.IncludingHidden(model.SearchStoreId), command.Page - 1, command.PageSize); var gridModel = new DataSourceResult { Data = collections.Select(x => x.ToModel()), Total = collections.TotalCount @@ -252,7 +253,8 @@ public async Task ExportXlsx([FromServices] IExportManager CategoryAddPopupList(DataSourceRequest command, DiscountModel.AddCategoryToDiscountModel model, [FromServices] ICategoryService categoryService) { var categories = await categoryService.GetAllCategories(parentId: null, categoryName: model.SearchCategoryName, - storeId: await scope.GetDefaultStoreIdAsync() ?? "", pageIndex: command.Page - 1, pageSize: command.PageSize, showHidden: true); + access: CatalogAccess.IncludingHidden(await scope.GetDefaultStoreIdAsync() ?? ""), + pageIndex: command.Page - 1, pageSize: command.PageSize); var items = new List(); foreach (var item in categories) { @@ -551,7 +553,8 @@ public async Task BrandAddPopupList(DataSourceRequest command, DiscountModel.AddBrandToDiscountModel model, [FromServices] IBrandService brandService) { var brands = await brandService.GetAllBrands(model.SearchBrandName, - await scope.GetDefaultStoreIdAsync() ?? "", command.Page - 1, command.PageSize, true); + CatalogAccess.IncludingHidden(await scope.GetDefaultStoreIdAsync() ?? ""), command.Page - 1, + command.PageSize); return Json(new DataSourceResult { Data = brands.Select(x => x.ToModel()), Total = brands.TotalCount }); } @@ -636,7 +639,8 @@ public async Task CollectionAddPopupList(DataSourceRequest comman DiscountModel.AddCollectionToDiscountModel model, [FromServices] ICollectionService collectionService) { var collections = await collectionService.GetAllCollections(model.SearchCollectionName, - await scope.GetDefaultStoreIdAsync() ?? "", command.Page - 1, command.PageSize, true); + CatalogAccess.IncludingHidden(await scope.GetDefaultStoreIdAsync() ?? ""), command.Page - 1, + command.PageSize); return Json(new DataSourceResult { Data = collections.Select(x => x.ToModel()), Total = collections.TotalCount }); } diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseOrderController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseOrderController.cs index d198b2a633..64a002b715 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseOrderController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseOrderController.cs @@ -1,6 +1,7 @@ using Grand.Business.Core.Interfaces.Checkout.Orders; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Common.Pdf; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Orders; using Grand.Domain.Permissions; using Grand.Infrastructure; @@ -181,11 +182,10 @@ public async Task ProductSearchAutoComplete(string term, const int productNumber = 15; var products = (await productService.SearchProducts( - storeId: scope.DefaultStoreId, + access: CatalogAccess.IncludingHidden(scope.DefaultStoreId), vendorId: scope.DefaultVendorId, keywords: term, - pageSize: productNumber, - showHidden: true)).products; + pageSize: productNumber)).products; var result = products.Select(p => new { label = p.Name, productid = p.Id }).ToList(); return Json(result); diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseOrderManagementController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseOrderManagementController.cs index 7ac2a74d8c..9f7478123e 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseOrderManagementController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseOrderManagementController.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Checkout.Orders; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Common.Pdf; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Orders; using Grand.Domain.Permissions; using Grand.Infrastructure; @@ -497,15 +498,14 @@ public async Task AddProductToOrder( if (!string.IsNullOrEmpty(model.SearchCategoryId)) categoryIds.Add(model.SearchCategoryId); var gridModel = new Grand.Web.Common.DataSource.DataSourceResult(); - var products = (await productService.SearchProducts(categoryIds: categoryIds, - storeId: Scope.DefaultStoreId, + var products = (await productService.SearchProducts( + access: CatalogAccess.IncludingHidden(Scope.DefaultStoreId), categoryIds: categoryIds, brandId: model.SearchBrandId, collectionId: model.SearchCollectionId, productType: model.SearchProductTypeId > 0 ? (Grand.Domain.Catalog.ProductType?)model.SearchProductTypeId : null, keywords: model.SearchProductName, pageIndex: command.Page - 1, - pageSize: command.PageSize, - showHidden: true)).products; + pageSize: command.PageSize)).products; gridModel.Data = products.Select(x => new OrderModel.AddOrderProductModel.ProductModel { Id = x.Id, Name = x.Name, Sku = x.Sku diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseProductController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseProductController.cs index 0f156277ab..d8039f7710 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseProductController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseProductController.cs @@ -6,6 +6,7 @@ using Grand.Business.Core.Interfaces.Common.Security; using Grand.Business.Core.Interfaces.ExportImport; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Common; using Grand.Domain.Media; @@ -225,7 +226,8 @@ public async Task DeleteSelected(ICollection selectedIds) // unscoped bulk-delete endpoint (any store staff could delete any product id in the system, // bypassing AccessToEntityByStore entirely) purely because MVC routes actions regardless of // whether a host's views ever link to them. - var products = await productService.GetProductsByIds(selectedIds.ToArray(), true); + var products = await productService.GetProductsByIds(selectedIds.ToArray(), + access: CatalogAccess.IncludingHidden()); var allowedIds = new List(); foreach (var product in products) if (await scope.HasAccess(product)) @@ -309,7 +311,8 @@ public async Task LoadProductFriendlyNames(string productIds) .Select(x => x.Trim()) .ToList(); - var products = await productService.GetProductsByIds(ids.ToArray(), true); + var products = await productService.GetProductsByIds(ids.ToArray(), + access: CatalogAccess.IncludingHidden()); for (var i = 0; i <= products.Count - 1; i++) { // Filters the friendly-name list, not a hard deny of the whole action: matches Store's @@ -1224,7 +1227,8 @@ public async Task AssociatedProductList(DataSourceRequest command // GetAssociatedProducts regardless of showHidden, unlike its storeId parameter (which only applies // when showHidden is false - moot here since all three hosts pass showHidden: true). var associatedProducts = await productService.GetAssociatedProducts(productId, - vendorId: AssociatedProductVendorId, showHidden: true); + access: CatalogAccess.IncludingHidden(), + vendorId: AssociatedProductVendorId); var associatedProductsModel = associatedProducts .Select(x => new ProductModel.AssociatedProductModel { Id = x.Id, @@ -1835,7 +1839,7 @@ public async Task ExportExcelSelected(string selectedIds, .Split([','], StringSplitOptions.RemoveEmptyEntries) .Select(x => x) .ToArray(); - products.AddRange(await productService.GetProductsByIds(ids, true)); + products.AddRange(await productService.GetProductsByIds(ids, access: CatalogAccess.IncludingHidden())); } // Unlike ExportExcelAll, selectedIds is caller-supplied and not derived from a scoped search - @@ -2011,7 +2015,8 @@ private async Task> FilterBulkEditProductsByAccess( .ToDictionary(g => g.Key, g => g.First()); if (byId.Count == 0) return []; - var loadedProducts = await productService.GetProductsByIds(byId.Keys.ToArray(), true); + var loadedProducts = await productService.GetProductsByIds(byId.Keys.ToArray(), + access: CatalogAccess.IncludingHidden()); var validProducts = new List(); foreach (var product in loadedProducts) if (await scope.HasAccess(product)) diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseProductReviewController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseProductReviewController.cs index 4e51a76645..167386f84b 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseProductReviewController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseProductReviewController.cs @@ -1,5 +1,6 @@ using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Permissions; using Grand.Web.AdminShared.Interfaces; @@ -144,10 +145,9 @@ public async Task ProductSearchAutoComplete(string term, const int productNumber = 15; var products = (await productService.SearchProducts( - storeId: scope.DefaultStoreId ?? "", + access: CatalogAccess.IncludingHidden(scope.DefaultStoreId ?? ""), keywords: term, - pageSize: productNumber, - showHidden: true)).products; + pageSize: productNumber)).products; var result = (from p in products select new diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseSearchController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseSearchController.cs index 8cf33750aa..0e2ea63f73 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseSearchController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseSearchController.cs @@ -1,7 +1,10 @@ using Grand.Business.Core.Interfaces.Catalog.Brands; using Grand.Business.Core.Interfaces.Catalog.Categories; using Grand.Business.Core.Interfaces.Catalog.Collections; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Admin; +using Grand.Domain.Customers; +using Grand.Infrastructure; using Grand.Web.Common.Controllers; using Grand.Web.Common.DataSource; using Grand.Web.Common.Helpers; @@ -13,22 +16,23 @@ public abstract class BaseSearchController( ICategoryService categoryService, IBrandService brandService, ICollectionService collectionService, - AdminSearchSettings adminSearchSettings) + AdminSearchSettings adminSearchSettings, + IContextAccessor contextAccessor) : BaseController { protected virtual string PickerStoreId => ""; + protected Customer CurrentCustomer => contextAccessor.WorkContext.CurrentCustomer; + [HttpGet] public virtual async Task Category(string categoryId, DataSourceRequestFilter model) { var categories = await categoryService.GetAllCategories( parentId: null, categoryName: model.GetNameFilterValue(), - storeId: PickerStoreId, + access: CatalogAccess.For(CurrentCustomer, PickerStoreId), pageIndex: 0, - pageSize: adminSearchSettings.CategorySizeLimit, - showHidden: false - ); + pageSize: adminSearchSettings.CategorySizeLimit); var gridModel = await DataSourceResultHelper.GetSearchResult(categoryId, categories, async category => await categoryService.GetFormattedBreadCrumb(category)); return Json(gridModel); @@ -39,11 +43,9 @@ public virtual async Task Collection(string collectionId, DataSou { var collections = await collectionService.GetAllCollections( collectionName: model.GetNameFilterValue(), - storeId: PickerStoreId, + access: CatalogAccess.For(CurrentCustomer, PickerStoreId), pageIndex: 0, - pageSize: adminSearchSettings.CollectionSizeLimit, - showHidden: false - ); + pageSize: adminSearchSettings.CollectionSizeLimit); var gridModel = await DataSourceResultHelper.GetSearchResult(collectionId, collections, collection => Task.FromResult(collection.Name)); return Json(gridModel); @@ -54,7 +56,7 @@ public virtual async Task Brand(string brandId, DataSourceRequest { var brands = await brandService.GetAllBrands( model.GetNameFilterValue(), - storeId: PickerStoreId, + access: CatalogAccess.For(CurrentCustomer, PickerStoreId), pageSize: adminSearchSettings.BrandSizeLimit); var gridModel = await DataSourceResultHelper.GetSearchResult(brandId, brands, brand => Task.FromResult(brand.Name)); diff --git a/src/Web/Grand.Web.AdminShared/Controllers/BaseSpecificationAttributeController.cs b/src/Web/Grand.Web.AdminShared/Controllers/BaseSpecificationAttributeController.cs index db1ed3a130..447a7ff393 100644 --- a/src/Web/Grand.Web.AdminShared/Controllers/BaseSpecificationAttributeController.cs +++ b/src/Web/Grand.Web.AdminShared/Controllers/BaseSpecificationAttributeController.cs @@ -1,6 +1,7 @@ using Grand.Business.Core.Extensions; using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Interfaces.Common.Localization; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Permissions; using Grand.Domain.Seo; @@ -43,11 +44,10 @@ public async Task UsedByProducts(DataSourceRequest command, strin if (optionIds.Count > 0) { var products = (await productService.SearchProducts( - storeId: scope.DefaultStoreId ?? "", + access: CatalogAccess.IncludingHidden(scope.DefaultStoreId ?? ""), specificationOptions: optionIds, pageIndex: command.Page - 1, - pageSize: command.PageSize, - showHidden: true)).products; + pageSize: command.PageSize)).products; total = products.TotalCount; foreach (var item in products) diff --git a/src/Web/Grand.Web.AdminShared/Services/CategoryViewModelService.cs b/src/Web/Grand.Web.AdminShared/Services/CategoryViewModelService.cs index d9f60512cf..3f35911937 100644 --- a/src/Web/Grand.Web.AdminShared/Services/CategoryViewModelService.cs +++ b/src/Web/Grand.Web.AdminShared/Services/CategoryViewModelService.cs @@ -6,6 +6,7 @@ using Grand.Business.Core.Interfaces.Common.Stores; using Grand.Business.Core.Interfaces.Customers; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Discounts; using Grand.Web.AdminShared.Extensions; @@ -78,10 +79,9 @@ public virtual async Task PrepareCategoryListModel(string sto var categories = await _categoryService.GetAllCategories( parentId: null, categoryName: model.SearchCategoryName, - storeId: model.SearchStoreId, + access: CatalogAccess.IncludingHidden(model.SearchStoreId), pageSize: pageSize, - pageIndex: pageIndex - 1, - showHidden: true); + pageIndex: pageIndex - 1); var categoryListModel = new List(); foreach (var x in categories) @@ -198,7 +198,8 @@ public virtual async Task DeleteCategory(Category category) PrepareCategoryProductModel(string categoryId, int pageIndex, int pageSize) { var productCategories = await _productCategoryService.GetProductCategoriesByCategoryId(categoryId, - pageIndex - 1, pageSize, true); + CatalogAccess.IncludingHidden(), + pageIndex - 1, pageSize); var categoryproducts = new List(); foreach (var item in productCategories) diff --git a/src/Web/Grand.Web.AdminShared/Services/CollectionViewModelService.cs b/src/Web/Grand.Web.AdminShared/Services/CollectionViewModelService.cs index c116f72c96..3afcbf8947 100644 --- a/src/Web/Grand.Web.AdminShared/Services/CollectionViewModelService.cs +++ b/src/Web/Grand.Web.AdminShared/Services/CollectionViewModelService.cs @@ -6,6 +6,7 @@ using Grand.Business.Core.Interfaces.Common.Stores; using Grand.Business.Core.Interfaces.Customers; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Discounts; using Grand.Infrastructure; @@ -181,8 +182,8 @@ public virtual async Task DeleteCollection(Collection collection) //collections model.AvailableCollections.Add(new SelectListItem { Text = _translationService.GetResource("Admin.Common.All"), Value = " " }); - foreach (var m in await _collectionService.GetAllCollections(collectionName: "", storeId: "", - showHidden: true)) + foreach (var m in await _collectionService.GetAllCollections(collectionName: "", + access: CatalogAccess.IncludingHidden())) model.AvailableCollections.Add(new SelectListItem { Text = m.Name, Value = m.Id }); //stores @@ -219,8 +220,8 @@ public virtual async PrepareCollectionProductModel(string collectionId, string storeId, int pageIndex, int pageSize) { var productCollections = await _productCollectionService.GetProductCollectionsByCollectionId(collectionId, - storeId, - pageIndex - 1, pageSize, true); + CatalogAccess.IncludingHidden(), + pageIndex - 1, pageSize); var items = new List(); foreach (var x in productCollections) items.Add(new CollectionModel.CollectionProductModel { diff --git a/src/Web/Grand.Web.AdminShared/Services/ProductViewModelService.cs b/src/Web/Grand.Web.AdminShared/Services/ProductViewModelService.cs index 9734f0709b..3f053f82e9 100644 --- a/src/Web/Grand.Web.AdminShared/Services/ProductViewModelService.cs +++ b/src/Web/Grand.Web.AdminShared/Services/ProductViewModelService.cs @@ -14,6 +14,7 @@ using Grand.Business.Core.Interfaces.Common.Stores; using Grand.Business.Core.Interfaces.Customers; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Common; using Grand.Domain.Directory; @@ -623,17 +624,16 @@ public virtual async Task PrepareProductListModel() // of any vendor filter on the model. Admin/Store have no DefaultVendorId, so this falls back to // model.SearchVendorId unchanged for them. var products = (await productService.SearchProducts( + access: CatalogAccess.IncludingHidden(model.SearchStoreId), categoryIds: categoryIds, brandId: model.SearchBrandId, collectionId: model.SearchCollectionId, - storeId: model.SearchStoreId, vendorId: scope.DefaultVendorId ?? model.SearchVendorId, warehouseId: model.SearchWarehouseId, productType: model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null, keywords: model.SearchProductName, pageIndex: pageIndex - 1, pageSize: pageSize, - showHidden: true, showOnHomePage: showOnHomePage, overridePublished: overridePublished, markedAsNewOnly: markedAsNewOnly @@ -718,15 +718,14 @@ public virtual async Task> PrepareProducts(ProductListModel model // vendorId: same override as PrepareProductsModel above - mirrors Vendor's original PrepareProducts, // which always passed vendorId: CurrentVendor.Id regardless of the search model. var products = (await productService.SearchProducts( + access: CatalogAccess.IncludingHidden(model.SearchStoreId), categoryIds: categoryIds, brandId: model.SearchBrandId, collectionId: model.SearchCollectionId, - storeId: model.SearchStoreId, vendorId: scope.DefaultVendorId ?? model.SearchVendorId, warehouseId: model.SearchWarehouseId, productType: model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null, keywords: model.SearchProductName, - showHidden: true, overridePublished: overridePublished )).products; @@ -876,7 +875,8 @@ public virtual async Task DeleteProduct(Product product) public virtual async Task DeleteSelected(IEnumerable selectedIds) { var products = new List(); - products.AddRange(await productService.GetProductsByIds(selectedIds.ToArray(), true)); + products.AddRange(await productService.GetProductsByIds(selectedIds.ToArray(), + access: CatalogAccess.IncludingHidden())); for (var i = 0; i < products.Count; i++) { await DeleteProduct(products[i]); @@ -1320,16 +1320,15 @@ public virtual async Task PrepareBulkEditListModel() // has no SearchVendorId field for a caller to (mis)supply in the first place, so there is nothing // to fall back to for Admin/Store, whose scope.DefaultVendorId is null and who intentionally see // every vendor's products in bulk-edit. - var products = (await productService.SearchProducts(categoryIds: searchCategoryIds, + var products = (await productService.SearchProducts( + access: CatalogAccess.IncludingHidden(model.SearchStoreId), categoryIds: searchCategoryIds, brandId: model.SearchBrandId, collectionId: model.SearchCollectionId, - storeId: model.SearchStoreId, vendorId: scope.DefaultVendorId ?? "", productType: model.SearchProductTypeId > 0 ? (ProductType?)model.SearchProductTypeId : null, keywords: model.SearchProductName, pageIndex: pageIndex - 1, - pageSize: pageSize, - showHidden: true)).products; + pageSize: pageSize)).products; var items = new List(); foreach (var x in products) @@ -2599,7 +2598,8 @@ protected virtual async Task UpdatePictureSeoNames(Product product) protected virtual async Task> GetChildCategoryIds(string parentCategoryId) { var categoriesIds = new List(); - var categories = await categoryService.GetAllCategoriesByParentCategoryId(parentCategoryId, true); + var categories = await categoryService.GetAllCategoriesByParentCategoryId( + access: CatalogAccess.IncludingHidden(), parentCategoryId: parentCategoryId); foreach (var category in categories) { categoriesIds.Add(category.Id); diff --git a/src/Web/Grand.Web.Common/Extensions/CatalogAccessExtensions.cs b/src/Web/Grand.Web.Common/Extensions/CatalogAccessExtensions.cs new file mode 100644 index 0000000000..0380a6b9b0 --- /dev/null +++ b/src/Web/Grand.Web.Common/Extensions/CatalogAccessExtensions.cs @@ -0,0 +1,19 @@ +using Grand.Business.Core.Utilities.Catalog; +using Grand.Infrastructure; + +namespace Grand.Web.Common.Extensions; + +/// +/// Builds the catalog access of the current request +/// +public static class CatalogAccessExtensions +{ + /// + /// Gets the catalog access of the current request: what its customer may see in its store + /// + public static CatalogAccess CatalogAccess(this IContextAccessor contextAccessor) + { + return Business.Core.Utilities.Catalog.CatalogAccess.For(contextAccessor.WorkContext.CurrentCustomer, + contextAccessor.StoreContext.CurrentStore.Id); + } +} diff --git a/src/Web/Grand.Web.Common/Extensions/ProductList.cs b/src/Web/Grand.Web.Common/Extensions/ProductList.cs index 89fa152502..ddddfed04c 100644 --- a/src/Web/Grand.Web.Common/Extensions/ProductList.cs +++ b/src/Web/Grand.Web.Common/Extensions/ProductList.cs @@ -1,4 +1,5 @@ using Grand.Business.Core.Interfaces.Catalog.Products; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain; using Grand.Domain.Catalog; @@ -16,17 +17,15 @@ public static async Task> PrepareProductList(this IProductSe searchCategoryIds.Add(searchCategoryId); var products = (await productService.SearchProducts( + access: CatalogAccess.IncludingHidden(searchStoreId), categoryIds: searchCategoryIds, brandId: searchBrandId, collectionId: searchCollectionId, - storeId: searchStoreId, vendorId: searchVendorId, productType: productTypeId > 0 ? (ProductType?)productTypeId : null, keywords: searchProductName, pageIndex: pageIndex - 1, - pageSize: pageSize, - showHidden: true - )).products; + pageSize: pageSize)).products; return products; } diff --git a/src/Web/Grand.Web.Store/Controllers/SearchController.cs b/src/Web/Grand.Web.Store/Controllers/SearchController.cs index 47220b73f1..98db238a71 100644 --- a/src/Web/Grand.Web.Store/Controllers/SearchController.cs +++ b/src/Web/Grand.Web.Store/Controllers/SearchController.cs @@ -20,7 +20,7 @@ public class SearchController( ICollectionService collectionService, AdminSearchSettings adminSearchSettings, IContextAccessor contextAccessor) - : BaseSearchController(categoryService, brandService, collectionService, adminSearchSettings) + : BaseSearchController(categoryService, brandService, collectionService, adminSearchSettings, contextAccessor) { - protected override string PickerStoreId => contextAccessor.WorkContext.CurrentCustomer.StaffStoreId; + protected override string PickerStoreId => CurrentCustomer.StaffStoreId; } diff --git a/src/Web/Grand.Web.Vendor/Controllers/SearchController.cs b/src/Web/Grand.Web.Vendor/Controllers/SearchController.cs index 93b12fbe9c..ab9cd4674d 100644 --- a/src/Web/Grand.Web.Vendor/Controllers/SearchController.cs +++ b/src/Web/Grand.Web.Vendor/Controllers/SearchController.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Catalog.Categories; using Grand.Business.Core.Interfaces.Catalog.Collections; using Grand.Domain.Admin; +using Grand.Infrastructure; using Grand.Web.AdminShared.Controllers; using Grand.Web.Common.Filters; using Grand.Web.Vendor.Extensions; @@ -17,7 +18,8 @@ public class SearchController( ICategoryService categoryService, IBrandService brandService, ICollectionService collectionService, - AdminSearchSettings adminSearchSettings) - : BaseSearchController(categoryService, brandService, collectionService, adminSearchSettings) + AdminSearchSettings adminSearchSettings, + IContextAccessor contextAccessor) + : BaseSearchController(categoryService, brandService, collectionService, adminSearchSettings, contextAccessor) { } diff --git a/src/Web/Grand.Web/Components/BlogPostProducts.cs b/src/Web/Grand.Web/Components/BlogPostProducts.cs index f27d511666..6342b6b9a4 100644 --- a/src/Web/Grand.Web/Components/BlogPostProducts.cs +++ b/src/Web/Grand.Web/Components/BlogPostProducts.cs @@ -1,7 +1,9 @@ using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Interfaces.Cms; using Grand.Domain.Catalog; +using Grand.Infrastructure; using Grand.Web.Common.Components; +using Grand.Web.Common.Extensions; using Grand.Web.Features.Models.Products; using Grand.Mediator; using Microsoft.AspNetCore.Mvc; @@ -16,12 +18,14 @@ public BlogPostProductsViewComponent( IProductService productService, IMediator mediator, IBlogService blogService, - CatalogSettings catalogSettings) + CatalogSettings catalogSettings, + IContextAccessor contextAccessor) { _productService = productService; _mediator = mediator; _blogService = blogService; _catalogSettings = catalogSettings; + _contextAccessor = contextAccessor; } #endregion @@ -34,7 +38,8 @@ public async Task InvokeAsync(string blogPostId, int? prod if (!blogproducts.Any()) return Content(""); - var products = await _productService.GetProductsByIds(blogproducts.Select(x => x.ProductId).ToArray()); + var products = await _productService.GetProductsByIds(blogproducts.Select(x => x.ProductId).ToArray(), + _contextAccessor.CatalogAccess()); if (!products.Any()) return Content(""); @@ -59,6 +64,7 @@ public async Task InvokeAsync(string blogPostId, int? prod private readonly IBlogService _blogService; private readonly CatalogSettings _catalogSettings; + private readonly IContextAccessor _contextAccessor; #endregion } \ No newline at end of file diff --git a/src/Web/Grand.Web/Components/CrossSellProducts.cs b/src/Web/Grand.Web/Components/CrossSellProducts.cs index 60944daa6e..f71cbb99fa 100644 --- a/src/Web/Grand.Web/Components/CrossSellProducts.cs +++ b/src/Web/Grand.Web/Components/CrossSellProducts.cs @@ -42,7 +42,8 @@ public async Task InvokeAsync(int? productThumbPictureSize .ToList(); var products = - await _productService.GetCrossSellProductsByShoppingCart(cart, _shoppingCartSettings.CrossSellsNumber); + await _productService.GetCrossSellProductsByShoppingCart(cart, _shoppingCartSettings.CrossSellsNumber, + _contextAccessor.WorkContext.CurrentCustomer, _contextAccessor.StoreContext.CurrentStore.Id); if (!products.Any()) return Content(""); diff --git a/src/Web/Grand.Web/Components/CustomerRecommendedProducts.cs b/src/Web/Grand.Web/Components/CustomerRecommendedProducts.cs index 845b420ccd..27b5b85540 100644 --- a/src/Web/Grand.Web/Components/CustomerRecommendedProducts.cs +++ b/src/Web/Grand.Web/Components/CustomerRecommendedProducts.cs @@ -1,6 +1,5 @@ using Grand.Business.Core.Queries.Catalog; using Grand.Domain.Catalog; -using Grand.Domain.Customers; using Grand.Infrastructure; using Grand.Web.Common.Components; using Grand.Web.Features.Models.Products; @@ -33,8 +32,8 @@ public async Task InvokeAsync(int? productThumbPictureSize return Content(""); var products = await _mediator.Send(new GetRecommendedProductsQuery { - CustomerGroupIds = _contextAccessor.WorkContext.CurrentCustomer.GetCustomerGroupIds(), - StoreId = _contextAccessor.StoreContext.CurrentStore.Id + StoreId = _contextAccessor.StoreContext.CurrentStore.Id, + Customer = _contextAccessor.WorkContext.CurrentCustomer }); if (!products.Any()) diff --git a/src/Web/Grand.Web/Components/HomePageBestSellers.cs b/src/Web/Grand.Web/Components/HomePageBestSellers.cs index aa370725eb..52434c9f57 100644 --- a/src/Web/Grand.Web/Components/HomePageBestSellers.cs +++ b/src/Web/Grand.Web/Components/HomePageBestSellers.cs @@ -5,6 +5,7 @@ using Grand.Infrastructure; using Grand.Infrastructure.Caching; using Grand.Web.Common.Components; +using Grand.Web.Common.Extensions; using Grand.Web.Events.Cache; using Grand.Web.Features.Models.Products; using Grand.Mediator; @@ -66,7 +67,7 @@ await _orderReportService.BestSellersReport( } //load products - var products = await _productService.GetProductsByIds(productIds.ToArray()); + var products = await _productService.GetProductsByIds(productIds.ToArray(), _contextAccessor.CatalogAccess()); if (!products.Any()) return Content(""); diff --git a/src/Web/Grand.Web/Components/HomePageProducts.cs b/src/Web/Grand.Web/Components/HomePageProducts.cs index 8dc02c7590..23a9e5a062 100644 --- a/src/Web/Grand.Web/Components/HomePageProducts.cs +++ b/src/Web/Grand.Web/Components/HomePageProducts.cs @@ -1,7 +1,9 @@ using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Domain.Catalog; +using Grand.Infrastructure; using Grand.Infrastructure.Caching; using Grand.Web.Common.Components; +using Grand.Web.Common.Extensions; using Grand.Web.Events.Cache; using Grand.Web.Features.Models.Products; using Grand.Mediator; @@ -17,12 +19,14 @@ public HomePageProductsViewComponent( IProductService productService, IMediator mediator, ICacheBase cacheBase, - CatalogSettings catalogSettings) + CatalogSettings catalogSettings, + IContextAccessor contextAccessor) { _productService = productService; _mediator = mediator; _cacheBase = cacheBase; _catalogSettings = catalogSettings; + _contextAccessor = contextAccessor; } #endregion @@ -34,7 +38,7 @@ public async Task InvokeAsync(int? productThumbPictureSize var productsIds = await _cacheBase.GetAsync(CacheKeyConst.HOMEPAGE_PRODUCTS_MODEL_KEY, async () => await _productService.GetAllProductsDisplayedOnHomePage()); - var products = await _productService.GetProductsByIds(productsIds.ToArray()); + var products = await _productService.GetProductsByIds(productsIds.ToArray(), _contextAccessor.CatalogAccess()); if (!products.Any()) return Content(""); @@ -58,6 +62,7 @@ public async Task InvokeAsync(int? productThumbPictureSize private readonly IMediator _mediator; private readonly ICacheBase _cacheBase; private readonly CatalogSettings _catalogSettings; + private readonly IContextAccessor _contextAccessor; #endregion } \ No newline at end of file diff --git a/src/Web/Grand.Web/Components/PersonalizedProducts.cs b/src/Web/Grand.Web/Components/PersonalizedProducts.cs index ad01ad3ac2..5d5fa9f396 100644 --- a/src/Web/Grand.Web/Components/PersonalizedProducts.cs +++ b/src/Web/Grand.Web/Components/PersonalizedProducts.cs @@ -32,7 +32,9 @@ public async Task InvokeAsync(int? productThumbPictureSize return Content(""); var products = await _mediator.Send(new GetPersonalizedProductsQuery { - CustomerId = _contextAccessor.WorkContext.CurrentCustomer.Id, ProductsNumber = _catalogSettings.PersonalizedProductsNumber + ProductsNumber = _catalogSettings.PersonalizedProductsNumber, + Customer = _contextAccessor.WorkContext.CurrentCustomer, + StoreId = _contextAccessor.StoreContext.CurrentStore.Id }); if (!products.Any()) diff --git a/src/Web/Grand.Web/Components/ProductsAlsoPurchased.cs b/src/Web/Grand.Web/Components/ProductsAlsoPurchased.cs index 4d2fd13e22..3dd1746c37 100644 --- a/src/Web/Grand.Web/Components/ProductsAlsoPurchased.cs +++ b/src/Web/Grand.Web/Components/ProductsAlsoPurchased.cs @@ -4,6 +4,7 @@ using Grand.Infrastructure; using Grand.Infrastructure.Caching; using Grand.Web.Common.Components; +using Grand.Web.Common.Extensions; using Grand.Web.Events.Cache; using Grand.Web.Features.Models.Products; using Grand.Mediator; @@ -51,7 +52,7 @@ public async Task InvokeAsync(string productId, int? produ ); //load products - var products = await _productService.GetProductsByIds(productIds); + var products = await _productService.GetProductsByIds(productIds, _contextAccessor.CatalogAccess()); if (!products.Any()) return Content(""); diff --git a/src/Web/Grand.Web/Components/RecentlyViewedProductsBlock.cs b/src/Web/Grand.Web/Components/RecentlyViewedProductsBlock.cs index ecec6506e7..c55267f340 100644 --- a/src/Web/Grand.Web/Components/RecentlyViewedProductsBlock.cs +++ b/src/Web/Grand.Web/Components/RecentlyViewedProductsBlock.cs @@ -34,7 +34,8 @@ public async Task InvokeAsync(int? productThumbPictureSize return Content(""); var preparePictureModel = productThumbPictureSize.HasValue; - var products = await _recentlyViewedProductsService.GetRecentlyViewedProducts(_contextAccessor.WorkContext.CurrentCustomer.Id, + var products = await _recentlyViewedProductsService.GetRecentlyViewedProducts(_contextAccessor.WorkContext.CurrentCustomer, + _contextAccessor.StoreContext.CurrentStore.Id, _catalogSettings.RecentlyViewedProductsNumber); if (!products.Any()) diff --git a/src/Web/Grand.Web/Components/RecommendedProducts.cs b/src/Web/Grand.Web/Components/RecommendedProducts.cs index 9c5a32639e..d7fc7b8ea4 100644 --- a/src/Web/Grand.Web/Components/RecommendedProducts.cs +++ b/src/Web/Grand.Web/Components/RecommendedProducts.cs @@ -1,6 +1,8 @@ using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Domain.Catalog; +using Grand.Infrastructure; using Grand.Web.Common.Components; +using Grand.Web.Common.Extensions; using Grand.Web.Features.Models.Products; using Grand.Mediator; using Microsoft.AspNetCore.Mvc; @@ -14,11 +16,13 @@ public class RecommendedProductsViewComponent : BaseViewComponent public RecommendedProductsViewComponent( IProductService productService, IMediator mediator, - CatalogSettings catalogSettings) + CatalogSettings catalogSettings, + IContextAccessor contextAccessor) { _productService = productService; _mediator = mediator; _catalogSettings = catalogSettings; + _contextAccessor = contextAccessor; } #endregion @@ -31,7 +35,7 @@ public async Task InvokeAsync(string productId, int? produ if (!productIds.Any()) return Content(""); - var products = await _productService.GetProductsByIds(productIds); + var products = await _productService.GetProductsByIds(productIds, _contextAccessor.CatalogAccess()); if (!products.Any()) return Content(""); @@ -55,6 +59,7 @@ public async Task InvokeAsync(string productId, int? produ private readonly IMediator _mediator; private readonly CatalogSettings _catalogSettings; + private readonly IContextAccessor _contextAccessor; #endregion } \ No newline at end of file diff --git a/src/Web/Grand.Web/Components/RelatedProducts.cs b/src/Web/Grand.Web/Components/RelatedProducts.cs index 139ecd33f9..1408cee2f6 100644 --- a/src/Web/Grand.Web/Components/RelatedProducts.cs +++ b/src/Web/Grand.Web/Components/RelatedProducts.cs @@ -1,6 +1,8 @@ using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Domain.Catalog; +using Grand.Infrastructure; using Grand.Web.Common.Components; +using Grand.Web.Common.Extensions; using Grand.Web.Features.Models.Products; using Grand.Mediator; using Microsoft.AspNetCore.Mvc; @@ -14,11 +16,13 @@ public class RelatedProductsViewComponent : BaseViewComponent public RelatedProductsViewComponent( IProductService productService, IMediator mediator, - CatalogSettings catalogSettings) + CatalogSettings catalogSettings, + IContextAccessor contextAccessor) { _productService = productService; _mediator = mediator; _catalogSettings = catalogSettings; + _contextAccessor = contextAccessor; } #endregion @@ -31,7 +35,7 @@ public async Task InvokeAsync(string productId, int? produ .Select(x => x.ProductId2).ToArray(); //load products - var products = await _productService.GetProductsByIds(productIds); + var products = await _productService.GetProductsByIds(productIds, _contextAccessor.CatalogAccess()); var model = await _mediator.Send(new GetProductOverview { PreparePictureModel = true, @@ -52,6 +56,7 @@ public async Task InvokeAsync(string productId, int? produ private readonly IMediator _mediator; private readonly CatalogSettings _catalogSettings; + private readonly IContextAccessor _contextAccessor; #endregion } \ No newline at end of file diff --git a/src/Web/Grand.Web/Components/SimilarProducts.cs b/src/Web/Grand.Web/Components/SimilarProducts.cs index 75f013a01d..6a3ef56da6 100644 --- a/src/Web/Grand.Web/Components/SimilarProducts.cs +++ b/src/Web/Grand.Web/Components/SimilarProducts.cs @@ -1,6 +1,8 @@ using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Domain.Catalog; +using Grand.Infrastructure; using Grand.Web.Common.Components; +using Grand.Web.Common.Extensions; using Grand.Web.Features.Models.Products; using Grand.Mediator; using Microsoft.AspNetCore.Mvc; @@ -14,11 +16,13 @@ public class SimilarProductsViewComponent : BaseViewComponent public SimilarProductsViewComponent( IProductService productService, IMediator mediator, - CatalogSettings catalogSettings) + CatalogSettings catalogSettings, + IContextAccessor contextAccessor) { _productService = productService; _mediator = mediator; _catalogSettings = catalogSettings; + _contextAccessor = contextAccessor; } #endregion @@ -32,7 +36,7 @@ public async Task InvokeAsync(string productId, int? produ if (!productIds.Any()) return Content(""); - var products = await _productService.GetProductsByIds(productIds); + var products = await _productService.GetProductsByIds(productIds, _contextAccessor.CatalogAccess()); if (!products.Any()) return Content(""); @@ -56,6 +60,7 @@ public async Task InvokeAsync(string productId, int? produ private readonly IMediator _mediator; private readonly CatalogSettings _catalogSettings; + private readonly IContextAccessor _contextAccessor; #endregion } \ No newline at end of file diff --git a/src/Web/Grand.Web/Components/SuggestedProducts.cs b/src/Web/Grand.Web/Components/SuggestedProducts.cs index 0953f7de9e..e1db22c840 100644 --- a/src/Web/Grand.Web/Components/SuggestedProducts.cs +++ b/src/Web/Grand.Web/Components/SuggestedProducts.cs @@ -32,8 +32,9 @@ public async Task InvokeAsync(int? productThumbPictureSize return Content(""); var products = await _mediator.Send(new GetSuggestedProductsQuery { - CustomerTagIds = _contextAccessor.WorkContext.CurrentCustomer.CustomerTags.ToArray(), - ProductsNumber = _catalogSettings.SuggestedProductsNumber + ProductsNumber = _catalogSettings.SuggestedProductsNumber, + Customer = _contextAccessor.WorkContext.CurrentCustomer, + StoreId = _contextAccessor.StoreContext.CurrentStore.Id }); if (!products.Any()) diff --git a/src/Web/Grand.Web/Controllers/ProductController.cs b/src/Web/Grand.Web/Controllers/ProductController.cs index 4f7ac0c1be..bcb7801b8b 100644 --- a/src/Web/Grand.Web/Controllers/ProductController.cs +++ b/src/Web/Grand.Web/Controllers/ProductController.cs @@ -72,7 +72,8 @@ public virtual async Task RecentlyViewedProducts() if (!_catalogSettings.RecentlyViewedProductsEnabled) return Content(""); - var products = await _recentlyViewedProductsService.GetRecentlyViewedProducts(_contextAccessor.WorkContext.CurrentCustomer.Id, + var products = await _recentlyViewedProductsService.GetRecentlyViewedProducts(_contextAccessor.WorkContext.CurrentCustomer, + _contextAccessor.StoreContext.CurrentStore.Id, _catalogSettings.RecentlyViewedProductsNumber); //prepare model @@ -94,7 +95,7 @@ public virtual async Task RelatedProducts(string productId, int? .Select(x => x.ProductId2).ToArray(); //load products - var products = await _productService.GetProductsByIds(productIds); + var products = await _productService.GetProductsByIds(productIds, _contextAccessor.CatalogAccess()); var model = await _mediator.Send(new GetProductOverview { PreparePictureModel = true, @@ -119,7 +120,7 @@ public virtual async Task NewProducts() return Content(""); var products = (await _productService.SearchProducts( - storeId: _contextAccessor.StoreContext.CurrentStore.Id, + access: _contextAccessor.CatalogAccess(), visibleIndividuallyOnly: true, markedAsNewOnly: true, orderBy: ProductSortingEnum.CreatedOn, diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetBrandAllHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetBrandAllHandler.cs index 1fbe7fdc33..de15ea1b9d 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetBrandAllHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetBrandAllHandler.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Catalog.Brands; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Media; using Grand.Web.Extensions; @@ -47,7 +48,8 @@ private async Task> PrepareBrands(GetBrandAll request, BrandLis request.Command.PageSize = _catalogSettings.MaxCatalogPageSize; var model = new List(); - var brands = await _brandService.GetAllBrands(brandName: "", storeId: request.Store.Id, + var brands = await _brandService.GetAllBrands(brandName: "", + access: CatalogAccess.For(request.Customer, request.Store.Id), pageIndex: request.Command.PageNumber - 1, pageSize: request.Command.PageSize ); diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetCategoryAllHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetCategoryAllHandler.cs index 829f448134..b5681ecc9d 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetCategoryAllHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetCategoryAllHandler.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Catalog.Categories; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Media; using Grand.Web.Extensions; @@ -48,7 +49,8 @@ private async Task> PrepareCategories(GetCategoryAll request request.Command.PageSize = _catalogSettings.MaxCatalogPageSize; var model = new List(); - var categories = await _categoryService.GetAllCategories(parentId: null, categoryName: "", storeId: request.Store.Id, + var categories = await _categoryService.GetAllCategories(parentId: null, categoryName: "", + access: CatalogAccess.For(request.Customer, request.Store.Id), pageIndex: request.Command.PageNumber - 1, pageSize: request.Command.PageSize ); diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetCategoryFeaturedProductsHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetCategoryFeaturedProductsHandler.cs index 404986e429..720f40a0f6 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetCategoryFeaturedProductsHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetCategoryFeaturedProductsHandler.cs @@ -3,6 +3,7 @@ using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Storage; using Grand.Business.Core.Queries.Catalog; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain; using Grand.Domain.Catalog; using Grand.Domain.Customers; @@ -56,7 +57,8 @@ public async Task> Handle(GetCategoryFeaturedProducts reque var model = await _cacheBase.GetAsync(categoriesCacheKey, async () => { var catlistmodel = new List(); - foreach (var x in await _categoryService.GetAllCategoriesFeaturedProductsOnHomePage()) + foreach (var x in await _categoryService.GetAllCategoriesFeaturedProductsOnHomePage( + access: CatalogAccess.For(request.Customer, request.Store.Id))) { var catModel = x.ToModel(request.Language); //prepare picture model diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetCategoryHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetCategoryHandler.cs index 88615fd485..7fa4f846c4 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetCategoryHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetCategoryHandler.cs @@ -4,6 +4,7 @@ using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Storage; using Grand.Business.Core.Queries.Catalog; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain; using Grand.Domain.Catalog; using Grand.Domain.Customers; @@ -87,7 +88,8 @@ public async Task Handle(GetCategory request, CancellationToken c storeId, languageId); model.CategoryBreadcrumb = await _cacheBase.GetAsync(breadcrumbCacheKey, async () => - (await _categoryService.GetCategoryBreadCrumb(request.Category)) + (await _categoryService.GetCategoryBreadCrumb(request.Category, + access: CatalogAccess.For(request.Customer, request.Store.Id))) .Select(catBr => new CategoryModel { Id = catBr.Id, Name = catBr.GetTranslation(x => x.Name, languageId), @@ -99,7 +101,9 @@ public async Task Handle(GetCategory request, CancellationToken c //subcategories var subCategories = new List(); - foreach (var x in (await _categoryService.GetAllCategoriesByParentCategoryId(request.Category.Id)).Where(x => + foreach (var x in (await _categoryService.GetAllCategoriesByParentCategoryId( + access: CatalogAccess.For(request.Customer, request.Store.Id), + parentCategoryId: request.Category.Id)).Where(x => !x.HideOnCatalog)) { var subCatModel = new CategoryModel.SubCategoryModel { diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetCategorySimpleHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetCategorySimpleHandler.cs index dc5e72f900..71d9c4e2d3 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetCategorySimpleHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetCategorySimpleHandler.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Catalog.Categories; using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Customers; using Grand.Domain.Media; @@ -65,7 +66,8 @@ private async Task> PrepareCategorySimpleModels(GetCat async Task PrepareCategories(string categoryId) { - var parentCategories = await _categoryService.GetAllCategories(categoryId, categoryName: "", storeId: request.Store.Id); + var parentCategories = await _categoryService.GetAllCategories(categoryId, categoryName: "", + access: CatalogAccess.For(request.Customer, request.Store.Id)); if (parentCategories.Any()) { categories.AddRange(parentCategories); @@ -79,7 +81,8 @@ async Task PrepareCategories(string categoryId) if (currentCategory != null) { var currentCategories = - await _categoryService.GetAllCategories(currentCategory.Id, categoryName: "", storeId: request.Store.Id); + await _categoryService.GetAllCategories(currentCategory.Id, categoryName: "", + access: CatalogAccess.For(request.Customer, request.Store.Id)); categories.AddRange(currentCategories); await PrepareCategories(currentCategory.ParentCategoryId); } diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetChildCategoryIdsHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetChildCategoryIdsHandler.cs index 5b50bbc1f1..f9507d152e 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetChildCategoryIdsHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetChildCategoryIdsHandler.cs @@ -1,4 +1,5 @@ using Grand.Business.Core.Interfaces.Catalog.Categories; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Customers; using Grand.Infrastructure.Caching; using Grand.Web.Events.Cache; @@ -32,7 +33,9 @@ private async Task> GetChildCategoryIds(GetChildCategoryIds request return await _cacheBase.GetAsync(cacheKey, async () => { var categoriesIds = new List(); - var categories = await _categoryService.GetAllCategoriesByParentCategoryId(request.ParentCategoryId); + var categories = await _categoryService.GetAllCategoriesByParentCategoryId( + access: CatalogAccess.For(request.Customer, request.Store.Id), + parentCategoryId: request.ParentCategoryId); foreach (var category in categories) { categoriesIds.Add(category.Id); diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetCollectionAllHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetCollectionAllHandler.cs index ba4c3912d5..82cf7c2e3b 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetCollectionAllHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetCollectionAllHandler.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Catalog.Collections; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Media; using Grand.Web.Extensions; @@ -48,7 +49,8 @@ private async Task> PrepareCollectionAll(GetCollectionAll request.Command.PageSize = _catalogSettings.MaxCatalogPageSize; var model = new List(); - var collections = await _collectionService.GetAllCollections(collectionName: "", storeId: request.Store.Id, + var collections = await _collectionService.GetAllCollections(collectionName: "", + access: CatalogAccess.For(request.Customer, request.Store.Id), pageIndex: request.Command.PageNumber - 1, pageSize: request.Command.PageSize); diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetCollectionFeaturedProductsHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetCollectionFeaturedProductsHandler.cs index 810727fe60..64d9299dd3 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetCollectionFeaturedProductsHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetCollectionFeaturedProductsHandler.cs @@ -3,6 +3,7 @@ using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Storage; using Grand.Business.Core.Queries.Catalog; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain; using Grand.Domain.Catalog; using Grand.Domain.Customers; @@ -57,7 +58,8 @@ public async Task> Handle(GetCollectionFeaturedProducts r var model = await _cacheBase.GetAsync(collectionCacheKey, async () => { var collectionList = new List(); - var collectionmodel = await _collectionService.GetAllCollectionFeaturedProductsOnHomePage(); + var collectionmodel = await _collectionService.GetAllCollectionFeaturedProductsOnHomePage( + access: CatalogAccess.For(request.Customer, request.Store.Id)); foreach (var x in collectionmodel) { var colModel = x.ToModel(request.Language); diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetCollectionNavigationHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetCollectionNavigationHandler.cs index 8aeb585935..3b9f5fd1c6 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetCollectionNavigationHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetCollectionNavigationHandler.cs @@ -1,5 +1,6 @@ using Grand.Business.Core.Extensions; using Grand.Business.Core.Interfaces.Catalog.Collections; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Customers; using Grand.Infrastructure.Caching; @@ -36,7 +37,7 @@ public async Task Handle(GetCollectionNavigation requ var currentCollection = await _collectionService.GetCollectionById(request.CurrentCollectionId); var collections = await _collectionService.GetAllCollections(collectionName: "", pageSize: _catalogSettings.CollectionsBlockItemsToDisplay, - storeId: request.Store.Id); + access: CatalogAccess.For(request.Customer, request.Store.Id)); var model = new CollectionNavigationModel { TotalCollections = collections.TotalCount }; diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageBrandsHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageBrandsHandler.cs index 7d253d9d98..36f1c0590e 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageBrandsHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageBrandsHandler.cs @@ -1,6 +1,7 @@ using Grand.Business.Core.Interfaces.Catalog.Brands; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Media; using Grand.Infrastructure.Caching; using Grand.Web.Events.Cache; @@ -41,7 +42,8 @@ public async Task> Handle(GetHomepageBrands request, Cancellat var model = await _cacheBase.GetAsync(brandsCacheKey, async () => { var modelBrands = new List(); - var allBrands = await _brandService.GetAllBrands(brandName: "", storeId: request.Store.Id); + var allBrands = await _brandService.GetAllBrands(brandName: "", + access: CatalogAccess.For(request.Customer, request.Store.Id)); foreach (var x in allBrands.Where(x => x.ShowOnHomePage)) { var brandModel = x.ToModel(request.Language); diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageCategoryHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageCategoryHandler.cs index bcea875130..a4293a798e 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageCategoryHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageCategoryHandler.cs @@ -2,6 +2,7 @@ using Grand.Business.Core.Interfaces.Catalog.Categories; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Customers; using Grand.Domain.Media; using Grand.Infrastructure.Caching; @@ -46,7 +47,8 @@ public async Task> Handle(GetHomepageCategory request, Canc var model = await _cacheBase.GetAsync(categoriesCacheKey, async () => { var cat = new List(); - foreach (var x in await _categoryService.GetAllCategoriesDisplayedOnHomePage()) + foreach (var x in await _categoryService.GetAllCategoriesDisplayedOnHomePage( + access: CatalogAccess.For(request.Customer, request.Store.Id))) { var catModel = x.ToModel(request.Language); //prepare picture model diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageCollectionsHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageCollectionsHandler.cs index 4f5123e794..6c78822489 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageCollectionsHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetHomepageCollectionsHandler.cs @@ -1,6 +1,7 @@ using Grand.Business.Core.Interfaces.Catalog.Collections; using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Media; using Grand.Infrastructure.Caching; using Grand.Web.Events.Cache; @@ -43,7 +44,8 @@ public async Task> Handle(GetHomepageCollections request, var model = await _cacheBase.GetAsync(collectionsCacheKey, async () => { var modelCollect = new List(); - var allcollections = await _collectionService.GetAllCollections(collectionName: "", storeId: request.Store.Id); + var allcollections = await _collectionService.GetAllCollections(collectionName: "", + access: CatalogAccess.For(request.Customer, request.Store.Id)); foreach (var x in allcollections.Where(x => x.ShowOnHomePage)) { var _model = x.ToModel(request.Language); diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetMenuHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetMenuHandler.cs index 43822a64c2..09bb4ac65f 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetMenuHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetMenuHandler.cs @@ -4,6 +4,7 @@ using Grand.Business.Core.Interfaces.Catalog.Collections; using Grand.Business.Core.Interfaces.Cms; using Grand.Business.Core.Interfaces.Storage; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Blogs; using Grand.Domain.Catalog; using Grand.Domain.Common; @@ -62,7 +63,7 @@ public async Task Handle(GetMenu request, CancellationToken cancellat request.Store.Id, string.Join(",", request.Customer.GetCustomerGroupIds())); var cachedCategoriesModel = await _cacheBase.GetAsync(cacheKey, async () => await PrepareCategorySimpleModels( request.Language, - await _categoryService.GetMenuCategories())); + await _categoryService.GetMenuCategories(customer: request.Customer, storeId: request.Store.Id))); //top menu pages var now = DateTime.UtcNow; @@ -80,7 +81,8 @@ public async Task Handle(GetMenu request, CancellationToken cancellat request.Language.Id, request.Store.Id); var cachedBrandModel = await _cacheBase.GetAsync(brandCacheKey, async () => - (await _brandService.GetAllBrands(brandName: "", storeId: request.Store.Id)) + (await _brandService.GetAllBrands(brandName: "", + access: CatalogAccess.For(request.Customer, request.Store.Id))) .Where(x => x.IncludeInMenu) .Select(t => new MenuModel.MenuBrandModel { Id = t.Id, @@ -95,7 +97,8 @@ public async Task Handle(GetMenu request, CancellationToken cancellat request.Language.Id, request.Store.Id); var cachedCollectionModel = await _cacheBase.GetAsync(collectionCacheKey, async () => - (await _collectionService.GetAllCollections(collectionName: "", storeId: request.Store.Id)) + (await _collectionService.GetAllCollections(collectionName: "", + access: CatalogAccess.For(request.Customer, request.Store.Id))) .Where(x => x.IncludeInMenu) .Select(t => new MenuModel.MenuCollectionModel { Id = t.Id, diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetSearchBoxHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetSearchBoxHandler.cs index 2a9bd5ad0d..7c0e40555d 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetSearchBoxHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetSearchBoxHandler.cs @@ -42,7 +42,8 @@ public async Task Handle(GetSearchBox request, CancellationToken return await _cacheBase.GetAsync(cacheKey, async () => { - var searchbocategories = await _categoryService.GetAllCategoriesSearchBox(); + var searchbocategories = await _categoryService.GetAllCategoriesSearchBox(customer: request.Customer, + storeId: request.Store.Id); var availableCategories = new List(); if (searchbocategories.Any()) diff --git a/src/Web/Grand.Web/Features/Handlers/Catalog/GetSearchHandler.cs b/src/Web/Grand.Web/Features/Handlers/Catalog/GetSearchHandler.cs index 0c2921b6d1..6639be3b6b 100644 --- a/src/Web/Grand.Web/Features/Handlers/Catalog/GetSearchHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Catalog/GetSearchHandler.cs @@ -7,6 +7,7 @@ using Grand.Business.Core.Interfaces.Common.Localization; using Grand.Business.Core.Interfaces.Customers; using Grand.Business.Core.Queries.Catalog; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Catalog; using Grand.Domain.Common; using Grand.Domain.Customers; @@ -99,12 +100,14 @@ public async Task Handle(GetSearch request, CancellationToken cance { var categoriesModel = new List(); //all categories - var allCategories = await _categoryService.GetAllCategories(parentId: null, categoryName: "", storeId: request.Store.Id, pageSize: 100); + var allCategories = await _categoryService.GetAllCategories(parentId: null, categoryName: "", + access: CatalogAccess.For(request.Customer, request.Store.Id), pageSize: 100); foreach (var c in allCategories) { //generate full category name (breadcrumb) var categoryBreadcrumb = ""; - var breadcrumb = _categoryService.GetCategoryBreadCrumb(c, allCategories); + var breadcrumb = _categoryService.GetCategoryBreadCrumb(c, allCategories, + access: CatalogAccess.For(request.Customer, request.Store.Id)); for (var i = 0; i <= breadcrumb.Count - 1; i++) { categoryBreadcrumb += breadcrumb[i].GetTranslation(x => x.Name, request.Language.Id); @@ -136,7 +139,9 @@ public async Task Handle(GetSearch request, CancellationToken cance }); } - var collections = await _collectionService.GetAllCollections(collectionName: "", storeId: request.Store.Id, pageSize: 100); + var collections = await _collectionService.GetAllCollections(collectionName: "", + access: CatalogAccess.For(request.Customer, request.Store.Id), + pageSize: 100); if (collections.Any()) { request.Model.AvailableCollections.Add(new SelectListItem { diff --git a/src/Web/Grand.Web/Features/Handlers/Common/GetSitemapHandler.cs b/src/Web/Grand.Web/Features/Handlers/Common/GetSitemapHandler.cs index e5307c9054..e85959c073 100644 --- a/src/Web/Grand.Web/Features/Handlers/Common/GetSitemapHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Common/GetSitemapHandler.cs @@ -3,6 +3,7 @@ using Grand.Business.Core.Interfaces.Catalog.Categories; using Grand.Business.Core.Interfaces.Catalog.Products; using Grand.Business.Core.Interfaces.Cms; +using Grand.Business.Core.Utilities.Catalog; using Grand.Domain.Blogs; using Grand.Domain.Common; using Grand.Domain.Customers; @@ -78,14 +79,16 @@ public async Task Handle(GetSitemap request, CancellationToken can //categories if (_commonSettings.SitemapIncludeCategories) { - var categories = await _categoryService.GetAllCategories(parentId: null, categoryName: "", storeId: request.Store.Id); + var categories = await _categoryService.GetAllCategories(parentId: null, categoryName: "", + access: CatalogAccess.For(request.Customer, request.Store.Id)); model.Categories = categories.Select(x => x.ToModel(request.Language)).ToList(); } //collections if (_commonSettings.SitemapIncludeBrands) { - var brands = await _brandService.GetAllBrands(brandName: "", storeId: request.Store.Id); + var brands = await _brandService.GetAllBrands(brandName: "", + access: CatalogAccess.For(request.Customer, request.Store.Id)); model.Brands = brands.Select(x => x.ToModel(request.Language)).ToList(); } @@ -94,7 +97,7 @@ public async Task Handle(GetSitemap request, CancellationToken can { //limit product to 200 until paging is supported on this page var products = (await _productService.SearchProducts( - storeId: request.Store.Id, + access: CatalogAccess.For(request.Customer, request.Store.Id), visibleIndividuallyOnly: true, pageSize: 200)).products; model.Products = products.Select(product => new ProductOverviewModel { diff --git a/src/Web/Grand.Web/Features/Handlers/Products/GetProductDetailsPageHandler.cs b/src/Web/Grand.Web/Features/Handlers/Products/GetProductDetailsPageHandler.cs index 6c52c308a1..7414d62688 100644 --- a/src/Web/Grand.Web/Features/Handlers/Products/GetProductDetailsPageHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Products/GetProductDetailsPageHandler.cs @@ -23,6 +23,7 @@ using Grand.Infrastructure; using Grand.Infrastructure.Caching; using Grand.Web.Events.Cache; +using Grand.Web.Common.Extensions; using Grand.Web.Extensions; using Grand.Web.Features.Models.Catalog; using Grand.Web.Features.Models.Products; @@ -312,7 +313,7 @@ or ManageInventoryMethod.ManageStockByAttributes && if (!isAssociatedProduct) { var associatedProducts = - await _productService.GetAssociatedProducts(product.Id, _contextAccessor.StoreContext.CurrentStore.Id); + await _productService.GetAssociatedProducts(product.Id, _contextAccessor.CatalogAccess()); foreach (var associatedProduct in associatedProducts) model.AssociatedProducts.Add( await PrepareProductDetailsModel(store, associatedProduct, null, true)); @@ -549,7 +550,8 @@ private async Task PrepareVendorBriefInfoModel(Product pro await _categoryService.GetCategoryById(productCategories.MinBy(x => x.DisplayOrder).CategoryId); if (category == null) return breadcrumbModel; - foreach (var catBr in await _categoryService.GetCategoryBreadCrumb(category)) + foreach (var catBr in await _categoryService.GetCategoryBreadCrumb(category, + _contextAccessor.CatalogAccess())) breadcrumbModel.CategoryBreadcrumb.Add(new CategorySimpleModel { Id = catBr.Id, Name = catBr.GetTranslation(x => x.Name, _contextAccessor.WorkContext.WorkingLanguage.Id), diff --git a/src/Web/Grand.Web/Features/Handlers/Products/GetProductOverviewHandler.cs b/src/Web/Grand.Web/Features/Handlers/Products/GetProductOverviewHandler.cs index 293045c3ff..cd8e484e7b 100644 --- a/src/Web/Grand.Web/Features/Handlers/Products/GetProductOverviewHandler.cs +++ b/src/Web/Grand.Web/Features/Handlers/Products/GetProductOverviewHandler.cs @@ -12,6 +12,7 @@ using Grand.Domain.Orders; using Grand.Domain.Tax; using Grand.Infrastructure; +using Grand.Web.Common.Extensions; using Grand.Web.Features.Models.Catalog; using Grand.Web.Features.Models.Products; using Grand.Web.Models.Catalog; @@ -225,7 +226,7 @@ private async Task PrepareProductOverviewModel(Product pro #region Grouped product var associatedProducts = - await _productService.GetAssociatedProducts(product.Id, _contextAccessor.StoreContext.CurrentStore.Id); + await _productService.GetAssociatedProducts(product.Id, _contextAccessor.CatalogAccess()); //add to cart button (ignore "DisableBuyButton" property for grouped products) priceModel.DisableBuyButton = !enableShoppingCart || !displayPrices;