PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.0.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.0.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 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 2.0.0, at app/Http/Controllers/BoardController.php

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