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/UserService.php +271 -58 1.91.62.1.0 View file →
@@ -3,8 +3,9 @@
3 3 namespace FluentBoards\App\Services;
4 4
5 5 use FluentBoards\App\Models\Activity;
6 6 use FluentBoards\App\Models\Meta;
7 +use FluentBoards\App\Models\Notification;
7 8 use FluentBoards\App\Models\Task;
8 9 use FluentBoards\App\Models\User;
9 10 use FluentBoards\App\Models\Board;
10 11 use FluentBoards\App\Models\Relation;
@@ -107,20 +108,46 @@
107 108
108 109 return $boardUsers;
109 110 }
110 111
112 + /**
113 + * Return associated users and roles only from boards shared with the requester.
114 + */
111 115 public function memberAssociatedTaskUsers($userId)
112 116 {
113 117 $user = User::find($userId);
114 - $boards = $user->whichBoards->pluck('id');
115 - $boardUsers = Relation::whereIn('object_id', $boards)
118 + $memberBoardIds = array_values(array_unique(array_filter(array_map(
119 + 'intval',
120 + $user->whichBoards->pluck('id')->toArray()
121 + ))));
122 + $requesterBoardIds = array_values(array_unique(array_filter(array_map(
123 + 'intval',
124 + PermissionManager::getBoardIdsForUser(get_current_user_id())
125 + ))));
126 + $sharedBoardIds = array_values(array_intersect($memberBoardIds, $requesterBoardIds));
127 +
128 + if (!$sharedBoardIds) {
129 + return [
130 + 'uniqueUsers' => [],
131 + 'userWiseBoardDesignation' => [],
132 + ];
133 + }
134 +
135 + $boardUsers = Relation::whereIn('object_id', $sharedBoardIds)
116 136 ->where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
117 137 ->pluck('foreign_id')->toArray();
118 138 $uniqueUsersIds = array_unique($boardUsers);
119 - $uniqueUsers = User::whereIn('ID', $uniqueUsersIds)->with('whichBoards')->get();
139 + $boardTable = (new Board())->getTable();
140 + $uniqueUsers = User::whereIn('ID', $uniqueUsersIds)
141 + ->with(['whichBoards' => function ($query) use ($boardTable, $sharedBoardIds) {
142 + $query->whereIn($boardTable . '.id', $sharedBoardIds);
143 + }])
144 + ->get();
120 145
121 146 $userWiseBoardDesignation = Relation::query()->whereIn('foreign_id', $uniqueUsersIds)
122 - ->where('object_type', Constant::OBJECT_TYPE_BOARD_USER)->get();
147 + ->whereIn('object_id', $sharedBoardIds)
148 + ->where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
149 + ->get();
123 150
124 151 $data = array();
125 152 $data['userWiseBoardDesignation'] = $userWiseBoardDesignation;
126 153
@@ -134,9 +161,9 @@
134 161 } elseif (PermissionManager::isFluentBoardsAdmin($uniqueUser['ID'])) {
135 162 $uniqueUser['is_super'] = true;
136 163 $uniqueUser['is_wpadmin'] = false;
137 164 } else {
138 - $uniqueUser['all_boards'] = Arr::get($uniqueUser, 'boards');
165 + $uniqueUser['all_boards'] = $uniqueUser->getRelation('whichBoards');
139 166 $uniqueUser['is_super'] = false;
140 167 $uniqueUser['is_wpadmin'] = false;
141 168 }
142 169 }
@@ -233,43 +260,64 @@
233 260 'tasks' => [],
234 261 'paginationInfo' => [
235 262 'current_page' => 1,
236 263 'last_page' => 1,
264 + 'per_page' => (int) $per_page,
237 265 'total' => 0,
238 266 ],
239 267 ];
240 268 }
241 269
242 - if($taskType == 'assigned') {
243 - $tasksQuery = $user->assignedTasks()->with(['stage', 'board'])->whereNull('archived_at')->whereNull('parent_id')->whereIn('board_id', $allowedBoardIds);
244 - } else if($taskType == 'mentioned') {
245 - $tasksQuery = $user->mentionedTasks()->with(['stage', 'board'])->whereNull('archived_at')->whereNull('parent_id')->whereIn('board_id', $allowedBoardIds);
270 + // Table view needs the labels column; list view ignores the extra relation.
271 + $taskRelations = ['stage', 'board', 'labels'];
272 +
273 + if ($taskType == 'assigned') {
274 + $tasksQuery = $user->assignedTasks()
275 + ->with($taskRelations)
276 + ->whereNull('archived_at')
277 + ->whereNull('parent_id')
278 + ->whereIn('board_id', $allowedBoardIds)
279 + ->where('status', '!=', 'closed')
280 + ->onActiveAvailableBoards();
281 + } else if ($taskType == 'mentioned') {
282 + $tasksQuery = $user->mentionedTasks()
283 + ->with($taskRelations)
284 + ->whereNull('archived_at')
285 + ->whereNull('parent_id')
286 + ->whereIn('board_id', $allowedBoardIds)
287 + ->onActiveAvailableBoards();
246 288 } else {
247 - // Get the task assigned to the user
248 - $tasksQuery = $user->tasks()->with(['stage', 'board'])->whereNull('archived_at') ->whereIn('board_id', $allowedBoardIds);
289 + // Watched tasks power the date-based and completed profile tabs.
290 + $tasksQuery = $user->tasks()
291 + ->with($taskRelations)
292 + ->whereNull('archived_at')
293 + ->whereIn('board_id', $allowedBoardIds)
294 + ->onActiveAvailableBoards();
249 295
250 - switch ($taskType) {
251 - case 'upcoming':
252 - $tasksQuery->upcoming()
253 - ->whereIn('board_id', $allowedBoardIds);
254 - break;
255 - case 'overdue':
256 - $tasksQuery->overdue()
257 - ->whereIn('board_id', $allowedBoardIds);
258 - break;
259 - case 'completed':
260 - $tasksQuery->where('status', 'closed')
261 - ->whereIn('board_id', $allowedBoardIds);
262 - break;
263 - default:
264 - $tasksQuery->whereNull('due_at')
265 - ->whereIn('board_id', $allowedBoardIds);
266 - break;
296 + switch ($taskType) {
297 + case 'upcoming':
298 + $tasksQuery->upcoming()
299 + ->whereIn('board_id', $allowedBoardIds);
300 + break;
301 + case 'due_today':
302 + $tasksQuery->dueToday()
303 + ->whereIn('board_id', $allowedBoardIds);
304 + break;
305 + case 'overdue':
306 + $tasksQuery->overdue()
307 + ->whereIn('board_id', $allowedBoardIds);
308 + break;
309 + case 'completed':
310 + $tasksQuery->where('status', 'closed')
311 + ->whereIn('board_id', $allowedBoardIds);
312 + break;
313 + default:
314 + $tasksQuery->whereNull('due_at')
315 + ->whereIn('board_id', $allowedBoardIds);
316 + break;
317 + }
267 318 }
268 - }
269 319
270 -
271 -
272 320 $currentUserId = get_current_user_id();
273 321 if ($currentUserId != $user->ID && !PermissionManager::isAdmin()) {
274 322 $currentUser = User::find($currentUserId);
275 323 $currentUserBoardIds = $currentUser->boards->pluck('id')->toArray();
@@ -295,9 +343,9 @@
295 343 }
296 344
297 345 // Apply ordering based on the specified order and orderBy
298 346 if ($orderBy === 'priority') {
299 - $tasksQuery->orderByRaw("FIELD(priority, 'High', 'Medium', 'Low') {$order}");
347 + $tasksQuery->orderByRaw("FIELD(priority, 'urgent', 'high', 'medium', 'low') {$order}");
300 348 } else if ($orderBy === 'due_at') {
301 349 $tasksQuery->orderByRaw("ISNULL(due_at), due_at {$order}");
302 350 } else {
303 351 $tasksQuery->orderBy($orderBy, $order);
@@ -309,44 +357,152 @@
309 357 'tasks' => $tasks->values()->toArray(),
310 358 'paginationInfo' => [
311 359 'current_page' => $tasks->currentPage(),
312 360 'last_page' => $tasks->lastPage(),
361 + 'per_page' => (int) $tasks->perPage(),
313 362 'total' => $tasks->total(),
314 363 ],
315 364 ];
316 365 }
317 366
367 + /**
368 + * Get totals for each task category available in the profile task tabs.
369 + */
370 + public function getMemberTaskCounts($userId, $boardIds = [])
371 + {
372 + $user = User::find($userId);
373 + $currentUserId = get_current_user_id();
374 + $allowedBoardIds = PermissionManager::getBoardIdsForUser($currentUserId);
318 375
376 + if (!$user || empty($allowedBoardIds)) {
377 + return array_fill_keys(
378 + ['due_today', 'assigned', 'upcoming', 'overdue', 'mentioned', 'completed', 'others'],
379 + 0
380 + );
381 + }
382 +
383 + if ($currentUserId != $user->ID && !PermissionManager::isAdmin()) {
384 + $currentUser = User::find($currentUserId);
385 + $currentUserBoardIds = $currentUser->boards->pluck('id')->toArray();
386 + $boardIds = empty($boardIds)
387 + ? $currentUserBoardIds
388 + : array_intersect($boardIds, $currentUserBoardIds);
389 + }
390 +
391 + // Keep the count queries aligned with the profile list without loading task models or relations.
392 + $applyTaskScope = function ($query) use ($allowedBoardIds, $boardIds) {
393 + $query->whereNull('archived_at')
394 + ->whereNull('parent_id')
395 + ->whereIn('board_id', $allowedBoardIds)
396 + ->onActiveAvailableBoards();
397 +
398 + if (!empty($boardIds)) {
399 + $query->whereIn('board_id', $boardIds);
400 + }
401 +
402 + return $query;
403 + };
404 + $watchedTasks = function () use ($user, $applyTaskScope) {
405 + return $applyTaskScope($user->tasks());
406 + };
407 +
408 + return [
409 + 'due_today' => (int) $watchedTasks()->dueToday()->count(),
410 + 'assigned' => (int) $applyTaskScope($user->assignedTasks())
411 + ->where('status', '!=', 'closed')
412 + ->count(),
413 + 'upcoming' => (int) $watchedTasks()->upcoming()->count(),
414 + 'overdue' => (int) $watchedTasks()->overdue()->count(),
415 + 'mentioned' => (int) $applyTaskScope($user->mentionedTasks())->count(),
416 + 'completed' => (int) $watchedTasks()->where('status', 'closed')->count(),
417 + 'others' => (int) $watchedTasks()->whereNull('due_at')->count(),
418 + ];
419 + }
420 +
421 +
422 + /**
423 + * Get a member's activities scoped to boards the requesting user can access.
424 + */
319 425 public function getMemberRelatedAcitivies($user_id, $page)
320 426 {
321 - $activities = Activity::query()->where('created_by', $user_id)
427 + $perPage = 40;
428 + $user_id = absint($user_id);
429 + $page = max(1, absint($page));
430 + $allowedBoardIds = array_values(array_filter(array_map(
431 + 'intval',
432 + PermissionManager::getBoardIdsForUser(get_current_user_id())
433 + )));
434 +
435 + if (empty($allowedBoardIds)) {
436 + return [
437 + 'activities' => [],
438 + 'pagination' => $this->getEmptyPaginationInfo($page, $perPage),
439 + ];
440 + }
441 +
442 + $allowedTaskIds = Task::query()
443 + ->select('id')
444 + ->whereIn('board_id', $allowedBoardIds);
445 +
446 + $activities = Activity::query()
447 + ->where('created_by', $user_id)
448 + ->where(function ($query) use ($allowedBoardIds, $allowedTaskIds) {
449 + $query->where(function ($boardQuery) use ($allowedBoardIds) {
450 + $boardQuery->where('object_type', Constant::ACTIVITY_BOARD)
451 + ->whereIn('object_id', $allowedBoardIds);
452 + })->orWhere(function ($taskQuery) use ($allowedTaskIds) {
453 + $taskQuery->where('object_type', Constant::ACTIVITY_TASK)
454 + ->whereIn('object_id', $allowedTaskIds);
455 + });
456 + })
322 457 ->orderBy('created_at', 'desc')
323 - ->with('user')->paginate(40, ['*'], 'page', $page);
458 + ->with('user')
459 + ->paginate($perPage, ['*'], 'page', $page);
324 460
325 - $activitiesToShow = array();
461 + $activitiesToShow = [];
326 462
327 463 foreach ($activities as $activity) {
328 464 if ($activity->object_type == Constant::ACTIVITY_BOARD) {
329 465 $activity->load('board');
330 -// if($activity->settings && $activity->settings['task_id']){
331 -// $activity->task = Task::findOrFail($activity->settings['task_id']);
332 -// }
333 - if (PermissionManager::userHasPermission($activity->board_id, get_current_user_id())) {
334 - $activitiesToShow[] = $activity;
335 - }
466 + $activitiesToShow[] = $activity;
336 467 } elseif ($activity->object_type == Constant::ACTIVITY_TASK) {
337 468 $activity->load('task');
338 - if ($activity->task && PermissionManager::userHasPermission($activity->task->board_id, get_current_user_id())) {
339 - $activitiesToShow[] = $activity;
340 - }
469 + $activitiesToShow[] = $activity;
341 470 }
342 471 }
472 +
343 473 return [
344 - 'activities' => $activitiesToShow,
345 - 'pagination' => $activities->toArray(),
474 + 'activities' => $activitiesToShow,
475 + 'pagination' => $this->getPaginationInfo($activities),
346 476 ];
347 477 }
348 478
479 + /**
480 + * Return pagination metadata without duplicating serialized row data.
481 + */
482 + private function getPaginationInfo($paginator)
483 + {
484 + return [
485 + 'current_page' => $paginator->currentPage(),
486 + 'last_page' => $paginator->lastPage(),
487 + 'per_page' => (int) $paginator->perPage(),
488 + 'total' => $paginator->total(),
489 + ];
490 + }
491 +
492 + /**
493 + * Return an empty pagination payload for callers without accessible boards.
494 + */
495 + private function getEmptyPaginationInfo($page, $perPage)
496 + {
497 + return [
498 + 'current_page' => max(1, (int) $page),
499 + 'last_page' => 1,
500 + 'per_page' => (int) $perPage,
501 + 'total' => 0,
502 + ];
503 + }
504 +
349 505 private function getTaskById($taskId)
350 506 {
351 507 return Task::findOrFail($taskId);
352 508 }
@@ -355,23 +511,80 @@
355 511 {
356 512 if(!$user_id) {
357 513 return [];
358 514 }
359 - $boardIds = [];
360 - $boards = [];
361 515 $user = User::find($user_id);
362 516 $currentUserId = get_current_user_id();
363 - if($currentUserId != $user->ID) {
364 - if(!PermissionManager::isAdmin()){
365 - $currentUser = User::find($currentUserId);
366 - $currentUserBoardIds = $currentUser->whichBoards->pluck('id')->toArray();
367 - $boardIds = $currentUserBoardIds;
517 +
518 + if ($currentUserId == $user->ID || PermissionManager::isAdmin($currentUserId)) {
519 + return $user->whichBoards;
520 + }
521 +
522 + $currentUser = User::find($currentUserId);
523 + $boardIds = $currentUser ? $currentUser->whichBoards->pluck('id')->toArray() : [];
524 +
525 + if (empty($boardIds)) {
526 + return [];
527 + }
528 +
529 + return $user->whichBoards()->whereIn('fbs_boards.id', $boardIds)->get();
530 + }
531 +
532 + /**
533 + * Returns four aggregate counts for a member's stats widget.
534 + *
535 + * Cross-user access is scoped to boards shared with the requesting user,
536 + * matching the contract of getMemberBoards() and getMemberAssociatedTasks().
537 + * Unread notifications are personal and only returned for self or admin.
538 + */
539 + public function getMemberStats(int $user_id): array
540 + {
541 + $empty = ['assigned_tasks' => 0, 'completed_tasks' => 0, 'total_boards' => 0, 'unread_notifications' => 0];
542 +
543 + $user = User::find($user_id);
544 + if (!$user) {
545 + return $empty;
546 + }
547 +
548 + $currentUserId = get_current_user_id();
549 + $isSelfOrAdmin = ($currentUserId === $user_id) || PermissionManager::isAdmin($currentUserId);
550 +
551 + // For cross-user access, restrict counts to boards the requesting user can also see.
552 + $allowedBoardIds = null;
553 + if (!$isSelfOrAdmin) {
554 + $currentUser = User::find($currentUserId);
555 + $allowedBoardIds = $currentUser ? $currentUser->whichBoards->pluck('id')->toArray() : [];
556 + if (empty($allowedBoardIds)) {
557 + return $empty;
368 558 }
369 559 }
370 - if (!empty($boardIds)) {
371 - $boards = $user->whichBoards()->whereIn('fbs_boards.id', $boardIds)->get();
372 - } else {
373 - $boards = $user->whichBoards;
560 +
561 + $assignedBase = $user->assignedTasks()->whereNull('archived_at')->whereNull('parent_id');
562 + if ($allowedBoardIds !== null) {
563 + $assignedBase->whereIn('board_id', $allowedBoardIds);
374 564 }
375 - return $boards;
565 +
566 + $boardsQuery = $user->whichBoards();
567 + if ($allowedBoardIds !== null) {
568 + $boardsQuery->whereIn('fbs_boards.id', $allowedBoardIds);
569 + }
570 +
571 + // Use Notification model with object_type guard — matches NotificationService::newNotificationNumber().
572 + // Notifications are personal; return 0 when a non-admin views another member's profile.
573 + $unreadNotifications = 0;
574 + if ($isSelfOrAdmin) {
575 + $unreadNotifications = Notification::query()
576 + ->where('object_type', Constant::OBJECT_TYPE_BOARD_NOTIFICATION)
577 + ->whereHas('users', function ($q) use ($user_id) {
578 + $q->where('user_id', $user_id)->whereNull('marked_read_at');
579 + })
580 + ->count();
581 + }
582 +
583 + return [
584 + 'assigned_tasks' => (int) (clone $assignedBase)->where('status', '!=', 'closed')->count(),
585 + 'completed_tasks' => (int) (clone $assignedBase)->where('status', 'closed')->count(),
586 + 'total_boards' => (int) $boardsQuery->count(),
587 + 'unread_notifications' => (int) $unreadNotifications,
588 + ];
376 589 }
377 590 }