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 +207 -5 1.22trunk View file →
@@ -30,8 +30,9 @@
30 30 }
31 31
32 32 public function memberAssociatedTaskUsers($user_id)
33 33 {
34 + $user_id = absint($user_id);
34 35 try {
35 36 $uniqueUsers = $this->userService->memberAssociatedTaskUsers($user_id);
36 37
37 38 return $this->sendSuccess([
@@ -45,9 +46,9 @@
45 46
46 47 public function searchFluentBoardsUser(Request $request)
47 48 {
48 49 try {
49 - $search_input = $request->searchInput . trim('');
50 + $search_input = $request->getSafe('searchInput', 'sanitize_text_field', '');
50 51
51 52 $boardUsers = $this->userService->searchFluentBoardsUser($search_input);
52 53
53 54 return $this->sendSuccess($boardUsers, 200);
@@ -57,10 +58,11 @@
57 58 }
58 59
59 60 public function searchMemberUser(Request $request, $user_id)
60 61 {
62 + $user_id = absint($user_id);
61 63 try {
62 - $search_input = $request->searchInput . trim('');
64 + $search_input = $request->getSafe('searchInput', 'sanitize_text_field', '');
63 65 $searchResult = $this->userService->searchMemberUser($search_input, $user_id);
64 66
65 67 return $this->sendSuccess([
66 68 'users' => $searchResult,
@@ -71,14 +73,25 @@
71 73 }
72 74
73 75 public function getMemberAssociatedTasks(Request $request, $user_id)
74 76 {
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 +
75 87 $requestData = [
76 - 'page' =>$request->getSafe('page', 'intval'),
88 + 'page' => $request->getSafe('page', 'intval', 1),
77 89 'taskType' => $request->getSafe('taskType', 'sanitize_text_field'),
78 - 'boardIds' => $boardIds = $request->getSafe('boardIds'),
90 + 'boardIds' => $boardIds,
79 91 'orderBy' => $request->getSafe('orderBy', 'sanitize_text_field'),
80 92 'order' => $request->getSafe('order', 'sanitize_text_field'),
93 + 'per_page' => $perPage
81 94 ];
82 95 try {
83 96 return $this->sendSuccess(
84 97 $this->userService->getMemberAssociatedTasks($user_id, $requestData)
@@ -87,11 +100,32 @@
87 100 return $this->sendError($e->getMessage(), 404);
88 101 }
89 102 }
90 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 +
91 124 public function getMemberRelatedAcitivies(Request $request, $user_id)
92 125 {
93 - $page = $request->getSafe('page');
126 + $user_id = absint($user_id);
127 + $page = $request->getSafe('page', 'intval', 1);
94 128 try {
95 129 return $this->sendSuccess(
96 130 $this->userService->getMemberRelatedAcitivies($user_id, $page)
97 131 , 200);
@@ -101,8 +135,20 @@
101 135 }
102 136
103 137 public function getMemberInfo($user_id)
104 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);
105 151 $user = User::findOrFail($user_id);
106 152
107 153 $user = Helper::sanitizeUserCollections($user);
108 154
@@ -121,8 +167,9 @@
121 167 }
122 168
123 169 public function getMemberBoards($user_id)
124 170 {
171 + $user_id = absint($user_id);
125 172 try {
126 173 return $this->sendSuccess(
127 174 [
128 175 'boards' => $this->userService->getMemberBoards($user_id)
@@ -130,5 +177,160 @@
130 177 } catch (\Exception $e) {
131 178 return $this->sendError($e->getMessage(), 404);
132 179 }
133 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 +}
134 336 }