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 +91 -13 2.0.4 → 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,
@@ -350,8 +359,10 @@
350 359 if (!empty($backgroundData)) {
351 360 $boardData['background'] = $backgroundData;
352 361 }
353 362
363 + $this->validateRequestedLabelPresets($request->get('labels'));
364 +
354 365 $board = $this->boardService->createBoard($boardData);
355 366 $this->createBoardLabelsFromRequest($request, $board->id);
356 367 $this->addBoardMembersFromRequest($request, $board->id);
357 368 $type = ucfirst($boardData['type']);
@@ -445,20 +456,53 @@
445 456
446 457 foreach ($labels as $label) {
447 458 $labelData = Helper::sanitizeLabel((array) $label);
448 459
449 - if (empty($labelData['label']) && empty($labelData['bg_color'])) {
460 + if (empty($labelData['label']) && empty($labelData['bg_color']) && empty($labelData['color_preset'])) {
450 461 continue;
451 462 }
452 463
453 - $this->labelService->createLabel([
464 + $labelPayload = [
454 465 'label' => $labelData['label'] ?? '',
455 466 'bg_color' => $labelData['bg_color'] ?? '#f3f4f6',
456 467 'color' => $labelData['color'] ?? '#1B2533',
457 - ], $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);
458 475 }
459 476 }
460 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 +
461 505 private function addBoardMembersFromRequest(Request $request, $boardId)
462 506 {
463 507 $memberIds = $request->get('member_ids');
464 508
@@ -500,10 +544,12 @@
500 544 return $this->sendError($e->getMessage(), 404);
501 545 }
502 546 }
503 547
548 + /** Return board details with a cursor covering changes made during retrieval. */
504 549 public function find(Request $request, $board_id)
505 550 {
551 + $syncStartedAt = current_time('mysql');
506 552 $board = Board::findOrFail($board_id);
507 553 $board->description = DescriptionMarkdownConverter::normalize($board->description);
508 554 $includeArchived = filter_var($request->get('include_archived', false), FILTER_VALIDATE_BOOLEAN);
509 555 $board->background = maybe_unserialize($board->background);
@@ -532,8 +578,9 @@
532 578 $this->boardService->updateRecentBoards($board_id);
533 579
534 580 $board->labelColor = Constant::TRELLO_COLOR_MAP;
535 581 $board->labelColorText = Constant::TEXT_COLOR_MAP;
582 + $board->labelColorPresets = Constant::LABEL_COLOR_PRESETS;
536 583
537 584 $board->users = Helper::sanitizeUserCollections($board->users);
538 585 $board->owner = Helper::sanitizeUserCollections($board->owner);
539 586
@@ -542,14 +589,24 @@
542 589 $board = apply_filters('fluent_boards/board_find', $board);
543 590
544 591 return [
545 592 'board' => $board,
546 - 'synced_at' => current_time('mysql')
593 + 'synced_at' => $syncStartedAt
547 594 ];
548 595 }
549 596
550 597 public function update(Request $request, $board_id)
551 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 +
552 609 $boardData = $this->boardSanitizeAndValidate($request->only(['title', 'description']), [
553 610 'title' => 'required|string',
554 611 'description' => 'nullable|string',
555 612 ]);
@@ -712,9 +769,8 @@
712 769
713 770 $formattedUsers[] = [
714 771 'ID' => $user->ID,
715 772 'display_name' => $name,
716 - 'user_login' => $user->user_login,
717 773 'email' => $user->user_email,
718 774 'photo' => fluent_boards_user_avatar($user->user_email, $name),
719 775 'role' => $this->boardUserRole($boardRelation),
720 776 'is_super' => in_array($user->ID, $superAdminIds),
@@ -803,20 +859,28 @@
803 859 }
804 860
805 861 public function addMembersInBoard(Request $request, $board_id)
806 862 {
807 - $memberId = $request->getSafe('memberId');
808 - $isViewerOnly = $request->getSafe('isViewerOnly');
863 + $memberId = $request->getSafe('memberId', 'intval');
864 + $isViewerOnly = $request->getSafe('isViewerOnly', 'sanitize_text_field');
809 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 +
810 873 if (!$member) {
811 874 return $this->sendError([
812 875 'message' => __('User already a member', 'fluent-boards'),
813 - ], 304);
876 + ], 409);
814 877 }
815 878
816 879
817 880 return [
818 881 'message' => __('Member added successfully', 'fluent-boards'),
882 + 'is_admin' => PermissionManager::isAdmin($memberId),
819 883 'member' => Helper::sanitizeUserCollections($member)
820 884 ];
821 885 }
822 886
@@ -935,9 +999,9 @@
935 999 'reset' => true,
936 1000 ];
937 1001 } elseif ($request->image_url) {
938 1002 $backgroundData = $this->boardSanitizeAndValidate($request->all(), [
939 - "id" => 'required',
1003 + 'id' => 'required|integer',
940 1004 'image_url' => 'required|string|url',
941 1005 ]);
942 1006 } elseif ($request->color) {
943 1007 $backgroundData = $this->boardSanitizeAndValidate($request->all(), [
@@ -1169,8 +1233,11 @@
1169 1233
1170 1234 return (bool) $permissionManager::currentUserCan('fcrm_read_contacts');
1171 1235 }
1172 1236
1237 + /**
1238 + * Duplicate a board with the selected board data options.
1239 + */
1173 1240 public function duplicateBoard(Request $request, $board_id)
1174 1241 {
1175 1242 $boardData = $this->taskSanitizeAndValidate($request->get('board'), [
1176 1243 'title' => 'required|string'
@@ -1179,8 +1246,10 @@
1179 1246 $boardData['source_board_id'] = $board_id;
1180 1247
1181 1248 $isWithLabels = $request->getSafe('isWithLabels');
1182 1249 $isWithTasks = $request->getSafe('isWithTasks');
1250 + $isWithMembers = $request->getSafe('isWithMembers');
1251 + $isWithCustomFields = $request->getSafe('isWithCustomFields', 'sanitize_text_field', 'yes');
1183 1252 $isWithTemplates = $request->getSafe('isWithTemplates');
1184 1253
1185 1254 try {
1186 1255 if(!PermissionManager::isAdmin()) {
@@ -1188,9 +1257,14 @@
1188 1257 throw new \Exception(esc_html($errorMessage), 400);
1189 1258 }
1190 1259 //create board
1191 1260 $newBoard = $this->boardService->copyBoard($boardData);
1261 + $customFieldMap = [];
1192 1262
1263 + if ($isWithCustomFields == 'yes') {
1264 + $customFieldMap = $this->boardService->copyCustomFieldsOfBoard($board_id, $newBoard);
1265 + }
1266 +
1193 1267 //label copy
1194 1268 $labelMap = [];
1195 1269
1196 1270 if ($isWithLabels == 'yes') {
@@ -1201,9 +1275,13 @@
1201 1275 $stageMapForCopyingTask = $this->stageService->copyStagesOfBoard($newBoard, $board_id, $isWithTemplates);
1202 1276
1203 1277 //copy tasks of selected stages
1204 1278 if ($isWithTasks == 'yes') {
1205 - $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);
1206 1284 }
1207 1285
1208 1286 return $this->sendSuccess([
1209 1287 'board' => $newBoard,