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 +105 -23 2.0.02.1.0 View file →
@@ -134,18 +134,15 @@
134 134 * Return board notification preferences with defaults and global watch fallbacks applied.
135 135 */
136 136 public function getBoardNotificationSettingsWithDefaults($boardId, $userId)
137 137 {
138 - $settings = array_merge(
139 - Constant::BOARD_NOTIFICATION_TYPES,
140 - $this->getGlobalWatchNotificationSettings($userId)
141 - );
138 + $settings = $this->getDefaultBoardNotificationSettings($userId);
142 139
143 140 $boardSettings = $this->getBoardNotificationSettingsOfUser($boardId, $userId);
144 141 if ($boardSettings && $boardSettings->preferences) {
145 142 $preferences = maybe_unserialize($boardSettings->preferences);
146 143 if (is_array($preferences)) {
147 - $settings = array_merge($settings, $preferences);
144 + $settings = array_merge($settings, array_intersect_key($preferences, $settings));
148 145 }
149 146 }
150 147
151 148 foreach ($settings as $key => $value) {
@@ -169,13 +166,9 @@
169 166 * Get global watch preferences for fallback when a board has no saved watch keys.
170 167 */
171 168 private function getGlobalWatchNotificationSettings($userId)
172 169 {
173 - $watchSettings = [
174 - Constant::GLOBAL_EMAIL_NOTIFICATION_CREATING_TASK => true,
175 - Constant::GLOBAL_EMAIL_NOTIFICATION_COMMENTING => true,
176 - Constant::GLOBAL_EMAIL_NOTIFICATION_ASSIGNING => true,
177 - ];
170 + $watchSettings = $this->getWatchNotificationDefaults();
178 171
179 172 $globalSettings = $this->getGlobalNotificationSettingsOfUser($userId);
180 173 if (!$globalSettings || !$globalSettings->value) {
181 174 return $watchSettings;
@@ -194,8 +187,25 @@
194 187
195 188 return $watchSettings;
196 189 }
197 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 +
198 208 /**
199 209 * Convert stored preference values into strict booleans.
200 210 */
201 211 private function normalizePreferenceValue($value)
@@ -209,13 +219,23 @@
209 219 $boardSettings = $this->getBoardNotificationSettingsOfUser($id, $userId);
210 220 if(empty($boardSettings)){
211 221 return;
212 222 }
213 - foreach ($newSettings as $index => $setting)
214 - {
215 - $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);
216 235 }
217 - $boardSettings->preferences = $newSettings;
236 +
237 + $boardSettings->preferences = $filteredSettings;
218 238 $boardSettings->save();
219 239
220 240 }
221 241
@@ -238,8 +258,33 @@
238 258
239 259 return $wathersToSendEmail;
240 260 }
241 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 +
242 287 public function checkIfEmailEnable($userId, $emailPurpose, $boardId)
243 288 {
244 289 if(
245 290 $this->checkIfEmailEnabled($boardId, $userId, $emailPurpose)
@@ -267,31 +312,68 @@
267 312
268 313 return false;
269 314 }
270 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 +
271 350 public function mentionInComment($comment, $mentionedUserIds)
272 351 {
273 - $uniqueIds = array_unique($mentionedUserIds);
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 + }));
274 357
275 - $uniqueIds = array_filter($uniqueIds, function($value) {
276 - return (int)$value !== get_current_user_id();
277 - });
358 + if (!$uniqueIds) {
359 + return;
360 + }
278 361
279 - //sending emails to mentioned users
280 - $mentionedUserEmails = User::whereIn('ID', $uniqueIds)->pluck('user_email');
281 - $this->sendMailAfterMention($comment->id, $mentionedUserEmails);
362 + // Queued delivery revalidates membership and preferences before sending.
363 + $this->sendMailAfterMention($comment->id, $uniqueIds);
282 364
283 365 //sending desktop notifications
284 366 do_action('fluent_boards/mention_comment_notification', $comment, $uniqueIds);
285 367 }
286 368
287 - public function sendMailAfterMention($commentId, $usersToSendEmail)
369 + public function sendMailAfterMention($commentId, $recipientUserIds)
288 370 {
289 371 $current_user_id = get_current_user_id();
290 372
291 373 /* this will run in background as soon as possible */
292 374 /* sending Model or Model Instance won't work here */
293 - as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_mention', [$commentId, $usersToSendEmail, $current_user_id], 'fluent-boards');
375 + as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_mention', [$commentId, $recipientUserIds, $current_user_id], 'fluent-boards');
294 376 }
295 377
296 378 public function getUnreadNotificationsOfTasks($task)
297 379 {