| @@ -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, |