Webiant Logo Webiant Logo
  1. No results found.

    Try your search with a different keyword or use * as a wildcard.

SpecificationAttributeService.cs

using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.Domain.Catalog;
using Nop.Data;
using Nop.Services.Security;
using Nop.Services.Stores;

namespace Nop.Services.Catalog;

/// 
/// Specification attribute service
/// 
public partial class SpecificationAttributeService : ISpecificationAttributeService
{
    #region Fields

    protected readonly CatalogSettings _catalogSettings;
    protected readonly IAclService _aclService;
    protected readonly ICategoryService _categoryService;
    protected readonly IRepository _productRepository;
    protected readonly IRepository _productCategoryRepository;
    protected readonly IRepository _productManufacturerRepository;
    protected readonly IRepository _productSpecificationAttributeRepository;
    protected readonly IRepository _specificationAttributeRepository;
    protected readonly IRepository _specificationAttributeOptionRepository;
    protected readonly IRepository _specificationAttributeGroupRepository;
    protected readonly IStoreContext _storeContext;
    protected readonly IStoreMappingService _storeMappingService;
    protected readonly IStaticCacheManager _staticCacheManager;
    protected readonly IWorkContext _workContext;

    #endregion

    #region Ctor

    public SpecificationAttributeService(
        CatalogSettings catalogSettings,
        IAclService aclService,
        ICategoryService categoryService,
        IRepository productRepository,
        IRepository productCategoryRepository,
        IRepository productManufacturerRepository,
        IRepository productSpecificationAttributeRepository,
        IRepository specificationAttributeRepository,
        IRepository specificationAttributeOptionRepository,
        IRepository specificationAttributeGroupRepository,
        IStoreContext storeContext,
        IStoreMappingService storeMappingService,
        IStaticCacheManager staticCacheManager,
        IWorkContext workContext)
    {
        _catalogSettings = catalogSettings;
        _aclService = aclService;
        _categoryService = categoryService;
        _productRepository = productRepository;
        _productCategoryRepository = productCategoryRepository;
        _productManufacturerRepository = productManufacturerRepository;
        _productSpecificationAttributeRepository = productSpecificationAttributeRepository;
        _specificationAttributeRepository = specificationAttributeRepository;
        _specificationAttributeOptionRepository = specificationAttributeOptionRepository;
        _specificationAttributeGroupRepository = specificationAttributeGroupRepository;
        _storeContext = storeContext;
        _storeMappingService = storeMappingService;
        _staticCacheManager = staticCacheManager;
        _workContext = workContext;
    }

    #endregion

    #region Utilities

    /// 
    /// Gets the available products query
    /// 
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the available products query
    /// 
    protected virtual async Task> GetAvailableProductsQueryAsync()
    {
        var productsQuery =
            from p in _productRepository.Table
            where !p.Deleted && p.Published &&
                  (p.ParentGroupedProductId == 0 || p.VisibleIndividually) &&
                  (!p.AvailableStartDateTimeUtc.HasValue || p.AvailableStartDateTimeUtc <= DateTime.UtcNow) &&
                  (!p.AvailableEndDateTimeUtc.HasValue || p.AvailableEndDateTimeUtc >= DateTime.UtcNow)
            select p;

        var store = await _storeContext.GetCurrentStoreAsync();
        var currentCustomer = await _workContext.GetCurrentCustomerAsync();

        //apply store mapping constraints
        productsQuery = await _storeMappingService.ApplyStoreMapping(productsQuery, store.Id);

        //apply ACL constraints
        productsQuery = await _aclService.ApplyAcl(productsQuery, currentCustomer);

        return productsQuery;
    }

    #endregion

    #region Methods

    #region Specification attribute group

    /// 
    /// Gets a specification attribute group
    /// 
    /// The specification attribute group identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the specification attribute group
    /// 
    public virtual async Task GetSpecificationAttributeGroupByIdAsync(int specificationAttributeGroupId)
    {
        return await _specificationAttributeGroupRepository.GetByIdAsync(specificationAttributeGroupId, cache => default);
    }

    /// 
    /// Gets specification attribute groups
    /// 
    /// Page index
    /// Page size
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the specification attribute groups
    /// 
    public virtual async Task> GetSpecificationAttributeGroupsAsync(int pageIndex = 0, int pageSize = int.MaxValue)
    {
        var query = from sag in _specificationAttributeGroupRepository.Table
            orderby sag.DisplayOrder, sag.Id
            select sag;

        return await query.ToPagedListAsync(pageIndex, pageSize);
    }

    /// 
    /// Gets product specification attribute groups
    /// 
    /// Product identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the specification attribute groups
    /// 
    public virtual async Task> GetProductSpecificationAttributeGroupsAsync(int productId)
    {
        var productAttributesForGroupQuery =
            from sa in _specificationAttributeRepository.Table
            join sao in _specificationAttributeOptionRepository.Table
                on sa.Id equals sao.SpecificationAttributeId
            join psa in _productSpecificationAttributeRepository.Table
                on sao.Id equals psa.SpecificationAttributeOptionId
            where psa.ProductId == productId && psa.ShowOnProductPage
            select sa.SpecificationAttributeGroupId;

        var availableGroupsQuery =
            from sag in _specificationAttributeGroupRepository.Table
            where productAttributesForGroupQuery.Any(groupId => groupId == sag.Id)
            select sag;

        var key = _staticCacheManager.PrepareKeyForDefaultCache(NopCatalogDefaults.SpecificationAttributeGroupByProductCacheKey, productId);

        return await _staticCacheManager.GetAsync(key, async () => await availableGroupsQuery.ToListAsync());
    }

    /// 
    /// Deletes a specification attribute group
    /// 
    /// The specification attribute group
    /// A task that represents the asynchronous operation
    public virtual async Task DeleteSpecificationAttributeGroupAsync(SpecificationAttributeGroup specificationAttributeGroup)
    {
        await _specificationAttributeGroupRepository.DeleteAsync(specificationAttributeGroup);
    }

    /// 
    /// Inserts a specification attribute group
    /// 
    /// The specification attribute group
    /// A task that represents the asynchronous operation
    public virtual async Task InsertSpecificationAttributeGroupAsync(SpecificationAttributeGroup specificationAttributeGroup)
    {
        await _specificationAttributeGroupRepository.InsertAsync(specificationAttributeGroup);
    }

    /// 
    /// Updates the specification attribute group
    /// 
    /// The specification attribute group
    /// A task that represents the asynchronous operation
    public virtual async Task UpdateSpecificationAttributeGroupAsync(SpecificationAttributeGroup specificationAttributeGroup)
    {
        await _specificationAttributeGroupRepository.UpdateAsync(specificationAttributeGroup);
    }

    #endregion

    #region Specification attribute

    /// 
    /// Gets a specification attribute
    /// 
    /// The specification attribute identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the specification attribute
    /// 
    public virtual async Task GetSpecificationAttributeByIdAsync(int specificationAttributeId)
    {
        return await _specificationAttributeRepository.GetByIdAsync(specificationAttributeId, cache => default);
    }

    /// 
    /// Gets specification attributes
    /// 
    /// The specification attribute identifiers
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the specification attributes
    /// 
    public virtual async Task> GetSpecificationAttributeByIdsAsync(int[] specificationAttributeIds)
    {
        return await _specificationAttributeRepository.GetByIdsAsync(specificationAttributeIds);
    }

    /// 
    /// Gets specification attributes
    /// 
    /// Page index
    /// Page size
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the specification attributes
    /// 
    public virtual async Task> GetSpecificationAttributesAsync(int pageIndex = 0, int pageSize = int.MaxValue)
    {
        var query = from sa in _specificationAttributeRepository.Table
            orderby sa.DisplayOrder, sa.Id
            select sa;

        return await query.ToPagedListAsync(pageIndex, pageSize);
    }

    /// 
    /// Gets specification attributes that have options
    /// 
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the specification attributes that have available options
    /// 
    public virtual async Task> GetSpecificationAttributesWithOptionsAsync()
    {
        var query = from sa in _specificationAttributeRepository.Table
            where _specificationAttributeOptionRepository.Table.Any(o => o.SpecificationAttributeId == sa.Id)
            orderby sa.DisplayOrder, sa.Id
            select sa;

        return await _staticCacheManager.GetAsync(_staticCacheManager.PrepareKeyForDefaultCache(NopCatalogDefaults.SpecificationAttributesWithOptionsCacheKey), async () => await query.ToListAsync());
    }

    /// 
    /// Gets specification attributes by group identifier
    /// 
    /// The specification attribute group identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the specification attributes
    /// 
    public virtual async Task> GetSpecificationAttributesByGroupIdAsync(int? specificationAttributeGroupId = null)
    {
        var query = _specificationAttributeRepository.Table;
        if (!specificationAttributeGroupId.HasValue || specificationAttributeGroupId > 0)
            query = query.Where(sa => sa.SpecificationAttributeGroupId == specificationAttributeGroupId);

        query = query.OrderBy(sa => sa.DisplayOrder).ThenBy(sa => sa.Id);

        return await query.ToListAsync();
    }

    /// 
    /// Deletes a specification attribute
    /// 
    /// The specification attribute
    /// A task that represents the asynchronous operation
    public virtual async Task DeleteSpecificationAttributeAsync(SpecificationAttribute specificationAttribute)
    {
        await _specificationAttributeRepository.DeleteAsync(specificationAttribute);
    }

    /// 
    /// Deletes specifications attributes
    /// 
    /// Specification attributes
    /// A task that represents the asynchronous operation
    public virtual async Task DeleteSpecificationAttributesAsync(IList specificationAttributes)
    {
        ArgumentNullException.ThrowIfNull(specificationAttributes);

        foreach (var specificationAttribute in specificationAttributes)
            await DeleteSpecificationAttributeAsync(specificationAttribute);
    }

    /// 
    /// Inserts a specification attribute
    /// 
    /// The specification attribute
    /// A task that represents the asynchronous operation
    public virtual async Task InsertSpecificationAttributeAsync(SpecificationAttribute specificationAttribute)
    {
        await _specificationAttributeRepository.InsertAsync(specificationAttribute);
    }

    /// 
    /// Updates the specification attribute
    /// 
    /// The specification attribute
    /// A task that represents the asynchronous operation
    public virtual async Task UpdateSpecificationAttributeAsync(SpecificationAttribute specificationAttribute)
    {
        await _specificationAttributeRepository.UpdateAsync(specificationAttribute);
    }

    #endregion

    #region Specification attribute option

    /// 
    /// Gets a specification attribute option
    /// 
    /// The specification attribute option identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the specification attribute option
    /// 
    public virtual async Task GetSpecificationAttributeOptionByIdAsync(int specificationAttributeOptionId)
    {
        return await _specificationAttributeOptionRepository.GetByIdAsync(specificationAttributeOptionId, cache => default);
    }

    /// 
    /// Get specification attribute options by identifiers
    /// 
    /// Identifiers
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the specification attribute options
    /// 
    public virtual async Task> GetSpecificationAttributeOptionsByIdsAsync(int[] specificationAttributeOptionIds)
    {
        return await _specificationAttributeOptionRepository.GetByIdsAsync(specificationAttributeOptionIds);
    }

    /// 
    /// Gets a specification attribute option by specification attribute id
    /// 
    /// The specification attribute identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the specification attribute option
    /// 
    public virtual async Task> GetSpecificationAttributeOptionsBySpecificationAttributeAsync(int specificationAttributeId)
    {
        var query = from sao in _specificationAttributeOptionRepository.Table
            orderby sao.DisplayOrder, sao.Id
            where sao.SpecificationAttributeId == specificationAttributeId
            select sao;

        var specificationAttributeOptions = await _staticCacheManager.GetAsync(_staticCacheManager.PrepareKeyForDefaultCache(NopCatalogDefaults.SpecificationAttributeOptionsCacheKey, specificationAttributeId), async () => await query.ToListAsync());

        return specificationAttributeOptions;
    }

    /// 
    /// Deletes a specification attribute option
    /// 
    /// The specification attribute option
    /// A task that represents the asynchronous operation
    public virtual async Task DeleteSpecificationAttributeOptionAsync(SpecificationAttributeOption specificationAttributeOption)
    {
        await _specificationAttributeOptionRepository.DeleteAsync(specificationAttributeOption);
    }

    /// 
    /// Inserts a specification attribute option
    /// 
    /// The specification attribute option
    /// A task that represents the asynchronous operation
    public virtual async Task InsertSpecificationAttributeOptionAsync(SpecificationAttributeOption specificationAttributeOption)
    {
        await _specificationAttributeOptionRepository.InsertAsync(specificationAttributeOption);
    }

    /// 
    /// Updates the specification attribute
    /// 
    /// The specification attribute option
    /// A task that represents the asynchronous operation
    public virtual async Task UpdateSpecificationAttributeOptionAsync(SpecificationAttributeOption specificationAttributeOption)
    {
        await _specificationAttributeOptionRepository.UpdateAsync(specificationAttributeOption);
    }

    /// 
    /// Returns a list of IDs of not existing specification attribute options
    /// 
    /// The IDs of the attribute options to check
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the list of IDs not existing specification attribute options
    /// 
    public virtual async Task GetNotExistingSpecificationAttributeOptionsAsync(int[] attributeOptionIds)
    {
        ArgumentNullException.ThrowIfNull(attributeOptionIds);

        var query = _specificationAttributeOptionRepository.Table;
        var queryFilter = attributeOptionIds.Distinct().ToArray();
        var filter = await query.Select(a => a.Id)
            .Where(m => queryFilter.Contains(m))
            .ToListAsync();
        return queryFilter.Except(filter).ToArray();
    }

    /// 
    /// Gets the filtrable specification attribute options by category id
    /// 
    /// The category id
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the specification attribute options
    /// 
    public virtual async Task> GetFiltrableSpecificationAttributeOptionsByCategoryIdAsync(int categoryId)
    {
        if (categoryId <= 0)
            return new List();

        var productsQuery = await GetAvailableProductsQueryAsync();

        IList subCategoryIds = null;

        if (_catalogSettings.ShowProductsFromSubcategories)
        {
            var store = await _storeContext.GetCurrentStoreAsync();
            subCategoryIds = await _categoryService.GetChildCategoryIdsAsync(categoryId, store.Id);
        }

        var productCategoryQuery =
            from pc in _productCategoryRepository.Table
            where (pc.CategoryId == categoryId || (_catalogSettings.ShowProductsFromSubcategories && subCategoryIds.Contains(pc.CategoryId))) &&
                  (_catalogSettings.IncludeFeaturedProductsInNormalLists || !pc.IsFeaturedProduct)
            select pc;

        var result =
            from sao in _specificationAttributeOptionRepository.Table
            join psa in _productSpecificationAttributeRepository.Table on sao.Id equals psa.SpecificationAttributeOptionId
            join p in productsQuery on psa.ProductId equals p.Id
            join pc in productCategoryQuery on p.Id equals pc.ProductId
            join sa in _specificationAttributeRepository.Table on sao.SpecificationAttributeId equals sa.Id
            where psa.AllowFiltering
            orderby
                sa.DisplayOrder, sa.Name,
                sao.DisplayOrder, sao.Name
            //linq2db don't specify 'sa' in 'SELECT' statement
            //see also https://github.com/nopSolutions/nopCommerce/issues/5425
            select new { sa, sao };

        var cacheKey = _staticCacheManager.PrepareKeyForDefaultCache(
            NopCatalogDefaults.SpecificationAttributeOptionsByCategoryCacheKey, categoryId.ToString());

        return await _staticCacheManager.GetAsync(cacheKey, async () => (await result.Distinct().ToListAsync()).Select(query => query.sao).ToList());
    }

    /// 
    /// Gets the filtrable specification attribute options by manufacturer id
    /// 
    /// The manufacturer id
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the specification attribute options
    /// 
    public virtual async Task> GetFiltrableSpecificationAttributeOptionsByManufacturerIdAsync(int manufacturerId)
    {
        if (manufacturerId <= 0)
            return new List();

        var productsQuery = await GetAvailableProductsQueryAsync();

        var productManufacturerQuery =
            from pm in _productManufacturerRepository.Table
            where pm.ManufacturerId == manufacturerId &&
                  (_catalogSettings.IncludeFeaturedProductsInNormalLists || !pm.IsFeaturedProduct)
            select pm;

        var result =
            from sao in _specificationAttributeOptionRepository.Table
            join psa in _productSpecificationAttributeRepository.Table on sao.Id equals psa.SpecificationAttributeOptionId
            join p in productsQuery on psa.ProductId equals p.Id
            join pm in productManufacturerQuery on p.Id equals pm.ProductId
            join sa in _specificationAttributeRepository.Table on sao.SpecificationAttributeId equals sa.Id
            where psa.AllowFiltering
            orderby
                sa.DisplayOrder, sa.Name,
                sao.DisplayOrder, sao.Name
            //linq2db don't specify 'sa' in 'SELECT' statement
            //see also https://github.com/nopSolutions/nopCommerce/issues/5425
            select new { sa, sao };

        var cacheKey = _staticCacheManager.PrepareKeyForDefaultCache(
            NopCatalogDefaults.SpecificationAttributeOptionsByManufacturerCacheKey, manufacturerId.ToString());

        return await _staticCacheManager.GetAsync(cacheKey, async () => (await result.Distinct().ToListAsync()).Select(query => query.sao).ToList());
    }

    #endregion

    #region Product specification attribute

    /// 
    /// Deletes a product specification attribute mapping
    /// 
    /// Product specification attribute
    /// A task that represents the asynchronous operation
    public virtual async Task DeleteProductSpecificationAttributeAsync(ProductSpecificationAttribute productSpecificationAttribute)
    {
        await _productSpecificationAttributeRepository.DeleteAsync(productSpecificationAttribute);
    }

    /// 
    /// Gets a product specification attribute mapping collection
    /// 
    /// Product identifier; 0 to load all records
    /// Specification attribute option identifier; 0 to load all records
    /// 0 to load attributes with AllowFiltering set to false, 1 to load attributes with AllowFiltering set to true, null to load all attributes
    /// 0 to load attributes with ShowOnProductPage set to false, 1 to load attributes with ShowOnProductPage set to true, null to load all attributes
    /// Specification attribute group identifier; 0 to load all records; null to load attributes without group
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the product specification attribute mapping collection
    /// 
    public virtual async Task> GetProductSpecificationAttributesAsync(int productId = 0,
        int specificationAttributeOptionId = 0, bool? allowFiltering = null, bool? showOnProductPage = null, int? specificationAttributeGroupId = 0)
    {
        var allowFilteringCacheStr = allowFiltering.HasValue ? allowFiltering.ToString() : "null";
        var showOnProductPageCacheStr = showOnProductPage.HasValue ? showOnProductPage.ToString() : "null";
        var specificationAttributeGroupIdCacheStr = specificationAttributeGroupId.HasValue ? specificationAttributeGroupId.ToString() : "null";

        var key = _staticCacheManager.PrepareKeyForDefaultCache(NopCatalogDefaults.ProductSpecificationAttributeByProductCacheKey,
            productId, specificationAttributeOptionId, allowFilteringCacheStr, showOnProductPageCacheStr, specificationAttributeGroupIdCacheStr);

        var query = _productSpecificationAttributeRepository.Table;
        if (productId > 0)
            query = query.Where(psa => psa.ProductId == productId);
        if (specificationAttributeOptionId > 0)
            query = query.Where(psa => psa.SpecificationAttributeOptionId == specificationAttributeOptionId);
        if (allowFiltering.HasValue)
            query = query.Where(psa => psa.AllowFiltering == allowFiltering.Value);
        if (!specificationAttributeGroupId.HasValue || specificationAttributeGroupId > 0)
        {
            query = from psa in query
                join sao in _specificationAttributeOptionRepository.Table
                    on psa.SpecificationAttributeOptionId equals sao.Id
                join sa in _specificationAttributeRepository.Table
                    on sao.SpecificationAttributeId equals sa.Id
                where sa.SpecificationAttributeGroupId == specificationAttributeGroupId
                select psa;
        }
        if (showOnProductPage.HasValue)
            query = query.Where(psa => psa.ShowOnProductPage == showOnProductPage.Value);
        query = query.OrderBy(psa => psa.DisplayOrder).ThenBy(psa => psa.Id);

        var productSpecificationAttributes = await _staticCacheManager.GetAsync(key, async () => await query.ToListAsync());

        return productSpecificationAttributes;
    }

    /// 
    /// Gets a product specification attribute mapping 
    /// 
    /// Product specification attribute mapping identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the product specification attribute mapping
    /// 
    public virtual async Task GetProductSpecificationAttributeByIdAsync(int productSpecificationAttributeId)
    {
        return await _productSpecificationAttributeRepository.GetByIdAsync(productSpecificationAttributeId);
    }

    /// 
    /// Inserts a product specification attribute mapping
    /// 
    /// Product specification attribute mapping
    /// A task that represents the asynchronous operation
    public virtual async Task InsertProductSpecificationAttributeAsync(ProductSpecificationAttribute productSpecificationAttribute)
    {
        await _productSpecificationAttributeRepository.InsertAsync(productSpecificationAttribute);
    }

    /// 
    /// Updates the product specification attribute mapping
    /// 
    /// Product specification attribute mapping
    /// A task that represents the asynchronous operation
    public virtual async Task UpdateProductSpecificationAttributeAsync(ProductSpecificationAttribute productSpecificationAttribute)
    {
        await _productSpecificationAttributeRepository.UpdateAsync(productSpecificationAttribute);
    }

    /// 
    /// Gets a count of product specification attribute mapping records
    /// 
    /// Product identifier; 0 to load all records
    /// The specification attribute option identifier; 0 to load all records
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the count
    /// 
    public virtual async Task GetProductSpecificationAttributeCountAsync(int productId = 0, int specificationAttributeOptionId = 0)
    {
        var query = _productSpecificationAttributeRepository.Table;
        if (productId > 0)
            query = query.Where(psa => psa.ProductId == productId);
        if (specificationAttributeOptionId > 0)
            query = query.Where(psa => psa.SpecificationAttributeOptionId == specificationAttributeOptionId);

        return await query.CountAsync();
    }

    /// 
    /// Get mapped products for specification attribute
    /// 
    /// The specification attribute identifier
    /// Page index
    /// Page size
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the products
    /// 
    public virtual async Task> GetProductsBySpecificationAttributeIdAsync(int specificationAttributeId, int pageIndex, int pageSize)
    {
        var query = from product in _productRepository.Table
            join psa in _productSpecificationAttributeRepository.Table on product.Id equals psa.ProductId
            join spao in _specificationAttributeOptionRepository.Table on psa.SpecificationAttributeOptionId equals spao.Id
            where spao.SpecificationAttributeId == specificationAttributeId
            orderby product.Name
            select product;

        return await query.ToPagedListAsync(pageIndex, pageSize);
    }

    #endregion

    #endregion
}