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
← All changes | app/Services/NotificationService.php +118 -18 2.0.4 → 2.2.0 View file →
@@ -12,8 +12,10 @@
12 12 class NotificationService
13 13 {
14 14 public function getAllNotifications($per_page, $page, $action = 'all')
15 15 {
16 + $per_page = Sanitize::perPage($per_page);
17 + $page = max(1, (int) $page);
16 18 $user = wp_get_current_user();
17 19
18 20 $query = Notification::where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
19 21 ->whereHas('users', function($q) use ($user) {
@@ -40,8 +42,10 @@
40 42 * @throws \Exception
41 43 */
42 44 public function getAllUnreadNotifications($per_page, $page)
43 45 {
46 + $per_page = Sanitize::perPage($per_page);
47 + $page = max(1, (int) $page);
44 48 $userId = get_current_user_id();
45 49 if (!$userId) {
46 50 throw new \Exception(esc_html__('You are not allowed to do that', 'fluent-boards'), 403);
47 51 }
@@ -243,23 +247,48 @@
243 247 $task = Task::findOrFail($taskId);
244 248 $watchers = $task->watchers;
245 249 $currentUserId = get_current_user_id();
246 250
247 - $wathersToSendEmail = array();
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 = [];
248 255
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 + foreach ($watchers as $watcher) {
257 + if (isset($eligibleLookup[$watcher->ID])) {
258 + $watchersToSendEmail[] = $watcher->user_email;
256 259 }
257 260 }
258 261
259 - return $wathersToSendEmail;
262 + return $watchersToSendEmail;
260 263 }
261 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 +
262 291 public function checkIfEmailEnable($userId, $emailPurpose, $boardId)
263 292 {
264 293 if(
265 294 $this->checkIfEmailEnabled($boardId, $userId, $emailPurpose)
@@ -269,8 +298,38 @@
269 298 return false;
270 299 }
271 300 }
272 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 +
273 332 public function checkIfEmailEnabled($boardId, $userId, $purpose)
274 333 {
275 334 $boardSettings = $this->getBoardNotificationSettingsOfUser($boardId, $userId);
276 335
@@ -287,31 +346,72 @@
287 346
288 347 return false;
289 348 }
290 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 +
291 384 public function mentionInComment($comment, $mentionedUserIds)
292 385 {
293 - $uniqueIds = array_unique($mentionedUserIds);
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 + }));
294 391
295 - $uniqueIds = array_filter($uniqueIds, function($value) {
296 - return (int)$value !== get_current_user_id();
297 - });
392 + if (!$uniqueIds) {
393 + return;
394 + }
298 395
299 - //sending emails to mentioned users
300 - $mentionedUserEmails = User::whereIn('ID', $uniqueIds)->pluck('user_email');
301 - $this->sendMailAfterMention($comment->id, $mentionedUserEmails);
396 + // Queued delivery revalidates membership and preferences before sending.
397 + $this->sendMailAfterMention($comment->id, $uniqueIds);
302 398
303 399 //sending desktop notifications
304 400 do_action('fluent_boards/mention_comment_notification', $comment, $uniqueIds);
305 401 }
306 402
307 - public function sendMailAfterMention($commentId, $usersToSendEmail)
403 + public function sendMailAfterMention($commentId, $recipientUserIds)
308 404 {
405 + if (empty($recipientUserIds)) {
406 + return;
407 + }
408 +
309 409 $current_user_id = get_current_user_id();
310 410
311 411 /* this will run in background as soon as possible */
312 412 /* sending Model or Model Instance won't work here */
313 - as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_mention', [$commentId, $usersToSendEmail, $current_user_id], 'fluent-boards');
413 + as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_mention', [$commentId, $recipientUserIds, $current_user_id], 'fluent-boards');
314 414 }
315 415
316 416 public function getUnreadNotificationsOfTasks($task)
317 417 {