Webiant Logo Webiant Logo
  1. No results found.

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

MenuService.cs

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

namespace Nop.Services.Menus;

/// 
/// Menu service
/// 
public partial class MenuService : IMenuService
{
    #region Fields

    protected readonly IAclService _aclService;
    protected readonly IRepository _menuRepository;
    protected readonly IRepository _menuItemRepository;
    protected readonly IStoreMappingService _storeMappingService;
    protected readonly IWorkContext _workContext;

    #endregion

    #region Ctor

    public MenuService(IAclService aclService,
        IRepository menuRepository,
        IRepository menuItemRepository,
        IStoreMappingService storeMappingService,
        IWorkContext workContext)
    {
        _aclService = aclService;
        _menuRepository = menuRepository;
        _menuItemRepository = menuItemRepository;
        _storeMappingService = storeMappingService;
        _workContext = workContext;
    }

    #endregion

    #region Utilities

    /// 
    /// Sort menu items for tree representation
    /// 
    /// Menu items for sort
    /// Parent menu item identifier
    /// A value indicating whether menu items without parent menu item in provided list (source) should be ignored
    /// 
    /// An enumerable containing the sorted menu items
    /// 
    protected virtual IEnumerable SortMenuItemsForTree(
        ILookup menuItemsByParentId,
        int parentId = 0,
        bool ignoreMenuItemWithoutExistingParent = false)
    {
        ArgumentNullException.ThrowIfNull(menuItemsByParentId);

        var remaining = parentId > 0
            ? new HashSet(0)
            : menuItemsByParentId.Select(g => g.Key).ToHashSet();
        remaining.Remove(parentId);

        foreach (var item in menuItemsByParentId[parentId].OrderBy(c => c.DisplayOrder).ThenBy(c => c.Id))
        {
            yield return item;

            remaining.Remove(item.Id);

            foreach (var subItem in SortMenuItemsForTree(menuItemsByParentId, item.Id, true))
            {
                yield return subItem;
                remaining.Remove(subItem.Id);
            }
        }

        if (ignoreMenuItemWithoutExistingParent)
            yield break;

        //find menu items without parent in provided menu item source and return them
        var orphans = remaining
            .SelectMany(id => menuItemsByParentId[id])
            .OrderBy(c => c.ParentId)
            .ThenBy(c => c.DisplayOrder)
            .ThenBy(c => c.Id);

        foreach (var orphan in orphans)
            yield return orphan;
    }

    #endregion

    #region Methods

    #region Menus

    /// 
    /// Deletes a menu
    /// 
    /// Menu
    /// A task that represents the asynchronous operation
    public virtual Task DeleteMenuAsync(Menu menu)
    {
        return _menuRepository.DeleteAsync(menu);
    }

    /// 
    /// Gets all menus
    /// 
    /// 
    /// Menu type
    /// Store identifier; 0 if you want to get all records
    /// A value indicating whether to load hidden records
    /// Page index
    /// Page size
    /// A task that represents the asynchronous operation
    /// The task result contains the menus
    /// 
    public virtual async Task> GetAllMenusAsync(
        MenuType? menuType = null,
        int storeId = 0,
        bool showHidden = false,
        int pageIndex = 0,
        int pageSize = int.MaxValue)
    {
        return await _menuRepository.GetAllPagedAsync(async query =>
        {
            if (menuType is not null)
                query = query.Where(m => m.MenuTypeId == (int)menuType);

            if (!showHidden)
            {
                query = query.Where(m => m.Published);

                //apply ACL constraints
                var customer = await _workContext.GetCurrentCustomerAsync();
                query = await _aclService.ApplyAcl(query, customer);
            }

            if (!showHidden || storeId > 0)
            {
                //apply store mapping constraints
                query = await _storeMappingService.ApplyStoreMapping(query, storeId);
            }

            return query
                .OrderBy(m => m.MenuTypeId)
                .ThenBy(m => m.DisplayOrder)
                .ThenBy(m => m.Id);

        }, pageIndex, pageSize, includeDeleted: false);
    }

    /// 
    /// Gets a menu by identifier
    /// 
    /// The menu identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains a menu
    /// 
    public virtual Task GetMenuByIdAsync(int menuId)
    {
        return _menuRepository.GetByIdAsync(menuId, cache => default, includeDeleted: false);
    }

    /// 
    /// Insert a menu
    /// 
    /// Menu
    /// A task that represents the asynchronous operation
    public virtual Task InsertMenuAsync(Menu menu)
    {
        return _menuRepository.InsertAsync(menu);
    }

    /// 
    /// Update a menu
    /// 
    /// Menu
    /// A task that represents the asynchronous operation
    public virtual Task UpdateMenuAsync(Menu menu)
    {
        return _menuRepository.UpdateAsync(menu);
    }

    #endregion

    #region Menu items

    /// 
    /// Deletes a menu item
    /// 
    /// Menu item
    /// A task that represents the asynchronous operation
    public virtual async Task DeleteMenuItemAsync(MenuItem menuItem)
    {
        if (menuItem is null)
            return;

        await deleteChildrenRecursive(menuItem.Id);
        await _menuItemRepository.DeleteAsync(menuItem);

        async Task deleteChildrenRecursive(int parentId)
        {
            var children = await GetAllMenuItemsAsync(parentMenuItemId: parentId, showHidden: true);

            foreach (var child in children)
            {
                await deleteChildrenRecursive(child.Id);
                await _menuItemRepository.DeleteAsync(child);
            }
        }
    }

    /// 
    /// Gets a menu item by identifier
    /// 
    /// The menu item identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains a menu item
    /// 
    public virtual Task GetMenuItemByIdAsync(int menuItemId)
    {
        return _menuItemRepository.GetByIdAsync(menuItemId, cache => default);
    }

    /// 
    /// Insert a menu item
    /// 
    /// Menu item
    /// A task that represents the asynchronous operation
    public virtual Task InsertMenuItemAsync(MenuItem menuItem)
    {
        return _menuItemRepository.InsertAsync(menuItem);
    }

    /// 
    /// Update a menu item
    /// 
    /// Menu item
    /// A task that represents the asynchronous operation
    public virtual async Task UpdateMenuItemAsync(MenuItem menuItem)
    {
        ArgumentNullException.ThrowIfNull(menuItem);

        //validate hierarchy
        var parentMenuItem = await GetMenuItemByIdAsync(menuItem.ParentId);
        while (parentMenuItem != null)
        {
            if (menuItem.Id == parentMenuItem.Id)
            {
                menuItem.ParentId = 0;
                break;
            }

            parentMenuItem = await GetMenuItemByIdAsync(parentMenuItem.ParentId);
        }

        await _menuItemRepository.UpdateAsync(menuItem);
    }

    /// 
    /// Get all menu items
    /// 
    /// Menu identifier; 0 or null to load all records
    /// Parent menu item identifier
    /// Store identifier; 0 if you want to get all records
    /// Depth to limit items
    /// A value indicating whether to sort menu items for tree representation
    /// A value indicating whether to show hidden records
    /// Page index
    /// Page size
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains menu items
    /// 
    public virtual async Task> GetAllMenuItemsAsync(
        int menuId = 0,
        int parentMenuItemId = 0,
        int storeId = 0,
        int depth = 0,
        bool treeSorting = false,
        bool showHidden = false,
        int pageIndex = 0, int pageSize = int.MaxValue)
    {
        var menuItems = await _menuItemRepository.GetAllAsync(async query =>
        {
            if (menuId > 0)
                query = query.Where(menuItem => menuItem.MenuId == menuId);

            if (parentMenuItemId > 0)
                query = query.Where(mi => mi.ParentId == parentMenuItemId);

            if (!showHidden)
            {
                query = query.Where(m => m.Published);

                //apply ACL constraints
                var customer = await _workContext.GetCurrentCustomerAsync();
                query = await _aclService.ApplyAcl(query, customer);
            }

            if (!showHidden || storeId > 0)
            {
                //apply store mapping constraints
                query = await _storeMappingService.ApplyStoreMapping(query, storeId);
            }

            return query
                .OrderBy(i => i.DisplayOrder)
                .ThenBy(i => i.Id);
        }, includeDeleted: false);

        if (depth > 0)
            menuItems = FilterMenuItemsByDepth(menuItems, parentMenuItemId, depth).ToList();

        if (treeSorting)
            menuItems = SortMenuItemsForTree(menuItems.ToLookup(c => c.ParentId)).ToList();

        //paging
        return new PagedList(menuItems, pageIndex, pageSize);
    }

    /// 
    /// Ensure that menu items don't exceed a certain depth 
    /// 
    /// Menu items for filtering
    /// Parent menu item identifier
    /// Depth to limit items
    /// 
    /// Menu items limited in depth
    /// 
    public virtual IEnumerable FilterMenuItemsByDepth(IEnumerable menuItems, int parentId = 0, int depthLimit = 1)
    {
        ArgumentNullException.ThrowIfNull(menuItems);

        if (depthLimit == 0)
            yield break;

        depthLimit--;

        foreach (var item in menuItems.Where(item => item.ParentId == parentId))
        {
            yield return item;

            foreach (var child in FilterMenuItemsByDepth(menuItems, item.Id, depthLimit).OrderBy(item => item.DisplayOrder))
                yield return child;
        }
    }

    #endregion

    #endregion
}