# fluent-boards/trunk/app/Http/Controllers/OptionsController.php

FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration, version trunk. 1,140 lines.

- Page: https://pluginprobe.com/plugins/fluent-boards/trunk/code/app/Http/Controllers/OptionsController.php
- Raw: https://pluginprobe.com/plugins/fluent-boards/trunk/raw/app/Http/Controllers/OptionsController.php
- Modified: 2026-09-09T13:50:06+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/fluent-boards/trunk/code/app/Http/Controllers/OptionsController.php#L10-L20`.

```php
<?php

namespace FluentBoards\App\Http\Controllers;

use FluentBoards\App\App;
use FluentBoards\App\Models\Meta;
use FluentBoards\App\Models\Task;
use FluentBoards\App\Models\User;
use FluentBoards\App\Models\Board;
use FluentBoards\App\Models\Relation;
use FluentBoards\App\Services\BoardService;
use FluentBoards\App\Services\DescriptionMarkdownConverter;
use FluentBoards\App\Services\Helper;
use FluentBoards\App\Services\OptionService;
use FluentBoards\App\Services\Constant;
use FluentBoards\App\Services\PermissionManager;
use FluentBoards\Framework\Http\Request\Request;
use FluentBoards\Framework\Support\Arr;
use FluentBoardsPro\App\Hooks\Handlers\ProScheduleHandler;

class OptionsController extends Controller
{
    private $boardService;
    private $optionService;

    public function __construct(BoardService $boardService, OptionService $optionService)
    {
        parent::__construct();
        $this->boardService = $boardService;
        $this->optionService = $optionService;
    }

    public function selectorOptions(Request $request)
    {
        try {
            $optionKey = $request->getSafe('option_key', 'sanitize_text_field');
            $search = $request->getSafe('search', 'sanitize_text_field');
            $includedIds = $request->getSafe('values');
            $boardId = $request->getSafe('board_id', 'intval');

            $options = [];
            if ('users' === $optionKey || 'task_assignees' === $optionKey) {  // no ajax/code is designed to handle this eventually will goto else

                if (!PermissionManager::isBoardManager($boardId)) {
                    throw new \Exception(esc_html__('You do not have permission to access this route', 'fluent-boards'));
                }

                if (!defined('FLUENT_BOARDS_PRO')) {
                    // get who has 'manage_options' capability
                    $users = PermissionManager::getAll_WP_Admins($search);

                } else {
                    // Search by user login, email, and nicename, first_name , last_name
                    $users = Helper::searchWordPressUsers($search);
                    $users = Helper::sanitizeUsersArray($users, $boardId);

                }

                $options = $this->addUserDataAsSelectorOption($users);

            } elseif ('board_create_users' === $optionKey) {
                if (!PermissionManager::userHasBoardCreationPermission()) {
                    throw new \Exception(esc_html__('You do not have permission to access this route', 'fluent-boards'));
                }

                if (!defined('FLUENT_BOARDS_PRO')) {
                    // Bound the lookup: this popover searches on focus (empty query too).
                    $users = PermissionManager::getAll_WP_Admins($search, 20);
                } else {
                    $users = Helper::searchWordPressUsers($search);
                }

                $options = $this->addUserDataAsSelectorOption($users);
                // Board creation can be delegated to members without `list_users`;
                // don't leak full account emails to them.
                $options = $this->maskSelectorEmailsForViewer($options);

            } elseif ('boards' === $optionKey) {
                $boards = Board::query()
                    ->byAccessUser(get_current_user_id())
                    ->when($search, function ($query) use ($search) {
                        // $search is already sanitized with sanitize_text_field above
                        return $query->where('title', 'LIKE', '%' . $search . '%');
                    })->take(20)->get();

                foreach ($boards as $board) {
                    $options[] = [
                        'id'               => $board->id,
                        'title'            => $board->title,
                        'left_side_value'  => $board->title,
                        'right_side_value' => $board->slug,
                    ];
                }
            } elseif ('tasks' === $optionKey) {
                if (!PermissionManager::userHasPermission($boardId)) {
                    throw new \Exception(esc_html__('You do not have permission to access this route', 'fluent-boards'));
                }

                // $boardId is already sanitized with intval above
                $tasks = Task::where('board_id', $boardId)
                    ->whereNull('archived_at')
                    ->whereNull('parent_id')
                    ->when($search, function ($query) use ($search) {
                        // $search is already sanitized with sanitize_text_field above
                        return $query->where('title', 'LIKE', '%' . $search . '%');
                    })->take(20)->get();

                foreach ($tasks as $task) {
                    $options[] = [
                        'id'               => $task->id,
                        'title'            => $task->title,
                        'board_id'         => $task->board_id,
                        'subtask_groups'   => $task->subtaskGroup
                    ];
                }

            } elseif ('assigned_in_task' == $optionKey) {
                if (!PermissionManager::userHasPermission($boardId)) {
                    throw new \Exception(esc_html__('You do not have permission to access this route', 'fluent-boards'));
                }

                $users = (new BoardService())->getAssigneesByBoard($boardId, $search);
                $options = $this->addUserDataAsSelectorOption($users);
            } else {
                $options = apply_filters('fluent_boards/ajax_options_' . $optionKey, [], $search, $includedIds);
            }

            return $this->sendSuccess([
                'options' => $options,
            ], 200);
        } catch (\Exception $e) {
            return $this->sendError($e->getMessage(), 404);
        }
    }

    private function addUserDataAsSelectorOption($users)
    {
        $options = [];
        foreach ($users as $user) {
            $options[] = [
                'id'               => $user->ID,
                'email'            => $user->user_email,
                'name'             => $user->display_name ?? $user->user_email,
                'title'            => $user->display_name . ' (' . $user->user_email . ')',
                'photo'            => get_avatar_url($user->user_email),
            ];
        }
        return $options;
    }

    /**
     * Obfuscate emails in selector options for viewers who lack the `list_users`
     * capability, keeping the current user's own email visible.
     */
    private function maskSelectorEmailsForViewer($options)
    {
        if (current_user_can('list_users')) {
            return $options;
        }

        $currentUser = wp_get_current_user();
        $currentUserEmail = ($currentUser && isset($currentUser->user_email)) ? $currentUser->user_email : '';

        foreach ($options as $index => $option) {
            $email = $option['email'] ?? '';

            if ($email === '' || $email === $currentUserEmail) {
                continue;
            }

            $maskedEmail = Helper::obfuscateEmail($email);

            // When there's no display name the raw email doubles as the name.
            if (($option['name'] ?? '') === $email) {
                $options[$index]['name'] = $maskedEmail;
            }

            $options[$index]['email'] = $maskedEmail;
            $options[$index]['title'] = ($options[$index]['name'] ?? $maskedEmail) . ' (' . $maskedEmail . ')';
        }

        return $options;
    }

    public function getCurrentUserPermissions()
    {
        try {
            $currentUserBoards = Relation::query()
                ->where('user_id', get_current_user_id())
                ->whereNotNull('board_id')
                ->where('status', 'ACTIVE')
                ->get();
            foreach ($currentUserBoards as &$currentUserBoardPermission) {
                $currentUserBoardPermission->permissions = \maybe_unserialize($currentUserBoardPermission->permissions);
            }

            return $this->sendSuccess(
                $currentUserBoards, 200
            );
        } catch (\Exception $e) {
            return $this->sendError($e->getMessage(), 404);
        }
    }

    public function getUserPermission(Request $request)
    {
        try {
            $boardId = $request->getSafe('boardId', 'intval');
            $userId = $request->getSafe('userId', 'intval');

            $boardUser = Relation::where('board_id', $boardId)
                ->where('user_id', $userId)
                ->where('status', 'ACTIVE')->first();
            if (!$boardUser->is_admin) {
                $boardUser->permissions = \maybe_unserialize($boardUser->permissions);
            }

            return $this->sendSuccess(
                $boardUser, 200
            );
        } catch (\Exception $e) {
            return $this->sendError($e->getMessage(), 404);
        }
    }

    public function updatedUserPermission(Request $request)
    {
        try {
            $permission = $request->getSafe('userPermission', 'sanitize_text_field');
            $updateType = $request->getSafe('updateType', 'sanitize_text_field');
            $boardId = $request->getSafe('boardId', 'intval');
            $userId = $request->getSafe('userId', 'intval');

            $boardUser = Relation::where('board_id', $boardId)->where('user_id', $userId)->status('ACTIVE')->first();

            if ('Board Admin' == $permission) {
                $boardUser->is_admin = 'add' == $updateType ? 1 : 0;
                $boardUser->save();
            } else {
                if (0 == $boardUser->is_admin) {
                    $permissionsAlreadyHave = maybe_unserialize($boardUser->permissions);
                    if (!in_array($permission, $permissionsAlreadyHave) && 'add' == $updateType) {
                        array_push($permissionsAlreadyHave, $permission);
                    } elseif (in_array($permission, $permissionsAlreadyHave) && 'remove' == $updateType) {
                        if (($key = array_search($permission, $permissionsAlreadyHave)) !== false) {
                            unset($permissionsAlreadyHave[$key]);
                        }
                    }
                    $boardUser->permissions = serialize($permissionsAlreadyHave);
                    $boardUser->save();
                }
            }

            $boardUser->permissions = maybe_unserialize($boardUser->permissions);

            return $this->sendSuccess(
                $boardUser, 200
            );
        } catch (\Exception $e) {
            return $this->sendError($e->getMessage(), 404);
        }
    }

    public function SetUserSuperAdmin($userId)
    {
        $userId = absint($userId);
        try {
            $this->optionService->createSuperAdmin($userId);
            return $this->sendSuccess([
                'message' => __('Member has been set super admin successfully!', 'fluent-boards')
            ], 200);

        } catch (\Exception $e) {
            return $this->sendError($e->getMessage(), 404);
        }
    }

    public function removeUserSuperAdmin($userId)
    {
        $userId = absint($userId);
        try {
            $this->optionService->removeUserSuperAdmin($userId);

            return $this->sendSuccess([
                'message' => __('User has been removed as super admin successfully!', 'fluent-boards'),
            ], 200);
        } catch (\Exception $e) {
            return $this->sendError($e->getMessage(), 404);
        }
    }

    public function IsUserAllBoardAdmin(Request $request)
    {
        try {
            $userId = $request->getSafe('id', 'intval');
            $isSuperAdmin = false;
            $superAdmin = Relation::where('board_id', null)->where('user_id', $userId)->where('status', 'ACTIVE')->first();
            $totalSuperAdmin = Relation::where('board_id', null)->where('status', 'ACTIVE')->count();
            $permissions = [];
            if ($superAdmin) {
                $isSuperAdmin = true;
                $permissions = \maybe_unserialize($superAdmin->permissions);
            }

            return $this->sendSuccess([
                'allBoardAdmin'      => $isSuperAdmin,
                'permissions'        => $permissions,
                'numberOfSuperAdmin' => $totalSuperAdmin,
            ], 200);
        } catch (\Exception $e) {
            return $this->sendError($e->getMessage(), 404);
        }
    }

    public function RemoveUserFromSuperAdmin(Request $request, $id)
    {
        $id = absint($id);
        try {
            $userId = $request->getSafe('id', 'intval');

            $superAdmin = Relation::where('board_id', null)->where('user_id', $userId)->first();
            $superAdmin->status = 'INACTIVE';
            $superAdmin->save();

            return $this->sendSuccess([
                'message' => __('User removed as super admin', 'fluent-boards'),
            ], 201);
        } catch (\Exception $e) {
            return $this->sendError($e->getMessage(), 404);
        }
    }

    public function removeUserFromBoard(Request $request)
    {
        try {
            $boardId = $request->getSafe('boardId', 'intval');
            $userId = $request->getSafe('userId', 'intval');

            $this->boardService->removeUserFromBoard($boardId, $userId);

            if (!PermissionManager::isAdmin($userId)) {
                $this->boardService->removeFromRecentlyOpened($boardId, $userId);
            }

            return $this->sendSuccess([
                'message' => __('User Removed from Board successfully!', 'fluent-boards'),
            ], 201);
        } catch (\Exception $e) {
            return $this->sendError($e->getMessage(), 404);
        }
    }

    public function addAsSuperAdmin(Request $request)
    {
        try {
            $rawUserIds = $request->getSafe('memberIds');
            // Sanitize array of user IDs
            $userIds = [];
            if (is_array($rawUserIds)) {
                $userIds = array_filter(array_map('intval', $rawUserIds));
            }
            foreach ($userIds as $userId) {
                $this->createSuperAdmin($userId);
            }

            return $this->sendSuccess([
                'message' => __('Fluent boards admin added', 'fluent-boards'),
            ], 201);
        } catch (\Exception $e) {
            return $this->sendError($e->getMessage(), 404);
        }
    }

    private function createSuperAdmin($userId)
    {
        try {
            $existUser = Meta::where('object_id', $userId)->first();
            if (!$existUser) {
                $meta = new Meta();
                $meta->object_id = $userId;
                $meta->object_type = Constant::FLUENT_BOARD_ADMIN;
                $meta->save();
            }
        } catch (\Exception $e) {
            return $this->sendError($e->getMessage(), 404);
        }
    }

    public function addMembersInBoards(Request $request)
    {
        try {
            $rawUserIds = $request->getSafe('memberIds');
            $rawBoardIds = $request->getSafe('boardIds');
            
            // Sanitize arrays of IDs
            $userIds = [];
            if (is_array($rawUserIds)) {
                $userIds = array_filter(array_map('intval', $rawUserIds));
            }
            $boardIds = [];
            if (is_array($rawBoardIds)) {
                $boardIds = array_filter(array_map('intval', $rawBoardIds));
            }

            foreach ($userIds as $userId) {
                foreach ($boardIds as $boardId) {
                    $this->boardService->addMembersInBoard($boardId, $userId);
                }
            }

            return $this->sendSuccess([
                'message' => __('Members added to boards', 'fluent-boards'),
            ], 201);
        } catch (\Exception $e) {
            return $this->sendError($e->getMessage(), 404);
        }
    }


    public function updateGlobalNotificationSettings(Request $request)
    {
        try {
            // updatedSettings is an array, sanitize each element
            $rawSettings = $request->get('updatedSettings');
            $newSettings = [];
            if (is_array($rawSettings)) {
                foreach ($rawSettings as $key => $value) {
                    $sanitizedKey = sanitize_text_field($key);
                    $sanitizedValue = sanitize_text_field($value);
                    $newSettings[$sanitizedKey] = $sanitizedValue;
                }
            }

            $this->optionService->updateGlobalNotificationSettings($newSettings);

            return $this->sendSuccess([
                'message' => __("Notification settings are updated", 'fluent-boards'),
            ], 201);
        } catch (\Exception $e) {
            return $this->sendError($e->getMessage(), 404);
        }
    }

    public function getGlobalNotificationSettings()
    {
        try {
            $globalSettings = $this->optionService->getGlobalNotificationSettings();
            if ($globalSettings->value)
                $currentSettings = maybe_unserialize($globalSettings->value);

            return $this->sendSuccess([
                'currentSettings' => $currentSettings,
            ], 200);
        } catch (\Exception $e) {
            return $this->sendError($e->getMessage(), 404);
        }
    }

    public function getBoardMembers(Request $request)
    {
        if (!PermissionManager::userHasAnyBoardAccess()) {
            return $this->sendError([
                'message' => __('You do not have permission to access this route', 'fluent-boards')
            ]);
        }

        $boardId = $request->getSafe('boardId', 'intval');

        if ($boardId && !PermissionManager::userHasPermission($boardId)) {
            return $this->sendError([
                'message' => __('You do not have permission to access this route', 'fluent-boards')
            ], 403);
        }

        $memberUserIdsQuery = Relation::where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
            ->select(['foreign_id'])
            ->groupBy('foreign_id');

        if ($boardId) {
            $memberUserIdsQuery->where('object_id', $boardId);
        } elseif (!PermissionManager::isAdmin()) {
            $boardIds = array_filter(array_map('intval', PermissionManager::getBoardIdsForUser()));
            $memberUserIdsQuery->whereIn('object_id', $boardIds);
        }

        $memberUserIds = $memberUserIdsQuery->get()
            ->pluck('foreign_id')->toArray();

        $members = [];

        if ($memberUserIds) {
            $memberUsers = get_users([
                'include' => $memberUserIds
            ]);


            foreach ($memberUsers as $memberUser) {
                $name = trim($memberUser->first_name . ' ' . $memberUser->last_name);
                if (!$name) {
                    $name = $memberUser->display_name;
                }

                $members[$memberUser->ID] = [
                    'ID'           => $memberUser->ID,
                    'display_name' => $name,
                    'photo'        => get_avatar_url($memberUser->user_email)
                ];
            }

        }

        $adminUsers = get_users([
            'role'    => 'administrator',
            'exclude' => $memberUserIds
        ]);

        foreach ($adminUsers as $user) {
            $name = trim($user->first_name . ' ' . $user->last_name);
            if (!$name) {
                $name = $user->display_name;
            }
            $members[$user->ID] = [
                'ID'           => $user->ID,
                'display_name' => $name,
                'photo'        => get_avatar_url($user->user_email)
            ];
        }

        $members = array_values($members);

        // sort members by name
        usort($members, function ($a, $b) {
            return strcmp($a['display_name'], $b['display_name']);
        });

        return [
            'members' => $members
        ];
    }

    public function globalSearch()
    {
        $currentUserId = get_current_user_id();

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- REST API endpoint, nonce verification handled by WordPress REST API
        $query = isset($_REQUEST['query']) ? strtolower(sanitize_text_field(wp_unslash($_REQUEST['query']))) : '';
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- REST API endpoint, nonce verification handled by WordPress REST API
        $scope = isset($_REQUEST['scope']) ? sanitize_text_field(wp_unslash($_REQUEST['scope'])) : 'all';

        // Pagination parameters
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- REST API endpoint, nonce verification handled by WordPress REST API
        $taskPage = isset($_REQUEST['task_page']) ? max(1, (int)$_REQUEST['task_page']) : 0;
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- REST API endpoint, nonce verification handled by WordPress REST API
        $boardPage = isset($_REQUEST['board_page']) ? max(1, (int)$_REQUEST['board_page']) : 0;
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- REST API endpoint, nonce verification handled by WordPress REST API
        $perPage = isset($_REQUEST['per_page']) ? (int)$_REQUEST['per_page'] : 20;
        $perPage = max(1, min(100, $perPage));

        // Build base queries
        $firstThreeChars = substr($query, 0, 3);
        $firstNineChars = substr($query, 0, 9);

        if($firstThreeChars == 'id:') {
            $idPart = preg_replace('/[^a-zA-Z0-9]/', '', substr($query, 3));
            $tasksQuery = Task::query()->where('parent_id', null)->whereRaw('id LIKE ?', ['%' . $idPart . '%']);
            $boardQuery = Board::query()->whereRaw('id LIKE ?', ['%' . $idPart . '%']);
        }elseif($firstNineChars == 'archived:') {
            $archivedPart = trim(substr($query, 9));
            $tasksQuery = Task::query()->where('parent_id', null)->whereNotNull('archived_at')->whereRaw('LOWER(title) LIKE ?', ['%' . $archivedPart . '%']);
            $boardQuery = Board::query()->whereNotNull('archived_at')->whereRaw('LOWER(title) LIKE ?', ['%' . $archivedPart . '%']);
        } else {
            $tasksQuery = Task::query()->where('parent_id', null)->whereRaw('LOWER(title) LIKE ?', ['%' . $query . '%']);
            $boardQuery = Board::query()->whereRaw('LOWER(title) LIKE ?', ['%' . $query . '%']);
        }

        // Apply scope and permissions
        $isUserAdmin = PermissionManager::isAdmin($currentUserId);
        if ($scope == 'all') {
            if (!$isUserAdmin) {
                $boardIds = PermissionManager::getBoardIdsForUser($currentUserId);
                $boardQuery->whereIn('id', $boardIds);
                $tasksQuery->whereIn('board_id', $boardIds);
            }
        } else {
            // For 'current_board' scope, we don't search boards
            $boardQuery->where('id', -1);

            $inBoard = absint($scope);
            if ($isUserAdmin || in_array($inBoard, PermissionManager::getBoardIdsForUser($currentUserId))) {
                $tasksQuery->where('board_id', $inBoard);
            } else {
                $tasksQuery->where('id', -1); // Force no results
            }
        }

        $allActiveBoardsIds = Board::query()->where('archived_at', null)->pluck('id')->toArray();

        $boards = [];
        $tasks = [];
        $totalBoards = 0;
        $totalTasks = 0;
        $formattedBoards = [];
        $formattedTasks = [];

        // Fetch Boards if requested
        if ($boardPage > 0) {
            $totalBoards = $boardQuery->count();
            $boardOffset = ($boardPage - 1) * $perPage;
            $boards = $boardQuery->skip($boardOffset)->take($perPage)->get();
        }

        // Fetch Tasks if requested
        if ($taskPage > 0) {
            $totalTasks = $tasksQuery->count();
            $taskOffset = ($taskPage - 1) * $perPage;
            $tasks = $tasksQuery->skip($taskOffset)->take($perPage)->get();
        }

        foreach ($boards as $board) {
            $formattedBoards[] = [
                'type'        => 'board',
                'id'          => $board->id,
                'title'       => $board->title,
                'description' => DescriptionMarkdownConverter::normalize($board->description),
            ];
        }
        foreach ($tasks as $task) {
            if (!in_array($task->board_id, $allActiveBoardsIds)) {
                continue;
            }

            $board = $task->board;
            $formattedTasks[] = [
                'type'        => 'task',
                'id'          => $task->id,
                'title'       => $task->title,
                'description' => DescriptionMarkdownConverter::normalize($task->description),
                'board_id'    => $task->board_id,
                'board'       => [
                    'id'    => $board->id,
                    'title' => $board->title,
                    'url'   => Helper::getBoardUrl($board->id)
                ],
                'stage'       => [
                    'id'    => $task->stage_id,
                    'title' => $task->stage->title ?? '',
                ],
            ];
        }

        return $this->sendSuccess([
            'tasks' => [
                'data' => $formattedTasks,
                'current_page' => $taskPage,
                'per_page' => $perPage,
                'total' => $totalTasks,
                'last_page' => (int) ceil($totalTasks / $perPage)
            ],
            'boards' => [
                'data' => $formattedBoards,
                'current_page' => $boardPage,
                'per_page' => $perPage,
                'total' => $totalBoards,
                'last_page' => (int) ceil($totalBoards / $perPage)
            ]
        ], 200);
    }

    public function getDashboardViewSettings(Request $request)
    {
        $view = $request->getSafe('view', 'sanitize_text_field');

        if ($view == 'kanbanview') {
            $globalSettings = $this->optionService->getDashboardViewSettings();
        } elseif ($view == 'listview') {
            $globalSettings = $this->optionService->getListViewPreferences();
        } elseif ($view == 'tableview') {
            $globalSettings = $this->optionService->getTableViewPreferences();
        } else {
            // Handle invalid view or default to one
            return $this->sendError(['message' => __('Invalid view type', 'fluent-boards')], 400);
        }

        if ($globalSettings->value)
            $currentSettings = maybe_unserialize($globalSettings->value);

        return $this->sendSuccess([
            'currentSettings' => $currentSettings,
        ], 200);
    }

    public function updateDashboardViewSettings(Request $request)
    {
        // updatedSettings is an array, sanitize each element
        $rawSettings = $request->get('updatedSettings');
        $newSettings = [];
        if (is_array($rawSettings)) {
            foreach ($rawSettings as $key => $value) {
                $sanitizedKey = sanitize_text_field($key);
                // Value could be string, boolean, or number - sanitize appropriately
                if (is_string($value)) {
                    $sanitizedValue = sanitize_text_field($value);
                } elseif (is_bool($value) || is_numeric($value)) {
                    $sanitizedValue = $value;
                } else {
                    $sanitizedValue = sanitize_text_field((string)$value);
                }
                $newSettings[$sanitizedKey] = $sanitizedValue;
            }
        }
        $view = $request->getSafe('view', 'sanitize_text_field');

        $this->optionService->updateDashboardViewSettings($newSettings, $view);

        if ($view == 'listview') {
            $message = __("List view settings updated successfully", 'fluent-boards');
        } elseif ($view == 'tableview') {
            $message = __("Table view settings updated successfully", 'fluent-boards');
        } else {
            $message = __("Card view settings updated successfully", 'fluent-boards');
        }

        return $this->sendSuccess([
            'message' => $message,
        ], 201);
    }


    public function getAddonsSettings()
    {
        $canAutoInstallKit = $this->canAutoInstallFluentKit();
        $kitPluginFile = 'fluent-toolkit/fluent-toolkit.php';
        $kitLoaded = defined('FLUENT_TOOLKIT_VERSION');
        $kitPluginExists = $this->isPluginInstalled($kitPluginFile);
        $kitActionText = __('Get FluentHub from GitHub', 'fluent-boards');

        if ($canAutoInstallKit) {
            $kitActionText = $kitPluginExists ? __('Activate FluentHub', 'fluent-boards') : __('Install FluentHub', 'fluent-boards');
        }

        $addOns = [
            'fluent-crm'     => [
                'title'          => __('FluentCRM', 'fluent-boards'),
                'logo'           => fluent_boards_mix('images/addons/fluent-crm.svg'),
                'is_installed'   => defined('FLUENTCRM'),
                'learn_more_url' => 'https://fluentcrm.com/',
                'associate_doc'  => 'https://fluentboards.com/docs/fluentboards-integration-with-fluentcrm/',
                'action_text'    => $this->isPluginInstalled('fluent-crm/fluent-crm.php') ? __('Activate FluentCRM', 'fluent-boards') : __('Install FluentCRM', 'fluent-boards'),
                'description'    => __('FluentCRM is a Self Hosted Email Marketing Automation Plugin for WordPress. Manage your leads and customers, email campaigns, automated email sequencing and many more', 'fluent-boards'),
                'short_desc'     => __('Email marketing automation', 'fluent-boards')
            ],
            'fluentform'     => [
                'title'          => __('Fluent Forms', 'fluent-boards'),
                'logo'           => fluent_boards_mix('images/addons/fluentform.png'),
                'is_installed'   => defined('FLUENTFORM'),
                'learn_more_url' => 'https://wordpress.org/plugins/fluentform/',
                'associate_doc'  => 'https://fluentboards.com/docs/fluentboards-integration-with-fluent-forms/',
                'action_text'    => $this->isPluginInstalled('fluent-form/fluent-form.php') ? __('Activate Fluent Forms', 'fluent-boards') : __('Install Fluent Forms', 'fluent-boards'),
                'description'    => __('Collect leads and build any type of forms, accept payments, connect with your CRM with the Fastest Contact Form Builder Plugin for WordPress', 'fluent-boards'),
                'short_desc'     => __('Create forms and accept payments', 'fluent-boards')
            ],
            'fluent-support' => [
                'title'          => __('Fluent Support', 'fluent-boards'),
                'logo'           => fluent_boards_mix('images/addons/fluent-support.svg'),
                'is_installed'   => defined('FLUENT_SUPPORT_VERSION'),
                'learn_more_url' => 'https://wordpress.org/plugins/fluent-connect/',
                'settings_url'   => admin_url('admin.php?page=fluent-support#/'),
                'associate_doc'  => 'https://fluentboards.com/docs/fluentboards-integration-with-fluentsupport/',
                'action_text'    => $this->isPluginInstalled('fluent-support/fluent-support.php') ? __('Activate Fluent Support', 'fluent-boards') : __('Install Fluent Support', 'fluent-boards'),
                'description'    => __('WordPress Helpdesk and Customer Support Ticket Plugin. Provide awesome support and manage customer queries right from your WordPress dashboard.', 'fluent-boards'),
                'short_desc'     => __('Customer support ticketing', 'fluent-boards')
            ],
            'fluent-smtp'    => [
                'title'          => __('Fluent SMTP', 'fluent-boards'),
                'logo'           => fluent_boards_mix('images/addons/fluent-smtp.svg'),
                'is_installed'   => defined('FLUENTMAIL'),
                'learn_more_url' => 'https://wordpress.org/plugins/fluent-smtp/',
                'associate_doc'  => admin_url('options-general.php?page=fluent-mail#/'),
                'action_text'    => $this->isPluginInstalled('fluent-smtp/fluent-smtp.php') ? __('Activate Fluent SMTP', 'fluent-boards') : __('Install Fluent SMTP', 'fluent-boards'),
                'description'    => __('The Ultimate SMTP and SES Plugin for WordPress. Connect with any SMTP, SendGrid, Mailgun, SES, Sendinblue, PepiPost, Google, Microsoft and more.', 'fluent-boards'),
                'short_desc'    => __('Reliable email delivery with SMTP', 'fluent-boards')
            ],
            'fluent-toolkit' => [
                'title'          => __('FluentHub', 'fluent-boards'),
                'logo'           => fluent_boards_mix('images/addons/fluent-toolkit.svg'),
                'is_installed'   => $kitLoaded,
                'learn_more_url' => 'https://github.com/WPManageNinja/fluent-toolkit',
                'settings_url'   => admin_url('admin.php?page=fluent-toolkit'),
                'associate_doc'  => 'https://github.com/WPManageNinja/fluent-toolkit',
                'action_text'    => $kitActionText,
                'install_route'  => $canAutoInstallKit ? 'admin/mcp/install-adapter' : '',
                'install_url'    => $canAutoInstallKit ? '' : 'https://github.com/WPManageNinja/fluent-toolkit',
                'description'    => __('Fluent Boards MCP tools become available after FluentHub is installed and active.', 'fluent-boards'),
                'short_desc'     => __('AI agent tools for Fluent Boards', 'fluent-boards')
            ],
        ];

        $addOns = apply_filters('fluent_boards/addons_settings', $addOns);

        $modules = fluent_boards_get_pref_settings(false);

        if (empty($modules['frontend']['render_type'])) {
            $modules['frontend']['render_type'] = 'standalone';
        }

        $modules['panel_url'] = fluent_boards_page_url();

        return [
            'addons'         => $addOns,
            'featureModules' => $modules
        ];
    }

    public function saveAddonsSettings(Request $request)
    {
        if (!defined('FLUENT_BOARDS_PRO')) {
            return $this->sendError([
                'message' => __('This feature is only available in Fluent Boards Pro', 'fluent-boards')
            ]);
        }

        $rawSettings = $request->get('settings', []);
        
        // Validate that settings is an array
        if (!is_array($rawSettings)) {
            return $this->sendError([
                'message' => __('Invalid settings format', 'fluent-boards')
            ], 400);
        }

        $prefSettings = fluent_boards_get_pref_settings(false);

        $settings = wp_parse_args($rawSettings, $prefSettings);

        $settings = Arr::only($settings, array_keys($prefSettings));
        
        // Sanitize slug if it exists
        if (isset($settings['frontend']['slug'])) {
            $settings['frontend']['slug'] = sanitize_title($settings['frontend']['slug']);
        }

        if (empty($settings['frontend']['slug'])) {
            $settings['frontend']['slug'] = 'projects';
        }

        if (defined('FLUENT_BOARDS_SLUG') && FLUENT_BOARDS_SLUG) {
            $settings['frontend']['slug'] = FLUENT_BOARDS_SLUG;
        }

        do_action('fluent_boards/saving_addons', $settings, $prefSettings);

        update_option('fluent_boards_modules', $settings, 'yes');

        if (isset($settings['recurring_task']['enabled']) && $settings['recurring_task']['enabled'] == 'no') {
            do_action('fluent_boards/recurring_task_disabled');
        }

        return $this->sendSuccess([
            'message'        => __('Settings are saved', 'fluent-boards'),
            'featureModules' => $settings
        ]);
    }

    public function installPlugin(Request $request)
    {
        if (!current_user_can('install_plugins')) {
            return $this->sendError([
                'message' => __('Sorry! you do not have permission to install plugin', 'fluent-boards')
            ]);
        }

        $plugin = $request->getSafe('plugin', 'sanitize_text_field');

        $acceptedFreePlugins = [
            'fluent-crm'     => 'fluent-crm.php',
            'fluentform'     => 'fluentform.php',
            'fluent-support' => 'fluent-support.php',
            'fluent-smtp'    => 'fluent-smtp.php'
        ];

        $acceptedPlugins = apply_filters('fluent_boards/accepted_plugins', $acceptedFreePlugins);

        if (!isset($acceptedPlugins[$plugin])) {
            return $this->sendError([
                'message' => __('Invalid plugin', 'fluent-boards')
            ]);
        }

        $pluginToInstall = [
            'name'      => __('Fluent Plugin', 'fluent-boards'),
            'repo-slug' => $plugin,
            'file'      => $acceptedPlugins[$plugin],
        ];

        // if plugin in free list then run background intaller otherwise call an action to install
        if (isset($acceptedFreePlugins[$plugin])) {
            $this->backgroundInstaller($pluginToInstall, $plugin);
        } else {
            do_action('fluent_boards/install_plugin', $pluginToInstall, $plugin);
        }

        return $this->sendSuccess([
            'message' => __('Plugin is being installed', 'fluent-boards')
        ]);
    }

    private function isPluginInstalled($plugin)
    {
        return file_exists(WP_PLUGIN_DIR . '/' . $plugin);
    }

    private function canAutoInstallFluentKit()
    {
        $canAutoInstall = (bool) apply_filters('fluent_kit/can_auto_install', false);

        if (!$canAutoInstall) {
            $canAutoInstall = (bool) apply_filters('fluent_toolkit/can_auto_install', false);
        }

        return $canAutoInstall;
    }

    private function backgroundInstaller($plugin_to_install, $plugin_id)
    {
        if (!empty($plugin_to_install['repo-slug'])) {
            require_once ABSPATH . 'wp-admin/includes/file.php';
            require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
            require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
            require_once ABSPATH . 'wp-admin/includes/plugin.php';

            WP_Filesystem();

            $skin = new \Automatic_Upgrader_Skin();
            $upgrader = new \WP_Upgrader($skin);
            $installed_plugins = array_reduce(array_keys(\get_plugins()), array($this, 'associate_plugin_file'), array());
            $plugin_slug = $plugin_to_install['repo-slug'];
            $plugin_file = isset($plugin_to_install['file']) ? $plugin_to_install['file'] : $plugin_slug . '.php';
            $installed = false;
            $activate = false;

            // See if the plugin is installed already.
            if (isset($installed_plugins[$plugin_file])) {
                $installed = true;
                $activate = !is_plugin_active($installed_plugins[$plugin_file]);
            }

            // Install this thing!
            if (!$installed) {
                // Suppress feedback.
                ob_start();

                try {
                    $plugin_information = plugins_api(
                        'plugin_information',
                        array(
                            'slug'   => $plugin_slug,
                            'fields' => array(
                                'short_description' => false,
                                'sections'          => false,
                                'requires'          => false,
                                'rating'            => false,
                                'ratings'           => false,
                                'downloaded'        => false,
                                'last_updated'      => false,
                                'added'             => false,
                                'tags'              => false,
                                'homepage'          => false,
                                'donate_link'       => false,
                                'author_profile'    => false,
                                'author'            => false,
                            ),
                        )
                    );

                    if (is_wp_error($plugin_information)) {
                        throw new \Exception(esc_html($plugin_information->get_error_message()));
                    }

                    $package = $plugin_information->download_link;
                    $download = $upgrader->download_package($package);

                    if (is_wp_error($download)) {
                        throw new \Exception(esc_html($download->get_error_message()));
                    }

                    $working_dir = $upgrader->unpack_package($download, true);

                    if (is_wp_error($working_dir)) {
                        throw new \Exception(esc_html($working_dir->get_error_message()));
                    }

                    $result = $upgrader->install_package(
                        array(
                            'source'                      => $working_dir,
                            'destination'                 => WP_PLUGIN_DIR,
                            'clear_destination'           => false,
                            'abort_if_destination_exists' => false,
                            'clear_working'               => true,
                            'hook_extra'                  => array(
                                'type'   => 'plugin',
                                'action' => 'install',
                            ),
                        )
                    );

                    if (is_wp_error($result)) {
                        throw new \Exception(esc_html($result->get_error_message()));
                    }

                    $activate = true;

                } catch (\Exception $e) {
                }

                // Discard feedback.
                ob_end_clean();
            }

            wp_clean_plugins_cache();

            // Activate this thing.
            if ($activate) {
                try {
                    $result = activate_plugin($installed ? $installed_plugins[$plugin_file] : $plugin_slug . '/' . $plugin_file);

                    if (is_wp_error($result)) {
                        throw new \Exception(esc_html($result->get_error_message()));
                    }
                } catch (\Exception $e) {
                }
            }
        }
    }

    private function associate_plugin_file($plugins, $key)
    {
        $path = explode('/', $key);
        $filename = end($path);
        $plugins[$filename] = $key;
        return $plugins;
    }

    public function getBoards(Request $request)
    {
        $boards = Board::select(['id', 'title', 'type'])
            ->byAccessUser(get_current_user_id())
            ->orderBy('title', 'ASC')
            ->get();

        return $this->sendSuccess([
            'boards' => $boards
        ]);
    }

    public function getPages(Request $request)
    {

        $db = App::getInstance('db');

        $allPages = $db->table('posts')->where('post_type', 'page')
            ->where('post_status', 'publish')
            ->select(['ID', 'post_title'])
            ->orderBy('post_title', 'ASC')
            ->get();

        $pages = [];
        foreach ($allPages as $page) {
            $pages[] = [
                'id'    => $page->ID,
                'title' => $page->post_title ? $page->post_title : __('(no title)', 'fluent-boards'),
                'url'   => esc_url_raw(get_permalink($page->ID))
            ];
        }

        return $this->sendSuccess([
            'pages' => $pages
        ]);
    }

    public function getGeneralSettings()
    {
        $settings = fluent_boards_get_option('general_settings', []);

        return $this->sendSuccess([
            'settings' => $settings,
            'server_timezone' => \wp_timezone_string()
        ]);

    }

    public function saveGeneralSettings(Request $request)
    {
        // check for pro version
        if (!defined('FLUENT_BOARDS_PRO')) {
            return $this->sendError([
                'message' => __('This feature is only available in Fluent Boards Pro. Please upgrade.', 'fluent-boards')
            ]);
        }
        // updatedSettings is an array, sanitize each element
        $rawSettings = $request->get('updatedSettings', []);
        $settings = [];
        if (is_array($rawSettings)) {
            foreach ($rawSettings as $key => $value) {
                $sanitizedKey = sanitize_text_field($key);
                // Value could be string, boolean, or number - sanitize appropriately
                if (is_string($value)) {
                    $sanitizedValue = sanitize_text_field($value);
                } elseif (is_bool($value) || is_numeric($value)) {
                    $sanitizedValue = $value;
                } else {
                    $sanitizedValue = sanitize_text_field((string)$value);
                }
                $settings[$sanitizedKey] = $sanitizedValue;
            }
        }

        $settings = apply_filters('fluent_boards/save_general_settings', $settings);

        $savedSettings = fluent_boards_update_option('general_settings', $settings);
        $savedGeneralSettings = \maybe_unserialize($savedSettings->value);

        $scheduleHandler = new ProScheduleHandler();

        $dailyReminderEnabled = $savedGeneralSettings['daily_reminder_enabled'] ?? false;

        if (filter_var($dailyReminderEnabled, FILTER_VALIDATE_BOOLEAN)) {
            // force schedule from this settings update
            $scheduleHandler->clearDailyTaskReminderScheduler();
            $scheduleHandler->scheduleDailyTaskReminder();
        }

        return $this->sendSuccess([
            'settings' => $savedGeneralSettings,
            'message' => __('Settings are saved', 'fluent-boards')
        ]);

    }

}

```
