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
fluent-boards / app / Services / NotificationService.php

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

432 lines 14.3 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 $user = wp_get_current_user();
17
18 $query = Notification::where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
19 ->whereHas('users', function($q) use ($user) {
20 $q->where('user_id', $user->ID);
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
32 foreach ($notifications as $notification){
33 $notification->read = $notification->checkReadOrNot();
34 }
35
36 return $notifications;
37 }
38
39 /**
40 * @throws \Exception
41 */
42 public function getAllUnreadNotifications($per_page, $page)
43 {
44 $userId = get_current_user_id();
45 if (!$userId) {
46 throw new \Exception(esc_html__('You are not allowed to do that', 'fluent-boards'), 403);
47 }
48 $unreadNotifications = Notification::where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
49 ->whereHas('users', function($q) use ($userId) {
50 $q->where('user_id', $userId)
51 ->whereNull('marked_read_at');
52 })->with('activitist', 'task')->orderBy('created_at', 'desc')->paginate($per_page, ['*'], 'page', $page);
53 return $unreadNotifications;
54 }
55
56 public function markAllRead()
57 {
58 $user = wp_get_current_user();
59
60 $userNotifications = NotificationUser::where('user_id', $user->ID)
61 ->with('notification')
62 ->get();
63
64 foreach ($userNotifications as $data)
65 {
66 $data->marked_read_at = current_time('mysql');
67 $data->save();
68 }
69 }
70
71 public function markNotificationRead($notificationId)
72 {
73 $user = wp_get_current_user();
74
75 $notification = NotificationUser::where('user_id', $user->ID)
76 ->where('notification_id', $notificationId)
77 ->first();
78 if (!$notification) {
79 throw new \Exception(esc_html__('Notification could not be found', 'fluent-boards'), 404);
80 }
81
82 $notification->marked_read_at = current_time('mysql');
83 $notification->save();
84 return $notification;
85 }
86
87 public function newNotificationNumber()
88 {
89 $user = wp_get_current_user();
90
91 $unread_notifications = Notification::query()->where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
92 ->whereHas('users', function($q) use ($user) {
93 $q->where('user_id', $user->ID)
94 ->whereNull('marked_read_at');
95 });
96
97 return $unread_notifications->count();
98 }
99
100 public function isCurrentUserObservingTask($task)
101 {
102 $currentUserId = get_current_user_id();
103 $observers = $task->watchers()->get()->pluck('ID');
104
105 foreach ($observers as $id){
106 if($id == $currentUserId)
107 return true;
108 }
109
110 return false;
111 }
112
113 public function getBoardNotificationSettingsOfUser($id, $userId)
114 {
115 $boardSettings = Relation::where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
116 ->where('object_id', $id)
117 ->where('foreign_id', $userId)
118 ->first();
119
120 return $boardSettings;
121 }
122
123 public function getGlobalNotificationSettingsOfUser($userId)
124 {
125 $settings = Meta::where('object_type', Constant::OBJECT_TYPE_USER)
126 ->where('object_id', $userId)
127 ->where('key', Constant::USER_GLOBAL_NOTIFICATIONS)
128 ->first();
129
130 return $settings;
131 }
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
216 public function updateBoardNotificationSettings($newSettings, $id)
217 {
218 $userId = get_current_user_id();
219 $boardSettings = $this->getBoardNotificationSettingsOfUser($id, $userId);
220 if(empty($boardSettings)){
221 return;
222 }
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;
238 $boardSettings->save();
239
240 }
241
242 public function filterAssigneeToSendEmail($taskId, $emailPurpose){
243 $task = Task::findOrFail($taskId);
244 $watchers = $task->watchers;
245 $currentUserId = get_current_user_id();
246
247 $wathersToSendEmail = array();
248
249 foreach ($watchers as $watcher){
250 if($watcher->ID != $currentUserId)
251 {
252 if($this->checkIfEmailEnable($watcher->ID, $emailPurpose, $task->board_id))
253 {
254 $wathersToSendEmail[] = $watcher->user_email;
255 }
256 }
257 }
258
259 return $wathersToSendEmail;
260 }
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
287 public function checkIfEmailEnable($userId, $emailPurpose, $boardId)
288 {
289 if(
290 $this->checkIfEmailEnabled($boardId, $userId, $emailPurpose)
291 ){
292 return true;
293 }else{
294 return false;
295 }
296 }
297
298 public function checkIfEmailEnabled($boardId, $userId, $purpose)
299 {
300 $boardSettings = $this->getBoardNotificationSettingsOfUser($boardId, $userId);
301
302 if($boardSettings){
303 $preferences = maybe_unserialize($boardSettings['preferences']);
304 if(!array_key_exists($purpose, $preferences)){
305 $preferences[$purpose] = true;
306 $boardSettings->preferences = $preferences;
307 $boardSettings->save();
308 return true;
309 }
310 return $preferences[$purpose];
311 }
312
313 return false;
314 }
315
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)
327 {
328 $boardId = absint($boardId);
329 $mentionedUserIds = array_values(array_unique(array_filter(array_map('absint', (array) $mentionedUserIds))));
330
331 if (!$boardId || !$mentionedUserIds) {
332 return [];
333 }
334
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 }));
348 }
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 }));
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 }
432