Webiant Logo Webiant Logo
  1. No results found.

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

ForumService.cs

using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Forums;
using Nop.Core.Domain.Seo;
using Nop.Data;
using Nop.Services.Common;
using Nop.Services.Customers;
using Nop.Services.Html;
using Nop.Services.Messages;
using Nop.Services.Seo;

namespace Nop.Services.Forums;

/// 
/// Forum service
/// 
public partial class ForumService : IForumService
{
    #region Fields

    protected readonly ForumSettings _forumSettings;
    protected readonly ICustomerService _customerService;
    protected readonly IGenericAttributeService _genericAttributeService;
    protected readonly IHtmlFormatter _htmlFormatter;
    protected readonly IRepository _customerRepository;
    protected readonly IRepository _forumRepository;
    protected readonly IRepository _forumGroupRepository;
    protected readonly IRepository _forumPostRepository;
    protected readonly IRepository _forumPostVoteRepository;
    protected readonly IRepository _forumSubscriptionRepository;
    protected readonly IRepository _forumTopicRepository;
    protected readonly IRepository _forumPrivateMessageRepository;
    protected readonly IStaticCacheManager _staticCacheManager;
    protected readonly IUrlRecordService _urlRecordService;
    protected readonly IWorkContext _workContext;
    protected readonly IWorkflowMessageService _workflowMessageService;
    protected readonly SeoSettings _seoSettings;

    #endregion

    #region Ctor

    public ForumService(ForumSettings forumSettings,
        ICustomerService customerService,
        IGenericAttributeService genericAttributeService,
        IHtmlFormatter htmlFormatter,
        IRepository customerRepository,
        IRepository forumRepository,
        IRepository forumGroupRepository,
        IRepository forumPostRepository,
        IRepository forumPostVoteRepository,
        IRepository forumSubscriptionRepository,
        IRepository forumTopicRepository,
        IRepository forumPrivateMessageRepository,
        IStaticCacheManager staticCacheManager,
        IUrlRecordService urlRecordService,
        IWorkContext workContext,
        IWorkflowMessageService workflowMessageService,
        SeoSettings seoSettings)
    {
        _forumSettings = forumSettings;
        _customerService = customerService;
        _genericAttributeService = genericAttributeService;
        _htmlFormatter = htmlFormatter;
        _customerRepository = customerRepository;
        _forumRepository = forumRepository;
        _forumGroupRepository = forumGroupRepository;
        _forumPostRepository = forumPostRepository;
        _forumPostVoteRepository = forumPostVoteRepository;
        _forumSubscriptionRepository = forumSubscriptionRepository;
        _forumTopicRepository = forumTopicRepository;
        _forumPrivateMessageRepository = forumPrivateMessageRepository;
        _staticCacheManager = staticCacheManager;
        _urlRecordService = urlRecordService;
        _workContext = workContext;
        _workflowMessageService = workflowMessageService;
        _seoSettings = seoSettings;
    }

    #endregion

    #region Utilities

    /// 
    /// Update forum stats
    /// 
    /// The forum identifier
    /// A task that represents the asynchronous operation
    protected virtual async Task UpdateForumStatsAsync(int forumId)
    {
        if (forumId == 0)
            return;

        var forum = await GetForumByIdAsync(forumId);
        if (forum == null)
            return;

        //number of topics
        var queryNumTopics = from ft in _forumTopicRepository.Table
            where ft.ForumId == forumId
            select ft.Id;
        var numTopics = await queryNumTopics.CountAsync();

        //number of posts
        var queryNumPosts = from ft in _forumTopicRepository.Table
            join fp in _forumPostRepository.Table on ft.Id equals fp.TopicId
            where ft.ForumId == forumId
            select fp.Id;
        var numPosts = await queryNumPosts.CountAsync();

        //last values
        var lastTopicId = 0;
        var lastPostId = 0;
        var lastPostCustomerId = 0;
        DateTime? lastPostTime = null;
        var queryLastValues = from ft in _forumTopicRepository.Table
            join fp in _forumPostRepository.Table on ft.Id equals fp.TopicId
            where ft.ForumId == forumId
            orderby fp.CreatedOnUtc descending, ft.CreatedOnUtc descending
            select new
            {
                LastTopicId = ft.Id,
                LastPostId = fp.Id,
                LastPostCustomerId = fp.CustomerId,
                LastPostTime = fp.CreatedOnUtc
            };
        var lastValues = await queryLastValues.FirstOrDefaultAsync();
        if (lastValues != null)
        {
            lastTopicId = lastValues.LastTopicId;
            lastPostId = lastValues.LastPostId;
            lastPostCustomerId = lastValues.LastPostCustomerId;
            lastPostTime = lastValues.LastPostTime;
        }

        //update forum
        forum.NumTopics = numTopics;
        forum.NumPosts = numPosts;
        forum.LastTopicId = lastTopicId;
        forum.LastPostId = lastPostId;
        forum.LastPostCustomerId = lastPostCustomerId;
        forum.LastPostTime = lastPostTime;
        await UpdateForumAsync(forum);
    }

    /// 
    /// Update forum topic stats
    /// 
    /// The forum topic identifier
    /// A task that represents the asynchronous operation
    protected virtual async Task UpdateForumTopicStatsAsync(int forumTopicId)
    {
        if (forumTopicId == 0)
            return;

        var forumTopic = await GetTopicByIdAsync(forumTopicId);
        if (forumTopic == null)
            return;

        //number of posts
        var queryNumPosts = from fp in _forumPostRepository.Table
            where fp.TopicId == forumTopicId
            select fp.Id;
        var numPosts = await queryNumPosts.CountAsync();

        //last values
        var lastPostId = 0;
        var lastPostCustomerId = 0;
        DateTime? lastPostTime = null;
        var queryLastValues = from fp in _forumPostRepository.Table
            where fp.TopicId == forumTopicId
            orderby fp.CreatedOnUtc descending
            select new
            {
                LastPostId = fp.Id,
                LastPostCustomerId = fp.CustomerId,
                LastPostTime = fp.CreatedOnUtc
            };
        var lastValues = await queryLastValues.FirstOrDefaultAsync();
        if (lastValues != null)
        {
            lastPostId = lastValues.LastPostId;
            lastPostCustomerId = lastValues.LastPostCustomerId;
            lastPostTime = lastValues.LastPostTime;
        }

        //update topic
        forumTopic.NumPosts = numPosts;
        forumTopic.LastPostId = lastPostId;
        forumTopic.LastPostCustomerId = lastPostCustomerId;
        forumTopic.LastPostTime = lastPostTime;

        await UpdateTopicAsync(forumTopic);
    }

    /// 
    /// Update customer stats
    /// 
    /// The customer identifier
    /// A task that represents the asynchronous operation
    protected virtual async Task UpdateCustomerStatsAsync(int customerId)
    {
        if (customerId == 0)
            return;

        var customer = await _customerService.GetCustomerByIdAsync(customerId);

        if (customer == null)
            return;

        var query = from fp in _forumPostRepository.Table
            where fp.CustomerId == customerId
            select fp.Id;
        var numPosts = await query.CountAsync();

        await _genericAttributeService.SaveAttributeAsync(customer, NopCustomerDefaults.ForumPostCountAttribute, numPosts);
    }

    /// 
    /// Gets a forum topic
    /// 
    /// The forum topic identifier
    /// The value indicating whether to increase forum topic views
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the forum Topic
    /// 
    protected virtual async Task GetTopicByIdAsync(int forumTopicId, bool increaseViews)
    {
        var forumTopic = await _forumTopicRepository.GetByIdAsync(forumTopicId, cache => default);

        if (forumTopic == null)
            return null;

        if (!increaseViews)
            return forumTopic;

        forumTopic.Views = ++forumTopic.Views;
        await UpdateTopicAsync(forumTopic);

        return forumTopic;
    }

    #endregion

    #region Methods

    /// 
    /// Deletes a forum group
    /// 
    /// Forum group
    /// A task that represents the asynchronous operation
    public virtual async Task DeleteForumGroupAsync(ForumGroup forumGroup)
    {
        await _forumGroupRepository.DeleteAsync(forumGroup);
    }

    /// 
    /// Gets a forum group
    /// 
    /// The forum group identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the forum group
    /// 
    public virtual async Task GetForumGroupByIdAsync(int forumGroupId)
    {
        return await _forumGroupRepository.GetByIdAsync(forumGroupId, cache => default);
    }

    /// 
    /// Gets all forum groups
    /// 
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the forum groups
    /// 
    public virtual async Task> GetAllForumGroupsAsync()
    {
        return await _forumGroupRepository.GetAllAsync(query =>
        {
            return from fg in query
                orderby fg.DisplayOrder, fg.Id
                select fg;
        }, cache => default);
    }

    /// 
    /// Inserts a forum group
    /// 
    /// Forum group
    /// A task that represents the asynchronous operation
    public virtual async Task InsertForumGroupAsync(ForumGroup forumGroup)
    {
        await _forumGroupRepository.InsertAsync(forumGroup);
    }

    /// 
    /// Updates the forum group
    /// 
    /// Forum group
    /// A task that represents the asynchronous operation
    public virtual async Task UpdateForumGroupAsync(ForumGroup forumGroup)
    {
        await _forumGroupRepository.UpdateAsync(forumGroup);
    }

    /// 
    /// Deletes a forum
    /// 
    /// Forum
    /// A task that represents the asynchronous operation
    public virtual async Task DeleteForumAsync(Forum forum)
    {
        ArgumentNullException.ThrowIfNull(forum);

        //delete forum subscriptions (topics)
        var queryTopicIds = from ft in _forumTopicRepository.Table
            where ft.ForumId == forum.Id
            select ft.Id;
        var queryFs1 = from fs in _forumSubscriptionRepository.Table
            where queryTopicIds.Contains(fs.TopicId)
            select fs;

        await _forumSubscriptionRepository.DeleteAsync(queryFs1.ToList());

        //delete forum subscriptions (forum)
        var queryFs2 = from fs in _forumSubscriptionRepository.Table
            where fs.ForumId == forum.Id
            select fs;

        await _forumSubscriptionRepository.DeleteAsync(queryFs2.ToList());

        //delete forum
        await _forumRepository.DeleteAsync(forum);
    }

    /// 
    /// Gets a forum
    /// 
    /// The forum identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the forum
    /// 
    public virtual async Task GetForumByIdAsync(int forumId)
    {
        return await _forumRepository.GetByIdAsync(forumId, cache => default);
    }

    /// 
    /// Gets forums by forum group identifier
    /// 
    /// The forum group identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the forums
    /// 
    public virtual async Task> GetAllForumsByGroupIdAsync(int forumGroupId)
    {
        var forums = await _forumRepository.GetAllAsync(query =>
        {
            return from f in query
                orderby f.DisplayOrder, f.Id
                where f.ForumGroupId == forumGroupId
                select f;
        }, cache => cache.PrepareKeyForDefaultCache(NopForumDefaults.ForumByForumGroupCacheKey, forumGroupId));

        return forums;
    }

    /// 
    /// Inserts a forum
    /// 
    /// Forum
    /// A task that represents the asynchronous operation
    public virtual async Task InsertForumAsync(Forum forum)
    {
        await _forumRepository.InsertAsync(forum);
    }

    /// 
    /// Updates the forum
    /// 
    /// Forum
    /// A task that represents the asynchronous operation
    public virtual async Task UpdateForumAsync(Forum forum)
    {
        // if the forum group is changed then clear cache for the previous group 
        // (we can't use the event consumer because it will work after saving the changes in DB)
        var forumToUpdate = await _forumRepository.LoadOriginalCopyAsync(forum);
        if (forumToUpdate.ForumGroupId != forum.ForumGroupId)
            await _staticCacheManager.RemoveAsync(NopForumDefaults.ForumByForumGroupCacheKey, forumToUpdate.ForumGroupId);

        await _forumRepository.UpdateAsync(forum);
    }

    /// 
    /// Deletes a forum topic
    /// 
    /// Forum topic
    /// A task that represents the asynchronous operation
    public virtual async Task DeleteTopicAsync(ForumTopic forumTopic)
    {
        ArgumentNullException.ThrowIfNull(forumTopic);

        var customerId = forumTopic.CustomerId;
        var forumId = forumTopic.ForumId;

        //delete topic
        await _forumTopicRepository.DeleteAsync(forumTopic);

        //delete forum subscriptions
        var queryFs = from ft in _forumSubscriptionRepository.Table
            where ft.TopicId == forumTopic.Id
            select ft;
        var forumSubscriptions = queryFs.ToList();

        await _forumSubscriptionRepository.DeleteAsync(forumSubscriptions);

        //update stats
        await UpdateForumStatsAsync(forumId);
        await UpdateCustomerStatsAsync(customerId);
    }

    /// 
    /// Gets a forum topic
    /// 
    /// The forum topic identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the forum Topic
    /// 
    public virtual async Task GetTopicByIdAsync(int forumTopicId)
    {
        return await GetTopicByIdAsync(forumTopicId, false);
    }

    /// 
    /// Gets all forum topics
    /// 
    /// The forum identifier
    /// The customer identifier
    /// Keywords
    /// Search type
    /// Limit by the last number days; 0 to load all topics
    /// Page index
    /// Page size
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the forum Topics
    /// 
    public virtual async Task> GetAllTopicsAsync(int forumId = 0,
        int customerId = 0, string keywords = "", ForumSearchType searchType = ForumSearchType.All,
        int limitDays = 0, int pageIndex = 0, int pageSize = int.MaxValue)
    {
        DateTime? limitDate = null;
        if (limitDays > 0)
            limitDate = DateTime.UtcNow.AddDays(-limitDays);

        var searchKeywords = !string.IsNullOrEmpty(keywords);
        var searchTopicTitles = searchType == ForumSearchType.All || searchType == ForumSearchType.TopicTitlesOnly;
        var searchPostText = searchType == ForumSearchType.All || searchType == ForumSearchType.PostTextOnly;

        var topics = await _forumTopicRepository.GetAllPagedAsync(query =>
        {
            var query1 = from ft in query
                join fp in _forumPostRepository.Table on ft.Id equals fp.TopicId
                where
                    (forumId == 0 || ft.ForumId == forumId) &&
                    (customerId == 0 || ft.CustomerId == customerId) &&
                    (!searchKeywords ||
                     (searchTopicTitles && ft.Subject.Contains(keywords)) ||
                     (searchPostText && fp.Text.Contains(keywords))) &&
                    (!limitDate.HasValue || limitDate.Value <= ft.LastPostTime)
                select ft.Id;

            var query2 = from ft in query
                where query1.Contains(ft.Id)
                orderby ft.TopicTypeId descending, ft.LastPostTime descending, ft.Id descending
                select ft;

            return query2;
        }, pageIndex, pageSize);

        return topics;
    }

    /// 
    /// Gets active forum topics
    /// 
    /// The forum identifier
    /// Page index
    /// Page size
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the forum Topics
    /// 
    public virtual async Task> GetActiveTopicsAsync(int forumId = 0,
        int pageIndex = 0, int pageSize = int.MaxValue)
    {
        var query1 = from ft in _forumTopicRepository.Table
            where
                (forumId == 0 || ft.ForumId == forumId) &&
                ft.LastPostTime.HasValue
            select ft.Id;

        var query2 = from ft in _forumTopicRepository.Table
            where query1.Contains(ft.Id)
            orderby ft.LastPostTime descending
            select ft;

        var topics = await query2.ToPagedListAsync(pageIndex, pageSize);

        return topics;
    }

    /// 
    /// Inserts a forum topic
    /// 
    /// Forum topic
    /// A value indicating whether to send notifications to subscribed customers
    /// A task that represents the asynchronous operation
    public virtual async Task InsertTopicAsync(ForumTopic forumTopic, bool sendNotifications)
    {
        await _forumTopicRepository.InsertAsync(forumTopic);

        //update stats
        await UpdateForumStatsAsync(forumTopic.ForumId);

        if (!sendNotifications)
            return;

        //send notifications
        var forum = await GetForumByIdAsync(forumTopic.ForumId);
        var subscriptions = await GetAllSubscriptionsAsync(forumId: forum.Id);
        var languageId = (await _workContext.GetWorkingLanguageAsync()).Id;

        foreach (var subscription in subscriptions)
        {
            if (subscription.CustomerId == forumTopic.CustomerId)
                continue;

            var customer = await _customerService.GetCustomerByIdAsync(subscription.CustomerId);

            if (!string.IsNullOrEmpty(customer?.Email))
                await _workflowMessageService.SendNewForumTopicMessageAsync(customer, forumTopic, forum, languageId);
        }
    }

    /// 
    /// Updates the forum topic
    /// 
    /// Forum topic
    /// A task that represents the asynchronous operation
    public virtual async Task UpdateTopicAsync(ForumTopic forumTopic)
    {
        await _forumTopicRepository.UpdateAsync(forumTopic);
    }

    /// 
    /// Moves the forum topic
    /// 
    /// The forum topic identifier
    /// New forum identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the moved forum topic
    /// 
    public virtual async Task MoveTopicAsync(int forumTopicId, int newForumId)
    {
        var forumTopic = await GetTopicByIdAsync(forumTopicId);
        if (forumTopic == null)
            return null;

        if (!await IsCustomerAllowedToMoveTopicAsync(await _workContext.GetCurrentCustomerAsync(), forumTopic))
            return forumTopic;

        var previousForumId = forumTopic.ForumId;
        var newForum = await GetForumByIdAsync(newForumId);

        if (newForum == null)
            return forumTopic;

        if (previousForumId == newForumId)
            return forumTopic;

        forumTopic.ForumId = newForum.Id;
        forumTopic.UpdatedOnUtc = DateTime.UtcNow;
        await UpdateTopicAsync(forumTopic);

        //update forum stats
        await UpdateForumStatsAsync(previousForumId);
        await UpdateForumStatsAsync(newForumId);
        return forumTopic;
    }

    /// 
    /// Deletes a forum post
    /// 
    /// Forum post
    /// A task that represents the asynchronous operation
    public virtual async Task DeletePostAsync(ForumPost forumPost)
    {
        ArgumentNullException.ThrowIfNull(forumPost);

        var forumTopicId = forumPost.TopicId;
        var customerId = forumPost.CustomerId;
        var forumTopic = await GetTopicByIdAsync(forumTopicId);
        var forumId = forumTopic.ForumId;

        //delete topic if it was the first post
        var deleteTopic = false;
        var firstPost = await GetFirstPostAsync(forumTopic);
        if (firstPost != null && firstPost.Id == forumPost.Id)
            deleteTopic = true;

        //delete forum post
        await _forumPostRepository.DeleteAsync(forumPost);

        //delete topic
        if (deleteTopic)
            await DeleteTopicAsync(forumTopic);

        //update stats
        if (!deleteTopic)
            await UpdateForumTopicStatsAsync(forumTopicId);

        await UpdateForumStatsAsync(forumId);
        await UpdateCustomerStatsAsync(customerId);
    }

    /// 
    /// Gets a forum post
    /// 
    /// The forum post identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the forum Post
    /// 
    public virtual async Task GetPostByIdAsync(int forumPostId)
    {
        return await _forumPostRepository.GetByIdAsync(forumPostId, cache => default, useShortTermCache: true);
    }

    /// 
    /// Gets all forum posts
    /// 
    /// The forum topic identifier
    /// The customer identifier
    /// Keywords
    /// Page index
    /// Page size
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the posts
    /// 
    public virtual async Task> GetAllPostsAsync(int forumTopicId = 0,
        int customerId = 0, string keywords = "",
        int pageIndex = 0, int pageSize = int.MaxValue)
    {
        return await GetAllPostsAsync(forumTopicId, customerId, keywords, true,
            pageIndex, pageSize);
    }

    /// 
    /// Gets all forum posts
    /// 
    /// The forum topic identifier
    /// The customer identifier
    /// Keywords
    /// Sort order
    /// Page index
    /// Page size
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the forum Posts
    /// 
    public virtual async Task> GetAllPostsAsync(int forumTopicId = 0, int customerId = 0,
        string keywords = "", bool ascSort = false,
        int pageIndex = 0, int pageSize = int.MaxValue)
    {
        var forumPosts = await _forumPostRepository.GetAllPagedAsync(query =>
        {
            if (forumTopicId > 0)
                query = query.Where(fp => forumTopicId == fp.TopicId);

            if (customerId > 0)
                query = query.Where(fp => customerId == fp.CustomerId);

            if (!string.IsNullOrEmpty(keywords))
                query = query.Where(fp => fp.Text.Contains(keywords));

            query = ascSort
                ? query.OrderBy(fp => fp.CreatedOnUtc).ThenBy(fp => fp.Id)
                : query.OrderByDescending(fp => fp.CreatedOnUtc).ThenBy(fp => fp.Id);

            return query;
        }, pageIndex, pageSize);

        return forumPosts;
    }

    /// 
    /// Inserts a forum post
    /// 
    /// The forum post
    /// A value indicating whether to send notifications to subscribed customers
    /// A task that represents the asynchronous operation
    public virtual async Task InsertPostAsync(ForumPost forumPost, bool sendNotifications)
    {
        await _forumPostRepository.InsertAsync(forumPost);

        //update stats
        var customerId = forumPost.CustomerId;
        var forumTopic = await GetTopicByIdAsync(forumPost.TopicId);
        var forumId = forumTopic.ForumId;

        await UpdateForumTopicStatsAsync(forumPost.TopicId);
        await UpdateForumStatsAsync(forumId);
        await UpdateCustomerStatsAsync(customerId);

        //notifications
        if (!sendNotifications)
            return;

        var forum = await GetForumByIdAsync(forumTopic.ForumId);
        var subscriptions = await GetAllSubscriptionsAsync(topicId: forumTopic.Id);

        var languageId = (await _workContext.GetWorkingLanguageAsync()).Id;

        var friendlyTopicPageIndex = await CalculateTopicPageIndexAsync(forumPost.TopicId,
            _forumSettings.PostsPageSize > 0 ? _forumSettings.PostsPageSize : 10,
            forumPost.Id) + 1;

        foreach (var subscription in subscriptions)
        {
            if (subscription.CustomerId == forumPost.CustomerId)
                continue;

            var customer = await _customerService.GetCustomerByIdAsync(subscription.CustomerId);

            if (!string.IsNullOrEmpty(customer?.Email))
                await _workflowMessageService.SendNewForumPostMessageAsync(customer, forumPost, forumTopic, forum, friendlyTopicPageIndex, languageId);
        }
    }

    /// 
    /// Updates the forum post
    /// 
    /// Forum post
    /// A task that represents the asynchronous operation
    public virtual async Task UpdatePostAsync(ForumPost forumPost)
    {
        await _forumPostRepository.UpdateAsync(forumPost);
    }

    /// 
    /// Deletes a private message
    /// 
    /// Private message
    /// A task that represents the asynchronous operation
    public virtual async Task DeletePrivateMessageAsync(PrivateMessage privateMessage)
    {
        await _forumPrivateMessageRepository.DeleteAsync(privateMessage);
    }

    /// 
    /// Gets a private message
    /// 
    /// The private message identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the private message
    /// 
    public virtual async Task GetPrivateMessageByIdAsync(int privateMessageId)
    {
        return await _forumPrivateMessageRepository.GetByIdAsync(privateMessageId, cache => default, useShortTermCache: true);
    }

    /// 
    /// Gets private messages
    /// 
    /// The store identifier; pass 0 to load all messages
    /// The customer identifier who sent the message
    /// The customer identifier who should receive the message
    /// A value indicating whether loaded messages are read. false - to load not read messages only, 1 to load read messages only, null to load all messages
    /// A value indicating whether loaded messages are deleted by author. false - messages are not deleted by author, null to load all messages
    /// A value indicating whether loaded messages are deleted by recipient. false - messages are not deleted by recipient, null to load all messages
    /// Keywords
    /// Page index
    /// Page size
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the private messages
    /// 
    public virtual async Task> GetAllPrivateMessagesAsync(int storeId, int fromCustomerId,
        int toCustomerId, bool? isRead, bool? isDeletedByAuthor, bool? isDeletedByRecipient,
        string keywords, int pageIndex = 0, int pageSize = int.MaxValue)
    {
        var privateMessages = await _forumPrivateMessageRepository.GetAllPagedAsync(query =>
        {
            if (storeId > 0)
                query = query.Where(pm => storeId == pm.StoreId);
            if (fromCustomerId > 0)
                query = query.Where(pm => fromCustomerId == pm.FromCustomerId);
            if (toCustomerId > 0)
                query = query.Where(pm => toCustomerId == pm.ToCustomerId);
            if (isRead.HasValue)
                query = query.Where(pm => isRead.Value == pm.IsRead);
            if (isDeletedByAuthor.HasValue)
                query = query.Where(pm => isDeletedByAuthor.Value == pm.IsDeletedByAuthor);
            if (isDeletedByRecipient.HasValue)
                query = query.Where(pm => isDeletedByRecipient.Value == pm.IsDeletedByRecipient);
            if (!string.IsNullOrEmpty(keywords))
            {
                query = query.Where(pm => pm.Subject.Contains(keywords));
                query = query.Where(pm => pm.Text.Contains(keywords));
            }

            query = query.OrderByDescending(pm => pm.CreatedOnUtc);

            return query;
        }, pageIndex, pageSize);

        return privateMessages;
    }

    /// 
    /// Inserts a private message
    /// 
    /// Private message
    /// A task that represents the asynchronous operation
    public virtual async Task InsertPrivateMessageAsync(PrivateMessage privateMessage)
    {
        await _forumPrivateMessageRepository.InsertAsync(privateMessage);

        var customerTo = await _customerService.GetCustomerByIdAsync(privateMessage.ToCustomerId)
                         ?? throw new NopException("Recipient could not be loaded");

        //UI notification
        await _genericAttributeService.SaveAttributeAsync(customerTo, NopCustomerDefaults.NotifiedAboutNewPrivateMessagesAttribute, false, privateMessage.StoreId);

        //Email notification
        if (_forumSettings.NotifyAboutPrivateMessages)
            await _workflowMessageService.SendPrivateMessageNotificationAsync(privateMessage, (await _workContext.GetWorkingLanguageAsync()).Id);
    }

    /// 
    /// Updates the private message
    /// 
    /// Private message
    /// A task that represents the asynchronous operation
    public virtual async Task UpdatePrivateMessageAsync(PrivateMessage privateMessage)
    {
        ArgumentNullException.ThrowIfNull(privateMessage);

        if (privateMessage.IsDeletedByAuthor && privateMessage.IsDeletedByRecipient)
            await _forumPrivateMessageRepository.DeleteAsync(privateMessage);
        else
            await _forumPrivateMessageRepository.UpdateAsync(privateMessage);
    }

    /// 
    /// Deletes a forum subscription
    /// 
    /// Forum subscription
    /// A task that represents the asynchronous operation
    public virtual async Task DeleteSubscriptionAsync(ForumSubscription forumSubscription)
    {
        await _forumSubscriptionRepository.DeleteAsync(forumSubscription);
    }

    /// 
    /// Gets a forum subscription
    /// 
    /// The forum subscription identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the forum subscription
    /// 
    public virtual async Task GetSubscriptionByIdAsync(int forumSubscriptionId)
    {
        return await _forumSubscriptionRepository.GetByIdAsync(forumSubscriptionId, cache => default, useShortTermCache: true);
    }

    /// 
    /// Gets forum subscriptions
    /// 
    /// The customer identifier
    /// The forum identifier
    /// The topic identifier
    /// Page index
    /// Page size
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the forum subscriptions
    /// 
    public virtual async Task> GetAllSubscriptionsAsync(int customerId = 0, int forumId = 0,
        int topicId = 0, int pageIndex = 0, int pageSize = int.MaxValue)
    {
        var forumSubscriptions = await _forumSubscriptionRepository.GetAllPagedAsync(query =>
        {
            var fsQuery = from fs in query
                join c in _customerRepository.Table on fs.CustomerId equals c.Id
                where
                    (customerId == 0 || fs.CustomerId == customerId) &&
                    (forumId == 0 || fs.ForumId == forumId) &&
                    (topicId == 0 || fs.TopicId == topicId) &&
                    c.Active &&
                    !c.Deleted
                select fs.SubscriptionGuid;

            var rez = from fs in query
                where fsQuery.Contains(fs.SubscriptionGuid)
                orderby fs.CreatedOnUtc descending, fs.SubscriptionGuid descending
                select fs;

            return rez;
        }, pageIndex, pageSize);

        return forumSubscriptions;
    }

    /// 
    /// Inserts a forum subscription
    /// 
    /// Forum subscription
    /// A task that represents the asynchronous operation
    public virtual async Task InsertSubscriptionAsync(ForumSubscription forumSubscription)
    {
        await _forumSubscriptionRepository.InsertAsync(forumSubscription);
    }

    /// 
    /// Check whether customer is allowed to create new topics
    /// 
    /// Customer
    /// Forum
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains true if allowed, otherwise false
    /// 
    public virtual async Task IsCustomerAllowedToCreateTopicAsync(Customer customer, Forum forum)
    {
        if (forum == null)
            return false;

        if (customer == null)
            return false;

        if (await _customerService.IsGuestAsync(customer) && !_forumSettings.AllowGuestsToCreateTopics)
            return false;

        return true;
    }

    /// 
    /// Check whether customer is allowed to edit topic
    /// 
    /// Customer
    /// Topic
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains true if allowed, otherwise false
    /// 
    public virtual async Task IsCustomerAllowedToEditTopicAsync(Customer customer, ForumTopic topic)
    {
        if (topic == null)
            return false;

        if (customer == null)
            return false;

        if (await _customerService.IsGuestAsync(customer))
            return false;

        if (await _customerService.IsForumModeratorAsync(customer))
            return true;

        if (!_forumSettings.AllowCustomersToEditPosts)
            return false;

        var ownTopic = customer.Id == topic.CustomerId;

        return ownTopic;
    }

    /// 
    /// Check whether customer is allowed to move topic
    /// 
    /// Customer
    /// Topic
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains true if allowed, otherwise false
    /// 
    public virtual async Task IsCustomerAllowedToMoveTopicAsync(Customer customer, ForumTopic topic)
    {
        if (topic == null)
            return false;

        if (customer == null)
            return false;

        if (await _customerService.IsGuestAsync(customer))
            return false;

        return await _customerService.IsForumModeratorAsync(customer);
    }

    /// 
    /// Check whether customer is allowed to delete topic
    /// 
    /// Customer
    /// Topic
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains true if allowed, otherwise false
    /// 
    public virtual async Task IsCustomerAllowedToDeleteTopicAsync(Customer customer, ForumTopic topic)
    {
        if (topic == null)
            return false;

        if (customer == null)
            return false;

        if (await _customerService.IsGuestAsync(customer))
            return false;

        if (await _customerService.IsForumModeratorAsync(customer))
            return true;

        if (!_forumSettings.AllowCustomersToDeletePosts)
            return false;

        var ownTopic = customer.Id == topic.CustomerId;

        return ownTopic;
    }

    /// 
    /// Check whether customer is allowed to create new post
    /// 
    /// Customer
    /// Topic
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains true if allowed, otherwise false
    /// 
    public virtual async Task IsCustomerAllowedToCreatePostAsync(Customer customer, ForumTopic topic)
    {
        if (topic == null)
            return false;

        if (customer == null)
            return false;

        if (await _customerService.IsGuestAsync(customer) && !_forumSettings.AllowGuestsToCreatePosts)
            return false;

        return true;
    }

    /// 
    /// Check whether customer is allowed to edit post
    /// 
    /// Customer
    /// Topic
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains true if allowed, otherwise false
    /// 
    public virtual async Task IsCustomerAllowedToEditPostAsync(Customer customer, ForumPost post)
    {
        if (post == null)
            return false;

        if (customer == null)
            return false;

        if (await _customerService.IsGuestAsync(customer))
            return false;

        if (await _customerService.IsForumModeratorAsync(customer))
            return true;

        if (!_forumSettings.AllowCustomersToEditPosts)
            return false;

        var ownPost = customer.Id == post.CustomerId;

        return ownPost;
    }

    /// 
    /// Check whether customer is allowed to delete post
    /// 
    /// Customer
    /// Topic
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains true if allowed, otherwise false
    /// 
    public virtual async Task IsCustomerAllowedToDeletePostAsync(Customer customer, ForumPost post)
    {
        if (post == null)
            return false;

        if (customer == null)
            return false;

        if (await _customerService.IsGuestAsync(customer))
            return false;

        if (await _customerService.IsForumModeratorAsync(customer))
            return true;

        if (!_forumSettings.AllowCustomersToDeletePosts)
            return false;

        var ownPost = customer.Id == post.CustomerId;

        return ownPost;
    }

    /// 
    /// Check whether customer is allowed to set topic priority
    /// 
    /// Customer
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains true if allowed, otherwise false
    /// 
    public virtual async Task IsCustomerAllowedToSetTopicPriorityAsync(Customer customer)
    {
        if (customer == null)
            return false;

        if (await _customerService.IsGuestAsync(customer))
            return false;

        return await _customerService.IsForumModeratorAsync(customer);
    }

    /// 
    /// Check whether customer is allowed to watch topics
    /// 
    /// Customer
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains true if allowed, otherwise false
    /// 
    public virtual async Task IsCustomerAllowedToSubscribeAsync(Customer customer)
    {
        if (customer == null)
            return false;

        if (customer.IsSearchEngineAccount() || await _customerService.IsGuestAsync(customer))
            return false;

        return true;
    }

    /// 
    /// Calculates topic page index by post identifier
    /// 
    /// Forum topic identifier
    /// Page size
    /// Post identifier
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the page index
    /// 
    public virtual async Task CalculateTopicPageIndexAsync(int forumTopicId, int pageSize, int postId)
    {
        var pageIndex = 0;
        var forumPosts = await GetAllPostsAsync(forumTopicId, ascSort: true);

        for (var i = 0; i < forumPosts.TotalCount; i++)
        {
            if (forumPosts[i].Id != postId)
                continue;

            if (pageSize > 0)
                pageIndex = i / pageSize;
        }

        return pageIndex;
    }

    /// 
    /// Get a post vote 
    /// 
    /// Post identifier
    /// Customer
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the post vote
    /// 
    public virtual async Task GetPostVoteAsync(int postId, Customer customer)
    {
        if (customer == null)
            return null;

        return await _forumPostVoteRepository.Table
            .FirstOrDefaultAsync(pv => pv.ForumPostId == postId && pv.CustomerId == customer.Id);
    }

    /// 
    /// Get post vote made since the parameter date
    /// 
    /// Customer
    /// Date
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the post votes count
    /// 
    public virtual async Task GetNumberOfPostVotesAsync(Customer customer, DateTime createdFromUtc)
    {
        if (customer == null)
            return 0;

        return await _forumPostVoteRepository.Table
            .CountAsync(pv => pv.CustomerId == customer.Id && pv.CreatedOnUtc > createdFromUtc);
    }

    /// 
    /// Insert a post vote
    /// 
    /// Post vote
    /// A task that represents the asynchronous operation
    public virtual async Task InsertPostVoteAsync(ForumPostVote postVote)
    {
        await _forumPostVoteRepository.InsertAsync(postVote);

        //update post
        var post = await GetPostByIdAsync(postVote.ForumPostId);
        post.VoteCount = postVote.IsUp ? ++post.VoteCount : --post.VoteCount;

        await UpdatePostAsync(post);
    }

    /// 
    /// Delete a post vote
    /// 
    /// Post vote
    /// A task that represents the asynchronous operation
    public virtual async Task DeletePostVoteAsync(ForumPostVote postVote)
    {
        ArgumentNullException.ThrowIfNull(postVote);

        await _forumPostVoteRepository.DeleteAsync(postVote);

        // update post
        var post = await GetPostByIdAsync(postVote.ForumPostId);
        post.VoteCount = postVote.IsUp ? --post.VoteCount : ++post.VoteCount;

        await UpdatePostAsync(post);
    }

    /// 
    /// Formats the forum post text
    /// 
    /// Forum post
    /// Formatted text
    public virtual string FormatPostText(ForumPost forumPost)
    {
        var text = forumPost.Text;

        if (string.IsNullOrEmpty(text))
            return string.Empty;

        switch (_forumSettings.ForumEditor)
        {
            case EditorType.SimpleTextBox:
            {
                text = _htmlFormatter.FormatText(text, false, true, false, false, false, false);
            }

                break;
            case EditorType.BBCodeEditor:
            {
                text = _htmlFormatter.FormatText(text, false, true, false, true, false, false);
            }

                break;
            default:
                break;
        }

        return text;
    }

    /// 
    /// Strips the topic subject
    /// 
    /// Forum topic
    /// Formatted subject
    public virtual string StripTopicSubject(ForumTopic forumTopic)
    {
        var subject = forumTopic.Subject;
        if (string.IsNullOrEmpty(subject))
            return subject;

        var strippedTopicMaxLength = _forumSettings.StrippedTopicMaxLength;
        if (strippedTopicMaxLength <= 0)
            return subject;

        if (subject.Length <= strippedTopicMaxLength)
            return subject;

        var index = subject.IndexOf(" ", strippedTopicMaxLength, StringComparison.Ordinal);

        if (index <= 0)
            return subject;

        subject = subject[0..index];
        subject += "...";

        return subject;
    }

    /// 
    /// Formats the forum signature text
    /// 
    /// Text
    /// Formatted text
    public virtual string FormatForumSignatureText(string text)
    {
        if (string.IsNullOrEmpty(text))
            return string.Empty;

        text = _htmlFormatter.FormatText(text, false, true, false, false, false, false);
        return text;
    }

    /// 
    /// Formats the private message text
    /// 
    /// Private message
    /// Formatted text
    public virtual string FormatPrivateMessageText(PrivateMessage pm)
    {
        var text = pm.Text;

        if (string.IsNullOrEmpty(text))
            return string.Empty;

        text = _htmlFormatter.FormatText(text, false, true, false, true, false, false);

        return text;
    }

    /// 
    /// Get first post
    /// 
    /// Forum topic
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the forum post
    /// 
    public virtual async Task GetFirstPostAsync(ForumTopic forumTopic)
    {
        ArgumentNullException.ThrowIfNull(forumTopic);

        var forumPosts = await GetAllPostsAsync(forumTopic.Id, 0, string.Empty, 0, 1);
        if (forumPosts.Any())
            return forumPosts[0];

        return null;
    }

    /// 
    /// Gets ForumGroup SE (search engine) name
    /// 
    /// ForumGroup
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the forumGroup SE (search engine) name
    /// 
    public virtual async Task GetForumGroupSeNameAsync(ForumGroup forumGroup)
    {
        ArgumentNullException.ThrowIfNull(forumGroup);

        var seName = await _urlRecordService.GetSeNameAsync(forumGroup.Name, _seoSettings.ConvertNonWesternChars, _seoSettings.AllowUnicodeCharsInUrls);

        return seName;
    }

    /// 
    /// Gets Forum SE (search engine) name
    /// 
    /// Forum
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the forum SE (search engine) name
    /// 
    public virtual async Task GetForumSeNameAsync(Forum forum)
    {
        ArgumentNullException.ThrowIfNull(forum);

        var seName = await _urlRecordService.GetSeNameAsync(forum.Name, _seoSettings.ConvertNonWesternChars, _seoSettings.AllowUnicodeCharsInUrls);

        return seName;
    }

    /// 
    /// Gets ForumTopic SE (search engine) name
    /// 
    /// ForumTopic
    /// 
    /// A task that represents the asynchronous operation
    /// The task result contains the forumTopic SE (search engine) name
    /// 
    public virtual async Task GetTopicSeNameAsync(ForumTopic forumTopic)
    {
        ArgumentNullException.ThrowIfNull(forumTopic);

        var seName = await _urlRecordService.GetSeNameAsync(forumTopic.Subject, _seoSettings.ConvertNonWesternChars, _seoSettings.AllowUnicodeCharsInUrls);

        // Trim SE name to avoid URLs that are too long
        var maxLength = NopSeoDefaults.ForumTopicLength;
        if (seName.Length > maxLength)
            seName = seName[0..maxLength];

        return seName;
    }

    #endregion
}