# fluent-boards/trunk/app/Services/NotificationService.php

FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration, version trunk. 432 lines.

- Page: https://pluginprobe.com/plugins/fluent-boards/trunk/code/app/Services/NotificationService.php
- Raw: https://pluginprobe.com/plugins/fluent-boards/trunk/raw/app/Services/NotificationService.php
- Modified: 2026-09-09T13:50:06+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/fluent-boards/trunk/code/app/Services/NotificationService.php#L10-L20`.

```php
<?php

namespace FluentBoards\App\Services;

use FluentBoards\App\Models\Meta;
use FluentBoards\App\Models\Notification;
use FluentBoards\App\Models\NotificationUser;
use FluentBoards\App\Models\Relation;
use FluentBoards\App\Models\Task;
use FluentBoards\App\Models\User;

class NotificationService
{
    public function getAllNotifications($per_page, $page, $action = 'all')
    {
        $user = wp_get_current_user();

        $query = Notification::where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
            ->whereHas('users', function($q) use ($user) {
                $q->where('user_id', $user->ID);
            });

        // Add action filter if not 'all'
        if ($action !== 'all') {
            $query->where('action', $action);
        }

        $notifications = $query->with('activitist', 'task')
            ->orderBy('created_at', 'desc')
            ->paginate($per_page, ['*'], 'page', $page);

        foreach ($notifications as $notification){
            $notification->read = $notification->checkReadOrNot();
        }

        return  $notifications;
    }

    /**
     * @throws \Exception
     */
    public function getAllUnreadNotifications($per_page, $page)
    {
        $userId = get_current_user_id();
        if (!$userId) {
            throw new \Exception(esc_html__('You are not allowed to do that', 'fluent-boards'), 403);
        }
        $unreadNotifications = Notification::where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
            ->whereHas('users', function($q) use ($userId) {
                $q->where('user_id', $userId)
                    ->whereNull('marked_read_at');
            })->with('activitist', 'task')->orderBy('created_at', 'desc')->paginate($per_page, ['*'], 'page', $page);
        return $unreadNotifications;
    }

    public function markAllRead()
    {
        $user = wp_get_current_user();

        $userNotifications = NotificationUser::where('user_id', $user->ID)
                                ->with('notification')
                                ->get();

        foreach ($userNotifications as $data)
        {
            $data->marked_read_at = current_time('mysql');
            $data->save();
        }
    }

    public function markNotificationRead($notificationId)
    {
        $user = wp_get_current_user();

        $notification = NotificationUser::where('user_id', $user->ID)
                                            ->where('notification_id', $notificationId)
                                            ->first();
        if (!$notification) {
            throw new \Exception(esc_html__('Notification could not be found', 'fluent-boards'), 404);
        }

        $notification->marked_read_at = current_time('mysql');
        $notification->save();
        return $notification;
    }

    public function newNotificationNumber()
    {
        $user = wp_get_current_user();

        $unread_notifications = Notification::query()->where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
            ->whereHas('users', function($q) use ($user) {
                $q->where('user_id', $user->ID)
                    ->whereNull('marked_read_at');
            });

        return $unread_notifications->count();
    }

    public function isCurrentUserObservingTask($task)
    {
        $currentUserId = get_current_user_id();
        $observers = $task->watchers()->get()->pluck('ID');

        foreach ($observers as $id){
            if($id == $currentUserId)
                return true;
        }

        return false;
    }

    public function getBoardNotificationSettingsOfUser($id, $userId)
    {
        $boardSettings =  Relation::where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
            ->where('object_id', $id)
            ->where('foreign_id', $userId)
            ->first();

        return $boardSettings;
    }

    public function getGlobalNotificationSettingsOfUser($userId)
    {
        $settings =  Meta::where('object_type', Constant::OBJECT_TYPE_USER)
            ->where('object_id', $userId)
            ->where('key', Constant::USER_GLOBAL_NOTIFICATIONS)
            ->first();

        return $settings;
    }

    /**
     * Return board notification preferences with defaults and global watch fallbacks applied.
     */
    public function getBoardNotificationSettingsWithDefaults($boardId, $userId)
    {
        $settings = $this->getDefaultBoardNotificationSettings($userId);

        $boardSettings = $this->getBoardNotificationSettingsOfUser($boardId, $userId);
        if ($boardSettings && $boardSettings->preferences) {
            $preferences = maybe_unserialize($boardSettings->preferences);
            if (is_array($preferences)) {
                $settings = array_merge($settings, array_intersect_key($preferences, $settings));
            }
        }

        foreach ($settings as $key => $value) {
            $settings[$key] = $this->normalizePreferenceValue($value);
        }

        return $settings;
    }

    /**
     * Check if a board-scoped auto-watch preference is enabled for a user.
     */
    public function isBoardAutoWatchEnabled($boardId, $userId, $preferenceKey)
    {
        $settings = $this->getBoardNotificationSettingsWithDefaults($boardId, $userId);

        return array_key_exists($preferenceKey, $settings) && $this->normalizePreferenceValue($settings[$preferenceKey]);
    }

    /**
     * Get global watch preferences for fallback when a board has no saved watch keys.
     */
    private function getGlobalWatchNotificationSettings($userId)
    {
        $watchSettings = $this->getWatchNotificationDefaults();

        $globalSettings = $this->getGlobalNotificationSettingsOfUser($userId);
        if (!$globalSettings || !$globalSettings->value) {
            return $watchSettings;
        }

        $preferences = maybe_unserialize($globalSettings->value);
        if (!is_array($preferences)) {
            return $watchSettings;
        }

        foreach (array_keys($watchSettings) as $key) {
            if (array_key_exists($key, $preferences)) {
                $watchSettings[$key] = $this->normalizePreferenceValue($preferences[$key]);
            }
        }

        return $watchSettings;
    }

    private function getDefaultBoardNotificationSettings($userId)
    {
        return array_merge(
            Constant::BOARD_NOTIFICATION_TYPES,
            $this->getGlobalWatchNotificationSettings($userId)
        );
    }

    private function getWatchNotificationDefaults()
    {
        return [
            Constant::GLOBAL_EMAIL_NOTIFICATION_CREATING_TASK => true,
            Constant::GLOBAL_EMAIL_NOTIFICATION_COMMENTING    => true,
            Constant::GLOBAL_EMAIL_NOTIFICATION_ASSIGNING     => true,
        ];
    }

    /**
     * Convert stored preference values into strict booleans.
     */
    private function normalizePreferenceValue($value)
    {
        return true === $value || 1 === $value || '1' === $value || 'true' === $value;
    }

    public function updateBoardNotificationSettings($newSettings, $id)
    {
        $userId = get_current_user_id();
        $boardSettings =  $this->getBoardNotificationSettingsOfUser($id, $userId);
        if(empty($boardSettings)){
            return;
        }

        $allowedSettings = array_merge(
            Constant::BOARD_NOTIFICATION_TYPES,
            $this->getWatchNotificationDefaults()
        );
        $filteredSettings = [];
        foreach ($newSettings as $index => $setting) {
            if (!array_key_exists($index, $allowedSettings)) {
                continue;
            }

            $filteredSettings[$index] = $this->normalizePreferenceValue($setting);
        }

        $boardSettings->preferences = $filteredSettings;
        $boardSettings->save();

    }

    public function filterAssigneeToSendEmail($taskId, $emailPurpose){
        $task = Task::findOrFail($taskId);
        $watchers = $task->watchers;
        $currentUserId = get_current_user_id();

        $wathersToSendEmail = array();

        foreach ($watchers as $watcher){
            if($watcher->ID != $currentUserId)
            {
                if($this->checkIfEmailEnable($watcher->ID, $emailPurpose, $task->board_id))
                {
                    $wathersToSendEmail[] = $watcher->user_email;
                }
            }
        }

        return $wathersToSendEmail;
    }

    /**
     * Get comment notification recipients as revocable user IDs for async delivery.
     *
     * @param int $taskId
     * @return array
     */
    public function getCommentRecipientUserIds($taskId)
    {
        $task = Task::findOrFail(absint($taskId));
        $currentUserId = get_current_user_id();
        $recipientUserIds = [];

        foreach ($task->watchers as $watcher) {
            $watcherId = absint($watcher->ID);
            if (
                $watcherId !== $currentUserId &&
                $this->checkIfEmailEnable($watcherId, Constant::BOARD_EMAIL_COMMENT, $task->board_id)
            ) {
                $recipientUserIds[] = $watcherId;
            }
        }

        return array_values(array_unique($recipientUserIds));
    }

    public function checkIfEmailEnable($userId, $emailPurpose, $boardId)
    {
        if(
            $this->checkIfEmailEnabled($boardId, $userId, $emailPurpose)
        ){
            return true;
        }else{
            return false;
        }
    }

    public function checkIfEmailEnabled($boardId, $userId, $purpose)
    {
        $boardSettings = $this->getBoardNotificationSettingsOfUser($boardId, $userId);

        if($boardSettings){
            $preferences = maybe_unserialize($boardSettings['preferences']);
            if(!array_key_exists($purpose, $preferences)){
                $preferences[$purpose] = true;
                $boardSettings->preferences = $preferences;
                $boardSettings->save();
                return true;
            }
            return $preferences[$purpose];
        }

        return false;
    }

    /**
     * Return the supplied user IDs that currently belong to a board.
     *
     * Mention recipients are board-scoped because comment content is private to
     * the board, even when a caller supplies a valid WordPress user ID.
     *
     * @param int $boardId
     * @param array $mentionedUserIds
     * @return array
     */
    public function resolveBoardMentionUserIds($boardId, $mentionedUserIds)
    {
        $boardId = absint($boardId);
        $mentionedUserIds = array_values(array_unique(array_filter(array_map('absint', (array) $mentionedUserIds))));

        if (!$boardId || !$mentionedUserIds) {
            return [];
        }

        $boardMemberIdLookup = [];
        $boardRelations = Relation::where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
            ->where('object_id', $boardId)
            ->whereIn('foreign_id', $mentionedUserIds)
            ->get(['foreign_id']);

        foreach ($boardRelations as $boardRelation) {
            $boardMemberIdLookup[absint($boardRelation->foreign_id)] = true;
        }

        return array_values(array_filter($mentionedUserIds, function ($mentionedUserId) use ($boardMemberIdLookup) {
            return isset($boardMemberIdLookup[$mentionedUserId]);
        }));
    }

    public function mentionInComment($comment, $mentionedUserIds)
    {
        $currentUserId = get_current_user_id();
        $uniqueIds = array_values(array_unique(array_filter(array_map('absint', (array) $mentionedUserIds))));
        $uniqueIds = array_values(array_filter($uniqueIds, function ($mentionedUserId) use ($currentUserId) {
            return $mentionedUserId !== $currentUserId;
        }));

        if (!$uniqueIds) {
            return;
        }

        // Queued delivery revalidates membership and preferences before sending.
        $this->sendMailAfterMention($comment->id, $uniqueIds);

        //sending desktop notifications
        do_action('fluent_boards/mention_comment_notification', $comment, $uniqueIds);
    }

    public function sendMailAfterMention($commentId, $recipientUserIds)
    {
        $current_user_id = get_current_user_id();

        /* this will run in background as soon as possible */
        /* sending Model or Model Instance won't work here */
        as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_mention', [$commentId, $recipientUserIds, $current_user_id], 'fluent-boards');
    }

    public function getUnreadNotificationsOfTasks($task)
    {
        $userId = get_current_user_id();
        if (!$userId) {
            throw new \Exception(esc_html__('You are not allowed to do that', 'fluent-boards'), 403);
        }
        $unreadNotifications = Notification::where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
            ->whereHas('users', function($q) use ($userId) {
                $q->where('user_id', $userId)
                    ->whereNull('marked_read_at');
            })->whereHas('task', function($q) use ($task) {
                $q->where('id', $task->id);
            })
            ->with('activitist')->orderBy('created_at', 'desc')->count();
        return $unreadNotifications;
    }

    public function getUnreadNotificationCountsByTaskIds($taskIds)
    {
        $userId = get_current_user_id();
        if (!$userId) {
            throw new \Exception(esc_html__('You are not allowed to do that', 'fluent-boards'), 403);
        }

        $taskIds = array_values(array_filter(array_map('intval', (array) $taskIds)));
        if (!$taskIds) {
            return [];
        }

        $rows = Notification::query()
            // Board/list views can return many tasks at once, so unread counts are
            // grouped in one query instead of issuing a count query per task.
            ->selectRaw('task_id, COUNT(*) as unread_count')
            ->join(
                (new NotificationUser())->getTable(),
                'fbs_notifications.id',
                '=',
                'fbs_notification_users.notification_id'
            )
            ->where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
            ->where('fbs_notification_users.user_id', $userId)
            ->whereNull('fbs_notification_users.marked_read_at')
            ->whereIn('task_id', $taskIds)
            ->groupBy('task_id')
            ->get();

        $notificationCounts = [];
        foreach ($rows as $row) {
            $notificationCounts[(int) $row->task_id] = (int) $row->unread_count;
        }

        return $notificationCounts;
    }
}

```
