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.1 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 All 44 releases
← All changes | app/Http/Controllers/UserController.php +80 -121 1.95.3 → 2.2.0 View file →
@@ -4,8 +4,9 @@
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\MCPAccessService;
8 9 use FluentBoards\App\Services\PermissionManager;
9 10 use FluentBoards\App\Services\UserService;
10 11 use FluentBoards\Framework\Http\Request\Request;
11 12 use FluentCrm\App\Models\Subscriber;
@@ -80,8 +81,10 @@
80 81 $boardIds = [];
81 82 if (is_array($rawBoardIds)) {
82 83 $boardIds = array_filter(array_map('intval', $rawBoardIds));
83 84 }
85 +
86 + $perPage = max(1, min(absint($request->getSafe('per_page', 'intval', 15)), 50));
84 87
85 88 $requestData = [
86 89 'page' => $request->getSafe('page', 'intval', 1),
87 90 'taskType' => $request->getSafe('taskType', 'sanitize_text_field'),
@@ -87,8 +90,9 @@
87 90 'taskType' => $request->getSafe('taskType', 'sanitize_text_field'),
88 91 'boardIds' => $boardIds,
89 92 'orderBy' => $request->getSafe('orderBy', 'sanitize_text_field'),
90 93 'order' => $request->getSafe('order', 'sanitize_text_field'),
94 + 'per_page' => $perPage
91 95 ];
92 96 try {
93 97 return $this->sendSuccess(
94 98 $this->userService->getMemberAssociatedTasks($user_id, $requestData)
@@ -97,8 +101,28 @@
97 101 return $this->sendError($e->getMessage(), 404);
98 102 }
99 103 }
100 104
105 + /**
106 + * Return profile task counts using the same categories shown on the dashboard.
107 + */
108 + public function getMemberTaskCounts(Request $request, $user_id)
109 + {
110 + $user_id = absint($user_id);
111 + $rawBoardIds = $request->getSafe('boardIds');
112 + $boardIds = is_array($rawBoardIds)
113 + ? array_filter(array_map('intval', $rawBoardIds))
114 + : [];
115 +
116 + try {
117 + return $this->sendSuccess([
118 + 'counts' => $this->userService->getMemberTaskCounts($user_id, $boardIds),
119 + ], 200);
120 + } catch (\Exception $e) {
121 + return $this->sendError($e->getMessage(), 404);
122 + }
123 + }
124 +
101 125 public function getMemberRelatedAcitivies(Request $request, $user_id)
102 126 {
103 127 $user_id = absint($user_id);
104 128 $page = $request->getSafe('page', 'intval', 1);
@@ -155,149 +179,84 @@
155 179 return $this->sendError($e->getMessage(), 404);
156 180 }
157 181 }
158 182
159 - public function updateDisplayName(Request $request, $user_id)
183 + public function getMemberStats($user_id)
160 184 {
161 185 $user_id = absint($user_id);
162 - $currentUserId = get_current_user_id();
163 186
164 - if ($currentUserId !== $user_id && !PermissionManager::isAdmin($currentUserId)) {
165 - return $this->sendError(
166 - __('You do not have permission to update this display name', 'fluent-boards'),
167 - 403
168 - );
169 - }
170 -
171 - $displayName = $request->getSafe('display_name', 'sanitize_text_field');
172 -
173 - if(!$displayName) {
174 - return $this->sendError('Display name is required', 400);
175 - }
176 -
177 - $updateResult = wp_update_user([
178 - 'ID' => $user_id,
179 - 'display_name' => $displayName,
180 - ]);
181 -
182 - if (is_wp_error($updateResult)) {
183 - return $this->sendError(
184 - $updateResult->get_error_message(),
185 - 400
187 + return $this->sendSuccess(
188 + $this->userService->getMemberStats($user_id),
189 + 200
186 190 );
187 - }
191 + }
188 192
189 - $user = User::findOrFail($user_id);
190 - $user = Helper::sanitizeUserCollections($user);
191 - $user->fbs_role = PermissionManager::isFluentBoardsAdmin($user_id) ? 'fbs_admin' : 'member';
192 - $user->is_wp_admin = user_can($user_id, 'manage_options') ? 'yes' : 'no';
193 -
194 - if (defined('FLUENTCRM')) {
195 - $subscriber = Subscriber::where('user_id', $user_id)->first();
196 - $user->fluentcrm_subscriber = $subscriber ?? null;
193 + /** Update the authenticated member's display name. */
194 + public function updateDisplayName(Request $request, $user_id)
195 + {
196 + try {
197 + return $this->sendSuccess([
198 + 'message' => __('Display name has been updated', 'fluent-boards'),
199 + 'user' => $this->userService->updateDisplayName($user_id, $request->get('display_name')),
200 + ]);
201 + } catch (\Exception $e) {
202 + return $this->sendError(['message' => $e->getMessage()], $e->getCode() === 403 ? 403 : 400);
197 203 }
198 -
199 - return $this->sendSuccess([
200 - 'message' => __('Display name has been updated', 'fluent-boards'),
201 - 'user' => $user,
202 - ], 200);
203 -
204 204 }
205 205
206 + /** Upload a profile photo for the authenticated member. */
206 207 public function updateProfilePhoto(Request $request, $user_id)
207 -{
208 - $user_id = absint($user_id);
209 - $currentUserId = get_current_user_id();
210 -
211 - // Only the user themself OR an admin can change the profile picture
212 - if ($currentUserId !== $user_id && !PermissionManager::isAdmin($currentUserId)) {
213 - return $this->sendError(
214 - __('You do not have permission to update this profile photo', 'fluent-boards'),
215 - 403
216 - );
208 + {
209 + try {
210 + return $this->sendSuccess([
211 + 'message' => __('Profile photo has been updated', 'fluent-boards'),
212 + 'user' => $this->userService->updateProfilePhoto($user_id, $_FILES['photo'] ?? []),
213 + ]);
214 + } catch (\Exception $e) {
215 + return $this->sendError(['message' => $e->getMessage()], $e->getCode() === 403 ? 403 : 400);
216 + }
217 217 }
218 218
219 - // We’ll use native WordPress upload handling
220 - if (empty($_FILES['photo']) || !empty($_FILES['photo']['error'])) {
221 - return $this->sendError(
222 - __('No photo uploaded or upload error', 'fluent-boards'),
223 - 400
224 - );
225 - }
219 + public function getMcpConnections(Request $request, $id)
220 + {
221 + if (!current_user_can('list_app_passwords', (int) $id)) {
222 + return $this->sendError(['message' => __('You are not allowed to list application passwords.', 'fluent-boards')], 403);
223 + }
226 224
227 - // Limit file size to 2MB for profile photos
228 - $maxSize = 2 * 1024 * 1024;
229 - if ($_FILES['photo']['size'] > $maxSize) {
230 - return $this->sendError(
231 - __('Photo must be under 2MB', 'fluent-boards'),
232 - 400
233 - );
225 + return $this->sendSuccess(MCPAccessService::getProfileState((int) $id));
234 226 }
235 227
236 - $file = $_FILES['photo'];
228 + /**
229 + * Create an MCP connection key. The password is only ever in this response.
230 + */
231 + public function createMcpConnection(Request $request, $id)
232 + {
233 + $result = MCPAccessService::createConnection((int) $id, (string) $request->get('name'));
237 234
238 - // Validate MIME type server-side (client-sent type is spoofable)
239 - $fileType = wp_check_filetype($file['name'], [
240 - 'jpg|jpeg|jpe' => 'image/jpeg',
241 - 'gif' => 'image/gif',
242 - 'png' => 'image/png',
243 - 'webp' => 'image/webp',
244 - ]);
235 + if (is_wp_error($result)) {
236 + $data = $result->get_error_data();
237 + return $this->sendError(['message' => $result->get_error_message()], $data['status'] ?? 400);
238 + }
245 239
246 - if (!$fileType['type']) {
247 - return $this->sendError(
248 - __('Invalid image type', 'fluent-boards'),
249 - 400
250 - );
251 - }
240 + nocache_headers();
252 241
253 - // Load WordPress upload helpers
254 - if (!function_exists('wp_handle_upload')) {
255 - require_once ABSPATH . 'wp-admin/includes/file.php';
242 + return $this->sendSuccess($result);
256 243 }
257 244
258 - $overrides = [
259 - 'test_form' => false,
260 - 'mimes' => [
261 - 'jpg|jpeg|jpe' => 'image/jpeg',
262 - 'gif' => 'image/gif',
263 - 'png' => 'image/png',
264 - 'webp' => 'image/webp',
265 - ],
266 - ];
245 + public function revokeMcpConnection(Request $request, $id, $uuid)
246 + {
247 + if (!current_user_can('delete_app_password', (int) $id, $uuid)) {
248 + return $this->sendError(['message' => __('You are not allowed to delete this application password.', 'fluent-boards')], 403);
249 + }
267 250
268 - $uploaded = wp_handle_upload($file, $overrides);
251 + $result = MCPAccessService::revokeConnection((int) $id, sanitize_text_field($uuid));
269 252
270 - if (isset($uploaded['error'])) {
271 - return $this->sendError(
272 - $uploaded['error'],
273 - 400
274 - );
275 - }
253 + if (is_wp_error($result)) {
254 + $data = $result->get_error_data();
255 + return $this->sendError(['message' => $result->get_error_message()], $data['status'] ?? 400);
256 + }
276 257
277 - $photoUrl = esc_url_raw($uploaded['url']);
278 -
279 - // Store custom profile photo in user meta
280 - update_user_meta($user_id, 'fbs_profile_photo', $photoUrl);
281 -
282 - // Reload user and sanitize same as in getMemberInfo()
283 - $user = User::findOrFail($user_id);
284 - $user = Helper::sanitizeUserCollections($user);
285 -
286 - // Override photo field if your sanitizer does not already use the meta
287 - $user->photo = $photoUrl;
288 -
289 - $user->fbs_role = PermissionManager::isFluentBoardsAdmin($user_id) ? 'fbs_admin' : 'member';
290 - $user->is_wp_admin = user_can($user_id, 'manage_options') ? 'yes' : 'no';
291 -
292 - if (defined('FLUENTCRM')) {
293 - $subscriber = Subscriber::where('user_id', $user_id)->first();
294 - $user->fluentcrm_subscriber = $subscriber ?? null;
258 + return $this->sendSuccess([
259 + 'message' => __('Connection revoked.', 'fluent-boards'),
260 + ]);
295 261 }
296 -
297 - return $this->sendSuccess([
298 - 'message' => __('Profile photo has been updated', 'fluent-boards'),
299 - 'photo_url' => $photoUrl,
300 - 'user' => $user,
301 - ], 200);
302 -}
303 262 }