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 / UserService.php

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

591 lines 21.7 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\Activity;
6 use FluentBoards\App\Models\Meta;
7 use FluentBoards\App\Models\Notification;
8 use FluentBoards\App\Models\Task;
9 use FluentBoards\App\Models\User;
10 use FluentBoards\App\Models\Board;
11 use FluentBoards\App\Models\Relation;
12 use FluentBoards\Framework\Support\Arr;
13
14
15 class UserService
16 {
17 public function allFluentBoardsUsers($boardId = null)
18 {
19 // Get the global admins first
20 $adminUserIds = Meta::query()->where('object_type', Constant::FLUENT_BOARD_ADMIN)
21 ->get()->pluck('object_id')->toArray();
22
23 $boardObjects = Relation::where('object_type', 'board_user');
24
25 if ($adminUserIds) {
26 $boardObjects = $boardObjects->whereNotIn('foreign_id', $adminUserIds);
27 }
28
29 if ($boardId) {
30 $boardObjects = $boardObjects->where('object_id', $boardId);
31 }
32
33 $boardObjects = $boardObjects->whereNotIn('foreign_id', $adminUserIds)
34 ->get();
35
36 $boardUserMaps = [];
37
38 $accessBoardIds = [];
39
40 foreach ($boardObjects as $boardObject) {
41 if (!isset($boardUserMaps[$boardObject->foreign_id])) {
42 $boardUserMaps[$boardObject->foreign_id] = [];
43 }
44
45 $accessBoardIds[] = $boardObject->object_id;
46
47 $boardUserMaps[$boardObject->foreign_id][] = [
48 'board_id' => $boardObject->object_id,
49 'role' => Arr::get($boardObject->settings, 'is_admin')
50 ? 'admin'
51 : (Arr::has($boardObject->settings, 'is_viewer_only') && Arr::get($boardObject->settings, 'is_viewer_only')
52 ? 'viewer'
53 : 'member')
54 ];
55 }
56
57 $accessBoardIds = array_unique($accessBoardIds);
58 $allUserIds = array_unique(array_merge($adminUserIds, array_keys($boardUserMaps)));
59
60 $users = get_users([
61 'include' => $allUserIds
62 ]);
63
64 $boardUsers = [];
65 $allBoards = Board::query()->whereIn('id', $accessBoardIds)->get()->keyBy('id');
66
67 foreach ($users as $user) {
68
69 $boards = Arr::get($boardUserMaps, $user->ID, []);
70
71 $formattedBoars = [];
72
73 foreach ($boards as $board) {
74 if (empty($allBoards[$board['board_id']])) {
75 continue;
76 }
77
78 $boardModel = $allBoards[$board['board_id']];
79 $formattedBoars[] = [
80 'id' => $boardModel->id,
81 'title' => $boardModel->title,
82 'role' => $board['role']
83 ];
84 }
85
86 $name = trim($user->first_name . ' ' . $user->last_name);
87
88 if (!$name) {
89 $name = $user->display_name;
90 }
91
92 $photo = fluent_boards_user_avatar($user->user_email, $name);
93
94 $boardUsers[] = [
95 'ID' => $user->ID,
96 'display_name' => $user->display_name,
97 'photo' => $photo,
98 'email' => $user->user_email,
99 'boards' => $formattedBoars,
100 'is_super' => in_array($user->ID, $adminUserIds),
101 'is_wpadmin' => $user->has_cap('manage_options')
102 ];
103 }
104
105 usort($boardUsers, function ($a, $b) {
106 return strcmp($a['display_name'], $b['display_name']);
107 });
108
109 return $boardUsers;
110 }
111
112 /**
113 * Return associated users and roles only from boards shared with the requester.
114 */
115 public function memberAssociatedTaskUsers($userId)
116 {
117 $user = User::find($userId);
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)
136 ->where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
137 ->pluck('foreign_id')->toArray();
138 $uniqueUsersIds = array_unique($boardUsers);
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();
145
146 $userWiseBoardDesignation = Relation::query()->whereIn('foreign_id', $uniqueUsersIds)
147 ->whereIn('object_id', $sharedBoardIds)
148 ->where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
149 ->get();
150
151 $data = array();
152 $data['userWiseBoardDesignation'] = $userWiseBoardDesignation;
153
154 foreach ($uniqueUsers as &$uniqueUser) {
155 if (user_can($uniqueUser['ID'], 'manage_options') && PermissionManager::isFluentBoardsAdmin($uniqueUser['ID'])) {
156 $uniqueUser['is_super'] = true;
157 $uniqueUser['is_wpadmin'] = true;
158 } elseif (user_can($uniqueUser['ID'], 'manage_options')) {
159 $uniqueUser['is_wpadmin'] = true;
160 $uniqueUser['is_super'] = false;
161 } elseif (PermissionManager::isFluentBoardsAdmin($uniqueUser['ID'])) {
162 $uniqueUser['is_super'] = true;
163 $uniqueUser['is_wpadmin'] = false;
164 } else {
165 $uniqueUser['all_boards'] = $uniqueUser->getRelation('whichBoards');
166 $uniqueUser['is_super'] = false;
167 $uniqueUser['is_wpadmin'] = false;
168 }
169 }
170
171 $data['uniqueUsers'] = $uniqueUsers;
172
173 return $data;
174 }
175
176 public function searchFluentBoardsUser($search_input)
177 {
178
179 $boardUsers = User::whereHas('whichBoards', function ($query) use ($search_input) {
180 $query->where('display_name', 'like', '%' . $search_input . '%');
181 })
182 ->where('display_name', 'like', '%' . $search_input . '%')
183 ->with('whichBoards')
184 ->get()
185 ->toArray();
186
187 foreach ($boardUsers as &$boardUser) {
188 if (user_can($boardUser['ID'], 'manage_options') && PermissionManager::isFluentBoardsAdmin($boardUser['ID'])) {
189 $boardUser['is_super'] = true;
190 $boardUser['is_wpadmin'] = true;
191 } elseif (user_can($boardUser['ID'], 'manage_options')) {
192 $boardUser['is_wpadmin'] = true;
193 $boardUser['is_super'] = false;
194 } elseif (PermissionManager::isFluentBoardsAdmin($boardUser['ID'])) {
195 $boardUser['is_super'] = true;
196 $boardUser['is_wpadmin'] = false;
197 } else {
198 $boardUser['all_boards'] = Arr::get($boardUser, 'boards');
199 $boardUser['is_super'] = false;
200 $boardUser['is_wpadmin'] = false;
201 }
202 }
203
204 return $boardUsers;
205 }
206
207 public function searchMemberUser($search_input, $userId)
208 {
209 $user = User::find($userId);
210 $boards = $user->boards->pluck('id');
211 $boardUsers = Relation::whereIn('board_id', $boards)->orWhere('board_id', null)->where('status', 'ACTIVE')->pluck('user_id')->toArray();
212 $uniqueUsersIds = array_unique($boardUsers);
213 // $uniqueUsers = User::whereIn('ID', $uniqueUsersIds)->with('boards')->get();
214
215 $searchResult = User::query()->whereIn('ID', $uniqueUsersIds)->with('boards')
216 ->where('display_name', 'like', '%' . $search_input . '%')
217 ->take(20)->get();
218
219 foreach ($searchResult as &$uniqueUser) {
220 if (PermissionManager::isAdmin($uniqueUser['ID'])) {
221 $uniqueUser['all_boards'] = Board::query()->where('is_archived', 0)->get();
222 $isFluentBoardsAdmin = Relation::where('user_id', $uniqueUser['ID'])
223 ->whereNull('board_id')
224 ->where('status', 'ACTIVE')
225 ->first();
226 if ($isFluentBoardsAdmin) {
227 $uniqueUser['is_super'] = true;
228 $uniqueUser['is_wpadmin'] = false;
229 } else {
230 $uniqueUser['is_super'] = false;
231 $uniqueUser['is_wpadmin'] = true;
232 }
233 } else {
234 $uniqueUser['all_boards'] = $uniqueUser['boards'];
235 $uniqueUser['is_super'] = false;
236 $uniqueUser['is_wpadmin'] = false;
237 }
238 }
239
240 return $searchResult;
241 }
242
243
244 public function getMemberAssociatedTasks($user_id, $requestData)
245 {
246 $taskType = $requestData['taskType'] ?? null;
247 $boardIds = !empty($requestData['boardIds']) ? $requestData['boardIds'] : [];
248 $orderBy = $requestData['orderBy'] ?? 'created_at';
249 $order = strtoupper($requestData['order'] ?? 'ASC');
250 $per_page = $requestData['per_page'] ?? 15;
251 $page = $requestData['page'] ?? 1;
252 $user = User::find($user_id);
253
254 $loggedInUserId = get_current_user_id();
255
256 $allowedBoardIds = PermissionManager::getBoardIdsForUser($loggedInUserId);
257
258 if (!$user || empty($allowedBoardIds)) {
259 return [
260 'tasks' => [],
261 'paginationInfo' => [
262 'current_page' => 1,
263 'last_page' => 1,
264 'per_page' => (int) $per_page,
265 'total' => 0,
266 ],
267 ];
268 }
269
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();
288 } else {
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();
295
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 }
318 }
319
320 $currentUserId = get_current_user_id();
321 if ($currentUserId != $user->ID && !PermissionManager::isAdmin()) {
322 $currentUser = User::find($currentUserId);
323 $currentUserBoardIds = $currentUser->boards->pluck('id')->toArray();
324
325 if (empty($boardIds)) {
326 $boardIds = $currentUserBoardIds;
327 } else {
328 // Get the intersection of the two arrays
329 $boardIds = array_intersect($boardIds, $currentUserBoardIds);
330 }
331 }
332
333 if (!empty($boardIds)) {
334 $tasksQuery->whereIn('board_id', $boardIds);
335 }
336
337 $sortOptions = ['priority', 'due_at', 'position', 'created_at', 'title'];
338 $orderOptions = ['ASC', 'DESC'];
339
340 // Validate order and orderBy parameters
341 if (!in_array($order, $orderOptions) || !in_array($orderBy, $sortOptions)) {
342 throw new \Exception(esc_html__('Invalid sort or orderBy parameter', 'fluent-boards'));
343 }
344
345 // Apply ordering based on the specified order and orderBy
346 if ($orderBy === 'priority') {
347 $tasksQuery->orderByRaw("FIELD(priority, 'urgent', 'high', 'medium', 'low') {$order}");
348 } else if ($orderBy === 'due_at') {
349 $tasksQuery->orderByRaw("ISNULL(due_at), due_at {$order}");
350 } else {
351 $tasksQuery->orderBy($orderBy, $order);
352 }
353
354 $tasks = $tasksQuery->paginate($per_page, ['*'], 'page', $page);
355
356 return [
357 'tasks' => $tasks->values()->toArray(),
358 'paginationInfo' => [
359 'current_page' => $tasks->currentPage(),
360 'last_page' => $tasks->lastPage(),
361 'per_page' => (int) $tasks->perPage(),
362 'total' => $tasks->total(),
363 ],
364 ];
365 }
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);
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 */
425 public function getMemberRelatedAcitivies($user_id, $page)
426 {
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 })
457 ->orderBy('created_at', 'desc')
458 ->with('user')
459 ->paginate($perPage, ['*'], 'page', $page);
460
461 $activitiesToShow = [];
462
463 foreach ($activities as $activity) {
464 if ($activity->object_type == Constant::ACTIVITY_BOARD) {
465 $activity->load('board');
466 $activitiesToShow[] = $activity;
467 } elseif ($activity->object_type == Constant::ACTIVITY_TASK) {
468 $activity->load('task');
469 $activitiesToShow[] = $activity;
470 }
471 }
472
473 return [
474 'activities' => $activitiesToShow,
475 'pagination' => $this->getPaginationInfo($activities),
476 ];
477 }
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
505 private function getTaskById($taskId)
506 {
507 return Task::findOrFail($taskId);
508 }
509
510 public function getMemberBoards($user_id)
511 {
512 if(!$user_id) {
513 return [];
514 }
515 $user = User::find($user_id);
516 $currentUserId = get_current_user_id();
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;
558 }
559 }
560
561 $assignedBase = $user->assignedTasks()->whereNull('archived_at')->whereNull('parent_id');
562 if ($allowedBoardIds !== null) {
563 $assignedBase->whereIn('board_id', $allowedBoardIds);
564 }
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 ];
589 }
590 }
591