PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.2.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.2.0
2.2.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 All 43 releases
fluent-boards / app / Services / NotificationService.php

NotificationService.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 2.2.0, at app/Services/NotificationService.php

470 lines 15.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBoards\App\Services;
4
5 use FluentBoards\App\Models\Meta;
6 use FluentBoards\App\Models\Notification;
7 use FluentBoards\App\Models\NotificationUser;
8 use FluentBoards\App\Models\Relation;
9 use FluentBoards\App\Models\Task;
10 use FluentBoards\App\Models\User;
11
12 class NotificationService
13 {
14 public function getAllNotifications($per_page, $page, $action = 'all')
15 {
16 $per_page = Sanitize::perPage($per_page);
17 $page = max(1, (int) $page);
18 $user = wp_get_current_user();
19
20 $query = Notification::where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
21 ->whereHas('users', function($q) use ($user) {
22 $q->where('user_id', $user->ID);
23 });
24
25 // Add action filter if not 'all'
26 if ($action !== 'all') {
27 $query->where('action', $action);
28 }
29
30 $notifications = $query->with('activitist', 'task')
31 ->orderBy('created_at', 'desc')
32 ->paginate($per_page, ['*'], 'page', $page);
33
34 foreach ($notifications as $notification){
35 $notification->read = $notification->checkReadOrNot();
36 }
37
38 return $notifications;
39 }
40
41 /**
42 * @throws \Exception
43 */
44 public function getAllUnreadNotifications($per_page, $page)
45 {
46 $per_page = Sanitize::perPage($per_page);
47 $page = max(1, (int) $page);
48 $userId = get_current_user_id();
49 if (!$userId) {
50 throw new \Exception(esc_html__('You are not allowed to do that', 'fluent-boards'), 403);
51 }
52 $unreadNotifications = Notification::where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
53 ->whereHas('users', function($q) use ($userId) {
54 $q->where('user_id', $userId)
55 ->whereNull('marked_read_at');
56 })->with('activitist', 'task')->orderBy('created_at', 'desc')->paginate($per_page, ['*'], 'page', $page);
57 return $unreadNotifications;
58 }
59
60 public function markAllRead()
61 {
62 $user = wp_get_current_user();
63
64 $userNotifications = NotificationUser::where('user_id', $user->ID)
65 ->with('notification')
66 ->get();
67
68 foreach ($userNotifications as $data)
69 {
70 $data->marked_read_at = current_time('mysql');
71 $data->save();
72 }
73 }
74
75 public function markNotificationRead($notificationId)
76 {
77 $user = wp_get_current_user();
78
79 $notification = NotificationUser::where('user_id', $user->ID)
80 ->where('notification_id', $notificationId)
81 ->first();
82 if (!$notification) {
83 throw new \Exception(esc_html__('Notification could not be found', 'fluent-boards'), 404);
84 }
85
86 $notification->marked_read_at = current_time('mysql');
87 $notification->save();
88 return $notification;
89 }
90
91 public function newNotificationNumber()
92 {
93 $user = wp_get_current_user();
94
95 $unread_notifications = Notification::query()->where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
96 ->whereHas('users', function($q) use ($user) {
97 $q->where('user_id', $user->ID)
98 ->whereNull('marked_read_at');
99 });
100
101 return $unread_notifications->count();
102 }
103
104 public function isCurrentUserObservingTask($task)
105 {
106 $currentUserId = get_current_user_id();
107 $observers = $task->watchers()->get()->pluck('ID');
108
109 foreach ($observers as $id){
110 if($id == $currentUserId)
111 return true;
112 }
113
114 return false;
115 }
116
117 public function getBoardNotificationSettingsOfUser($id, $userId)
118 {
119 $boardSettings = Relation::where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
120 ->where('object_id', $id)
121 ->where('foreign_id', $userId)
122 ->first();
123
124 return $boardSettings;
125 }
126
127 public function getGlobalNotificationSettingsOfUser($userId)
128 {
129 $settings = Meta::where('object_type', Constant::OBJECT_TYPE_USER)
130 ->where('object_id', $userId)
131 ->where('key', Constant::USER_GLOBAL_NOTIFICATIONS)
132 ->first();
133
134 return $settings;
135 }
136
137 /**
138 * Return board notification preferences with defaults and global watch fallbacks applied.
139 */
140 public function getBoardNotificationSettingsWithDefaults($boardId, $userId)
141 {
142 $settings = $this->getDefaultBoardNotificationSettings($userId);
143
144 $boardSettings = $this->getBoardNotificationSettingsOfUser($boardId, $userId);
145 if ($boardSettings && $boardSettings->preferences) {
146 $preferences = maybe_unserialize($boardSettings->preferences);
147 if (is_array($preferences)) {
148 $settings = array_merge($settings, array_intersect_key($preferences, $settings));
149 }
150 }
151
152 foreach ($settings as $key => $value) {
153 $settings[$key] = $this->normalizePreferenceValue($value);
154 }
155
156 return $settings;
157 }
158
159 /**
160 * Check if a board-scoped auto-watch preference is enabled for a user.
161 */
162 public function isBoardAutoWatchEnabled($boardId, $userId, $preferenceKey)
163 {
164 $settings = $this->getBoardNotificationSettingsWithDefaults($boardId, $userId);
165
166 return array_key_exists($preferenceKey, $settings) && $this->normalizePreferenceValue($settings[$preferenceKey]);
167 }
168
169 /**
170 * Get global watch preferences for fallback when a board has no saved watch keys.
171 */
172 private function getGlobalWatchNotificationSettings($userId)
173 {
174 $watchSettings = $this->getWatchNotificationDefaults();
175
176 $globalSettings = $this->getGlobalNotificationSettingsOfUser($userId);
177 if (!$globalSettings || !$globalSettings->value) {
178 return $watchSettings;
179 }
180
181 $preferences = maybe_unserialize($globalSettings->value);
182 if (!is_array($preferences)) {
183 return $watchSettings;
184 }
185
186 foreach (array_keys($watchSettings) as $key) {
187 if (array_key_exists($key, $preferences)) {
188 $watchSettings[$key] = $this->normalizePreferenceValue($preferences[$key]);
189 }
190 }
191
192 return $watchSettings;
193 }
194
195 private function getDefaultBoardNotificationSettings($userId)
196 {
197 return array_merge(
198 Constant::BOARD_NOTIFICATION_TYPES,
199 $this->getGlobalWatchNotificationSettings($userId)
200 );
201 }
202
203 private function getWatchNotificationDefaults()
204 {
205 return [
206 Constant::GLOBAL_EMAIL_NOTIFICATION_CREATING_TASK => true,
207 Constant::GLOBAL_EMAIL_NOTIFICATION_COMMENTING => true,
208 Constant::GLOBAL_EMAIL_NOTIFICATION_ASSIGNING => true,
209 ];
210 }
211
212 /**
213 * Convert stored preference values into strict booleans.
214 */
215 private function normalizePreferenceValue($value)
216 {
217 return true === $value || 1 === $value || '1' === $value || 'true' === $value;
218 }
219
220 public function updateBoardNotificationSettings($newSettings, $id)
221 {
222 $userId = get_current_user_id();
223 $boardSettings = $this->getBoardNotificationSettingsOfUser($id, $userId);
224 if(empty($boardSettings)){
225 return;
226 }
227
228 $allowedSettings = array_merge(
229 Constant::BOARD_NOTIFICATION_TYPES,
230 $this->getWatchNotificationDefaults()
231 );
232 $filteredSettings = [];
233 foreach ($newSettings as $index => $setting) {
234 if (!array_key_exists($index, $allowedSettings)) {
235 continue;
236 }
237
238 $filteredSettings[$index] = $this->normalizePreferenceValue($setting);
239 }
240
241 $boardSettings->preferences = $filteredSettings;
242 $boardSettings->save();
243
244 }
245
246 public function filterAssigneeToSendEmail($taskId, $emailPurpose){
247 $task = Task::findOrFail($taskId);
248 $watchers = $task->watchers;
249 $currentUserId = get_current_user_id();
250
251 $watcherIds = array_diff($watchers->pluck('ID')->toArray(), [$currentUserId]);
252 $eligibleIds = $this->getEmailEnabledUserIds($task->board_id, $watcherIds, $emailPurpose);
253 $eligibleLookup = array_fill_keys($eligibleIds, true);
254 $watchersToSendEmail = [];
255
256 foreach ($watchers as $watcher) {
257 if (isset($eligibleLookup[$watcher->ID])) {
258 $watchersToSendEmail[] = $watcher->user_email;
259 }
260 }
261
262 return $watchersToSendEmail;
263 }
264
265 /**
266 * Get comment notification recipients as revocable user IDs for async delivery.
267 *
268 * @param int $taskId
269 * @return array
270 */
271 public function getCommentRecipientUserIds($taskId)
272 {
273 $task = Task::findOrFail(absint($taskId));
274 $currentUserId = get_current_user_id();
275 $watchers = $task->watchers;
276 $watcherIds = array_diff($watchers->pluck('ID')->toArray(), [$currentUserId]);
277 $eligibleIds = $this->getEmailEnabledUserIds($task->board_id, $watcherIds, Constant::BOARD_EMAIL_COMMENT);
278 $eligibleLookup = array_fill_keys($eligibleIds, true);
279 $recipientUserIds = [];
280
281 foreach ($watchers as $watcher) {
282 $watcherId = absint($watcher->ID);
283 if (isset($eligibleLookup[$watcherId])) {
284 $recipientUserIds[] = $watcherId;
285 }
286 }
287
288 return array_values(array_unique($recipientUserIds));
289 }
290
291 public function checkIfEmailEnable($userId, $emailPurpose, $boardId)
292 {
293 if(
294 $this->checkIfEmailEnabled($boardId, $userId, $emailPurpose)
295 ){
296 return true;
297 }else{
298 return false;
299 }
300 }
301
302 /**
303 * Resolve board email recipients in one query; missing preference keys default to enabled without writes.
304 */
305 public function getEmailEnabledUserIds($boardId, $userIds, $purpose)
306 {
307 $boardId = absint($boardId);
308 $userIds = array_values(array_unique(array_filter(array_map('intval', (array) $userIds))));
309 if (!$boardId || !$userIds) {
310 return [];
311 }
312
313 // Match the first membership used by individual checks, including old duplicates.
314 $relations = Relation::where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
315 ->where('object_id', $boardId)
316 ->whereIn('foreign_id', $userIds)
317 ->orderBy('id', 'ASC')
318 ->get(['foreign_id', 'preferences'])
319 ->unique('foreign_id');
320
321 $enabledUserIds = [];
322 foreach ($relations as $relation) {
323 $preferences = (array) maybe_unserialize($relation->preferences);
324 if (!array_key_exists($purpose, $preferences) || $preferences[$purpose]) {
325 $enabledUserIds[] = (int) $relation->foreign_id;
326 }
327 }
328
329 return array_values(array_unique($enabledUserIds));
330 }
331
332 public function checkIfEmailEnabled($boardId, $userId, $purpose)
333 {
334 $boardSettings = $this->getBoardNotificationSettingsOfUser($boardId, $userId);
335
336 if($boardSettings){
337 $preferences = maybe_unserialize($boardSettings['preferences']);
338 if(!array_key_exists($purpose, $preferences)){
339 $preferences[$purpose] = true;
340 $boardSettings->preferences = $preferences;
341 $boardSettings->save();
342 return true;
343 }
344 return $preferences[$purpose];
345 }
346
347 return false;
348 }
349
350 /**
351 * Return the supplied user IDs that currently belong to a board.
352 *
353 * Mention recipients are board-scoped because comment content is private to
354 * the board, even when a caller supplies a valid WordPress user ID.
355 *
356 * @param int $boardId
357 * @param array $mentionedUserIds
358 * @return array
359 */
360 public function resolveBoardMentionUserIds($boardId, $mentionedUserIds)
361 {
362 $boardId = absint($boardId);
363 $mentionedUserIds = array_values(array_unique(array_filter(array_map('absint', (array) $mentionedUserIds))));
364
365 if (!$boardId || !$mentionedUserIds) {
366 return [];
367 }
368
369 $boardMemberIdLookup = [];
370 $boardRelations = Relation::where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
371 ->where('object_id', $boardId)
372 ->whereIn('foreign_id', $mentionedUserIds)
373 ->get(['foreign_id']);
374
375 foreach ($boardRelations as $boardRelation) {
376 $boardMemberIdLookup[absint($boardRelation->foreign_id)] = true;
377 }
378
379 return array_values(array_filter($mentionedUserIds, function ($mentionedUserId) use ($boardMemberIdLookup) {
380 return isset($boardMemberIdLookup[$mentionedUserId]);
381 }));
382 }
383
384 public function mentionInComment($comment, $mentionedUserIds)
385 {
386 $currentUserId = get_current_user_id();
387 $uniqueIds = array_values(array_unique(array_filter(array_map('absint', (array) $mentionedUserIds))));
388 $uniqueIds = array_values(array_filter($uniqueIds, function ($mentionedUserId) use ($currentUserId) {
389 return $mentionedUserId !== $currentUserId;
390 }));
391
392 if (!$uniqueIds) {
393 return;
394 }
395
396 // Queued delivery revalidates membership and preferences before sending.
397 $this->sendMailAfterMention($comment->id, $uniqueIds);
398
399 //sending desktop notifications
400 do_action('fluent_boards/mention_comment_notification', $comment, $uniqueIds);
401 }
402
403 public function sendMailAfterMention($commentId, $recipientUserIds)
404 {
405 if (empty($recipientUserIds)) {
406 return;
407 }
408
409 $current_user_id = get_current_user_id();
410
411 /* this will run in background as soon as possible */
412 /* sending Model or Model Instance won't work here */
413 as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_mention', [$commentId, $recipientUserIds, $current_user_id], 'fluent-boards');
414 }
415
416 public function getUnreadNotificationsOfTasks($task)
417 {
418 $userId = get_current_user_id();
419 if (!$userId) {
420 throw new \Exception(esc_html__('You are not allowed to do that', 'fluent-boards'), 403);
421 }
422 $unreadNotifications = Notification::where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
423 ->whereHas('users', function($q) use ($userId) {
424 $q->where('user_id', $userId)
425 ->whereNull('marked_read_at');
426 })->whereHas('task', function($q) use ($task) {
427 $q->where('id', $task->id);
428 })
429 ->with('activitist')->orderBy('created_at', 'desc')->count();
430 return $unreadNotifications;
431 }
432
433 public function getUnreadNotificationCountsByTaskIds($taskIds)
434 {
435 $userId = get_current_user_id();
436 if (!$userId) {
437 throw new \Exception(esc_html__('You are not allowed to do that', 'fluent-boards'), 403);
438 }
439
440 $taskIds = array_values(array_filter(array_map('intval', (array) $taskIds)));
441 if (!$taskIds) {
442 return [];
443 }
444
445 $rows = Notification::query()
446 // Board/list views can return many tasks at once, so unread counts are
447 // grouped in one query instead of issuing a count query per task.
448 ->selectRaw('task_id, COUNT(*) as unread_count')
449 ->join(
450 (new NotificationUser())->getTable(),
451 'fbs_notifications.id',
452 '=',
453 'fbs_notification_users.notification_id'
454 )
455 ->where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
456 ->where('fbs_notification_users.user_id', $userId)
457 ->whereNull('fbs_notification_users.marked_read_at')
458 ->whereIn('task_id', $taskIds)
459 ->groupBy('task_id')
460 ->get();
461
462 $notificationCounts = [];
463 foreach ($rows as $row) {
464 $notificationCounts[(int) $row->task_id] = (int) $row->unread_count;
465 }
466
467 return $notificationCounts;
468 }
469 }
470