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/Http/Controllers/BoardController.php +95 -14 2.0.0 → 2.2.0 View file →
@@ -62,8 +62,17 @@
62 62 $type = $request->getSafe('type', 'sanitize_text_field', 'to-do');
63 63
64 64 $order = $request->getSafe('order', 'sanitize_text_field', 'created_at');
65 65 $orderBy = $request->getSafe('orderBy', 'sanitize_text_field', 'DESC');
66 + $order = is_string($order) ? sanitize_sql_orderby($order) : false;
67 + if (!in_array($order, ['created_at', 'title', 'updated_at', 'id'], true)) {
68 + $order = 'created_at';
69 + }
70 + $orderBy = is_string($orderBy) ? strtoupper($orderBy) : 'DESC';
71 + if (!in_array($orderBy, ['ASC', 'DESC'], true)) {
72 + $orderBy = 'DESC';
73 + }
74 +
66 75 $searchInput = $request->getSafe('searchInput', 'sanitize_text_field');
67 76
68 77 $option = $request->getSafe('option', 'sanitize_text_field');
69 78 $folderId = $request->getSafe('fid', 'intval'); // Get folder ID from request
@@ -85,10 +94,10 @@
85 94
86 95 // Scope pinned before pagination, from the same id source as getBoardCounts(),
87 96 // so the pinned page and board_counts.pinned always agree. Filtering pinned
88 97 // after pagination would drop pinned boards that fall on a later active page.
98 + $pinnedIds = $this->boardService->getPinnedBoardIds();
89 99 if ($option == 'pinned') {
90 - $pinnedIds = $this->boardService->getPinnedBoardIds();
91 100
92 101 $relatedBoardsQuery = $pinnedIds
93 102 ? $relatedBoardsQuery->whereIn('id', $pinnedIds)
94 103 : $relatedBoardsQuery->where('id', 0);
@@ -110,15 +119,15 @@
110 119 }
111 120
112 121 $relatedBoards = $relatedBoardsQuery->orderBy($order, $orderBy)
113 122 ->withCount('completedTasks')
114 - ->with('stages', 'users')
123 + ->with('stages', 'users', 'boardMeta', 'viewerMembership')
115 124 ->paginate($per_page);
116 125
117 126 foreach ($relatedBoards as $relatedBoard) {
118 127 $relatedBoard->description = DescriptionMarkdownConverter::normalize($relatedBoard->description);
119 128 $relatedBoard->users = Helper::sanitizeUserCollections($relatedBoard->users);
120 - $relatedBoard->is_pinned = $this->boardService->isPinned($relatedBoard->id);
129 + $relatedBoard->is_pinned = in_array((int) $relatedBoard->id, $pinnedIds, true);
121 130 }
122 131
123 132 $response = [
124 133 'boards' => $relatedBoards,
@@ -228,9 +237,12 @@
228 237 {
229 238 $boards = $this->boardService->getRecentBoards();
230 239
231 240 if (!$boards || $boards->isEmpty()) {
232 - $boards = Board::where('type', 'to-do')->excludeTemplates()->byAccessUser(get_current_user_id())
241 + $boards = Board::whereNull('archived_at')
242 + ->excludeTemplates()
243 + ->availableInCurrentInstall()
244 + ->byAccessUser(get_current_user_id())
233 245 ->limit(4)
234 246 ->withCount('completedTasks')
235 247 ->with(['stages', 'users'])
236 248 ->get();
@@ -347,8 +359,10 @@
347 359 if (!empty($backgroundData)) {
348 360 $boardData['background'] = $backgroundData;
349 361 }
350 362
363 + $this->validateRequestedLabelPresets($request->get('labels'));
364 +
351 365 $board = $this->boardService->createBoard($boardData);
352 366 $this->createBoardLabelsFromRequest($request, $board->id);
353 367 $this->addBoardMembersFromRequest($request, $board->id);
354 368 $type = ucfirst($boardData['type']);
@@ -442,20 +456,53 @@
442 456
443 457 foreach ($labels as $label) {
444 458 $labelData = Helper::sanitizeLabel((array) $label);
445 459
446 - if (empty($labelData['label']) && empty($labelData['bg_color'])) {
460 + if (empty($labelData['label']) && empty($labelData['bg_color']) && empty($labelData['color_preset'])) {
447 461 continue;
448 462 }
449 463
450 - $this->labelService->createLabel([
464 + $labelPayload = [
451 465 'label' => $labelData['label'] ?? '',
452 466 'bg_color' => $labelData['bg_color'] ?? '#f3f4f6',
453 467 'color' => $labelData['color'] ?? '#1B2533',
454 - ], $boardId);
468 + ];
469 +
470 + if (array_key_exists('color_preset', $labelData)) {
471 + $labelPayload['color_preset'] = $labelData['color_preset'];
472 + }
473 +
474 + $this->labelService->createLabel($labelPayload, $boardId);
455 475 }
456 476 }
457 477
478 + /**
479 + * Reject unsupported label preset ids before creating any board records.
480 + *
481 + * @param mixed $labels
482 + * @return void
483 + * @throws \Exception
484 + */
485 + private function validateRequestedLabelPresets($labels)
486 + {
487 + if (!is_array($labels)) {
488 + return;
489 + }
490 +
491 + foreach ($labels as $label) {
492 + $labelData = Helper::sanitizeLabel((array) $label);
493 + $presetId = $labelData[Constant::LABEL_COLOR_PRESET_SETTING] ?? null;
494 +
495 + if ($presetId === null || $presetId === '') {
496 + continue;
497 + }
498 +
499 + if (!is_string($presetId) || !Constant::getLabelColorPreset($presetId)) {
500 + throw new \Exception(esc_html__('Invalid label color preset', 'fluent-boards'));
501 + }
502 + }
503 + }
504 +
458 505 private function addBoardMembersFromRequest(Request $request, $boardId)
459 506 {
460 507 $memberIds = $request->get('member_ids');
461 508
@@ -497,10 +544,12 @@
497 544 return $this->sendError($e->getMessage(), 404);
498 545 }
499 546 }
500 547
548 + /** Return board details with a cursor covering changes made during retrieval. */
501 549 public function find(Request $request, $board_id)
502 550 {
551 + $syncStartedAt = current_time('mysql');
503 552 $board = Board::findOrFail($board_id);
504 553 $board->description = DescriptionMarkdownConverter::normalize($board->description);
505 554 $includeArchived = filter_var($request->get('include_archived', false), FILTER_VALIDATE_BOOLEAN);
506 555 $board->background = maybe_unserialize($board->background);
@@ -529,8 +578,9 @@
529 578 $this->boardService->updateRecentBoards($board_id);
530 579
531 580 $board->labelColor = Constant::TRELLO_COLOR_MAP;
532 581 $board->labelColorText = Constant::TEXT_COLOR_MAP;
582 + $board->labelColorPresets = Constant::LABEL_COLOR_PRESETS;
533 583
534 584 $board->users = Helper::sanitizeUserCollections($board->users);
535 585 $board->owner = Helper::sanitizeUserCollections($board->owner);
536 586
@@ -539,14 +589,24 @@
539 589 $board = apply_filters('fluent_boards/board_find', $board);
540 590
541 591 return [
542 592 'board' => $board,
543 - 'synced_at' => current_time('mysql')
593 + 'synced_at' => $syncStartedAt
544 594 ];
545 595 }
546 596
547 597 public function update(Request $request, $board_id)
548 598 {
599 + // Board identity (title/description) is manager-only. This action shares the
600 + // `update` name with CommentController@update under the same policy group, so the
601 + // guard lives here rather than in a SingleBoardPolicy::update() method that would
602 + // also block ordinary members from editing their own comments.
603 + if (!PermissionManager::isBoardManager(absint($board_id))) {
604 + return $this->sendError([
605 + 'message' => __('You do not have permission to edit this board.', 'fluent-boards'),
606 + ], 403);
607 + }
608 +
549 609 $boardData = $this->boardSanitizeAndValidate($request->only(['title', 'description']), [
550 610 'title' => 'required|string',
551 611 'description' => 'nullable|string',
552 612 ]);
@@ -709,9 +769,8 @@
709 769
710 770 $formattedUsers[] = [
711 771 'ID' => $user->ID,
712 772 'display_name' => $name,
713 - 'user_login' => $user->user_login,
714 773 'email' => $user->user_email,
715 774 'photo' => fluent_boards_user_avatar($user->user_email, $name),
716 775 'role' => $this->boardUserRole($boardRelation),
717 776 'is_super' => in_array($user->ID, $superAdminIds),
@@ -800,20 +859,28 @@
800 859 }
801 860
802 861 public function addMembersInBoard(Request $request, $board_id)
803 862 {
804 - $memberId = $request->getSafe('memberId');
805 - $isViewerOnly = $request->getSafe('isViewerOnly');
863 + $memberId = $request->getSafe('memberId', 'intval');
864 + $isViewerOnly = $request->getSafe('isViewerOnly', 'sanitize_text_field');
806 865 $member = $this->boardService->addMembersInBoard($board_id, $memberId, $isViewerOnly);
866 +
867 + if ($member === null) {
868 + return $this->sendError([
869 + 'message' => __('User not found.', 'fluent-boards'),
870 + ], 404);
871 + }
872 +
807 873 if (!$member) {
808 874 return $this->sendError([
809 875 'message' => __('User already a member', 'fluent-boards'),
810 - ], 304);
876 + ], 409);
811 877 }
812 878
813 879
814 880 return [
815 881 'message' => __('Member added successfully', 'fluent-boards'),
882 + 'is_admin' => PermissionManager::isAdmin($memberId),
816 883 'member' => Helper::sanitizeUserCollections($member)
817 884 ];
818 885 }
819 886
@@ -932,9 +999,9 @@
932 999 'reset' => true,
933 1000 ];
934 1001 } elseif ($request->image_url) {
935 1002 $backgroundData = $this->boardSanitizeAndValidate($request->all(), [
936 - "id" => 'required',
1003 + 'id' => 'required|integer',
937 1004 'image_url' => 'required|string|url',
938 1005 ]);
939 1006 } elseif ($request->color) {
940 1007 $backgroundData = $this->boardSanitizeAndValidate($request->all(), [
@@ -1166,8 +1233,11 @@
1166 1233
1167 1234 return (bool) $permissionManager::currentUserCan('fcrm_read_contacts');
1168 1235 }
1169 1236
1237 + /**
1238 + * Duplicate a board with the selected board data options.
1239 + */
1170 1240 public function duplicateBoard(Request $request, $board_id)
1171 1241 {
1172 1242 $boardData = $this->taskSanitizeAndValidate($request->get('board'), [
1173 1243 'title' => 'required|string'
@@ -1176,8 +1246,10 @@
1176 1246 $boardData['source_board_id'] = $board_id;
1177 1247
1178 1248 $isWithLabels = $request->getSafe('isWithLabels');
1179 1249 $isWithTasks = $request->getSafe('isWithTasks');
1250 + $isWithMembers = $request->getSafe('isWithMembers');
1251 + $isWithCustomFields = $request->getSafe('isWithCustomFields', 'sanitize_text_field', 'yes');
1180 1252 $isWithTemplates = $request->getSafe('isWithTemplates');
1181 1253
1182 1254 try {
1183 1255 if(!PermissionManager::isAdmin()) {
@@ -1185,9 +1257,14 @@
1185 1257 throw new \Exception(esc_html($errorMessage), 400);
1186 1258 }
1187 1259 //create board
1188 1260 $newBoard = $this->boardService->copyBoard($boardData);
1261 + $customFieldMap = [];
1189 1262
1263 + if ($isWithCustomFields == 'yes') {
1264 + $customFieldMap = $this->boardService->copyCustomFieldsOfBoard($board_id, $newBoard);
1265 + }
1266 +
1190 1267 //label copy
1191 1268 $labelMap = [];
1192 1269
1193 1270 if ($isWithLabels == 'yes') {
@@ -1198,9 +1275,13 @@
1198 1275 $stageMapForCopyingTask = $this->stageService->copyStagesOfBoard($newBoard, $board_id, $isWithTemplates);
1199 1276
1200 1277 //copy tasks of selected stages
1201 1278 if ($isWithTasks == 'yes') {
1202 - $this->taskService->copyTasks($board_id, $stageMapForCopyingTask, $newBoard, $labelMap,$isWithTemplates);
1279 + $this->taskService->copyTasks($board_id, $stageMapForCopyingTask, $newBoard, $labelMap, $isWithTemplates, $customFieldMap);
1280 + }
1281 +
1282 + if ($isWithMembers == 'yes') {
1283 + $this->boardService->copyMembersOfBoard($board_id, $newBoard);
1203 1284 }
1204 1285
1205 1286 return $this->sendSuccess([
1206 1287 'board' => $newBoard,