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 -62 1.952.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,47 +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 'due_today':
256 - $tasksQuery->dueToday()
257 - ->whereIn('board_id', $allowedBoardIds);
258 - break;
259 - case 'overdue':
260 - $tasksQuery->overdue()
261 - ->whereIn('board_id', $allowedBoardIds);
262 - break;
263 - case 'completed':
264 - $tasksQuery->where('status', 'closed')
265 - ->whereIn('board_id', $allowedBoardIds);
266 - break;
267 - default:
268 - $tasksQuery->whereNull('due_at')
269 - ->whereIn('board_id', $allowedBoardIds);
270 - 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 + }
271 318 }
272 - }
273 319
274 -
275 -
276 320 $currentUserId = get_current_user_id();
277 321 if ($currentUserId != $user->ID && !PermissionManager::isAdmin()) {
278 322 $currentUser = User::find($currentUserId);
279 323 $currentUserBoardIds = $currentUser->boards->pluck('id')->toArray();
@@ -299,9 +343,9 @@
299 343 }
300 344
301 345 // Apply ordering based on the specified order and orderBy
302 346 if ($orderBy === 'priority') {
303 - $tasksQuery->orderByRaw("FIELD(priority, 'High', 'Medium', 'Low') {$order}");
347 + $tasksQuery->orderByRaw("FIELD(priority, 'urgent', 'high', 'medium', 'low') {$order}");
304 348 } else if ($orderBy === 'due_at') {
305 349 $tasksQuery->orderByRaw("ISNULL(due_at), due_at {$order}");
306 350 } else {
307 351 $tasksQuery->orderBy($orderBy, $order);
@@ -313,44 +357,152 @@
313 357 'tasks' => $tasks->values()->toArray(),
314 358 'paginationInfo' => [
315 359 'current_page' => $tasks->currentPage(),
316 360 'last_page' => $tasks->lastPage(),
361 + 'per_page' => (int) $tasks->perPage(),
317 362 'total' => $tasks->total(),
318 363 ],
319 364 ];
320 365 }
321 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);
322 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 + */
323 425 public function getMemberRelatedAcitivies($user_id, $page)
324 426 {
325 - $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 + })
326 457 ->orderBy('created_at', 'desc')
327 - ->with('user')->paginate(40, ['*'], 'page', $page);
458 + ->with('user')
459 + ->paginate($perPage, ['*'], 'page', $page);
328 460
329 - $activitiesToShow = array();
461 + $activitiesToShow = [];
330 462
331 463 foreach ($activities as $activity) {
332 464 if ($activity->object_type == Constant::ACTIVITY_BOARD) {
333 465 $activity->load('board');
334 -// if($activity->settings && $activity->settings['task_id']){
335 -// $activity->task = Task::findOrFail($activity->settings['task_id']);
336 -// }
337 - if (PermissionManager::userHasPermission($activity->board_id, get_current_user_id())) {
338 - $activitiesToShow[] = $activity;
339 - }
466 + $activitiesToShow[] = $activity;
340 467 } elseif ($activity->object_type == Constant::ACTIVITY_TASK) {
341 468 $activity->load('task');
342 - if ($activity->task && PermissionManager::userHasPermission($activity->task->board_id, get_current_user_id())) {
343 - $activitiesToShow[] = $activity;
344 - }
469 + $activitiesToShow[] = $activity;
345 470 }
346 471 }
472 +
347 473 return [
348 - 'activities' => $activitiesToShow,
349 - 'pagination' => $activities->toArray(),
474 + 'activities' => $activitiesToShow,
475 + 'pagination' => $this->getPaginationInfo($activities),
350 476 ];
351 477 }
352 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 +
353 505 private function getTaskById($taskId)
354 506 {
355 507 return Task::findOrFail($taskId);
356 508 }
@@ -359,23 +511,80 @@
359 511 {
360 512 if(!$user_id) {
361 513 return [];
362 514 }
363 - $boardIds = [];
364 - $boards = [];
365 515 $user = User::find($user_id);
366 516 $currentUserId = get_current_user_id();
367 - if($currentUserId != $user->ID) {
368 - if(!PermissionManager::isAdmin()){
369 - $currentUser = User::find($currentUserId);
370 - $currentUserBoardIds = $currentUser->whichBoards->pluck('id')->toArray();
371 - $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;
372 558 }
373 559 }
374 - if (!empty($boardIds)) {
375 - $boards = $user->whichBoards()->whereIn('fbs_boards.id', $boardIds)->get();
376 - } else {
377 - $boards = $user->whichBoards;
560 +
561 + $assignedBase = $user->assignedTasks()->whereNull('archived_at')->whereNull('parent_id');
562 + if ($allowedBoardIds !== null) {
563 + $assignedBase->whereIn('board_id', $allowedBoardIds);
378 564 }
379 - 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 + ];
380 589 }
381 590 }