PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.1.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.1.0
2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 All 42 releases
← All changes | app/Services/NotificationService.php +252 -21 1.132.1.0 View file →
@@ -6,20 +6,30 @@
6 6 use FluentBoards\App\Models\Notification;
7 7 use FluentBoards\App\Models\NotificationUser;
8 8 use FluentBoards\App\Models\Relation;
9 9 use FluentBoards\App\Models\Task;
10 +use FluentBoards\App\Models\User;
10 11
11 12 class NotificationService
12 13 {
13 - public function getAllNotifications($per_page, $page)
14 + public function getAllNotifications($per_page, $page, $action = 'all')
14 15 {
15 16 $user = wp_get_current_user();
16 17
17 - $notifications = Notification::where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
18 + $query = Notification::where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
18 19 ->whereHas('users', function($q) use ($user) {
19 20 $q->where('user_id', $user->ID);
20 - })->with('activitist', 'task')->orderBy('created_at', 'desc')->paginate($per_page, ['*'], 'page', $page);
21 + });
21 22
23 + // Add action filter if not 'all'
24 + if ($action !== 'all') {
25 + $query->where('action', $action);
26 + }
27 +
28 + $notifications = $query->with('activitist', 'task')
29 + ->orderBy('created_at', 'desc')
30 + ->paginate($per_page, ['*'], 'page', $page);
31 +
22 32 foreach ($notifications as $notification){
23 33 $notification->read = $notification->checkReadOrNot();
24 34 }
25 35
@@ -32,9 +42,9 @@
32 42 public function getAllUnreadNotifications($per_page, $page)
33 43 {
34 44 $userId = get_current_user_id();
35 45 if (!$userId) {
36 - throw new \Exception('You are not allowed to do that', 403);
46 + throw new \Exception(esc_html__('You are not allowed to do that', 'fluent-boards'), 403);
37 47 }
38 48 $unreadNotifications = Notification::where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
39 49 ->whereHas('users', function($q) use ($userId) {
40 50 $q->where('user_id', $userId)
@@ -64,8 +74,12 @@
64 74
65 75 $notification = NotificationUser::where('user_id', $user->ID)
66 76 ->where('notification_id', $notificationId)
67 77 ->first();
78 + if (!$notification) {
79 + throw new \Exception(esc_html__('Notification could not be found', 'fluent-boards'), 404);
80 + }
81 +
68 82 $notification->marked_read_at = current_time('mysql');
69 83 $notification->save();
70 84 return $notification;
71 85 }
@@ -115,17 +129,113 @@
115 129
116 130 return $settings;
117 131 }
118 132
133 + /**
134 + * Return board notification preferences with defaults and global watch fallbacks applied.
135 + */
136 + public function getBoardNotificationSettingsWithDefaults($boardId, $userId)
137 + {
138 + $settings = $this->getDefaultBoardNotificationSettings($userId);
139 +
140 + $boardSettings = $this->getBoardNotificationSettingsOfUser($boardId, $userId);
141 + if ($boardSettings && $boardSettings->preferences) {
142 + $preferences = maybe_unserialize($boardSettings->preferences);
143 + if (is_array($preferences)) {
144 + $settings = array_merge($settings, array_intersect_key($preferences, $settings));
145 + }
146 + }
147 +
148 + foreach ($settings as $key => $value) {
149 + $settings[$key] = $this->normalizePreferenceValue($value);
150 + }
151 +
152 + return $settings;
153 + }
154 +
155 + /**
156 + * Check if a board-scoped auto-watch preference is enabled for a user.
157 + */
158 + public function isBoardAutoWatchEnabled($boardId, $userId, $preferenceKey)
159 + {
160 + $settings = $this->getBoardNotificationSettingsWithDefaults($boardId, $userId);
161 +
162 + return array_key_exists($preferenceKey, $settings) && $this->normalizePreferenceValue($settings[$preferenceKey]);
163 + }
164 +
165 + /**
166 + * Get global watch preferences for fallback when a board has no saved watch keys.
167 + */
168 + private function getGlobalWatchNotificationSettings($userId)
169 + {
170 + $watchSettings = $this->getWatchNotificationDefaults();
171 +
172 + $globalSettings = $this->getGlobalNotificationSettingsOfUser($userId);
173 + if (!$globalSettings || !$globalSettings->value) {
174 + return $watchSettings;
175 + }
176 +
177 + $preferences = maybe_unserialize($globalSettings->value);
178 + if (!is_array($preferences)) {
179 + return $watchSettings;
180 + }
181 +
182 + foreach (array_keys($watchSettings) as $key) {
183 + if (array_key_exists($key, $preferences)) {
184 + $watchSettings[$key] = $this->normalizePreferenceValue($preferences[$key]);
185 + }
186 + }
187 +
188 + return $watchSettings;
189 + }
190 +
191 + private function getDefaultBoardNotificationSettings($userId)
192 + {
193 + return array_merge(
194 + Constant::BOARD_NOTIFICATION_TYPES,
195 + $this->getGlobalWatchNotificationSettings($userId)
196 + );
197 + }
198 +
199 + private function getWatchNotificationDefaults()
200 + {
201 + return [
202 + Constant::GLOBAL_EMAIL_NOTIFICATION_CREATING_TASK => true,
203 + Constant::GLOBAL_EMAIL_NOTIFICATION_COMMENTING => true,
204 + Constant::GLOBAL_EMAIL_NOTIFICATION_ASSIGNING => true,
205 + ];
206 + }
207 +
208 + /**
209 + * Convert stored preference values into strict booleans.
210 + */
211 + private function normalizePreferenceValue($value)
212 + {
213 + return true === $value || 1 === $value || '1' === $value || 'true' === $value;
214 + }
215 +
119 216 public function updateBoardNotificationSettings($newSettings, $id)
120 217 {
121 218 $userId = get_current_user_id();
122 219 $boardSettings = $this->getBoardNotificationSettingsOfUser($id, $userId);
123 - foreach ($newSettings as $index => $setting)
124 - {
125 - $newSettings[$index] = $setting == 'true' ? true : false;
220 + if(empty($boardSettings)){
221 + return;
126 222 }
127 - $boardSettings->preferences = $newSettings;
223 +
224 + $allowedSettings = array_merge(
225 + Constant::BOARD_NOTIFICATION_TYPES,
226 + $this->getWatchNotificationDefaults()
227 + );
228 + $filteredSettings = [];
229 + foreach ($newSettings as $index => $setting) {
230 + if (!array_key_exists($index, $allowedSettings)) {
231 + continue;
232 + }
233 +
234 + $filteredSettings[$index] = $this->normalizePreferenceValue($setting);
235 + }
236 +
237 + $boardSettings->preferences = $filteredSettings;
128 238 $boardSettings->save();
129 239
130 240 }
131 241
@@ -148,8 +258,33 @@
148 258
149 259 return $wathersToSendEmail;
150 260 }
151 261
262 + /**
263 + * Get comment notification recipients as revocable user IDs for async delivery.
264 + *
265 + * @param int $taskId
266 + * @return array
267 + */
268 + public function getCommentRecipientUserIds($taskId)
269 + {
270 + $task = Task::findOrFail(absint($taskId));
271 + $currentUserId = get_current_user_id();
272 + $recipientUserIds = [];
273 +
274 + foreach ($task->watchers as $watcher) {
275 + $watcherId = absint($watcher->ID);
276 + if (
277 + $watcherId !== $currentUserId &&
278 + $this->checkIfEmailEnable($watcherId, Constant::BOARD_EMAIL_COMMENT, $task->board_id)
279 + ) {
280 + $recipientUserIds[] = $watcherId;
281 + }
282 + }
283 +
284 + return array_values(array_unique($recipientUserIds));
285 + }
286 +
152 287 public function checkIfEmailEnable($userId, $emailPurpose, $boardId)
153 288 {
154 289 if(
155 290 $this->checkIfEmailEnabled($boardId, $userId, $emailPurpose)
@@ -177,24 +312,120 @@
177 312
178 313 return false;
179 314 }
180 315
181 - public function checkIfEmailEnabledGlobally($userId, $purpose)
316 + /**
317 + * Return the supplied user IDs that currently belong to a board.
318 + *
319 + * Mention recipients are board-scoped because comment content is private to
320 + * the board, even when a caller supplies a valid WordPress user ID.
321 + *
322 + * @param int $boardId
323 + * @param array $mentionedUserIds
324 + * @return array
325 + */
326 + public function resolveBoardMentionUserIds($boardId, $mentionedUserIds)
182 327 {
183 - $globalSettings = $this->getGlobalNotificationSettingsOfUser($userId);
328 + $boardId = absint($boardId);
329 + $mentionedUserIds = array_values(array_unique(array_filter(array_map('absint', (array) $mentionedUserIds))));
184 330
185 - if($globalSettings){
186 - $preferences = maybe_unserialize($globalSettings['value']);
187 - if(!array_key_exists($purpose, $preferences)){
188 - $preferences[$purpose] = true;
189 - $globalSettings->preferences = $preferences;
190 - $globalSettings->save();
191 - return true;
192 - }
193 - return $preferences[$purpose];
331 + if (!$boardId || !$mentionedUserIds) {
332 + return [];
194 333 }
195 334
196 - return true;
335 + $boardMemberIdLookup = [];
336 + $boardRelations = Relation::where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
337 + ->where('object_id', $boardId)
338 + ->whereIn('foreign_id', $mentionedUserIds)
339 + ->get(['foreign_id']);
340 +
341 + foreach ($boardRelations as $boardRelation) {
342 + $boardMemberIdLookup[absint($boardRelation->foreign_id)] = true;
343 + }
344 +
345 + return array_values(array_filter($mentionedUserIds, function ($mentionedUserId) use ($boardMemberIdLookup) {
346 + return isset($boardMemberIdLookup[$mentionedUserId]);
347 + }));
197 348 }
198 -}
199 349
350 + public function mentionInComment($comment, $mentionedUserIds)
351 + {
352 + $currentUserId = get_current_user_id();
353 + $uniqueIds = array_values(array_unique(array_filter(array_map('absint', (array) $mentionedUserIds))));
354 + $uniqueIds = array_values(array_filter($uniqueIds, function ($mentionedUserId) use ($currentUserId) {
355 + return $mentionedUserId !== $currentUserId;
356 + }));
200 357
358 + if (!$uniqueIds) {
359 + return;
360 + }
361 +
362 + // Queued delivery revalidates membership and preferences before sending.
363 + $this->sendMailAfterMention($comment->id, $uniqueIds);
364 +
365 + //sending desktop notifications
366 + do_action('fluent_boards/mention_comment_notification', $comment, $uniqueIds);
367 + }
368 +
369 + public function sendMailAfterMention($commentId, $recipientUserIds)
370 + {
371 + $current_user_id = get_current_user_id();
372 +
373 + /* this will run in background as soon as possible */
374 + /* sending Model or Model Instance won't work here */
375 + as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_mention', [$commentId, $recipientUserIds, $current_user_id], 'fluent-boards');
376 + }
377 +
378 + public function getUnreadNotificationsOfTasks($task)
379 + {
380 + $userId = get_current_user_id();
381 + if (!$userId) {
382 + throw new \Exception(esc_html__('You are not allowed to do that', 'fluent-boards'), 403);
383 + }
384 + $unreadNotifications = Notification::where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
385 + ->whereHas('users', function($q) use ($userId) {
386 + $q->where('user_id', $userId)
387 + ->whereNull('marked_read_at');
388 + })->whereHas('task', function($q) use ($task) {
389 + $q->where('id', $task->id);
390 + })
391 + ->with('activitist')->orderBy('created_at', 'desc')->count();
392 + return $unreadNotifications;
393 + }
394 +
395 + public function getUnreadNotificationCountsByTaskIds($taskIds)
396 + {
397 + $userId = get_current_user_id();
398 + if (!$userId) {
399 + throw new \Exception(esc_html__('You are not allowed to do that', 'fluent-boards'), 403);
400 + }
401 +
402 + $taskIds = array_values(array_filter(array_map('intval', (array) $taskIds)));
403 + if (!$taskIds) {
404 + return [];
405 + }
406 +
407 + $rows = Notification::query()
408 + // Board/list views can return many tasks at once, so unread counts are
409 + // grouped in one query instead of issuing a count query per task.
410 + ->selectRaw('task_id, COUNT(*) as unread_count')
411 + ->join(
412 + (new NotificationUser())->getTable(),
413 + 'fbs_notifications.id',
414 + '=',
415 + 'fbs_notification_users.notification_id'
416 + )
417 + ->where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
418 + ->where('fbs_notification_users.user_id', $userId)
419 + ->whereNull('fbs_notification_users.marked_read_at')
420 + ->whereIn('task_id', $taskIds)
421 + ->groupBy('task_id')
422 + ->get();
423 +
424 + $notificationCounts = [];
425 + foreach ($rows as $row) {
426 + $notificationCounts[(int) $row->task_id] = (int) $row->unread_count;
427 + }
428 +
429 + return $notificationCounts;
430 + }
431 +}