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 +70 -9 2.0.15 → 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,9 +589,9 @@
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)
@@ -722,9 +769,8 @@
722 769
723 770 $formattedUsers[] = [
724 771 'ID' => $user->ID,
725 772 'display_name' => $name,
726 - 'user_login' => $user->user_login,
727 773 'email' => $user->user_email,
728 774 'photo' => fluent_boards_user_avatar($user->user_email, $name),
729 775 'role' => $this->boardUserRole($boardRelation),
730 776 'is_super' => in_array($user->ID, $superAdminIds),
@@ -832,8 +878,9 @@
832 878
833 879
834 880 return [
835 881 'message' => __('Member added successfully', 'fluent-boards'),
882 + 'is_admin' => PermissionManager::isAdmin($memberId),
836 883 'member' => Helper::sanitizeUserCollections($member)
837 884 ];
838 885 }
839 886
@@ -1186,8 +1233,11 @@
1186 1233
1187 1234 return (bool) $permissionManager::currentUserCan('fcrm_read_contacts');
1188 1235 }
1189 1236
1237 + /**
1238 + * Duplicate a board with the selected board data options.
1239 + */
1190 1240 public function duplicateBoard(Request $request, $board_id)
1191 1241 {
1192 1242 $boardData = $this->taskSanitizeAndValidate($request->get('board'), [
1193 1243 'title' => 'required|string'
@@ -1196,8 +1246,10 @@
1196 1246 $boardData['source_board_id'] = $board_id;
1197 1247
1198 1248 $isWithLabels = $request->getSafe('isWithLabels');
1199 1249 $isWithTasks = $request->getSafe('isWithTasks');
1250 + $isWithMembers = $request->getSafe('isWithMembers');
1251 + $isWithCustomFields = $request->getSafe('isWithCustomFields', 'sanitize_text_field', 'yes');
1200 1252 $isWithTemplates = $request->getSafe('isWithTemplates');
1201 1253
1202 1254 try {
1203 1255 if(!PermissionManager::isAdmin()) {
@@ -1205,9 +1257,14 @@
1205 1257 throw new \Exception(esc_html($errorMessage), 400);
1206 1258 }
1207 1259 //create board
1208 1260 $newBoard = $this->boardService->copyBoard($boardData);
1261 + $customFieldMap = [];
1209 1262
1263 + if ($isWithCustomFields == 'yes') {
1264 + $customFieldMap = $this->boardService->copyCustomFieldsOfBoard($board_id, $newBoard);
1265 + }
1266 +
1210 1267 //label copy
1211 1268 $labelMap = [];
1212 1269
1213 1270 if ($isWithLabels == 'yes') {
@@ -1218,9 +1275,13 @@
1218 1275 $stageMapForCopyingTask = $this->stageService->copyStagesOfBoard($newBoard, $board_id, $isWithTemplates);
1219 1276
1220 1277 //copy tasks of selected stages
1221 1278 if ($isWithTasks == 'yes') {
1222 - $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);
1223 1284 }
1224 1285
1225 1286 return $this->sendSuccess([
1226 1287 'board' => $newBoard,