PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.91.6
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.91.6
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 1.41 All 42 releases
fluent-boards / app / Http / Controllers / BoardController.php

BoardController.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 1.91.6, at app/Http/Controllers/BoardController.php

1,238 lines 41.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBoards\App\Http\Controllers;
4
5 use FluentBoards\App\Models\Attachment;
6 use FluentBoards\App\Models\Meta;
7 use FluentBoards\App\Models\Relation;
8 use FluentBoards\App\Models\Task;
9 use FluentBoards\App\Models\User;
10 use FluentBoards\App\Models\Board;
11 use FluentBoards\App\Services\CommentService;
12 use FluentBoards\App\Services\Constant;
13 use FluentBoards\App\Services\Helper;
14 use FluentBoards\App\Models\Stage;
15 use FluentBoards\App\Services\InstallService;
16 use FluentBoards\App\Services\StageService;
17 use FluentBoards\App\Services\TaskService;
18 use FluentBoards\App\Services\BoardService;
19 use FluentBoards\App\Services\UploadService;
20 use FluentBoards\Framework\Http\Request\Request;
21 use FluentBoards\App\Services\PermissionManager;
22 use FluentBoards\App\Hooks\Handlers\BoardHandler;
23 use FluentBoards\App\Hooks\Handlers\BoardMenuHandler;
24 use FluentBoards\App\Services\LabelService;
25 use FluentBoards\Framework\Support\Arr;
26 use FluentBoards\Framework\Support\Collection;
27 use FluentBoardsPro\App\Services\AttachmentService;
28 use FluentBoardsPro\App\Services\CustomFieldService;
29 use FluentBoardsPro\App\Services\ProHelper;
30 use FluentBoardsPro\App\Services\RemoteUrlParser;
31 use FluentCrm\App\Models\Subscriber;
32
33 class BoardController extends Controller
34 {
35 private $boardService;
36 private $taskService;
37 private $stageService;
38 private $labelService;
39
40 public function __construct(
41 BoardService $boardService,
42 TaskService $taskService,
43 StageService $stageService,
44 LabelService $labelService
45 )
46 {
47 parent::__construct();
48 $this->boardService = $boardService;
49 $this->taskService = $taskService;
50 $this->stageService = $stageService;
51 $this->labelService = $labelService;
52 }
53
54 public function getBoards(Request $request)
55 {
56 $per_page = $request->getSafe('per_page', 'intval', 100);
57 $userId = get_current_user_id();
58 $type = $request->getSafe('type', 'sanitize_text_field', 'to-do');
59
60 $order = $request->getSafe('order', 'sanitize_text_field', 'created_at');
61 $orderBy = $request->getSafe('orderBy', 'sanitize_text_field', 'DESC');
62 $searchInput = $request->getSafe('searchInput', 'sanitize_text_field');
63
64 $option = $request->getSafe('option', 'sanitize_text_field');
65 $folderId = $request->getSafe('fid', 'intval'); // Get folder ID from request
66
67 // Initialize the query based on archive status
68 if (!defined('FLUENT_ROADMAP')) {
69 if ($option == 'archived') {
70 $relatedBoardsQuery = Board::whereNotNull('archived_at')->where('type', 'to-do')->byAccessUser($userId);
71 } else {
72 $relatedBoardsQuery = Board::whereNull('archived_at')->where('type', 'to-do')->byAccessUser($userId);
73 }
74 } else {
75 if ($option == 'archived') {
76 $relatedBoardsQuery = Board::whereNotNull('archived_at')->byAccessUser($userId);
77 } else {
78 $relatedBoardsQuery = Board::whereNull('archived_at')->byAccessUser($userId);
79 }
80 }
81
82 // If folder ID is provided, filter boards by folder
83 if ($folderId && defined('FLUENT_BOARDS_PRO')) {
84 $boardIds = ProHelper::getBoardIdsByFolder($folderId);
85 $relatedBoardsQuery = $relatedBoardsQuery->whereIn('id', $boardIds);
86 }
87
88 // Add search functionality
89 if (!empty($searchInput)) {
90 $relatedBoardsQuery = $relatedBoardsQuery->where('title', 'like', '%' . $searchInput . '%');
91 }
92
93 $relatedBoards = $relatedBoardsQuery->orderBy($order, $orderBy)
94 ->withCount('completedTasks')
95 ->with('stages', 'users')
96 ->paginate($per_page);
97
98 foreach ($relatedBoards as $relatedBoard) {
99 $relatedBoard->users = Helper::sanitizeUserCollections($relatedBoard->users);
100 $relatedBoard->is_pinned = $this->boardService->isPinned($relatedBoard->id);
101 }
102
103 $response = [
104 'boards' => $relatedBoards
105 ];
106
107 // Include folder mapping if pro version is available - ALWAYS include for consistency
108 if (defined('FLUENT_BOARDS_PRO')) {
109 $response['folder_mapping'] = $this->getBoardFolderMapping($userId);
110 if ($folderId) {
111 $response['current_folder'] = $this->getCurrentFolderInfo($folderId);
112 }
113 } else {
114 // Include empty folder mapping for consistency
115 $response['folder_mapping'] = [];
116 }
117
118 return $this->sendSuccess($response);
119 }
120
121 /**
122 * Get folder mapping for boards
123 */
124 private function getBoardFolderMapping($userId)
125 {
126 if (!defined('FLUENT_BOARDS_PRO')) {
127 return [];
128 }
129
130 $folderService = new \FluentBoardsPro\App\Services\FolderService();
131 $folders = $folderService->getFolders($userId);
132
133 $mapping = [];
134 foreach ($folders as $folder) {
135 $mapping[$folder->id] = [
136 'id' => $folder->id,
137 'title' => $folder->title,
138 'board_ids' => $folder->boards ? $folder->boards->pluck('id')->toArray() : []
139 ];
140 }
141
142 return $mapping;
143 }
144
145 /**
146 * Get current folder information
147 */
148 private function getCurrentFolderInfo($folderId)
149 {
150 if (!defined('FLUENT_BOARDS_PRO')) {
151 return null;
152 }
153
154 $folderService = new \FluentBoardsPro\App\Services\FolderService();
155 $folder = $folderService->getFolderById($folderId);
156
157 if (!$folder) {
158 return null;
159 }
160
161 return [
162 'id' => $folder->id,
163 'title' => $folder->title,
164 'board_count' => $folder->boards ? $folder->boards->count() : 0
165 ];
166 }
167
168 /**
169 * Get the list of boards and their associated stages for the current user.
170 *
171 * @param \FluentBoards\Framework\Http\Request\Request $request
172 * @return \WP_REST_Response
173 */
174 public function getBoardsList(Request $request)
175 {
176 $userId = get_current_user_id();
177
178 // Query to fetch boards that are not archived and accessible by the user
179 // Check if the FLUENT_ROADMAP constant is defined
180 if (!defined('FLUENT_ROADMAP')) {
181 $relatedBoardsQuery = Board::whereNull('archived_at')->where('type', 'to-do')->byAccessUser($userId);
182 } else {
183 $relatedBoardsQuery = Board::whereNull('archived_at')->byAccessUser($userId);
184 }
185
186 $relatedBoards = $relatedBoardsQuery->with('stages')->get();
187
188 // Fetch the stages associated with the boards
189 $stages = Stage::whereIn('board_id', $relatedBoards->pluck('id'))->where('archived_at', null)->get();
190
191 return $this->sendSuccess([
192 'boards' => $relatedBoards,
193 'all_stages' => $stages,
194 ], 200);
195 }
196 public function getOnlyBoardsByUser(Request $request)
197 {
198 try {
199 $userId = get_current_user_id();
200
201 $searchInput = $request->getSafe('searchInput', 'sanitize_text_field');
202
203
204 if(!defined('FLUENT_ROADMAP'))
205 {
206 $relatedBoardsQuery = Board::whereNull('archived_at')->where('type', 'to-do')->byAccessUser($userId);
207 } else {
208 $relatedBoardsQuery = Board::whereNull('archived_at')->byAccessUser($userId);
209 }
210
211 if (!empty($searchInput)) {
212 $relatedBoardsQuery = $relatedBoardsQuery->where('title', 'like', '%' . $searchInput . '%');
213 }
214
215 $relatedBoards = $relatedBoardsQuery->orderBy('created_at', 'DESC')->get();
216
217 return $this->sendSuccess([
218 'boards' => $relatedBoards
219 ]);
220 } catch (\Exception $e) {
221 return $this->sendError([
222 'message' => $e->getMessage()
223 ]);
224 }
225 }
226
227 public function getRecentBoards()
228 {
229 $boards = $this->boardService->getRecentBoards();
230
231 if (!$boards || $boards->isEmpty()) {
232 $boards = Board::where('type', 'to-do')->byAccessUser(get_current_user_id())
233 ->limit(4)
234 ->withCount('completedTasks')
235 ->with(['stages', 'users'])
236 ->get();
237 }
238
239 foreach ($boards as $board) {
240 $board->users = Helper::sanitizeUserCollections($board->users);
241 $board->is_pinned = $this->boardService->isPinned($board->id);
242 }
243
244 return [
245 'boards' => $boards,
246 ];
247 }
248
249 /*
250 * TODO: Refactor this method , remove this
251 */
252 public function getBoardsByType($type)
253 {
254 $boards = $this->boardService->getBoardsByType($type);
255
256 return $this->sendSuccess([
257 'boards' => $boards,
258 ]);
259 }
260
261 public function createFirstBoard(Request $request)
262 {
263 $boardData = $this->boardSanitizeAndValidate($request->get('board'), [
264 'title' => 'required|string',
265 'description' => 'nullable',
266 'type' => 'required|string',
267 'currency' => 'nullable|string',
268 'crm_contact_id' => 'nullable|numeric',
269 ]);
270
271 $installFluentCRM = $request->getSafe('withFluentCRM', 'sanitize_text_field') == 'yes' ? true : false;
272
273 $postStages = $request->get('stages');
274 if (!is_array($postStages)) {
275 $postStages = [];
276 }
277 $stageData = array();
278 foreach ($postStages as $stage) {
279 $temp = $this->stageSanitizeAndValidate($stage, [
280 'title' => 'required|string',
281 ]);
282 $stageData[] = $temp;
283 }
284
285 $taskData = null;
286 if ($request->get('task')) {
287 $taskData = $this->taskSanitizeAndValidate($request->get('task'), [
288 'title' => 'required|string',
289 ]);
290 }
291
292 $board = $this->boardService->createBoard($boardData);
293 $this->labelService->createDefaultLabel($board->id);
294 $type = ucfirst($boardData['type']);
295 $stage = $this->stageService->createStages($board, $stageData);
296
297 if ($taskData) {
298 $taskData['board_id'] = $board->id;
299 $taskData['stage_id'] = $stage->id;
300 $this->taskService->createTask($taskData, $board->id);
301 }
302
303 do_action('fluent_boards/board_created', $board);
304
305 if ($installFluentCRM && !defined('FLUENTCRM')) {
306 InstallService::install('fluent-crm');
307 }
308
309 return [
310 'message' => __('Board has been created', 'fluent-boards'),
311 'board' => $board,
312 ];
313 }
314
315 public function skipOnboarding(Request $request)
316 {
317 $onboarding = Meta::where('key', Constant::FBS_ONBOARDING)->first();
318 if($onboarding && $onboarding->value == 'no'){
319 $onboarding->value = 'yes' ;
320 $onboarding->save();
321 }
322
323 return [
324 'message' => __('Onboarding skipped successfully', 'fluent-boards'),
325 ];
326 }
327
328 public function create(Request $request)
329 {
330 $boardData = $this->boardSanitizeAndValidate($request->get('board'), [
331 'title' => 'required|string',
332 'description' => 'nullable',
333 'type' => 'required|string',
334 'currency' => 'nullable|string',
335 'crm_contact_id' => 'nullable|numeric',
336 'folder_id' => 'nullable',
337 ]);
338
339 try {
340 $board = $this->boardService->createBoard($boardData);
341 $this->labelService->createDefaultLabel($board->id);
342 $type = ucfirst($boardData['type']);
343
344 if (isset($boardData['type']) && $boardData['type'] == 'roadmap') {
345 $stages = $request->get('stages');
346 $sanitizedStages = [];
347 if (is_array($stages) && !empty($stages)) {
348 foreach ($stages as $stage) {
349 $sanitizedStages[] = $this->stageSanitizeAndValidate($stage, [
350 'title' => 'required|string',
351 'slug' => 'nullable|string',
352 'position' => 'nullable|numeric'
353 ]);
354 }
355 }
356 $this->stageService->createRoadmapStages($board, $sanitizedStages);
357 } else {
358 $this->stageService->createDefaultStages($board);
359 }
360
361 // if board is created from crm contact
362 if (isset($boardData['crm_contact_id'])) {
363 $this->boardService->updateAssociateMember($boardData['crm_contact_id'], $board->id);
364 }
365
366 do_action('fluent_boards/board_created', $board);
367
368
369 if(defined('FLUENT_BOARDS_PRO')) {
370 $folderId = $request->getSafe('folder_id', 'intval');
371 if ($folderId) {
372 (new \FluentBoardsPro\App\Services\FolderService())->addBoardToFolder($folderId, [$board->id]);
373 }
374 }
375
376 $message = __('Board has been created successfully', 'fluent-boards');
377
378 return $this->send([
379 'message' => $message,
380 'board' => $board,
381 ], 201);
382 } catch (\Exception $e) {
383 return $this->sendError([
384 'message' => $e->getMessage()
385 ]);
386 }
387 }
388
389 public function getArchivedStage(Request $request, $board_id)
390 {
391 try {
392 $pagination = $request->getSafe('noPagination', 'boolval', false);
393 $per_page = $request->getSafe('per_page', 'intval', 30);
394 $page = $request->getSafe('page', 'intval', 1);
395
396 if ($pagination) {
397 $stages = Stage::where('board_id', $board_id)
398 ->whereNotNull('archived_at')
399 ->orderBy('created_at', 'DESC')
400 ->get();
401 } else {
402 $stages = Stage::where('board_id', $board_id)
403 ->whereNotNull('archived_at')
404 ->orderBy('created_at', 'DESC')
405 ->paginate($per_page, ['*'], 'page', $page);
406 }
407
408 return $this->sendSuccess([
409 'stages' => $stages,
410 ], 200);
411 } catch (\Exception $e) {
412 return $this->sendError($e->getMessage(), 404);
413 }
414 }
415
416 public function find($board_id)
417 {
418 $board = Board::findOrFail($board_id);
419 $board->background = maybe_unserialize($board->background);
420 $board->createdOn = $board->created_at->format('Y-m-d');
421
422 $board->load(['users', 'stages', 'labels', 'owner']);
423
424 if (defined('FLUENT_BOARDS_PRO')){
425 $customFiledPositionMeta = $board->getMetaByKey('custom_field_positions');
426 if(!$customFiledPositionMeta) {
427 (new CustomFieldService())->reIndexCustomFieldPositions($board_id);
428 $board->updateMeta('custom_field_positions', 'yes');
429 }
430
431 $board->load(['customFields']);
432 }
433
434 $this->boardService->updateRecentBoards($board_id);
435
436 $board->labelColor = Constant::TRELLO_COLOR_MAP;
437 $board->labelColorText = Constant::TEXT_COLOR_MAP;
438
439 $board->users = Helper::sanitizeUserCollections($board->users);
440 $board->owner = Helper::sanitizeUserCollections($board->owner);
441
442 $board->is_pinned = $this->boardService->isPinned($board->id);
443
444 $board = apply_filters('fluent_boards/board_find', $board);
445
446 return [
447 'board' => $board
448 ];
449 }
450
451 public function update(Request $request, $board_id)
452 {
453 $boardData = $this->boardSanitizeAndValidate($request->only(['title', 'description']), [
454 'title' => 'required|string',
455 'description' => 'nullable|string',
456 ]);
457
458 $board = Board::findOrFail($board_id);
459
460 $oldBoard = clone $board;
461 $board->fill($boardData);
462 $board->save();
463
464 do_action('fluent_boards/board_updated', $board, $oldBoard);
465
466 return [
467 'message' => __('Board has been updated', 'fluent-boards'),
468 'board' => $board,
469 'stages' => $board->stages()->get(),
470 ];
471 }
472
473 public function archiveStage($board_id, $stage_id)
474 {
475 try {
476 $stage = Stage::findOrFail($stage_id);
477 $board = Board::findOrFail($stage->board_id);
478
479 $updatedStage = $this->boardService->archiveStage($board->id, $stage);
480
481 return $this->sendSuccess([
482 'updatedStage' => $updatedStage,
483 'message' => __('Stage has been archived', 'fluent-boards'),
484 ], 200);
485 } catch (\Exception $e) {
486 return $this->sendError($e->getMessage(), 400);
487 }
488 }
489
490 public function restoreStage($board_id, $stage_id)
491 {
492 try {
493 $stage = Stage::findOrFail($stage_id);
494 $board = Board::findOrFail($board_id);
495
496 $updatedStage = $this->boardService->restoreStage($board->id, $stage);
497
498 return $this->sendSuccess([
499 'success' => true,
500 'updatedStage' => $updatedStage,
501 'message' => __('Stage has been restored', 'fluent-boards')
502 ], 200);
503 } catch (\Exception $e) {
504 return $this->sendError($e->getMessage(), 400);
505 }
506 }
507
508
509 public function repositionStages(Request $request, $board_id)
510 {
511 $incomingList = $request->get('list');
512 if (!is_array($incomingList)) {
513 $incomingList = [];
514 }
515 $incomingList = array_map('intval', $incomingList);
516 try {
517 $this->boardService->repositionStages($board_id, $incomingList);
518 return $this->sendSuccess([
519 'message' => __('Stages Reordered', 'fluent-boards'),
520 'updatedStages' => $this->stageService->getLastOneMinuteUpdatedStages($board_id)
521 ], 200);
522 } catch (\Exception $e) {
523 return $this->sendError($e->getMessage(), 400);
524 }
525 }
526
527 public function getAssigneesByBoard($board_id)
528 {
529 return $this->sendSuccess([
530 'data' => $this->boardService->getAssigneesByBoard($board_id),
531 ], 200);
532 }
533
534 public function delete($board_id)
535 {
536 try {
537 if (!PermissionManager::isAdmin()) {
538 throw new \Exception(esc_html__('You do not have permission to delete this board', 'fluent-boards'), 400);
539 }
540 $this->boardService->deleteBoard($board_id);
541
542 return $this->sendSuccess([
543 'message' => __('Board has been deleted', 'fluent-boards'),
544 ], 200);
545 } catch (\Exception $e) {
546 return $this->sendError($e->getMessage(), 400);
547 }
548 }
549
550 public function getCurrencies()
551 {
552 return BoardHandler::getCurrencies();
553 }
554
555 public function getActivities(Request $request, $board_id)
556 {
557 try {
558 $activities = $this->boardService->getActivities($board_id, [
559 'per_page' => $request->getSafe('per_page', 'intval', 40),
560 'page' => $request->getSafe('page', 'intval', 1),
561 ]);
562 return $this->sendSuccess([
563 'activities' => $activities,
564 ], 200);
565 } catch (\Exception $e) {
566 return $this->sendError($e->getMessage(), 404);
567 }
568 }
569
570 /*
571 * TODO: Refactor this method - for Masiur
572 */
573 public function getBoardUsers($board_id)
574 {
575 $board = Board::findOrFail($board_id);
576
577 $boardObjects = Relation::where('object_type', 'board_user')
578 ->where('object_id', $board_id)
579 ->get()->keyBy('foreign_id');
580
581 $superAdminIds = Meta::query()->where('object_type', Constant::FLUENT_BOARD_ADMIN)
582 ->get()->pluck('object_id')->toArray();
583
584 $userIds = $boardObjects->pluck('foreign_id')->toArray();
585
586 $coreUsers = [];
587 if ($userIds) {
588 // Get the users who are in the board (members and managers
589 $coreUsers = get_users([
590 'include' => $userIds
591 ]);
592 }
593
594 $formattedUsers = [];
595
596 foreach ($coreUsers as $user) {
597 $name = trim($user->first_name . ' ' . $user->last_name);
598 if (!$name) {
599 $name = $user->display_name;
600 }
601
602 $boardRelation = $boardObjects[$user->ID] ?? null;
603
604
605 $formattedUsers[] = [
606 'ID' => $user->ID,
607 'display_name' => $name,
608 'user_login' => $user->user_login,
609 'email' => $user->user_email,
610 'photo' => fluent_boards_user_avatar($user->user_email, $name),
611 'role' => $this->boardUserRole($boardRelation),
612 'is_super' => in_array($user->ID, $superAdminIds),
613 'is_wpadmin' => $user->has_cap('manage_options')
614 ];
615 }
616
617 // order formatted users by display_name
618 usort($formattedUsers, function ($a, $b) {
619 return strcmp($a['display_name'], $b['display_name']);
620 });
621
622 $returnData = [
623 'users' => Helper::sanitizeUsersArray($formattedUsers, $board_id),
624 'global_admins' => []
625 ];
626
627 if (!PermissionManager::isAdmin(get_current_user_id())) {
628 return $returnData;
629 }
630
631 /*
632 * These are the rest of the admin users who are not in the board
633 */
634 $adminUserIds = Meta::query()->where('object_type', Constant::FLUENT_BOARD_ADMIN)
635 ->whereNotIn('object_id', $userIds)
636 ->get()
637 ->pluck('object_id')
638 ->toArray();
639
640 if ($adminUserIds) {
641 $adminUsers = get_users([
642 'include' => $adminUserIds,
643 ]);
644
645 $formattedAdminUsers = [];
646
647 foreach ($adminUsers as $user) {
648 $name = trim($user->first_name . ' ' . $user->last_name);
649 if (!$name) {
650 $name = $user->display_name;
651 }
652
653 $formattedAdminUsers[] = [
654 'ID' => $user->ID,
655 'display_name' => $name,
656 'email' => $user->user_email,
657 'photo' => fluent_boards_user_avatar($user->user_email, $name),
658 'role' => 'admin',
659 'is_super' => in_array($user->ID, $superAdminIds),
660 'is_wpadmin' => $user->has_cap('manage_options')
661 ];
662 }
663
664 // order formatted users by display_name
665 usort($formattedAdminUsers, function ($a, $b) {
666 return strcmp($a['display_name'], $b['display_name']);
667 });
668
669 $returnData['global_admins'] = Helper::sanitizeUsersArray($formattedAdminUsers, $board_id);
670 }
671
672 return $this->sendSuccess($returnData, 200);
673 }
674
675
676 public function removeUserFromBoard($board_id, $userId)
677 {
678 $this->boardService->removeUserFromBoard($board_id, $userId);
679
680 if (!PermissionManager::isAdmin($userId)) {
681 $this->boardService->removeFromRecentlyOpened($board_id, $userId);
682 }
683
684 return [
685 'message' => __('Member removed successfully', 'fluent-boards'),
686 ];
687 }
688
689 public function addMembersInBoard(Request $request, $board_id)
690 {
691 $memberId = $request->getSafe('memberId');
692 $isViewerOnly = $request->getSafe('isViewerOnly');
693 $member = $this->boardService->addMembersInBoard($board_id, $memberId, $isViewerOnly);
694 if (!$member) {
695 return $this->sendError([
696 'message' => __('User already a member', 'fluent-boards'),
697 ], 304);
698 }
699
700
701 return [
702 'message' => __('Member added successfully', 'fluent-boards'),
703 'member' => Helper::sanitizeUserCollections($member)
704 ];
705 }
706
707 private function boardSanitizeAndValidate($data, array $rules = [])
708 {
709 $data = Helper::sanitizeBoard($data);
710
711 return $this->validate($data, $rules);
712 }
713
714 private function stageSanitizeAndValidate($data, array $rules = [])
715 {
716 $data = Helper::sanitizeStage($data);
717
718 return $this->validate($data, $rules);
719 }
720
721 private function taskSanitizeAndValidate($data, array $rules = [])
722 {
723 $data = Helper::sanitizeTask($data);
724
725 return $this->validate($data, $rules);
726 }
727
728 public function searchBoards(Request $request)
729 {
730 $per_page = $request->getSafe('per_page', 'intval', 10);
731 $search_input = $request->getSafe('searchInput', 'sanitize_text_field', '');
732 $type = $request->getSafe('type', 'sanitize_text_field', 'to-do');
733
734 $currentUserId = get_current_user_id();
735
736 if (PermissionManager::isAdmin($currentUserId)) {
737 $boards = Board::query()->where('type', $type)
738 ->where('title', 'like', '%' . $search_input . '%')
739 ->with('stages', 'tasks', 'users')
740 ->paginate($per_page);
741
742 foreach ($boards as $board) {
743 $board->users = Helper::sanitizeUserCollections($board->users);
744 }
745
746 } else {
747 $currentUser = User::find($currentUserId);
748 $boards = $currentUser->boards()->where('type', $type)->where('title', 'like', '%' . $search_input . '%')->paginate($per_page);
749 }
750
751 return [
752 'boards' => $boards,
753 ];
754 }
755
756 public function getUsersOfBoards()
757 {
758 $userBoards = $this->boardService->getUsersOfBoards();
759
760 return $this->sendSuccess([
761 'userBoards' => $userBoards,
762 ], 200);
763 }
764
765
766
767 /**
768 * Refactor this code form me - Masiur
769 * change stage settings is_public for roadmap user and admin view
770 * @param $board_id
771 * @param $stage_id
772 * @return
773 */
774 public function changeStageView($board_id, $stage_id)
775 {
776 try {
777 $stage = Stage::findOrFail($stage_id);
778 $message = __('The stage is made public!', 'fluent-boards');
779 $settings = $stage->settings;
780
781 if (isset($settings['is_public'])) {
782 if ($settings['is_public']) {
783 $settings['is_public'] = false;
784 $message = __('The stage is made admin only!', 'fluent-boards');
785 } else {
786 $settings['is_public'] = true;
787 }
788 } else {
789 $settings['is_public'] = true;
790 }
791
792 $stage->settings = $settings;
793 $stage->save();
794 return $this->sendSuccess([
795 'message' => $message,
796 'stage' => $stage
797 ]);
798 } catch (\Exception $e) {
799 return $this->sendError($e->getMessage(), 400);
800 }
801 }
802
803
804 /**
805 * Set board background image or color
806 * @param \FluentBoards\Framework\Http\Request\Request $request
807 * @return
808 */
809 public function setBoardBackground(Request $request, $board_id)
810 {
811 // sanitize and validate image_url
812 if ($request->image_url) {
813 $backgroundData = $this->boardSanitizeAndValidate($request->all(), [
814 "id" => 'required',
815 'image_url' => 'required|string|url',
816 ]);
817 }
818
819 // sanitize and validate color
820 if ($request->color) {
821 $backgroundData = $this->boardSanitizeAndValidate($request->all(), [
822 "id" => 'required',
823 'color' => 'required',
824 ]);
825 }
826
827 try {
828 if (!$board_id) {
829 $errorMessage = __('Board id is required', 'fluent-boards');
830 throw new \Exception(esc_html($errorMessage), 400);
831 }
832
833 return $this->sendSuccess([
834 'message' => __('Background updated successfully', 'fluent-boards'),
835 'background' => $this->boardService->setBoardBackground($backgroundData, $board_id),
836 ]);
837 } catch (\Exception $e) {
838 return $this->sendError($e->getMessage(), 400);
839 }
840 }
841
842
843 /**
844 * Summary of getStageTaskAvailablePositions
845 * @param mixed $board_id
846 * @param mixed $stage_slug
847 * @return $availablePositions as an array
848 * @throws \Exception
849 */
850 public function getStageTaskAvailablePositions($board_id, $stage_id)
851 {
852 try {
853 if ($board_id && $stage_id) {
854 $availablePositions = $this->boardService->getStageTaskAvailablePositions($board_id, $stage_id);
855 return $this->sendSuccess([
856 'availablePositions' => $availablePositions
857 ], 200);
858 } else {
859 $message = '';
860 if (!$board_id) {
861 $message = 'Board id ';
862 }
863 if (!$stage_id) {
864 $message = 'Stage ';
865 }
866 throw new \Exception(esc_html($message . 'is required'), 400);
867 }
868 } catch (\Exception $e) {
869 return $this->sendError($e->getMessage(), 400);
870 }
871 }
872
873 public function getAssociateCrmContacts($board_id)
874 {
875 try {
876 $contactAssociatedTasks = Task::with('board')->where('board_id', $board_id)
877 ->whereNotNull('crm_contact_id')
878 ->get();
879
880 $formattedContacts = Collection::make($contactAssociatedTasks)
881 ->groupBy('crm_contact_id')
882 ->map(function ($tasks, $contactId) {
883 $subscriber = Subscriber::find($contactId);
884 if (!$subscriber) {
885 return null; // Skip if subscriber not found
886 }
887
888 return [
889 'name' => $subscriber->first_name . ' ' . $subscriber->last_name,
890 'photo' => $subscriber->photo,
891 'email' => $subscriber->email,
892 'crm_contact_id' => $contactId,
893 'id' => $contactId,
894 'tasks' => $tasks,
895 ];
896 })
897 ->filter()->toArray();
898
899
900 return $this->sendSuccess([
901 'associatedContacts' => $formattedContacts
902 ], 200);
903 } catch (\Exception $e) {
904 return $this->sendError($e->getMessage(), 404);
905 }
906 }
907
908 public function updateAssociateCrmContact(Request $request, $board_id)
909 {
910 $value = $request->getSafe('value');
911 $this->boardService->updateAssociateMember($value, $board_id);
912
913 return $this->sendSuccess([
914 'message' => __('Associated Crm Member has been updated', 'fluent-boards'),
915 ], 200);
916 }
917
918 public function hasDataChanged($board_id)
919 {
920 return $this->boardService->hasDataChanged($board_id);
921 }
922
923 public function createStage(Request $request, $board_id)
924 {
925 $stageData = $this->stageSanitizeAndValidate($request->all(), [
926 'title' => 'required|string',
927 'position' => 'nullable|numeric'
928 ]);
929
930 $board = Board::find($board_id);
931 $stage = $this->stageService->createStage($stageData, $board_id);
932
933 do_action('fluent_boards/board_stage_added', $board, $stage);
934
935 $updatedStates = (new StageService())->getLastOneMinuteUpdatedStages($board_id);
936
937 return [
938 'updatedStages' => $updatedStates,
939 'message' => __('stage has been created', 'fluent-boards'),
940 ];
941 }
942
943 public function moveAllTasks(Request $request, $board_id)
944 {
945 $oldStageId = $request->getSafe('oldStageId', 'intval');
946 $newStageId = $request->getSafe('newStageId', 'intval');
947
948 if (!$oldStageId || !$newStageId) {
949 return $this->sendError(__('Invalid stage IDs provided', 'fluent-boards'), 400);
950 }
951
952 // Verify stages exist and belong to the board
953 $oldStage = Stage::where('id', $oldStageId)->where('board_id', $board_id)->first();
954 $newStage = Stage::where('id', $newStageId)->where('board_id', $board_id)->first();
955
956 if (!$oldStage || !$newStage) {
957 return $this->sendError(__('One or both stages do not exist or do not belong to this board', 'fluent-boards'), 400);
958 }
959
960 $updates = $this->stageService->moveAllTasks($oldStageId, $newStageId, $board_id);
961
962 return [
963 'message' => __('Tasks have been moved', 'fluent-boards'),
964 'updatedTasks' => $updates,
965 ];
966
967 }
968
969 public function archiveAllTasksInStage($board_id, $stage_id)
970 {
971 $updates = $this->stageService->archiveAllTasksInStage($stage_id, $board_id);
972 return [
973 'message' => __('Tasks have been archived', 'fluent-boards'),
974 'updatedTasks' => $updates,
975 ];
976 }
977
978 public function getAssociatedBoards(Request $request, $associated_id)
979 {
980 $associatedBoards = $this->boardService->getAssociatedBoards($associated_id);
981 return [
982 'boards' => $associatedBoards,
983 ];
984 }
985
986 public function duplicateBoard(Request $request, $board_id)
987 {
988 $boardData = $this->taskSanitizeAndValidate($request->get('board'), [
989 'title' => 'required|string'
990 ]);
991
992 $boardData['source_board_id'] = $board_id;
993
994 $isWithLabels = $request->getSafe('isWithLabels');
995 $isWithTasks = $request->getSafe('isWithTasks');
996 $isWithTemplates = $request->getSafe('isWithTemplates');
997
998 try {
999 if(!PermissionManager::isAdmin()) {
1000 $errorMessage = __('You do not have permission to duplicate board', 'fluent-boards');
1001 throw new \Exception(esc_html($errorMessage), 400);
1002 }
1003 //create board
1004 $newBoard = $this->boardService->copyBoard($boardData);
1005
1006 //label copy
1007 $labelMap = [];
1008
1009 if ($isWithLabels == 'yes') {
1010 $labelMap = $this->labelService->copyLabelsOfBoard($board_id, $newBoard);
1011 }
1012
1013 //stage copy
1014 $stageMapForCopyingTask = $this->stageService->copyStagesOfBoard($newBoard, $board_id, $isWithTemplates);
1015
1016 //copy tasks of selected stages
1017 if ($isWithTasks == 'yes') {
1018 $this->taskService->copyTasks($board_id, $stageMapForCopyingTask, $newBoard, $labelMap,$isWithTemplates);
1019 }
1020
1021 return $this->sendSuccess([
1022 'board' => $newBoard,
1023 ], 200);
1024 } catch (\Exception $e) {
1025 return $this->sendError($e->getMessage(), 400);
1026 }
1027 }
1028
1029 public function importFromBoard(Request $request, $board_id)
1030 {
1031 $selectedStages = $request->getSafe('selectedStages');
1032 $position = $request->getSafe('position', 'intval');
1033
1034 // Validate and sanitize selectedStages array
1035 if (!is_array($selectedStages)) {
1036 $selectedStages = [$selectedStages];
1037 }
1038 $selectedStages = array_filter(array_map('intval', $selectedStages));
1039
1040 try {
1041 $this->stageService->importStagesFromBoard($board_id, $selectedStages, $position);
1042
1043 return $this->sendSuccess([
1044 'message' => __('Import successfully', 'fluent-boards'),
1045 ], 200);
1046
1047 } catch (\Exception $e) {
1048 return $this->sendError($e->getMessage(), 400);
1049 }
1050 }
1051
1052 public function getBoardDefaultBackgroundColors()
1053 {
1054 return [
1055 'solidColors' => Constant::BOARD_BACKGROUND_DEFAULT_SOLID_COLORS,
1056 'gradients' => Constant::BOARD_BACKGROUND_DEFAULT_GRADIENT_COLORS
1057 ];
1058 }
1059
1060 /*
1061 * TODO: For Masiur - I will update this later
1062 */
1063 public function updateBoardProperties(Request $request, $board_id)
1064 {
1065 $pageId = $request->getSafe('page_id');
1066 $enable_stage_change_email = $request->getSafe('enable_stage_change_email');
1067
1068 $board = Board::findOrFail($board_id);
1069
1070 $board->updateMeta('roadmap_page_id', $pageId);
1071 $board->updateMeta('enable_stage_change_email', $enable_stage_change_email);
1072
1073 $board = $board->fresh();
1074
1075 return [
1076 'message' => __('Board has been updated', 'fluent-boards'),
1077 'board' => apply_filters('fluent_boards/board_find', $board)
1078 ];
1079 }
1080
1081 public function archiveBoard($board_id)
1082 {
1083 try {
1084 $board = $this->boardService->archiveBoard($board_id);
1085
1086 return [
1087 'board' => $board,
1088 'message' => __('Board has been archived successfully!', 'fluent-boards')
1089 ];
1090 } catch (\Exception $e) {
1091 return $this->sendError($e->getMessage(), 400);
1092 }
1093 }
1094
1095 public function restoreBoard($board_id)
1096 {
1097 try {
1098 $board = $this->boardService->restoreBoard($board_id);
1099
1100 return [
1101 'board' => $board,
1102 'message' => __('Board has been restored successfully!', 'fluent-boards')
1103 ];
1104 } catch (\Exception $e) {
1105 return $this->sendError($e->getMessage(), 400);
1106 }
1107 }
1108
1109 private function boardUserRole($boardRelation)
1110 {
1111 return $boardRelation && Arr::get($boardRelation->settings, 'is_admin')
1112 ? 'manager'
1113 : ($boardRelation && Arr::has($boardRelation->settings, 'is_viewer_only') && Arr::get($boardRelation->settings, 'is_viewer_only')
1114 ? 'viewer'
1115 : 'member');
1116 }
1117 public function uploadBoardBackground(Request $request,$board_id)
1118 {
1119 $file = Arr::get($request->files(), 'file')->toArray();
1120 (new \FluentBoards\App\Services\UploadService)->validateFile($file);
1121
1122 $uploadInfo = UploadService::handleFileUpload( $request->files(), $board_id);
1123
1124 $fileData = $uploadInfo[0];
1125 $initialDataData = [
1126 'type' => 'url',
1127 'url' => '',
1128 'name' => '',
1129 'size' => 0,
1130 ];
1131
1132 $attachData = array_merge($initialDataData, $fileData);
1133 $UrlMeta = [];
1134 if($attachData['type'] == 'url') {
1135 $UrlMeta = RemoteUrlParser::parse($attachData['url']);
1136 }
1137 $uid = wp_generate_uuid4();
1138 $fileUploadedData = new Attachment();
1139 $fileUploadedData->object_id = $board_id;
1140 $fileUploadedData->object_type = Constant::BOARD_BACKGROUND_IMAGE;
1141 $fileUploadedData->attachment_type = $attachData['type'];
1142 $fileUploadedData->title = (new TaskService())->setTitle($attachData['type'], $attachData['name'], $UrlMeta);
1143 $fileUploadedData->file_path = $attachData['type'] != 'url' ? $attachData['file'] : null;
1144 $fileUploadedData->full_url = esc_url($attachData['url']);
1145 $fileUploadedData->file_size = $attachData['size'];
1146 $fileUploadedData->settings = $attachData['type'] == 'url' ? [
1147 'meta' => $UrlMeta
1148 ] : '';
1149 $fileUploadedData->driver = 'local';
1150 $fileUploadedData->file_hash = md5($uid . wp_rand(0, 1000));
1151 $fileUploadedData->save();
1152 if(!!defined('FLUENT_BOARDS_PRO_VERSION')) {
1153 $mediaData = (new AttachmentService())->processMediaData($fileData, $file);
1154 $fileUploadedData['driver'] = $mediaData['driver'];
1155 $fileUploadedData['file_path'] = $mediaData['file_path'];
1156 $fileUploadedData['full_url'] = $mediaData['full_url'];
1157 $fileUploadedData->save();
1158 }
1159
1160 $board = Board::find($board_id);
1161 $oldBackground = $board->background;
1162 $publicUrl = (new CommentService())->createPublicUrl($fileUploadedData, $board_id);
1163 $background = [
1164 'color' => null,
1165 'id' => $fileUploadedData->id,
1166 'image_url' => $publicUrl,
1167 'is_image' => true,
1168 ];
1169 $board->background = $background;
1170 $board->save();
1171 do_action('fluent_boards/board_background_updated', $board_id, $oldBackground);
1172
1173 return $this->sendSuccess([
1174 'message' => __('Background updated successfully', 'fluent-boards'),
1175 'background' => $board->background,
1176 ]);
1177 }
1178
1179 public function getPinnedBoards()
1180 {
1181 $pinnedBoards = $this->boardService->getPinnedBoards();
1182
1183 return $this->sendSuccess([
1184 'pinnedBoards' => $pinnedBoards,
1185 ], 200);
1186 }
1187
1188 public function pinBoard($boardId)
1189 {
1190 $this->boardService->pinBoard($boardId);
1191
1192 return $this->sendSuccess([
1193 'message' => __('The Board has been pinned', 'fluent-boards'),
1194 ], 200);
1195 }
1196
1197 public function unpinBoard($boardId)
1198 {
1199 $remove = $this->boardService->unpinBoard($boardId);
1200
1201 if (!$remove) {
1202 return $this->sendError([
1203 'message' => __('Board is not pinned', 'fluent-boards'),
1204 ], 400);
1205 }
1206
1207 return $this->sendSuccess([
1208 'message' => __('Board is removed from pinned boards', 'fluent-boards'),
1209 ], 200);
1210 }
1211
1212 public function getBoardFolder($board_id)
1213 {
1214 try {
1215 $folder = $this->boardService->getBoardFolder($board_id);
1216 return $this->sendSuccess([
1217 'folder' => $folder,
1218 ], 200);
1219 } catch (\Exception $e) {
1220 return $this->sendError($e->getMessage(), 400);
1221 }
1222 }
1223
1224 public function getBoardMenuItems($board_id)
1225 {
1226 try {
1227 $menuItems = (new BoardMenuHandler())->getMenuItems($board_id);
1228
1229 return $this->sendSuccess([
1230 'menu_items' => $menuItems
1231 ], 200);
1232 } catch (\Exception $e) {
1233 return $this->sendError($e->getMessage(), 500);
1234 }
1235 }
1236
1237 }
1238