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
← All changes | app/Http/Controllers/UserController.php +237 -5 1.12trunk View file →
@@ -4,11 +4,14 @@
4 4
5 5 use FluentBoards\App\Models\Board;
6 6 use FluentBoards\App\Models\User;
7 7 use FluentBoards\App\Services\Helper;
8 +use FluentBoards\App\Services\PermissionManager;
8 9 use FluentBoards\App\Services\UserService;
9 10 use FluentBoards\Framework\Http\Request\Request;
11 +use FluentCrm\App\Models\Subscriber;
10 12
13 +
11 14 class UserController extends Controller
12 15 {
13 16 private UserService $userService;
14 17
@@ -27,8 +30,9 @@
27 30 }
28 31
29 32 public function memberAssociatedTaskUsers($user_id)
30 33 {
34 + $user_id = absint($user_id);
31 35 try {
32 36 $uniqueUsers = $this->userService->memberAssociatedTaskUsers($user_id);
33 37
34 38 return $this->sendSuccess([
@@ -42,9 +46,9 @@
42 46
43 47 public function searchFluentBoardsUser(Request $request)
44 48 {
45 49 try {
46 - $search_input = $request->searchInput . trim('');
50 + $search_input = $request->getSafe('searchInput', 'sanitize_text_field', '');
47 51
48 52 $boardUsers = $this->userService->searchFluentBoardsUser($search_input);
49 53
50 54 return $this->sendSuccess($boardUsers, 200);
@@ -54,10 +58,11 @@
54 58 }
55 59
56 60 public function searchMemberUser(Request $request, $user_id)
57 61 {
62 + $user_id = absint($user_id);
58 63 try {
59 - $search_input = $request->searchInput . trim('');
64 + $search_input = $request->getSafe('searchInput', 'sanitize_text_field', '');
60 65 $searchResult = $this->userService->searchMemberUser($search_input, $user_id);
61 66
62 67 return $this->sendSuccess([
63 68 'users' => $searchResult,
@@ -68,12 +73,29 @@
68 73 }
69 74
70 75 public function getMemberAssociatedTasks(Request $request, $user_id)
71 76 {
72 - $page = $request->getSafe('page');
77 + $user_id = absint($user_id);
78 + // Sanitize boardIds array
79 + $rawBoardIds = $request->getSafe('boardIds');
80 + $boardIds = [];
81 + if (is_array($rawBoardIds)) {
82 + $boardIds = array_filter(array_map('intval', $rawBoardIds));
83 + }
84 +
85 + $perPage = max(1, min(absint($request->getSafe('per_page', 'intval', 15)), 50));
86 +
87 + $requestData = [
88 + 'page' => $request->getSafe('page', 'intval', 1),
89 + 'taskType' => $request->getSafe('taskType', 'sanitize_text_field'),
90 + 'boardIds' => $boardIds,
91 + 'orderBy' => $request->getSafe('orderBy', 'sanitize_text_field'),
92 + 'order' => $request->getSafe('order', 'sanitize_text_field'),
93 + 'per_page' => $perPage
94 + ];
73 95 try {
74 96 return $this->sendSuccess(
75 - $this->userService->getMemberAssociatedTasks($user_id, $page)
97 + $this->userService->getMemberAssociatedTasks($user_id, $requestData)
76 98 , 200);
77 99 } catch (\Exception $e) {
78 100 return $this->sendError($e->getMessage(), 404);
79 101 }
@@ -78,11 +100,32 @@
78 100 return $this->sendError($e->getMessage(), 404);
79 101 }
80 102 }
81 103
104 + /**
105 + * Return profile task counts using the same categories shown on the dashboard.
106 + */
107 + public function getMemberTaskCounts(Request $request, $user_id)
108 + {
109 + $user_id = absint($user_id);
110 + $rawBoardIds = $request->getSafe('boardIds');
111 + $boardIds = is_array($rawBoardIds)
112 + ? array_filter(array_map('intval', $rawBoardIds))
113 + : [];
114 +
115 + try {
116 + return $this->sendSuccess([
117 + 'counts' => $this->userService->getMemberTaskCounts($user_id, $boardIds),
118 + ], 200);
119 + } catch (\Exception $e) {
120 + return $this->sendError($e->getMessage(), 404);
121 + }
122 + }
123 +
82 124 public function getMemberRelatedAcitivies(Request $request, $user_id)
83 125 {
84 - $page = $request->getSafe('page');
126 + $user_id = absint($user_id);
127 + $page = $request->getSafe('page', 'intval', 1);
85 128 try {
86 129 return $this->sendSuccess(
87 130 $this->userService->getMemberRelatedAcitivies($user_id, $page)
88 131 , 200);
@@ -92,13 +135,202 @@
92 135 }
93 136
94 137 public function getMemberInfo($user_id)
95 138 {
139 + if(!PermissionManager::isFluentBoardsUser($user_id)) {
140 + return $this->sendError(
141 + [
142 + 'message' => __('You are not authorized to access this resource', 'fluent-boards'),
143 + 'code' => 'fluent_boards_unauthorized',
144 + 'status' => 403
145 + ],
146 + 403
147 + );
148 + }
149 +
150 + $user_id = absint($user_id);
96 151 $user = User::findOrFail($user_id);
97 152
98 153 $user = Helper::sanitizeUserCollections($user);
99 154
155 + $user->fbs_role = PermissionManager::isFluentBoardsAdmin($user_id) ? 'fbs_admin' : 'member';
156 +
157 + $user->is_wp_admin = user_can($user_id, 'manage_options') ? 'yes' : 'no';
158 +
159 + if (defined('FLUENTCRM')) {
160 + $subscriber = Subscriber::where('user_id', $user_id)->first();
161 + $user->fluentcrm_subscriber = $subscriber ?? null;
162 + }
163 +
100 164 return [
101 165 'user' => $user
102 166 ];
103 167 }
168 +
169 + public function getMemberBoards($user_id)
170 + {
171 + $user_id = absint($user_id);
172 + try {
173 + return $this->sendSuccess(
174 + [
175 + 'boards' => $this->userService->getMemberBoards($user_id)
176 + ], 200);
177 + } catch (\Exception $e) {
178 + return $this->sendError($e->getMessage(), 404);
179 + }
180 + }
181 +
182 + public function getMemberStats($user_id)
183 + {
184 + $user_id = absint($user_id);
185 +
186 + return $this->sendSuccess(
187 + $this->userService->getMemberStats($user_id),
188 + 200
189 + );
190 + }
191 +
192 + public function updateDisplayName(Request $request, $user_id)
193 + {
194 + $user_id = absint($user_id);
195 + $currentUserId = get_current_user_id();
196 +
197 + if ($currentUserId !== $user_id && !PermissionManager::isAdmin($currentUserId)) {
198 + return $this->sendError(
199 + __('You do not have permission to update this display name', 'fluent-boards'),
200 + 403
201 + );
202 + }
203 +
204 + $displayName = $request->getSafe('display_name', 'sanitize_text_field');
205 +
206 + if(!$displayName) {
207 + return $this->sendError('Display name is required', 400);
208 + }
209 +
210 + $updateResult = wp_update_user([
211 + 'ID' => $user_id,
212 + 'display_name' => $displayName,
213 + ]);
214 +
215 + if (is_wp_error($updateResult)) {
216 + return $this->sendError(
217 + $updateResult->get_error_message(),
218 + 400
219 + );
220 + }
221 +
222 + $user = User::findOrFail($user_id);
223 + $user = Helper::sanitizeUserCollections($user);
224 + $user->fbs_role = PermissionManager::isFluentBoardsAdmin($user_id) ? 'fbs_admin' : 'member';
225 + $user->is_wp_admin = user_can($user_id, 'manage_options') ? 'yes' : 'no';
226 +
227 + if (defined('FLUENTCRM')) {
228 + $subscriber = Subscriber::where('user_id', $user_id)->first();
229 + $user->fluentcrm_subscriber = $subscriber ?? null;
230 + }
231 +
232 + return $this->sendSuccess([
233 + 'message' => __('Display name has been updated', 'fluent-boards'),
234 + 'user' => $user,
235 + ], 200);
236 +
237 + }
238 +
239 + public function updateProfilePhoto(Request $request, $user_id)
240 +{
241 + $user_id = absint($user_id);
242 + $currentUserId = get_current_user_id();
243 +
244 + // Only the user themself OR an admin can change the profile picture
245 + if ($currentUserId !== $user_id && !PermissionManager::isAdmin($currentUserId)) {
246 + return $this->sendError(
247 + __('You do not have permission to update this profile photo', 'fluent-boards'),
248 + 403
249 + );
250 + }
251 +
252 + // We’ll use native WordPress upload handling
253 + if (empty($_FILES['photo']) || !empty($_FILES['photo']['error'])) {
254 + return $this->sendError(
255 + __('No photo uploaded or upload error', 'fluent-boards'),
256 + 400
257 + );
258 + }
259 +
260 + // Limit file size to 2MB for profile photos
261 + $maxSize = 2 * 1024 * 1024;
262 + if ($_FILES['photo']['size'] > $maxSize) {
263 + return $this->sendError(
264 + __('Photo must be under 2MB', 'fluent-boards'),
265 + 400
266 + );
267 + }
268 +
269 + $file = $_FILES['photo'];
270 +
271 + // Validate MIME type server-side (client-sent type is spoofable)
272 + $fileType = wp_check_filetype($file['name'], [
273 + 'jpg|jpeg|jpe' => 'image/jpeg',
274 + 'gif' => 'image/gif',
275 + 'png' => 'image/png',
276 + 'webp' => 'image/webp',
277 + ]);
278 +
279 + if (!$fileType['type']) {
280 + return $this->sendError(
281 + __('Invalid image type', 'fluent-boards'),
282 + 400
283 + );
284 + }
285 +
286 + // Load WordPress upload helpers
287 + if (!function_exists('wp_handle_upload')) {
288 + require_once ABSPATH . 'wp-admin/includes/file.php';
289 + }
290 +
291 + $overrides = [
292 + 'test_form' => false,
293 + 'mimes' => [
294 + 'jpg|jpeg|jpe' => 'image/jpeg',
295 + 'gif' => 'image/gif',
296 + 'png' => 'image/png',
297 + 'webp' => 'image/webp',
298 + ],
299 + ];
300 +
301 + $uploaded = wp_handle_upload($file, $overrides);
302 +
303 + if (isset($uploaded['error'])) {
304 + return $this->sendError(
305 + $uploaded['error'],
306 + 400
307 + );
308 + }
309 +
310 + $photoUrl = esc_url_raw($uploaded['url']);
311 +
312 + // Store custom profile photo in user meta
313 + update_user_meta($user_id, 'fbs_profile_photo', $photoUrl);
314 +
315 + // Reload user and sanitize same as in getMemberInfo()
316 + $user = User::findOrFail($user_id);
317 + $user = Helper::sanitizeUserCollections($user);
318 +
319 + // Override photo field if your sanitizer does not already use the meta
320 + $user->photo = $photoUrl;
321 +
322 + $user->fbs_role = PermissionManager::isFluentBoardsAdmin($user_id) ? 'fbs_admin' : 'member';
323 + $user->is_wp_admin = user_can($user_id, 'manage_options') ? 'yes' : 'no';
324 +
325 + if (defined('FLUENTCRM')) {
326 + $subscriber = Subscriber::where('user_id', $user_id)->first();
327 + $user->fluentcrm_subscriber = $subscriber ?? null;
328 + }
329 +
330 + return $this->sendSuccess([
331 + 'message' => __('Profile photo has been updated', 'fluent-boards'),
332 + 'photo_url' => $photoUrl,
333 + 'user' => $user,
334 + ], 200);
335 +}
104 336 }