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/UserService.php +366 -66 2.0.0 → 2.2.0 View file →
@@ -1,8 +1,9 @@
1 1 <?php
2 2
3 3 namespace FluentBoards\App\Services;
4 4
5 +use Exception;
5 6 use FluentBoards\App\Models\Activity;
6 7 use FluentBoards\App\Models\Meta;
7 8 use FluentBoards\App\Models\Notification;
8 9 use FluentBoards\App\Models\Task;
@@ -13,8 +14,214 @@
13 14
14 15
15 16 class UserService
16 17 {
18 + /** Save only the current member's public display name. */
19 + public function updateDisplayName($userId, $displayName)
20 + {
21 + $userId = absint($userId);
22 + $this->assertCanUpdateProfile($userId);
23 + $displayName = is_string($displayName) ? trim(sanitize_text_field($displayName)) : '';
24 + if ($displayName === '') {
25 + throw new Exception(__('Display name is required', 'fluent-boards'), 400);
26 + }
27 + // WordPress treats "0" as empty and replaces it with the login name.
28 + if ($displayName === '0') {
29 + throw new Exception(__('Display name cannot be "0"', 'fluent-boards'), 400);
30 + }
31 +
32 + if (mb_strlen($displayName, 'UTF-8') > Constant::PROFILE_DISPLAY_NAME_MAX_LENGTH) {
33 + throw new Exception(__('Display name must be 250 characters or fewer', 'fluent-boards'), 400);
34 + }
35 +
36 + $expectedName = null;
37 + // Validate the final, unslashed storage value after WordPress and plugin filters.
38 + $validateStorage = function ($data, $update, $targetUserId) use ($userId, &$expectedName) {
39 + if (!$update || (int) $targetUserId !== $userId) {
40 + return $data;
41 + }
42 + $expectedName = $data['display_name'] ?? null;
43 + if (!is_string($expectedName) || $expectedName === '') {
44 + throw new Exception(__('Display name is required', 'fluent-boards'), 400);
45 + }
46 + if (mb_strlen($expectedName, 'UTF-8') > Constant::PROFILE_DISPLAY_NAME_MAX_LENGTH) {
47 + throw new Exception(__('Display name must be 250 characters or fewer', 'fluent-boards'), 400);
48 + }
49 + return $data;
50 + };
51 + add_filter('wp_pre_insert_user_data', $validateStorage, PHP_INT_MAX, 3);
52 + try {
53 + $result = wp_update_user(['ID' => $userId, 'display_name' => wp_slash($displayName)]);
54 + } finally {
55 + remove_filter('wp_pre_insert_user_data', $validateStorage, PHP_INT_MAX);
56 + }
57 + if (is_wp_error($result)) {
58 + throw new Exception($result->get_error_message(), 400);
59 + }
60 +
61 + // WordPress can return a user ID after a failed database update.
62 + clean_user_cache($userId);
63 + $user = get_userdata($userId);
64 + if (!$user) {
65 + throw new Exception(__('Could not load the updated profile', 'fluent-boards'), 400);
66 + }
67 + if ($expectedName === null || $user->display_name !== $expectedName) {
68 + throw new Exception(__('Could not save the display name. Please try again.', 'fluent-boards'), 400);
69 + }
70 + $displayName = $user->display_name;
71 + do_action('fluent_boards/profile_name_updated', $userId, $displayName);
72 + return ['display_name' => $displayName];
73 + }
74 +
75 + /** Validate and store a custom avatar without requiring media-library access. */
76 + public function updateProfilePhoto($userId, $file)
77 + {
78 + $userId = absint($userId);
79 + $this->assertCanUpdateProfile($userId);
80 + if (!is_array($file) || !isset($file['error'], $file['size'], $file['tmp_name'], $file['name'])
81 + || $file['error'] !== UPLOAD_ERR_OK || !is_string($file['tmp_name']) || !is_string($file['name'])
82 + || !is_uploaded_file($file['tmp_name'])) {
83 + throw new Exception(__('No photo uploaded or upload error', 'fluent-boards'), 400);
84 + }
85 + if (filesize($file['tmp_name']) <= 0 || filesize($file['tmp_name']) > Constant::PROFILE_PHOTO_MAX_BYTES) {
86 + throw new Exception(__('Photo must be 2 MB or smaller', 'fluent-boards'), 400);
87 + }
88 +
89 + $mimes = ['jpg|jpeg|jpe' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'webp' => 'image/webp'];
90 + // Inspect the file contents, not the browser MIME or filename alone.
91 + if (!in_array(wp_get_image_mime($file['tmp_name']), $mimes, true)) {
92 + throw new Exception(__('Choose a JPG, PNG, GIF or WebP image', 'fluent-boards'), 400);
93 + }
94 + $dimensions = wp_getimagesize($file['tmp_name']);
95 + if (!$dimensions || $dimensions[0] < 1 || $dimensions[1] < 1
96 + || max($dimensions[0], $dimensions[1]) > Constant::PROFILE_PHOTO_MAX_DIMENSION
97 + || $dimensions[0] * $dimensions[1] > Constant::PROFILE_PHOTO_MAX_PIXELS) {
98 + throw new Exception(__('Photo dimensions must not exceed 4096 pixels per side or 12 megapixels', 'fluent-boards'));
99 + }
100 + if (!function_exists('wp_handle_upload')) {
101 + require_once ABSPATH . 'wp-admin/includes/file.php';
102 + }
103 + $uploaded = wp_handle_upload($file, ['test_form' => false, 'mimes' => $mimes]);
104 + if (isset($uploaded['error'])) {
105 + throw new Exception($uploaded['error'], 400);
106 + }
107 +
108 + $uploaded = $this->createProfileAvatar($uploaded);
109 + $this->replaceProfilePhoto($userId, $uploaded);
110 + $photoUrl = esc_url_raw($uploaded['url']);
111 + do_action('fluent_boards/profile_photo_updated', $userId, $photoUrl);
112 + return ['photo' => fluent_boards_user_avatar(wp_get_current_user()->user_email)];
113 + }
114 +
115 + /** Re-encode a bounded avatar and discard the source before persisting profile changes. */
116 + private function createProfileAvatar(array $uploaded)
117 + {
118 + $directory = dirname($uploaded['file']);
119 + $filename = wp_unique_filename($directory, pathinfo($uploaded['file'], PATHINFO_FILENAME) . '-avatar.png');
120 + $avatarPath = $directory . '/' . $filename;
121 +
122 + try {
123 + $editor = wp_get_image_editor($uploaded['file']);
124 + if (is_wp_error($editor)) {
125 + throw new Exception($editor->get_error_message());
126 + }
127 + $size = $editor->get_size();
128 + if (max($size['width'], $size['height']) > Constant::PROFILE_AVATAR_SIZE) {
129 + $resized = $editor->resize(Constant::PROFILE_AVATAR_SIZE, Constant::PROFILE_AVATAR_SIZE, false);
130 + if (is_wp_error($resized)) {
131 + throw new Exception($resized->get_error_message());
132 + }
133 + }
134 + $avatar = $editor->save($avatarPath, 'image/png');
135 + if (is_wp_error($avatar)) {
136 + throw new Exception($avatar->get_error_message());
137 + }
138 +
139 + return [
140 + 'file' => $avatar['path'],
141 + 'url' => trailingslashit(dirname($uploaded['url'])) . wp_basename($avatar['path']),
142 + 'type' => $avatar['mime-type'],
143 + ];
144 + } catch (Exception $e) {
145 + wp_delete_file($avatarPath);
146 + throw $e;
147 + } finally {
148 + wp_delete_file($uploaded['file']);
149 + }
150 + }
151 +
152 + /** Serialize replacement across requests sharing the same WordPress user table. */
153 + private function replaceProfilePhoto($userId, array $uploaded)
154 + {
155 + global $wpdb;
156 +
157 + $lockName = Constant::USER_PROFILE_PHOTO_UPLOAD . ':' . md5(DB_NAME . ':' . $wpdb->users . ':' . $userId);
158 + if ((int) $wpdb->get_var($wpdb->prepare('SELECT GET_LOCK(%s, 5)', $lockName)) !== 1) {
159 + wp_delete_file($uploaded['file']);
160 + throw new Exception(__('The profile photo is being updated. Please try again.', 'fluent-boards'));
161 + }
162 +
163 + try {
164 + // A waiting request may have cached metadata from before the previous upload.
165 + wp_cache_delete($userId, 'user_meta');
166 + $this->persistProfilePhoto($userId, $uploaded);
167 + } finally {
168 + $wpdb->get_var($wpdb->prepare('SELECT RELEASE_LOCK(%s)', $lockName));
169 + }
170 + }
171 +
172 + /** Track uploads we own; legacy or externally supplied avatar URLs never authorize deletion. */
173 + private function persistProfilePhoto($userId, array $uploaded)
174 + {
175 + $previousUrl = get_user_meta($userId, Constant::USER_PROFILE_PHOTO, true);
176 + $previousUpload = get_user_meta($userId, Constant::USER_PROFILE_PHOTO_UPLOAD, true);
177 + $ownedUpload = [
178 + 'file' => $uploaded['file'],
179 + 'url' => esc_url_raw($uploaded['url']),
180 + 'blog_id' => get_current_blog_id(),
181 + ];
182 +
183 + if (!update_user_meta($userId, Constant::USER_PROFILE_PHOTO_UPLOAD, $ownedUpload)) {
184 + wp_delete_file($uploaded['file']);
185 + throw new Exception(__('The profile photo could not be saved. Please try again.', 'fluent-boards'));
186 + }
187 + if (!update_user_meta($userId, Constant::USER_PROFILE_PHOTO, $ownedUpload['url'])) {
188 + if ($previousUpload) {
189 + update_user_meta($userId, Constant::USER_PROFILE_PHOTO_UPLOAD, $previousUpload);
190 + } else {
191 + delete_user_meta($userId, Constant::USER_PROFILE_PHOTO_UPLOAD);
192 + }
193 + wp_delete_file($uploaded['file']);
194 + throw new Exception(__('The profile photo could not be saved. Please try again.', 'fluent-boards'));
195 + }
196 +
197 + // Only a matching, locally tracked upload can be retired after both writes succeed.
198 + if (!is_array($previousUpload)
199 + || ($previousUpload['url'] ?? null) !== $previousUrl
200 + || ($previousUpload['blog_id'] ?? null) !== get_current_blog_id()
201 + || !is_string($previousUpload['file'] ?? null)
202 + || $previousUpload['file'] === $ownedUpload['file']) {
203 + return;
204 + }
205 +
206 + $uploads = wp_get_upload_dir();
207 + $previousFile = realpath($previousUpload['file']);
208 + $uploadsDirectory = realpath($uploads['basedir']);
209 + if (!$uploads['error'] && $previousFile && $uploadsDirectory
210 + && strpos(wp_normalize_path($previousFile), trailingslashit(wp_normalize_path($uploadsDirectory))) === 0
211 + && is_file($previousFile)) {
212 + wp_delete_file($previousFile);
213 + }
214 + }
215 +
216 + /** Profile editing requires Fluent Boards membership and remains self-service for administrators. */
217 + private function assertCanUpdateProfile($userId)
218 + {
219 + if (!$userId || $userId !== get_current_user_id() || !PermissionManager::isFluentBoardsUser($userId)) {
220 + throw new Exception(__('You can only update your own profile', 'fluent-boards'), 403);
221 + }
222 + }
223 +
17 224 public function allFluentBoardsUsers($boardId = null)
18 225 {
19 226 // Get the global admins first
20 227 $adminUserIds = Meta::query()->where('object_type', Constant::FLUENT_BOARD_ADMIN)
@@ -108,20 +315,46 @@
108 315
109 316 return $boardUsers;
110 317 }
111 318
319 + /**
320 + * Return associated users and roles only from boards shared with the requester.
321 + */
112 322 public function memberAssociatedTaskUsers($userId)
113 323 {
114 324 $user = User::find($userId);
115 - $boards = $user->whichBoards->pluck('id');
116 - $boardUsers = Relation::whereIn('object_id', $boards)
325 + $memberBoardIds = array_values(array_unique(array_filter(array_map(
326 + 'intval',
327 + $user->whichBoards->pluck('id')->toArray()
328 + ))));
329 + $requesterBoardIds = array_values(array_unique(array_filter(array_map(
330 + 'intval',
331 + PermissionManager::getBoardIdsForUser(get_current_user_id())
332 + ))));
333 + $sharedBoardIds = array_values(array_intersect($memberBoardIds, $requesterBoardIds));
334 +
335 + if (!$sharedBoardIds) {
336 + return [
337 + 'uniqueUsers' => [],
338 + 'userWiseBoardDesignation' => [],
339 + ];
340 + }
341 +
342 + $boardUsers = Relation::whereIn('object_id', $sharedBoardIds)
117 343 ->where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
118 344 ->pluck('foreign_id')->toArray();
119 345 $uniqueUsersIds = array_unique($boardUsers);
120 - $uniqueUsers = User::whereIn('ID', $uniqueUsersIds)->with('whichBoards')->get();
346 + $boardTable = (new Board())->getTable();
347 + $uniqueUsers = User::whereIn('ID', $uniqueUsersIds)
348 + ->with(['whichBoards' => function ($query) use ($boardTable, $sharedBoardIds) {
349 + $query->whereIn($boardTable . '.id', $sharedBoardIds);
350 + }])
351 + ->get();
121 352
122 353 $userWiseBoardDesignation = Relation::query()->whereIn('foreign_id', $uniqueUsersIds)
123 - ->where('object_type', Constant::OBJECT_TYPE_BOARD_USER)->get();
354 + ->whereIn('object_id', $sharedBoardIds)
355 + ->where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
356 + ->get();
124 357
125 358 $data = array();
126 359 $data['userWiseBoardDesignation'] = $userWiseBoardDesignation;
127 360
@@ -135,9 +368,9 @@
135 368 } elseif (PermissionManager::isFluentBoardsAdmin($uniqueUser['ID'])) {
136 369 $uniqueUser['is_super'] = true;
137 370 $uniqueUser['is_wpadmin'] = false;
138 371 } else {
139 - $uniqueUser['all_boards'] = Arr::get($uniqueUser, 'boards');
372 + $uniqueUser['all_boards'] = $uniqueUser->getRelation('whichBoards');
140 373 $uniqueUser['is_super'] = false;
141 374 $uniqueUser['is_wpadmin'] = false;
142 375 }
143 376 }
@@ -220,10 +453,10 @@
220 453 $taskType = $requestData['taskType'] ?? null;
221 454 $boardIds = !empty($requestData['boardIds']) ? $requestData['boardIds'] : [];
222 455 $orderBy = $requestData['orderBy'] ?? 'created_at';
223 456 $order = strtoupper($requestData['order'] ?? 'ASC');
224 - $per_page = $requestData['per_page'] ?? 15;
225 - $page = $requestData['page'] ?? 1;
457 + $per_page = Sanitize::perPage($requestData['per_page'] ?? 15, 50);
458 + $page = max(1, (int) ($requestData['page'] ?? 1));
226 459 $user = User::find($user_id);
227 460
228 461 $loggedInUserId = get_current_user_id();
229 462
@@ -243,42 +476,55 @@
243 476
244 477 // Table view needs the labels column; list view ignores the extra relation.
245 478 $taskRelations = ['stage', 'board', 'labels'];
246 479
247 - if($taskType == 'assigned') {
248 - $tasksQuery = $user->assignedTasks()->with($taskRelations)->whereNull('archived_at')->whereNull('parent_id')->whereIn('board_id', $allowedBoardIds);
249 - } else if($taskType == 'mentioned') {
250 - $tasksQuery = $user->mentionedTasks()->with($taskRelations)->whereNull('archived_at')->whereNull('parent_id')->whereIn('board_id', $allowedBoardIds);
480 + if ($taskType == 'assigned') {
481 + $tasksQuery = $user->assignedTasks()
482 + ->with($taskRelations)
483 + ->whereNull('archived_at')
484 + ->whereNull('parent_id')
485 + ->whereIn('board_id', $allowedBoardIds)
486 + ->where('status', '!=', 'closed')
487 + ->onActiveAvailableBoards();
488 + } else if ($taskType == 'mentioned') {
489 + $tasksQuery = $user->mentionedTasks()
490 + ->with($taskRelations)
491 + ->whereNull('archived_at')
492 + ->whereNull('parent_id')
493 + ->whereIn('board_id', $allowedBoardIds)
494 + ->onActiveAvailableBoards();
251 495 } else {
252 - // Get the task assigned to the user
253 - $tasksQuery = $user->tasks()->with($taskRelations)->whereNull('archived_at') ->whereIn('board_id', $allowedBoardIds);
496 + // Watched tasks power the date-based and completed profile tabs.
497 + $tasksQuery = $user->tasks()
498 + ->with($taskRelations)
499 + ->whereNull('archived_at')
500 + ->whereIn('board_id', $allowedBoardIds)
501 + ->onActiveAvailableBoards();
254 502
255 - switch ($taskType) {
256 - case 'upcoming':
257 - $tasksQuery->upcoming()
258 - ->whereIn('board_id', $allowedBoardIds);
259 - break;
260 - case 'due_today':
261 - $tasksQuery->dueToday()
262 - ->whereIn('board_id', $allowedBoardIds);
263 - break;
264 - case 'overdue':
265 - $tasksQuery->overdue()
266 - ->whereIn('board_id', $allowedBoardIds);
267 - break;
268 - case 'completed':
269 - $tasksQuery->where('status', 'closed')
270 - ->whereIn('board_id', $allowedBoardIds);
271 - break;
272 - default:
273 - $tasksQuery->whereNull('due_at')
274 - ->whereIn('board_id', $allowedBoardIds);
275 - break;
503 + switch ($taskType) {
504 + case 'upcoming':
505 + $tasksQuery->upcoming()
506 + ->whereIn('board_id', $allowedBoardIds);
507 + break;
508 + case 'due_today':
509 + $tasksQuery->dueToday()
510 + ->whereIn('board_id', $allowedBoardIds);
511 + break;
512 + case 'overdue':
513 + $tasksQuery->overdue()
514 + ->whereIn('board_id', $allowedBoardIds);
515 + break;
516 + case 'completed':
517 + $tasksQuery->where('status', 'closed')
518 + ->whereIn('board_id', $allowedBoardIds);
519 + break;
520 + default:
521 + $tasksQuery->whereNull('due_at')
522 + ->whereIn('board_id', $allowedBoardIds);
523 + break;
524 + }
276 525 }
277 - }
278 526
279 -
280 -
281 527 $currentUserId = get_current_user_id();
282 528 if ($currentUserId != $user->ID && !PermissionManager::isAdmin()) {
283 529 $currentUser = User::find($currentUserId);
284 530 $currentUserBoardIds = $currentUser->boards->pluck('id')->toArray();
@@ -352,9 +598,10 @@
352 598 // Keep the count queries aligned with the profile list without loading task models or relations.
353 599 $applyTaskScope = function ($query) use ($allowedBoardIds, $boardIds) {
354 600 $query->whereNull('archived_at')
355 601 ->whereNull('parent_id')
356 - ->whereIn('board_id', $allowedBoardIds);
602 + ->whereIn('board_id', $allowedBoardIds)
603 + ->onActiveAvailableBoards();
357 604
358 605 if (!empty($boardIds)) {
359 606 $query->whereIn('board_id', $boardIds);
360 607 }
@@ -366,9 +613,11 @@
366 613 };
367 614
368 615 return [
369 616 'due_today' => (int) $watchedTasks()->dueToday()->count(),
370 - 'assigned' => (int) $applyTaskScope($user->assignedTasks())->count(),
617 + 'assigned' => (int) $applyTaskScope($user->assignedTasks())
618 + ->where('status', '!=', 'closed')
619 + ->count(),
371 620 'upcoming' => (int) $watchedTasks()->upcoming()->count(),
372 621 'overdue' => (int) $watchedTasks()->overdue()->count(),
373 622 'mentioned' => (int) $applyTaskScope($user->mentionedTasks())->count(),
374 623 'completed' => (int) $watchedTasks()->where('status', 'closed')->count(),
@@ -376,38 +625,91 @@
376 625 ];
377 626 }
378 627
379 628
629 + /**
630 + * Get a member's activities scoped to boards the requesting user can access.
631 + */
380 632 public function getMemberRelatedAcitivies($user_id, $page)
381 633 {
382 - $activities = Activity::query()->where('created_by', $user_id)
634 + $perPage = 40;
635 + $user_id = absint($user_id);
636 + $page = max(1, absint($page));
637 + $allowedBoardIds = array_values(array_filter(array_map(
638 + 'intval',
639 + PermissionManager::getBoardIdsForUser(get_current_user_id())
640 + )));
641 +
642 + if (empty($allowedBoardIds)) {
643 + return [
644 + 'activities' => [],
645 + 'pagination' => $this->getEmptyPaginationInfo($page, $perPage),
646 + ];
647 + }
648 +
649 + $allowedTaskIds = Task::query()
650 + ->select('id')
651 + ->whereIn('board_id', $allowedBoardIds);
652 +
653 + $activities = Activity::query()
654 + ->where('created_by', $user_id)
655 + ->where(function ($query) use ($allowedBoardIds, $allowedTaskIds) {
656 + $query->where(function ($boardQuery) use ($allowedBoardIds) {
657 + $boardQuery->where('object_type', Constant::ACTIVITY_BOARD)
658 + ->whereIn('object_id', $allowedBoardIds);
659 + })->orWhere(function ($taskQuery) use ($allowedTaskIds) {
660 + $taskQuery->where('object_type', Constant::ACTIVITY_TASK)
661 + ->whereIn('object_id', $allowedTaskIds);
662 + });
663 + })
383 664 ->orderBy('created_at', 'desc')
384 - ->with('user')->paginate(40, ['*'], 'page', $page);
665 + ->with('user')
666 + ->paginate($perPage, ['*'], 'page', $page);
385 667
386 - $activitiesToShow = array();
668 + $activitiesToShow = [];
387 669
388 670 foreach ($activities as $activity) {
389 671 if ($activity->object_type == Constant::ACTIVITY_BOARD) {
390 672 $activity->load('board');
391 -// if($activity->settings && $activity->settings['task_id']){
392 -// $activity->task = Task::findOrFail($activity->settings['task_id']);
393 -// }
394 - if (PermissionManager::userHasPermission($activity->board_id, get_current_user_id())) {
395 - $activitiesToShow[] = $activity;
396 - }
673 + $activitiesToShow[] = $activity;
397 674 } elseif ($activity->object_type == Constant::ACTIVITY_TASK) {
398 675 $activity->load('task');
399 - if ($activity->task && PermissionManager::userHasPermission($activity->task->board_id, get_current_user_id())) {
400 - $activitiesToShow[] = $activity;
401 - }
676 + $activitiesToShow[] = $activity;
402 677 }
403 678 }
679 +
404 680 return [
405 - 'activities' => $activitiesToShow,
406 - 'pagination' => $activities->toArray(),
681 + 'activities' => $activitiesToShow,
682 + 'pagination' => $this->getPaginationInfo($activities),
407 683 ];
408 684 }
409 685
686 + /**
687 + * Return pagination metadata without duplicating serialized row data.
688 + */
689 + private function getPaginationInfo($paginator)
690 + {
691 + return [
692 + 'current_page' => $paginator->currentPage(),
693 + 'last_page' => $paginator->lastPage(),
694 + 'per_page' => (int) $paginator->perPage(),
695 + 'total' => $paginator->total(),
696 + ];
697 + }
698 +
699 + /**
700 + * Return an empty pagination payload for callers without accessible boards.
701 + */
702 + private function getEmptyPaginationInfo($page, $perPage)
703 + {
704 + return [
705 + 'current_page' => max(1, (int) $page),
706 + 'last_page' => 1,
707 + 'per_page' => (int) $perPage,
708 + 'total' => 0,
709 + ];
710 + }
711 +
410 712 private function getTaskById($taskId)
411 713 {
412 714 return Task::findOrFail($taskId);
413 715 }
@@ -416,25 +718,23 @@
416 718 {
417 719 if(!$user_id) {
418 720 return [];
419 721 }
420 - $boardIds = [];
421 - $boards = [];
422 722 $user = User::find($user_id);
423 723 $currentUserId = get_current_user_id();
424 - if($currentUserId != $user->ID) {
425 - if(!PermissionManager::isAdmin()){
426 - $currentUser = User::find($currentUserId);
427 - $currentUserBoardIds = $currentUser->whichBoards->pluck('id')->toArray();
428 - $boardIds = $currentUserBoardIds;
429 - }
724 +
725 + if ($currentUserId == $user->ID || PermissionManager::isAdmin($currentUserId)) {
726 + return $user->whichBoards;
430 727 }
431 - if (!empty($boardIds)) {
432 - $boards = $user->whichBoards()->whereIn('fbs_boards.id', $boardIds)->get();
433 - } else {
434 - $boards = $user->whichBoards;
728 +
729 + $currentUser = User::find($currentUserId);
730 + $boardIds = $currentUser ? $currentUser->whichBoards->pluck('id')->toArray() : [];
731 +
732 + if (empty($boardIds)) {
733 + return [];
435 734 }
436 - return $boards;
735 +
736 + return $user->whichBoards()->whereIn('fbs_boards.id', $boardIds)->get();
437 737 }
438 738
439 739 /**
440 740 * Returns four aggregate counts for a member's stats widget.