PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / trunk
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration vtrunk
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 1.45 All 41 releases
fluent-boards / app / Http / Controllers / BoardController.php

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

1,500 lines 51.5 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\DescriptionMarkdownConverter;
14 use FluentBoards\App\Services\Helper;
15 use FluentBoards\App\Models\Stage;
16 use FluentBoards\App\Services\InstallService;
17 use FluentBoards\App\Services\StageService;
18 use FluentBoards\App\Services\TaskService;
19 use FluentBoards\App\Services\BoardService;
20 use FluentBoards\App\Services\FolderService;
21 use FluentBoards\App\Services\UploadService;
22 use FluentBoards\Framework\Http\Request\Request;
23 use FluentBoards\App\Services\PermissionManager;
24 use FluentBoards\App\Services\PublicAccessService;
25 use FluentBoards\App\Hooks\Handlers\BoardHandler;
26 use FluentBoards\App\Hooks\Handlers\BoardMenuHandler;
27 use FluentBoards\App\Services\LabelService;
28 use FluentBoards\Framework\Support\Arr;
29 use FluentBoards\Framework\Support\Collection;
30 use FluentBoardsPro\App\Services\AttachmentService;
31 use FluentBoardsPro\App\Services\CustomFieldService;
32 use FluentBoardsPro\App\Services\RemoteUrlParser;
33
34 class BoardController extends Controller
35 {
36 private const LEGACY_BOARD_ASSOCIATED_CRM_CONTACT = 'crm_contact';
37
38 private $boardService;
39 private $taskService;
40 private $stageService;
41 private $labelService;
42
43 public function __construct(
44 BoardService $boardService,
45 TaskService $taskService,
46 StageService $stageService,
47 LabelService $labelService
48 )
49 {
50 parent::__construct();
51 $this->boardService = $boardService;
52 $this->taskService = $taskService;
53 $this->stageService = $stageService;
54 $this->labelService = $labelService;
55 }
56
57 public function getBoards(Request $request)
58 {
59 $per_page = $request->getSafe('per_page', 'intval', 100);
60 $per_page = max(1, min(100, $per_page));
61 $userId = get_current_user_id();
62 $type = $request->getSafe('type', 'sanitize_text_field', 'to-do');
63
64 $order = $request->getSafe('order', 'sanitize_text_field', 'created_at');
65 $orderBy = $request->getSafe('orderBy', 'sanitize_text_field', 'DESC');
66 $searchInput = $request->getSafe('searchInput', 'sanitize_text_field');
67
68 $option = $request->getSafe('option', 'sanitize_text_field');
69 $folderId = $request->getSafe('fid', 'intval'); // Get folder ID from request
70
71 // Initialize the query based on archive status
72 if (!defined('FLUENT_ROADMAP')) {
73 if ($option == 'archived') {
74 $relatedBoardsQuery = Board::whereNotNull('archived_at')->where('type', 'to-do')->byAccessUser($userId);
75 } else {
76 $relatedBoardsQuery = Board::whereNull('archived_at')->where('type', 'to-do')->byAccessUser($userId);
77 }
78 } else {
79 if ($option == 'archived') {
80 $relatedBoardsQuery = Board::whereNotNull('archived_at')->byAccessUser($userId);
81 } else {
82 $relatedBoardsQuery = Board::whereNull('archived_at')->byAccessUser($userId);
83 }
84 }
85
86 // Scope pinned before pagination, from the same id source as getBoardCounts(),
87 // so the pinned page and board_counts.pinned always agree. Filtering pinned
88 // after pagination would drop pinned boards that fall on a later active page.
89 if ($option == 'pinned') {
90 $pinnedIds = $this->boardService->getPinnedBoardIds();
91
92 $relatedBoardsQuery = $pinnedIds
93 ? $relatedBoardsQuery->whereIn('id', $pinnedIds)
94 : $relatedBoardsQuery->where('id', 0);
95 }
96
97 if ($folderId) {
98 $boardIds = (new FolderService())->getBoardIdsByFolder($folderId);
99 $relatedBoardsQuery = $boardIds
100 ? $relatedBoardsQuery->whereIn('id', $boardIds)
101 : $relatedBoardsQuery->where('id', 0);
102 }
103
104 // Filter out boards that are templates (exclude boards where settings->is_template is true)
105 $relatedBoardsQuery = $relatedBoardsQuery->excludeTemplates();
106
107 // Add search functionality
108 if (!empty($searchInput)) {
109 $relatedBoardsQuery = $relatedBoardsQuery->where('title', 'like', '%' . $searchInput . '%');
110 }
111
112 $relatedBoards = $relatedBoardsQuery->orderBy($order, $orderBy)
113 ->withCount('completedTasks')
114 ->with('stages', 'users')
115 ->paginate($per_page);
116
117 foreach ($relatedBoards as $relatedBoard) {
118 $relatedBoard->description = DescriptionMarkdownConverter::normalize($relatedBoard->description);
119 $relatedBoard->users = Helper::sanitizeUserCollections($relatedBoard->users);
120 $relatedBoard->is_pinned = $this->boardService->isPinned($relatedBoard->id);
121 }
122
123 $response = [
124 'boards' => $relatedBoards,
125 'board_counts' => $this->boardService->getBoardCounts($userId)
126 ];
127
128 $folderMapping = $this->getBoardFolderMapping($userId);
129 $response['folder_mapping'] = $folderMapping;
130 if ($folderId) {
131 $response['current_folder'] = isset($folderMapping[$folderId])
132 ? $this->getCurrentFolderInfoFromMapping($folderMapping[$folderId])
133 : null;
134 }
135
136 return $this->sendSuccess($response);
137 }
138
139 /**
140 * Get folder mapping for boards
141 */
142 private function getBoardFolderMapping($userId)
143 {
144 $folderService = new FolderService();
145 $folders = $folderService->getFolders($userId);
146
147 $mapping = [];
148 foreach ($folders as $folder) {
149 $mapping[$folder->id] = [
150 'id' => $folder->id,
151 'title' => $folder->title,
152 'board_ids' => $folder->boards ? $folder->boards->pluck('id')->toArray() : []
153 ];
154 }
155
156 return $mapping;
157 }
158
159 private function getCurrentFolderInfoFromMapping(array $folder)
160 {
161 return [
162 'id' => $folder['id'],
163 'title' => $folder['title'],
164 'board_count' => count($folder['board_ids'])
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')->excludeTemplates()->byAccessUser($userId);
182 } else {
183 $relatedBoardsQuery = Board::whereNull('archived_at')->excludeTemplates()->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')->excludeTemplates()->byAccessUser($userId);
207 } else {
208 $relatedBoardsQuery = Board::whereNull('archived_at')->excludeTemplates()->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::whereNull('archived_at')
233 ->excludeTemplates()
234 ->availableInCurrentInstall()
235 ->byAccessUser(get_current_user_id())
236 ->limit(4)
237 ->withCount('completedTasks')
238 ->with(['stages', 'users'])
239 ->get();
240 }
241
242 foreach ($boards as $board) {
243 $board->users = Helper::sanitizeUserCollections($board->users);
244 $board->is_pinned = $this->boardService->isPinned($board->id);
245 }
246
247 return [
248 'boards' => $boards,
249 ];
250 }
251
252 /*
253 * TODO: Refactor this method , remove this
254 */
255 public function getBoardsByType($type)
256 {
257 $boards = $this->boardService->getBoardsByType($type);
258
259 return $this->sendSuccess([
260 'boards' => $boards,
261 ]);
262 }
263
264 public function createFirstBoard(Request $request)
265 {
266 $boardData = $this->boardSanitizeAndValidate($request->get('board'), [
267 'title' => 'required|string',
268 'description' => 'nullable',
269 'type' => 'required|string',
270 'currency' => 'nullable|string',
271 'crm_contact_id' => 'nullable|numeric',
272 ]);
273
274 $installFluentCRM = $request->getSafe('withFluentCRM', 'sanitize_text_field') == 'yes' ? true : false;
275
276 $postStages = $request->get('stages');
277 if (!is_array($postStages)) {
278 $postStages = [];
279 }
280 $stageData = array();
281 foreach ($postStages as $stage) {
282 $temp = $this->stageSanitizeAndValidate($stage, [
283 'title' => 'required|string',
284 ]);
285 $stageData[] = $temp;
286 }
287
288 $taskData = null;
289 if ($request->get('task')) {
290 $taskData = $this->taskSanitizeAndValidate($request->get('task'), [
291 'title' => 'required|string',
292 ]);
293 }
294
295 $board = $this->boardService->createBoard($boardData);
296 $this->labelService->createDefaultLabel($board->id);
297 $type = ucfirst($boardData['type']);
298 $stage = $this->stageService->createStages($board, $stageData);
299
300 if ($taskData) {
301 $taskData['board_id'] = $board->id;
302 $taskData['stage_id'] = $stage->id;
303 $this->taskService->createTask($taskData, $board->id);
304 }
305
306 do_action('fluent_boards/board_created', $board);
307
308 if ($installFluentCRM && !defined('FLUENTCRM')) {
309 InstallService::install('fluent-crm');
310 }
311
312 return [
313 'message' => __('Board has been created', 'fluent-boards'),
314 'board' => $board,
315 ];
316 }
317
318 public function skipOnboarding(Request $request)
319 {
320 $onboarding = Meta::where('key', Constant::FBS_ONBOARDING)->first();
321 if($onboarding && $onboarding->value == 'no'){
322 $onboarding->value = 'yes' ;
323 $onboarding->save();
324 }
325
326 return [
327 'message' => __('Onboarding skipped successfully', 'fluent-boards'),
328 ];
329 }
330
331 public function create(Request $request)
332 {
333 $boardData = $this->boardSanitizeAndValidate($request->get('board'), [
334 'title' => 'required|string',
335 'description' => 'nullable',
336 'type' => 'required|string',
337 'currency' => 'nullable|string',
338 'crm_contact_id' => 'nullable|numeric',
339 'folder_id' => 'nullable',
340 ]);
341
342 try {
343 $folderId = $request->getSafe('folder_id', 'intval');
344 $folderService = new FolderService();
345 if ($folderId) {
346 $folderService->assertCanModifyFolder($folderId);
347 }
348
349 $backgroundData = $this->sanitizeCreateBoardBackground($request->get('background'));
350 if (!empty($backgroundData)) {
351 $boardData['background'] = $backgroundData;
352 }
353
354 $board = $this->boardService->createBoard($boardData);
355 $this->createBoardLabelsFromRequest($request, $board->id);
356 $this->addBoardMembersFromRequest($request, $board->id);
357 $type = ucfirst($boardData['type']);
358 $stages = $request->get('stages');
359 $sanitizedStages = [];
360
361 if (is_array($stages) && !empty($stages)) {
362 foreach ($stages as $stage) {
363 $sanitizedStages[] = $this->stageSanitizeAndValidate($stage, [
364 'title' => 'required|string',
365 'slug' => 'nullable|string',
366 'position' => 'nullable|numeric'
367 ]);
368 }
369 }
370
371 if (isset($boardData['type']) && $boardData['type'] == 'roadmap') {
372 $this->stageService->createRoadmapStages($board, $sanitizedStages);
373 } elseif (!empty($sanitizedStages)) {
374 $this->stageService->createStages($board, $sanitizedStages);
375 } else {
376 $this->stageService->createDefaultStages($board);
377 }
378
379 // if board is created from crm contact
380 if (isset($boardData['crm_contact_id'])) {
381 $this->boardService->updateAssociateMember($boardData['crm_contact_id'], $board->id);
382 }
383
384 do_action('fluent_boards/board_created', $board);
385
386
387 if ($folderId) {
388 $folderService->addBoardToFolder($folderId, [$board->id]);
389 }
390
391 $message = __('Board has been created successfully', 'fluent-boards');
392
393 return $this->send([
394 'message' => $message,
395 'board' => $board,
396 ], 201);
397 } catch (\Exception $e) {
398 return $this->sendError([
399 'message' => $e->getMessage()
400 ]);
401 }
402 }
403
404 private function sanitizeCreateBoardBackground($background)
405 {
406 if (!is_array($background) || empty($background['id'])) {
407 return '';
408 }
409
410 $backgroundId = sanitize_text_field($background['id']);
411
412 // Only accept ids from the curated solid/gradient palettes and always
413 // persist the canonical value from the constant (never the client-supplied
414 // color) so arbitrary CSS can't be stored and later rendered into a style.
415 $allowedBackgrounds = [];
416 foreach (array_merge(
417 Constant::BOARD_BACKGROUND_DEFAULT_SOLID_COLORS,
418 Constant::BOARD_BACKGROUND_DEFAULT_GRADIENT_COLORS
419 ) as $option) {
420 if (isset($option['id'], $option['value'])) {
421 $allowedBackgrounds[$option['id']] = $option['value'];
422 }
423 }
424
425 if (!isset($allowedBackgrounds[$backgroundId])) {
426 return '';
427 }
428
429 return [
430 'id' => $backgroundId,
431 'color' => $allowedBackgrounds[$backgroundId],
432 'is_image' => false,
433 'image_url' => null,
434 ];
435 }
436
437 private function createBoardLabelsFromRequest(Request $request, $boardId)
438 {
439 $labels = $request->get('labels');
440
441 if (!is_array($labels)) {
442 $this->labelService->createDefaultLabel($boardId);
443 return;
444 }
445
446 foreach ($labels as $label) {
447 $labelData = Helper::sanitizeLabel((array) $label);
448
449 if (empty($labelData['label']) && empty($labelData['bg_color'])) {
450 continue;
451 }
452
453 $this->labelService->createLabel([
454 'label' => $labelData['label'] ?? '',
455 'bg_color' => $labelData['bg_color'] ?? '#f3f4f6',
456 'color' => $labelData['color'] ?? '#1B2533',
457 ], $boardId);
458 }
459 }
460
461 private function addBoardMembersFromRequest(Request $request, $boardId)
462 {
463 $memberIds = $request->get('member_ids');
464
465 if (!is_array($memberIds)) {
466 return;
467 }
468
469 $memberIds = array_filter(array_unique(array_map('intval', $memberIds)));
470 $currentUserId = get_current_user_id();
471
472 foreach ($memberIds as $memberId) {
473 if ($memberId === $currentUserId) {
474 continue;
475 }
476
477 $this->boardService->addMembersInBoard($boardId, $memberId);
478 }
479 }
480
481 /**
482 * Get archived stages for a board with optional pagination and archive actor metadata.
483 */
484 public function getArchivedStage(Request $request, $board_id)
485 {
486 try {
487 $board_id = absint($board_id);
488 $sanitizedParams = [
489 'noPagination' => $request->getSafe('noPagination', 'boolval', false),
490 'per_page' => $request->getSafe('per_page', 'intval', 30),
491 'page' => $request->getSafe('page', 'intval', 1),
492 ];
493
494 $stages = $this->stageService->getArchivedStages($sanitizedParams, $board_id);
495
496 return $this->sendSuccess([
497 'stages' => $stages,
498 ], 200);
499 } catch (\Exception $e) {
500 return $this->sendError($e->getMessage(), 404);
501 }
502 }
503
504 public function find(Request $request, $board_id)
505 {
506 $board = Board::findOrFail($board_id);
507 $board->description = DescriptionMarkdownConverter::normalize($board->description);
508 $includeArchived = filter_var($request->get('include_archived', false), FILTER_VALIDATE_BOOLEAN);
509 $board->background = maybe_unserialize($board->background);
510 $board->createdOn = $board->created_at->format('Y-m-d');
511
512 $board->load(['users', 'labels', 'owner']);
513
514 if ($includeArchived) {
515 $board->stages = Stage::where('board_id', $board_id)
516 ->orderBy('position', 'asc')
517 ->get();
518 } else {
519 $board->load('stages');
520 }
521
522 if (defined('FLUENT_BOARDS_PRO')){
523 $customFiledPositionMeta = $board->getMetaByKey('custom_field_positions');
524 if(!$customFiledPositionMeta) {
525 (new CustomFieldService())->reIndexCustomFieldPositions($board_id);
526 $board->updateMeta('custom_field_positions', 'yes');
527 }
528
529 $board->load(['customFields']);
530 }
531
532 $this->boardService->updateRecentBoards($board_id);
533
534 $board->labelColor = Constant::TRELLO_COLOR_MAP;
535 $board->labelColorText = Constant::TEXT_COLOR_MAP;
536
537 $board->users = Helper::sanitizeUserCollections($board->users);
538 $board->owner = Helper::sanitizeUserCollections($board->owner);
539
540 $board->is_pinned = $this->boardService->isPinned($board->id);
541
542 $board = apply_filters('fluent_boards/board_find', $board);
543
544 return [
545 'board' => $board,
546 'synced_at' => current_time('mysql')
547 ];
548 }
549
550 public function update(Request $request, $board_id)
551 {
552 // Board identity (title/description) is manager-only. This action shares the
553 // `update` name with CommentController@update under the same policy group, so the
554 // guard lives here rather than in a SingleBoardPolicy::update() method that would
555 // also block ordinary members from editing their own comments.
556 if (!PermissionManager::isBoardManager(absint($board_id))) {
557 return $this->sendError([
558 'message' => __('You do not have permission to edit this board.', 'fluent-boards'),
559 ], 403);
560 }
561
562 $boardData = $this->boardSanitizeAndValidate($request->only(['title', 'description']), [
563 'title' => 'required|string',
564 'description' => 'nullable|string',
565 ]);
566
567 $board = Board::findOrFail($board_id);
568 $boardData['description'] = DescriptionMarkdownConverter::normalize($boardData['description']);
569
570 $oldBoard = clone $board;
571 $board->fill($boardData);
572 $board->save();
573
574 do_action('fluent_boards/board_updated', $board, $oldBoard);
575
576 return [
577 'message' => __('Board has been updated', 'fluent-boards'),
578 'board' => $board,
579 'stages' => $board->stages()->get(),
580 ];
581 }
582
583 public function archiveStage($board_id, $stage_id)
584 {
585 $board_id = absint($board_id);
586 $stage_id = absint($stage_id);
587
588 try {
589 $stage = $this->findStageOnBoard($stage_id, $board_id);
590
591 $updatedStage = $this->boardService->archiveStage($board_id, $stage);
592
593 return $this->sendSuccess([
594 'updatedStage' => $updatedStage,
595 'message' => __('Stage has been archived', 'fluent-boards'),
596 ], 200);
597 } catch (\Exception $e) {
598 return $this->sendError($e->getMessage(), 400);
599 }
600 }
601
602 public function restoreStage($board_id, $stage_id)
603 {
604 $board_id = absint($board_id);
605 $stage_id = absint($stage_id);
606
607 try {
608 $stage = $this->findStageOnBoard($stage_id, $board_id);
609
610 $updatedStage = $this->boardService->restoreStage($board_id, $stage);
611
612 return $this->sendSuccess([
613 'success' => true,
614 'updatedStage' => $updatedStage,
615 'message' => __('Stage has been restored', 'fluent-boards')
616 ], 200);
617 } catch (\Exception $e) {
618 return $this->sendError($e->getMessage(), 400);
619 }
620 }
621
622
623 public function repositionStages(Request $request, $board_id)
624 {
625 $incomingList = $request->get('list');
626 if (!is_array($incomingList)) {
627 $incomingList = [];
628 }
629 $incomingList = array_map('intval', $incomingList);
630 try {
631 foreach ($incomingList as $stageId) {
632 $this->findStageOnBoard($stageId, $board_id);
633 }
634
635 $this->boardService->repositionStages($board_id, $incomingList);
636 return $this->sendSuccess([
637 'message' => __('Stages Reordered', 'fluent-boards'),
638 'updatedStages' => $this->stageService->getLastOneMinuteUpdatedStages($board_id)
639 ], 200);
640 } catch (\Exception $e) {
641 return $this->sendError($e->getMessage(), 400);
642 }
643 }
644
645 public function getAssigneesByBoard($board_id)
646 {
647 return $this->sendSuccess([
648 'data' => $this->boardService->getAssigneesByBoard($board_id),
649 ], 200);
650 }
651
652 public function delete($board_id)
653 {
654 try {
655 if (!PermissionManager::isAdmin()) {
656 throw new \Exception(esc_html__('You do not have permission to delete this board', 'fluent-boards'), 400);
657 }
658 $this->boardService->deleteBoard($board_id);
659
660 return $this->sendSuccess([
661 'message' => __('Board has been deleted', 'fluent-boards'),
662 ], 200);
663 } catch (\Exception $e) {
664 return $this->sendError($e->getMessage(), 400);
665 }
666 }
667
668 public function getCurrencies()
669 {
670 return BoardHandler::getCurrencies();
671 }
672
673 public function getActivities(Request $request, $board_id)
674 {
675 try {
676 $activities = $this->boardService->getActivities($board_id, [
677 'per_page' => $request->getSafe('per_page', 'intval', 40),
678 'page' => $request->getSafe('page', 'intval', 1),
679 ]);
680 return $this->sendSuccess([
681 'activities' => $activities,
682 ], 200);
683 } catch (\Exception $e) {
684 return $this->sendError($e->getMessage(), 404);
685 }
686 }
687
688 /*
689 * TODO: Refactor this method - for Masiur
690 */
691 public function getBoardUsers($board_id)
692 {
693 $board = Board::findOrFail($board_id);
694
695 $boardObjects = Relation::where('object_type', 'board_user')
696 ->where('object_id', $board_id)
697 ->get()->keyBy('foreign_id');
698
699 $superAdminIds = Meta::query()->where('object_type', Constant::FLUENT_BOARD_ADMIN)
700 ->get()->pluck('object_id')->toArray();
701
702 $userIds = $boardObjects->pluck('foreign_id')->toArray();
703
704 $coreUsers = [];
705 if ($userIds) {
706 // Get the users who are in the board (members and managers
707 $coreUsers = get_users([
708 'include' => $userIds
709 ]);
710 }
711
712 $formattedUsers = [];
713
714 foreach ($coreUsers as $user) {
715 $name = trim($user->first_name . ' ' . $user->last_name);
716 if (!$name) {
717 $name = $user->display_name;
718 }
719
720 $boardRelation = $boardObjects[$user->ID] ?? null;
721
722
723 $formattedUsers[] = [
724 'ID' => $user->ID,
725 'display_name' => $name,
726 'user_login' => $user->user_login,
727 'email' => $user->user_email,
728 'photo' => fluent_boards_user_avatar($user->user_email, $name),
729 'role' => $this->boardUserRole($boardRelation),
730 'is_super' => in_array($user->ID, $superAdminIds),
731 'is_wpadmin' => $user->has_cap('manage_options')
732 ];
733 }
734
735 // order formatted users by display_name
736 usort($formattedUsers, function ($a, $b) {
737 return strcmp($a['display_name'], $b['display_name']);
738 });
739
740 $returnData = [
741 'users' => Helper::sanitizeUsersArray($formattedUsers, $board_id),
742 'global_admins' => []
743 ];
744
745 if (!PermissionManager::isAdmin(get_current_user_id())) {
746 return $returnData;
747 }
748
749 /*
750 * These are the rest of the Fluent Boards and WordPress admins who are not in the board.
751 */
752 $fluentBoardAdminIds = Meta::query()->where('object_type', Constant::FLUENT_BOARD_ADMIN)
753 ->whereNotIn('object_id', $userIds)
754 ->get()
755 ->pluck('object_id')
756 ->toArray();
757
758 $wordPressAdminIds = get_users([
759 'capability' => 'manage_options',
760 'exclude' => $userIds,
761 'fields' => 'ID',
762 ]);
763
764 $adminUserIds = array_values(array_unique(array_map('intval', array_merge($fluentBoardAdminIds, $wordPressAdminIds))));
765
766 if ($adminUserIds) {
767 $adminUsers = get_users([
768 'include' => $adminUserIds,
769 ]);
770
771 $formattedAdminUsers = [];
772
773 foreach ($adminUsers as $user) {
774 $name = trim($user->first_name . ' ' . $user->last_name);
775 if (!$name) {
776 $name = $user->display_name;
777 }
778
779 $formattedAdminUsers[] = [
780 'ID' => $user->ID,
781 'display_name' => $name,
782 'email' => $user->user_email,
783 'photo' => fluent_boards_user_avatar($user->user_email, $name),
784 'role' => 'admin',
785 'is_super' => in_array($user->ID, $superAdminIds),
786 'is_wpadmin' => $user->has_cap('manage_options')
787 ];
788 }
789
790 // order formatted users by display_name
791 usort($formattedAdminUsers, function ($a, $b) {
792 return strcmp($a['display_name'], $b['display_name']);
793 });
794
795 $returnData['global_admins'] = Helper::sanitizeUsersArray($formattedAdminUsers, $board_id);
796 }
797
798 return $this->sendSuccess($returnData, 200);
799 }
800
801
802 public function removeUserFromBoard($board_id, $userId)
803 {
804 $this->boardService->removeUserFromBoard($board_id, $userId);
805
806 if (!PermissionManager::isAdmin($userId)) {
807 $this->boardService->removeFromRecentlyOpened($board_id, $userId);
808 }
809
810 return [
811 'message' => __('Member removed successfully', 'fluent-boards'),
812 ];
813 }
814
815 public function addMembersInBoard(Request $request, $board_id)
816 {
817 $memberId = $request->getSafe('memberId', 'intval');
818 $isViewerOnly = $request->getSafe('isViewerOnly', 'sanitize_text_field');
819 $member = $this->boardService->addMembersInBoard($board_id, $memberId, $isViewerOnly);
820
821 if ($member === null) {
822 return $this->sendError([
823 'message' => __('User not found.', 'fluent-boards'),
824 ], 404);
825 }
826
827 if (!$member) {
828 return $this->sendError([
829 'message' => __('User already a member', 'fluent-boards'),
830 ], 409);
831 }
832
833
834 return [
835 'message' => __('Member added successfully', 'fluent-boards'),
836 'member' => Helper::sanitizeUserCollections($member)
837 ];
838 }
839
840 private function boardSanitizeAndValidate($data, array $rules = [])
841 {
842 $data = Helper::sanitizeBoard($data);
843
844 return $this->validate($data, $rules);
845 }
846
847 private function stageSanitizeAndValidate($data, array $rules = [])
848 {
849 $data = Helper::sanitizeStage($data);
850
851 return $this->validate($data, $rules);
852 }
853
854 private function taskSanitizeAndValidate($data, array $rules = [])
855 {
856 $data = Helper::sanitizeTask($data);
857
858 return $this->validate($data, $rules);
859 }
860
861 public function searchBoards(Request $request)
862 {
863 $per_page = $request->getSafe('per_page', 'intval', 10);
864 $search_input = $request->getSafe('searchInput', 'sanitize_text_field', '');
865 $type = $request->getSafe('type', 'sanitize_text_field', 'to-do');
866
867 $currentUserId = get_current_user_id();
868
869 if (PermissionManager::isAdmin($currentUserId)) {
870 $boards = Board::query()->where('type', $type)
871 ->where('title', 'like', '%' . $search_input . '%')
872 ->with('stages', 'tasks', 'users')
873 ->paginate($per_page);
874
875 foreach ($boards as $board) {
876 $board->users = Helper::sanitizeUserCollections($board->users);
877 }
878
879 } else {
880 $currentUser = User::find($currentUserId);
881 $boards = $currentUser->boards()->where('type', $type)->where('title', 'like', '%' . $search_input . '%')->paginate($per_page);
882 }
883
884 return [
885 'boards' => $boards,
886 ];
887 }
888
889 public function getUsersOfBoards()
890 {
891 $userBoards = $this->boardService->getUsersOfBoards();
892
893 return $this->sendSuccess([
894 'userBoards' => $userBoards,
895 ], 200);
896 }
897
898
899
900 /**
901 * Refactor this code form me - Masiur
902 * change stage settings is_public for roadmap user and admin view
903 * @param $board_id
904 * @param $stage_id
905 * @return
906 */
907 public function changeStageView($board_id, $stage_id)
908 {
909 $board_id = absint($board_id);
910 $stage_id = absint($stage_id);
911
912 try {
913 $stage = $this->findStageOnBoard($stage_id, $board_id);
914 $message = __('The stage is made public!', 'fluent-boards');
915 $settings = $stage->settings;
916
917 if (isset($settings['is_public'])) {
918 if ($settings['is_public']) {
919 $settings['is_public'] = false;
920 $message = __('The stage is made private!', 'fluent-boards');
921 } else {
922 $settings['is_public'] = true;
923 }
924 } else {
925 $settings['is_public'] = true;
926 }
927
928 $stage->settings = $settings;
929 $stage->save();
930 return $this->sendSuccess([
931 'message' => $message,
932 'stage' => $stage
933 ]);
934 } catch (\Exception $e) {
935 return $this->sendError($e->getMessage(), 400);
936 }
937 }
938
939
940 /**
941 * Set or reset board background image/color.
942 * @param \FluentBoards\Framework\Http\Request\Request $request
943 * @return
944 */
945 public function setBoardBackground(Request $request, $board_id)
946 {
947 $backgroundData = [];
948 $isResetRequest = $request->getSafe('reset', 'rest_sanitize_boolean');
949
950 if ($isResetRequest) {
951 $backgroundData = [
952 'reset' => true,
953 ];
954 } elseif ($request->image_url) {
955 $backgroundData = $this->boardSanitizeAndValidate($request->all(), [
956 'id' => 'required|integer',
957 'image_url' => 'required|string|url',
958 ]);
959 } elseif ($request->color) {
960 $backgroundData = $this->boardSanitizeAndValidate($request->all(), [
961 "id" => 'required',
962 'color' => 'required',
963 ]);
964 }
965
966 try {
967 if (!$board_id) {
968 $errorMessage = __('Board id is required', 'fluent-boards');
969 throw new \Exception(esc_html($errorMessage), 400);
970 }
971
972 if (empty($backgroundData)) {
973 $errorMessage = __('Background data is required', 'fluent-boards');
974 throw new \Exception(esc_html($errorMessage), 400);
975 }
976
977 return $this->sendSuccess([
978 'message' => __('Background updated successfully', 'fluent-boards'),
979 'background' => $this->boardService->setBoardBackground($backgroundData, $board_id),
980 ]);
981 } catch (\Exception $e) {
982 return $this->sendError($e->getMessage(), 400);
983 }
984 }
985
986
987 /**
988 * Summary of getStageTaskAvailablePositions
989 * @param mixed $board_id
990 * @param mixed $stage_slug
991 * @return $availablePositions as an array
992 * @throws \Exception
993 */
994 public function getStageTaskAvailablePositions(Request $request, $board_id, $stage_id)
995 {
996 try {
997 if ($board_id && $stage_id) {
998 $taskId = $request->getSafe('task_id', 'intval');
999 $availablePositions = $this->boardService->getStageTaskAvailablePositions($board_id, $stage_id, $taskId);
1000 return $this->sendSuccess([
1001 'availablePositions' => $availablePositions['availablePositions'],
1002 'moveTargets' => $availablePositions['moveTargets'],
1003 'currentMoveTargetKey' => $availablePositions['currentMoveTargetKey'],
1004 'defaultMoveTargetKey' => $availablePositions['defaultMoveTargetKey'],
1005 ], 200);
1006 } else {
1007 $message = '';
1008 if (!$board_id) {
1009 $message = 'Board id ';
1010 }
1011 if (!$stage_id) {
1012 $message = 'Stage ';
1013 }
1014 throw new \Exception(esc_html($message . 'is required'), 400);
1015 }
1016 } catch (\Exception $e) {
1017 return $this->sendError($e->getMessage(), 400);
1018 }
1019 }
1020
1021 public function getAssociateCrmContacts($board_id)
1022 {
1023 try {
1024 $contactAssociatedTasks = Task::with('board')->where('board_id', $board_id)
1025 ->whereNotNull('crm_contact_id')
1026 ->get();
1027
1028 $tasksByContact = [];
1029 foreach ($contactAssociatedTasks as $task) {
1030 $tasksByContact[absint($task->crm_contact_id)][] = $task;
1031 }
1032
1033 $boardContactIds = Meta::query()
1034 ->where('object_id', absint($board_id))
1035 ->where('object_type', Constant::OBJECT_TYPE_BOARD)
1036 ->whereIn('key', [
1037 Constant::BOARD_ASSOCIATED_CRM_CONTACT,
1038 self::LEGACY_BOARD_ASSOCIATED_CRM_CONTACT,
1039 ])
1040 ->pluck('value')
1041 ->toArray();
1042 $boardContactIds = array_values(array_unique(array_filter(array_map('absint', $boardContactIds))));
1043
1044 $contactIds = array_values(array_unique(array_filter(array_map('absint', array_merge(
1045 array_keys($tasksByContact),
1046 $boardContactIds
1047 )))));
1048
1049 usort($contactIds, function ($firstContactId, $secondContactId) use ($boardContactIds) {
1050 return (int) in_array($secondContactId, $boardContactIds, true) - (int) in_array($firstContactId, $boardContactIds, true);
1051 });
1052
1053 $formattedContacts = Collection::make($contactIds)
1054 ->map(function ($contactId) use ($tasksByContact, $boardContactIds) {
1055 $contact = Helper::crm_contact($contactId);
1056 if (!$contact) {
1057 return null; // Skip if subscriber not found
1058 }
1059
1060 $tasks = $tasksByContact[$contactId] ?? [];
1061 $contact['name'] = trim(($contact['first_name'] ?? '') . ' ' . ($contact['last_name'] ?? '')) ?: ($contact['full_name'] ?? $contact['email'] ?? '');
1062 $contact['crm_contact_id'] = $contactId;
1063 $contact['is_board_contact'] = in_array($contactId, $boardContactIds, true);
1064 $contact['tasks'] = $tasks;
1065
1066 return $contact;
1067 })
1068 ->filter()->toArray();
1069
1070
1071 return $this->sendSuccess([
1072 'associatedContacts' => $formattedContacts
1073 ], 200);
1074 } catch (\Exception $e) {
1075 return $this->sendError($e->getMessage(), 404);
1076 }
1077 }
1078
1079 public function updateAssociateCrmContact(Request $request, $board_id)
1080 {
1081 $value = $request->getSafe('value');
1082 $this->boardService->updateAssociateMember($value, $board_id);
1083
1084 return $this->sendSuccess([
1085 'message' => __('Associated Crm Member has been updated', 'fluent-boards'),
1086 ], 200);
1087 }
1088
1089 public function hasDataChanged(Request $request, $board_id)
1090 {
1091 $includeArchived = filter_var($request->get('include_archived', false), FILTER_VALIDATE_BOOLEAN);
1092 $since = $request->getSafe('since', 'sanitize_text_field');
1093 return $this->boardService->hasDataChanged($board_id, $includeArchived, $since);
1094 }
1095
1096 public function createStage(Request $request, $board_id)
1097 {
1098 $stageData = $this->stageSanitizeAndValidate($request->all(), [
1099 'title' => 'required|string',
1100 'position' => 'nullable|numeric'
1101 ]);
1102
1103 $board = Board::find($board_id);
1104 $stage = $this->stageService->createStage($stageData, $board_id);
1105
1106 do_action('fluent_boards/board_stage_added', $board, $stage);
1107
1108 $updatedStates = (new StageService())->getLastOneMinuteUpdatedStages($board_id);
1109
1110 return [
1111 'updatedStages' => $updatedStates,
1112 'message' => __('stage has been created', 'fluent-boards'),
1113 ];
1114 }
1115
1116 public function moveAllTasks(Request $request, $board_id)
1117 {
1118 $oldStageId = $request->getSafe('oldStageId', 'intval');
1119 $newStageId = $request->getSafe('newStageId', 'intval');
1120
1121 if (!$oldStageId || !$newStageId) {
1122 return $this->sendError(__('Invalid stage IDs provided', 'fluent-boards'), 400);
1123 }
1124
1125 // Verify stages exist and belong to the board
1126 $oldStage = Stage::where('id', $oldStageId)->where('board_id', $board_id)->first();
1127 $newStage = Stage::where('id', $newStageId)->where('board_id', $board_id)->first();
1128
1129 if (!$oldStage || !$newStage) {
1130 return $this->sendError(__('One or both stages do not exist or do not belong to this board', 'fluent-boards'), 400);
1131 }
1132
1133 $updates = $this->stageService->moveAllTasks($oldStageId, $newStageId, $board_id);
1134
1135 return [
1136 'message' => __('Tasks have been moved', 'fluent-boards'),
1137 'updatedTasks' => $updates,
1138 ];
1139
1140 }
1141
1142 public function archiveAllTasksInStage($board_id, $stage_id)
1143 {
1144 $board_id = absint($board_id);
1145 $stage_id = absint($stage_id);
1146
1147 try {
1148 $this->findStageOnBoard($stage_id, $board_id);
1149 $updates = $this->stageService->archiveAllTasksInStage($stage_id, $board_id);
1150
1151 return [
1152 'message' => __('Tasks have been archived', 'fluent-boards'),
1153 'updatedTasks' => $updates,
1154 ];
1155 } catch (\Exception $e) {
1156 return $this->sendError($e->getMessage(), 400);
1157 }
1158 }
1159
1160 public function getAssociatedBoards(Request $request, $associated_id)
1161 {
1162 if (!$this->currentUserCanReadCrmContacts()) {
1163 return $this->sendError(esc_html__('You do not have permission to view CRM contact boards', 'fluent-boards'), 403);
1164 }
1165
1166 $associatedId = absint($associated_id);
1167
1168 if (!$associatedId) {
1169 return $this->sendError(__('Invalid CRM contact', 'fluent-boards'), 400);
1170 }
1171
1172 $associatedBoards = $this->boardService->getAssociatedBoards($associatedId, get_current_user_id());
1173
1174 return [
1175 'boards' => $associatedBoards,
1176 ];
1177 }
1178
1179 private function currentUserCanReadCrmContacts()
1180 {
1181 $permissionManager = 'FluentCrm\\App\\Services\\PermissionManager';
1182
1183 if (!class_exists($permissionManager)) {
1184 return false;
1185 }
1186
1187 return (bool) $permissionManager::currentUserCan('fcrm_read_contacts');
1188 }
1189
1190 public function duplicateBoard(Request $request, $board_id)
1191 {
1192 $boardData = $this->taskSanitizeAndValidate($request->get('board'), [
1193 'title' => 'required|string'
1194 ]);
1195
1196 $boardData['source_board_id'] = $board_id;
1197
1198 $isWithLabels = $request->getSafe('isWithLabels');
1199 $isWithTasks = $request->getSafe('isWithTasks');
1200 $isWithTemplates = $request->getSafe('isWithTemplates');
1201
1202 try {
1203 if(!PermissionManager::isAdmin()) {
1204 $errorMessage = __('You do not have permission to duplicate board', 'fluent-boards');
1205 throw new \Exception(esc_html($errorMessage), 400);
1206 }
1207 //create board
1208 $newBoard = $this->boardService->copyBoard($boardData);
1209
1210 //label copy
1211 $labelMap = [];
1212
1213 if ($isWithLabels == 'yes') {
1214 $labelMap = $this->labelService->copyLabelsOfBoard($board_id, $newBoard);
1215 }
1216
1217 //stage copy
1218 $stageMapForCopyingTask = $this->stageService->copyStagesOfBoard($newBoard, $board_id, $isWithTemplates);
1219
1220 //copy tasks of selected stages
1221 if ($isWithTasks == 'yes') {
1222 $this->taskService->copyTasks($board_id, $stageMapForCopyingTask, $newBoard, $labelMap,$isWithTemplates);
1223 }
1224
1225 return $this->sendSuccess([
1226 'board' => $newBoard,
1227 ], 200);
1228 } catch (\Exception $e) {
1229 return $this->sendError($e->getMessage(), 400);
1230 }
1231 }
1232
1233 public function importFromBoard(Request $request, $board_id)
1234 {
1235 $selectedStages = $request->getSafe('selectedStages');
1236 $position = $request->getSafe('position', 'intval');
1237
1238 // Validate and sanitize selectedStages array
1239 if (!is_array($selectedStages)) {
1240 $selectedStages = [$selectedStages];
1241 }
1242 $selectedStages = array_filter(array_map('intval', $selectedStages));
1243
1244 try {
1245 $this->stageService->importStagesFromBoard($board_id, $selectedStages, $position);
1246
1247 return $this->sendSuccess([
1248 'message' => __('Import successfully', 'fluent-boards'),
1249 ], 200);
1250
1251 } catch (\Exception $e) {
1252 return $this->sendError($e->getMessage(), 400);
1253 }
1254 }
1255
1256 public function getBoardDefaultBackgroundColors()
1257 {
1258 return [
1259 'solidColors' => Constant::BOARD_BACKGROUND_DEFAULT_SOLID_COLORS,
1260 'gradients' => Constant::BOARD_BACKGROUND_DEFAULT_GRADIENT_COLORS
1261 ];
1262 }
1263
1264 /*
1265 * TODO: For Masiur - I will update this later
1266 */
1267 public function updateBoardProperties(Request $request, $board_id)
1268 {
1269 $pageId = $request->getSafe('page_id');
1270 $enable_stage_change_email = $request->getSafe('enable_stage_change_email');
1271
1272 $board = Board::findOrFail($board_id);
1273
1274 $board->updateMeta('roadmap_page_id', $pageId);
1275 $board->updateMeta('enable_stage_change_email', $enable_stage_change_email);
1276
1277 $board = $board->fresh();
1278
1279 return [
1280 'message' => __('Board has been updated', 'fluent-boards'),
1281 'board' => apply_filters('fluent_boards/board_find', $board)
1282 ];
1283 }
1284
1285 public function archiveBoard($board_id)
1286 {
1287 try {
1288 $board = $this->boardService->archiveBoard($board_id);
1289
1290 return [
1291 'board' => $board,
1292 'message' => __('Board has been archived successfully!', 'fluent-boards')
1293 ];
1294 } catch (\Exception $e) {
1295 return $this->sendError($e->getMessage(), 400);
1296 }
1297 }
1298
1299 public function restoreBoard($board_id)
1300 {
1301 try {
1302 $board = $this->boardService->restoreBoard($board_id);
1303
1304 return [
1305 'board' => $board,
1306 'message' => __('Board has been restored successfully!', 'fluent-boards')
1307 ];
1308 } catch (\Exception $e) {
1309 return $this->sendError($e->getMessage(), 400);
1310 }
1311 }
1312
1313 private function boardUserRole($boardRelation)
1314 {
1315 return $boardRelation && Arr::get($boardRelation->settings, 'is_admin')
1316 ? 'manager'
1317 : ($boardRelation && Arr::has($boardRelation->settings, 'is_viewer_only') && Arr::get($boardRelation->settings, 'is_viewer_only')
1318 ? 'viewer'
1319 : 'member');
1320 }
1321 public function uploadBoardBackground(Request $request,$board_id)
1322 {
1323 $file = Arr::get($request->files(), 'file')->toArray();
1324 (new \FluentBoards\App\Services\UploadService)->validateFile($file);
1325
1326 $uploadInfo = UploadService::handleFileUpload( $request->files(), $board_id);
1327
1328 $fileData = $uploadInfo[0];
1329 $initialDataData = [
1330 'type' => 'url',
1331 'url' => '',
1332 'name' => '',
1333 'size' => 0,
1334 ];
1335
1336 $attachData = array_merge($initialDataData, $fileData);
1337 $UrlMeta = [];
1338 if($attachData['type'] == 'url') {
1339 $UrlMeta = RemoteUrlParser::parse($attachData['url']);
1340 }
1341 $uid = wp_generate_uuid4();
1342 $fileUploadedData = new Attachment();
1343 $fileUploadedData->object_id = $board_id;
1344 $fileUploadedData->object_type = Constant::BOARD_BACKGROUND_IMAGE;
1345 $fileUploadedData->attachment_type = $attachData['type'];
1346 $fileUploadedData->title = (new TaskService())->setTitle($attachData['type'], $attachData['name'], $UrlMeta);
1347 $fileUploadedData->file_path = $attachData['type'] != 'url' ? $attachData['file'] : null;
1348 $fileUploadedData->full_url = esc_url($attachData['url']);
1349 $fileUploadedData->file_size = $attachData['size'];
1350 $fileUploadedData->settings = $attachData['type'] == 'url' ? [
1351 'meta' => $UrlMeta
1352 ] : '';
1353 $fileUploadedData->driver = 'local';
1354 $fileUploadedData->file_hash = md5($uid . wp_rand(0, 1000));
1355 $fileUploadedData->save();
1356 if(!!defined('FLUENT_BOARDS_PRO_VERSION')) {
1357 $mediaData = (new AttachmentService())->processMediaData($fileData, $file);
1358 $fileUploadedData['driver'] = $mediaData['driver'];
1359 $fileUploadedData['file_path'] = $mediaData['file_path'];
1360 $fileUploadedData['full_url'] = $mediaData['full_url'];
1361 $fileUploadedData->save();
1362 }
1363
1364 $board = Board::find($board_id);
1365 $oldBackground = $board->background;
1366 $publicUrl = (new CommentService())->createPublicUrl($fileUploadedData, $board_id);
1367 $background = [
1368 'color' => null,
1369 'id' => $fileUploadedData->id,
1370 'image_url' => $publicUrl,
1371 'is_image' => true,
1372 ];
1373 $board->background = $background;
1374 $board->save();
1375 do_action('fluent_boards/board_background_updated', $board_id, $oldBackground);
1376
1377 return $this->sendSuccess([
1378 'message' => __('Background updated successfully', 'fluent-boards'),
1379 'background' => $board->background,
1380 ]);
1381 }
1382
1383 public function getPinnedBoards()
1384 {
1385 $pinnedBoards = $this->boardService->getPinnedBoards();
1386
1387 return $this->sendSuccess([
1388 'pinnedBoards' => $pinnedBoards,
1389 ], 200);
1390 }
1391
1392 public function pinBoard($boardId)
1393 {
1394 $this->boardService->pinBoard($boardId);
1395
1396 return $this->sendSuccess([
1397 'message' => __('The Board has been pinned', 'fluent-boards'),
1398 ], 200);
1399 }
1400
1401 public function unpinBoard($boardId)
1402 {
1403 $remove = $this->boardService->unpinBoard($boardId);
1404
1405 if (!$remove) {
1406 return $this->sendError([
1407 'message' => __('Board is not pinned', 'fluent-boards'),
1408 ], 400);
1409 }
1410
1411 return $this->sendSuccess([
1412 'message' => __('Board is removed from pinned boards', 'fluent-boards'),
1413 ], 200);
1414 }
1415
1416 public function getBoardFolder($board_id)
1417 {
1418 try {
1419 $folder = $this->boardService->getBoardFolder($board_id);
1420 return $this->sendSuccess([
1421 'folder' => $folder,
1422 ], 200);
1423 } catch (\Exception $e) {
1424 return $this->sendError($e->getMessage(), 400);
1425 }
1426 }
1427
1428 public function getBoardMenuItems($board_id)
1429 {
1430 try {
1431 $menuItems = (new BoardMenuHandler())->getMenuItems($board_id);
1432
1433 return $this->sendSuccess([
1434 'menu_items' => $menuItems
1435 ], 200);
1436 } catch (\Exception $e) {
1437 return $this->sendError($e->getMessage(), 500);
1438 }
1439 }
1440
1441 public function getPublicAccessSettings($board_id)
1442 {
1443 $board_id = absint($board_id);
1444 $board = Board::findOrFail($board_id);
1445
1446 $enabled = (bool) $board->getMetaByKey('public_access_enabled');
1447 $shortcode = $enabled ? '[fluent_board_public id="' . $board_id . '"]' : '';
1448
1449 return $this->sendSuccess([
1450 'enabled' => $enabled,
1451 'shortcode' => $shortcode,
1452 ], 200);
1453 }
1454
1455 public function togglePublicAccess(Request $request, $board_id)
1456 {
1457 $board_id = absint($board_id);
1458 $board = Board::findOrFail($board_id);
1459
1460 $enabled = filter_var(
1461 $request->getSafe('enabled', 'sanitize_text_field', false),
1462 FILTER_VALIDATE_BOOLEAN
1463 );
1464
1465 $board->updateMeta('public_access_enabled', $enabled ? '1' : '');
1466
1467 $shortcode = $enabled ? '[fluent_board_public id="' . $board_id . '"]' : '';
1468
1469 return $this->sendSuccess([
1470 'message' => $enabled
1471 ? __('Public access has been enabled', 'fluent-boards')
1472 : __('Public access has been disabled', 'fluent-boards'),
1473 'enabled' => $enabled,
1474 'shortcode' => $shortcode,
1475 ], 200);
1476 }
1477
1478 /**
1479 * Resolve a stage only when it belongs to the requested board.
1480 *
1481 * @param int $stageId
1482 * @param int $boardId
1483 * @return Stage
1484 * @throws \Exception
1485 */
1486 private function findStageOnBoard($stageId, $boardId)
1487 {
1488 $stage = Stage::where('id', absint($stageId))
1489 ->where('board_id', absint($boardId))
1490 ->first();
1491
1492 if (!$stage) {
1493 throw new \Exception(esc_html__('Stage not found', 'fluent-boards'));
1494 }
1495
1496 return $stage;
1497 }
1498
1499 }
1500