PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / trunk
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration vtrunk
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 1.45 All 41 releases
← All changes | app/Services/NotificationService.php +249 -21 1.20trunk 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,8 +129,91 @@
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);
@@ -122,13 +219,23 @@
122 219 $boardSettings = $this->getBoardNotificationSettingsOfUser($id, $userId);
123 220 if(empty($boardSettings)){
124 221 return;
125 222 }
126 - foreach ($newSettings as $index => $setting)
127 - {
128 - $newSettings[$index] = $setting == 'true' ? true : false;
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);
129 235 }
130 - $boardSettings->preferences = $newSettings;
236 +
237 + $boardSettings->preferences = $filteredSettings;
131 238 $boardSettings->save();
132 239
133 240 }
134 241
@@ -151,8 +258,33 @@
151 258
152 259 return $wathersToSendEmail;
153 260 }
154 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 +
155 287 public function checkIfEmailEnable($userId, $emailPurpose, $boardId)
156 288 {
157 289 if(
158 290 $this->checkIfEmailEnabled($boardId, $userId, $emailPurpose)
@@ -180,24 +312,120 @@
180 312
181 313 return false;
182 314 }
183 315
184 - 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)
185 327 {
186 - $globalSettings = $this->getGlobalNotificationSettingsOfUser($userId);
328 + $boardId = absint($boardId);
329 + $mentionedUserIds = array_values(array_unique(array_filter(array_map('absint', (array) $mentionedUserIds))));
187 330
188 - if($globalSettings){
189 - $preferences = maybe_unserialize($globalSettings['value']);
190 - if(!array_key_exists($purpose, $preferences)){
191 - $preferences[$purpose] = true;
192 - $globalSettings->preferences = $preferences;
193 - $globalSettings->save();
194 - return true;
195 - }
196 - return $preferences[$purpose];
331 + if (!$boardId || !$mentionedUserIds) {
332 + return [];
197 333 }
198 334
199 - 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 + }));
200 348 }
201 -}
202 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 + }));
203 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 +}