PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.91.6
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.91.6
2.1.0 2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 All 42 releases
fluent-boards / app / Http / Controllers / OptionsController.php

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

1,033 lines 40.0 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\App;
6 use FluentBoards\App\Models\Meta;
7 use FluentBoards\App\Models\Task;
8 use FluentBoards\App\Models\User;
9 use FluentBoards\App\Models\Board;
10 use FluentBoards\App\Models\Relation;
11 use FluentBoards\App\Services\BoardService;
12 use FluentBoards\App\Services\Helper;
13 use FluentBoards\App\Services\OptionService;
14 use FluentBoards\App\Services\Constant;
15 use FluentBoards\App\Services\PermissionManager;
16 use FluentBoards\Framework\Http\Request\Request;
17 use FluentBoards\Framework\Support\Arr;
18 use FluentBoardsPro\App\Hooks\Handlers\ProScheduleHandler;
19
20 class OptionsController extends Controller
21 {
22 private $boardService;
23 private $optionService;
24
25 public function __construct(BoardService $boardService, OptionService $optionService)
26 {
27 parent::__construct();
28 $this->boardService = $boardService;
29 $this->optionService = $optionService;
30 }
31
32 public function selectorOptions(Request $request)
33 {
34 try {
35 $optionKey = $request->getSafe('option_key', 'sanitize_text_field');
36 $search = $request->getSafe('search', 'sanitize_text_field');
37 $includedIds = $request->getSafe('values');
38 $boardId = $request->getSafe('board_id', 'intval');
39
40 $options = [];
41 if ('users' === $optionKey || 'task_assignees' === $optionKey) { // no ajax/code is designed to handle this eventually will goto else
42
43 if (!PermissionManager::isBoardManager($boardId)) {
44 throw new \Exception(esc_html__('You do not have permission to access this route', 'fluent-boards'));
45 }
46
47 if (!defined('FLUENT_BOARDS_PRO')) {
48 // get who has 'manage_options' capability
49 $users = PermissionManager::getAll_WP_Admins($search);
50
51 } else {
52 // Search by user login, email, and nicename, first_name , last_name
53 $users = Helper::searchWordPressUsers($search);
54 $users = Helper::sanitizeUsersArray($users, $boardId);
55
56 }
57
58 $options = $this->addUserDataAsSelectorOption($users);
59
60 } elseif ('boards' === $optionKey) {
61 $boards = Board::query()
62 ->when($search, function ($query) use ($search) {
63 // $search is already sanitized with sanitize_text_field above
64 return $query->where('title', 'LIKE', '%' . $search . '%');
65 })->take(20)->get();
66
67 foreach ($boards as $board) {
68 $options[] = [
69 'id' => $board->id,
70 'title' => $board->title,
71 'left_side_value' => $board->title,
72 'right_side_value' => $board->slug,
73 ];
74 }
75 } elseif ('tasks' === $optionKey) {
76 // $boardId is already sanitized with intval above
77 $tasks = Task::where('board_id', $boardId)
78 ->whereNull('archived_at')
79 ->whereNull('parent_id')
80 ->when($search, function ($query) use ($search) {
81 // $search is already sanitized with sanitize_text_field above
82 return $query->where('title', 'LIKE', '%' . $search . '%');
83 })->take(20)->get();
84
85 foreach ($tasks as $task) {
86 $options[] = [
87 'id' => $task->id,
88 'title' => $task->title,
89 'board_id' => $task->board_id,
90 'subtask_groups' => $task->subtaskGroup
91 ];
92 }
93
94 } elseif ('assigned_in_task' == $optionKey) {
95 $users = (new BoardService())->getAssigneesByBoard($boardId, $search);
96 $options = $this->addUserDataAsSelectorOption($users);
97 } else {
98 $options = apply_filters('fluent_boards/ajax_options_' . $optionKey, [], $search, $includedIds);
99 }
100
101 return $this->sendSuccess([
102 'options' => $options,
103 ], 200);
104 } catch (\Exception $e) {
105 return $this->sendError($e->getMessage(), 404);
106 }
107 }
108
109 private function addUserDataAsSelectorOption($users)
110 {
111 $options = [];
112 foreach ($users as $user) {
113 $options[] = [
114 'id' => $user->ID,
115 'email' => $user->user_email,
116 'name' => $user->display_name ?? $user->user_email,
117 'title' => $user->display_name . ' (' . $user->user_email . ')',
118 'photo' => get_avatar_url($user->user_email),
119 ];
120 }
121 return $options;
122 }
123
124 public function getCurrentUserPermissions()
125 {
126 try {
127 $currentUserBoards = Relation::query()
128 ->where('user_id', get_current_user_id())
129 ->whereNotNull('board_id')
130 ->where('status', 'ACTIVE')
131 ->get();
132 foreach ($currentUserBoards as &$currentUserBoardPermission) {
133 $currentUserBoardPermission->permissions = \maybe_unserialize($currentUserBoardPermission->permissions);
134 }
135
136 return $this->sendSuccess(
137 $currentUserBoards, 200
138 );
139 } catch (\Exception $e) {
140 return $this->sendError($e->getMessage(), 404);
141 }
142 }
143
144 public function getUserPermission(Request $request)
145 {
146 try {
147 $boardId = $request->getSafe('boardId', 'intval');
148 $userId = $request->getSafe('userId', 'intval');
149
150 $boardUser = Relation::where('board_id', $boardId)
151 ->where('user_id', $userId)
152 ->where('status', 'ACTIVE')->first();
153 if (!$boardUser->is_admin) {
154 $boardUser->permissions = \maybe_unserialize($boardUser->permissions);
155 }
156
157 return $this->sendSuccess(
158 $boardUser, 200
159 );
160 } catch (\Exception $e) {
161 return $this->sendError($e->getMessage(), 404);
162 }
163 }
164
165 public function updatedUserPermission(Request $request)
166 {
167 try {
168 $permission = $request->getSafe('userPermission', 'sanitize_text_field');
169 $updateType = $request->getSafe('updateType', 'sanitize_text_field');
170 $boardId = $request->getSafe('boardId', 'intval');
171 $userId = $request->getSafe('userId', 'intval');
172
173 $boardUser = Relation::where('board_id', $boardId)->where('user_id', $userId)->status('ACTIVE')->first();
174
175 if ('Board Admin' == $permission) {
176 $boardUser->is_admin = 'add' == $updateType ? 1 : 0;
177 $boardUser->save();
178 } else {
179 if (0 == $boardUser->is_admin) {
180 $permissionsAlreadyHave = maybe_unserialize($boardUser->permissions);
181 if (!in_array($permission, $permissionsAlreadyHave) && 'add' == $updateType) {
182 array_push($permissionsAlreadyHave, $permission);
183 } elseif (in_array($permission, $permissionsAlreadyHave) && 'remove' == $updateType) {
184 if (($key = array_search($permission, $permissionsAlreadyHave)) !== false) {
185 unset($permissionsAlreadyHave[$key]);
186 }
187 }
188 $boardUser->permissions = serialize($permissionsAlreadyHave);
189 $boardUser->save();
190 }
191 }
192
193 $boardUser->permissions = maybe_unserialize($boardUser->permissions);
194
195 return $this->sendSuccess(
196 $boardUser, 200
197 );
198 } catch (\Exception $e) {
199 return $this->sendError($e->getMessage(), 404);
200 }
201 }
202
203 public function SetUserSuperAdmin($userId)
204 {
205 $userId = absint($userId);
206 try {
207 $this->optionService->createSuperAdmin($userId);
208 return $this->sendSuccess([
209 'message' => __('Member has been set super admin successfully!', 'fluent-boards')
210 ], 200);
211
212 } catch (\Exception $e) {
213 return $this->sendError($e->getMessage(), 404);
214 }
215 }
216
217 public function removeUserSuperAdmin($userId)
218 {
219 $userId = absint($userId);
220 try {
221 $this->optionService->removeUserSuperAdmin($userId);
222
223 return $this->sendSuccess([
224 'message' => __('User has been removed as super admin successfully!', 'fluent-boards'),
225 ], 200);
226 } catch (\Exception $e) {
227 return $this->sendError($e->getMessage(), 404);
228 }
229 }
230
231 public function IsUserAllBoardAdmin(Request $request)
232 {
233 try {
234 $userId = $request->getSafe('id', 'intval');
235 $isSuperAdmin = false;
236 $superAdmin = Relation::where('board_id', null)->where('user_id', $userId)->where('status', 'ACTIVE')->first();
237 $totalSuperAdmin = Relation::where('board_id', null)->where('status', 'ACTIVE')->count();
238 $permissions = [];
239 if ($superAdmin) {
240 $isSuperAdmin = true;
241 $permissions = \maybe_unserialize($superAdmin->permissions);
242 }
243
244 return $this->sendSuccess([
245 'allBoardAdmin' => $isSuperAdmin,
246 'permissions' => $permissions,
247 'numberOfSuperAdmin' => $totalSuperAdmin,
248 ], 200);
249 } catch (\Exception $e) {
250 return $this->sendError($e->getMessage(), 404);
251 }
252 }
253
254 public function RemoveUserFromSuperAdmin(Request $request, $id)
255 {
256 $id = absint($id);
257 try {
258 $userId = $request->getSafe('id', 'intval');
259
260 $superAdmin = Relation::where('board_id', null)->where('user_id', $userId)->first();
261 $superAdmin->status = 'INACTIVE';
262 $superAdmin->save();
263
264 return $this->sendSuccess([
265 'message' => __('User removed as super admin', 'fluent-boards'),
266 ], 201);
267 } catch (\Exception $e) {
268 return $this->sendError($e->getMessage(), 404);
269 }
270 }
271
272 public function removeUserFromBoard(Request $request)
273 {
274 try {
275 $boardId = $request->getSafe('boardId', 'intval');
276 $userId = $request->getSafe('userId', 'intval');
277
278 $this->boardService->removeUserFromBoard($boardId, $userId);
279
280 if (!PermissionManager::isAdmin($userId)) {
281 $this->boardService->removeFromRecentlyOpened($boardId, $userId);
282 }
283
284 return $this->sendSuccess([
285 'message' => __('User Removed from Board successfully!', 'fluent-boards'),
286 ], 201);
287 } catch (\Exception $e) {
288 return $this->sendError($e->getMessage(), 404);
289 }
290 }
291
292 public function addAsSuperAdmin(Request $request)
293 {
294 try {
295 $rawUserIds = $request->getSafe('memberIds');
296 // Sanitize array of user IDs
297 $userIds = [];
298 if (is_array($rawUserIds)) {
299 $userIds = array_filter(array_map('intval', $rawUserIds));
300 }
301 foreach ($userIds as $userId) {
302 $this->createSuperAdmin($userId);
303 }
304
305 return $this->sendSuccess([
306 'message' => __('Fluent boards admin added', 'fluent-boards'),
307 ], 201);
308 } catch (\Exception $e) {
309 return $this->sendError($e->getMessage(), 404);
310 }
311 }
312
313 private function createSuperAdmin($userId)
314 {
315 try {
316 $existUser = Meta::where('object_id', $userId)->first();
317 if (!$existUser) {
318 $meta = new Meta();
319 $meta->object_id = $userId;
320 $meta->object_type = Constant::FLUENT_BOARD_ADMIN;
321 $meta->save();
322 }
323 } catch (\Exception $e) {
324 return $this->sendError($e->getMessage(), 404);
325 }
326 }
327
328 public function addMembersInBoards(Request $request)
329 {
330 try {
331 $rawUserIds = $request->getSafe('memberIds');
332 $rawBoardIds = $request->getSafe('boardIds');
333
334 // Sanitize arrays of IDs
335 $userIds = [];
336 if (is_array($rawUserIds)) {
337 $userIds = array_filter(array_map('intval', $rawUserIds));
338 }
339 $boardIds = [];
340 if (is_array($rawBoardIds)) {
341 $boardIds = array_filter(array_map('intval', $rawBoardIds));
342 }
343
344 foreach ($userIds as $userId) {
345 foreach ($boardIds as $boardId) {
346 $this->boardService->addMembersInBoard($boardId, $userId);
347 }
348 }
349
350 return $this->sendSuccess([
351 'message' => __('Members added to boards', 'fluent-boards'),
352 ], 201);
353 } catch (\Exception $e) {
354 return $this->sendError($e->getMessage(), 404);
355 }
356 }
357
358
359 public function updateGlobalNotificationSettings(Request $request)
360 {
361 try {
362 // updatedSettings is an array, sanitize each element
363 $rawSettings = $request->get('updatedSettings');
364 $newSettings = [];
365 if (is_array($rawSettings)) {
366 foreach ($rawSettings as $key => $value) {
367 $sanitizedKey = sanitize_text_field($key);
368 $sanitizedValue = sanitize_text_field($value);
369 $newSettings[$sanitizedKey] = $sanitizedValue;
370 }
371 }
372
373 $this->optionService->updateGlobalNotificationSettings($newSettings);
374
375 return $this->sendSuccess([
376 'message' => __("Notification settings are updated", 'fluent-boards'),
377 ], 201);
378 } catch (\Exception $e) {
379 return $this->sendError($e->getMessage(), 404);
380 }
381 }
382
383 public function getGlobalNotificationSettings()
384 {
385 try {
386 $globalSettings = $this->optionService->getGlobalNotificationSettings();
387 if ($globalSettings->value)
388 $currentSettings = maybe_unserialize($globalSettings->value);
389
390 return $this->sendSuccess([
391 'currentSettings' => $currentSettings,
392 ], 200);
393 } catch (\Exception $e) {
394 return $this->sendError($e->getMessage(), 404);
395 }
396 }
397
398 public function getBoardMembers(Request $request)
399 {
400 if (!PermissionManager::userHasAnyBoardAccess()) {
401 return $this->sendError([
402 'message' => __('You do not have permission to access this route', 'fluent-boards')
403 ]);
404 }
405
406 $boardId = $request->getSafe('boardId', 'intval');
407
408 $memberUserIds = Relation::where('object_type', 'board_user')
409 ->select(['foreign_id'])
410 ->groupBy('foreign_id');
411
412 if ($boardId) {
413 $memberUserIds = $memberUserIds->where('object_id', $boardId);
414 }
415
416 $members = [];
417
418 $memberUserIds = $memberUserIds->get()
419 ->pluck('foreign_id')->toArray();
420
421
422 if ($memberUserIds) {
423 $memberUsers = get_users([
424 'include' => $memberUserIds
425 ]);
426
427
428 foreach ($memberUsers as $memberUser) {
429 $name = trim($memberUser->first_name . ' ' . $memberUser->last_name);
430 if (!$name) {
431 $name = $memberUser->display_name;
432 }
433
434 $members[$memberUser->ID] = [
435 'ID' => $memberUser->ID,
436 'display_name' => $name,
437 'photo' => get_avatar_url($memberUser->user_email)
438 ];
439 }
440
441 }
442
443 $adminUsers = get_users([
444 'role' => 'administrator',
445 'exclude' => $memberUserIds
446 ]);
447
448 foreach ($adminUsers as $user) {
449 $name = trim($user->first_name . ' ' . $user->last_name);
450 if (!$name) {
451 $name = $user->display_name;
452 }
453 $members[$user->ID] = [
454 'ID' => $user->ID,
455 'display_name' => $name,
456 'photo' => get_avatar_url($user->user_email)
457 ];
458 }
459
460 $members = array_values($members);
461
462 // sort members by name
463 usort($members, function ($a, $b) {
464 return strcmp($a['display_name'], $b['display_name']);
465 });
466
467 return [
468 'members' => $members
469 ];
470 }
471
472 public function quickSearch()
473 {
474 $currentUserId = get_current_user_id();
475
476 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- REST API endpoint, nonce verification handled by WordPress REST API
477 $query = isset($_REQUEST['query']) ? strtolower(sanitize_text_field(wp_unslash($_REQUEST['query']))) : '';
478 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- REST API endpoint, nonce verification handled by WordPress REST API
479 $scope = isset($_REQUEST['scope']) ? sanitize_text_field(wp_unslash($_REQUEST['scope'])) : 'all';
480
481 // Pagination parameters
482 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- REST API endpoint, nonce verification handled by WordPress REST API
483 $taskPage = isset($_REQUEST['task_page']) ? max(1, (int)$_REQUEST['task_page']) : 0;
484 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- REST API endpoint, nonce verification handled by WordPress REST API
485 $boardPage = isset($_REQUEST['board_page']) ? max(1, (int)$_REQUEST['board_page']) : 0;
486 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- REST API endpoint, nonce verification handled by WordPress REST API
487 $perPage = isset($_REQUEST['per_page']) ? (int)$_REQUEST['per_page'] : 20;
488
489 // Build base queries
490 $firstThreeChars = substr($query, 0, 3);
491 $firstNineChars = substr($query, 0, 9);
492
493 if($firstThreeChars == 'id:') {
494 $idPart = preg_replace('/[^a-zA-Z0-9]/', '', substr($query, 3));
495 $tasksQuery = Task::query()->where('parent_id', null)->whereRaw('id LIKE ?', ['%' . $idPart . '%']);
496 $boardQuery = Board::query()->whereRaw('id LIKE ?', ['%' . $idPart . '%']);
497 }elseif($firstNineChars == 'archived:') {
498 $archivedPart = trim(substr($query, 9));
499 $tasksQuery = Task::query()->where('parent_id', null)->whereNotNull('archived_at')->whereRaw('LOWER(title) LIKE ?', ['%' . $archivedPart . '%']);
500 $boardQuery = Board::query()->whereNotNull('archived_at')->whereRaw('LOWER(title) LIKE ?', ['%' . $archivedPart . '%']);
501 } else {
502 $tasksQuery = Task::query()->where('parent_id', null)->whereRaw('LOWER(title) LIKE ?', ['%' . $query . '%']);
503 $boardQuery = Board::query()->whereRaw('LOWER(title) LIKE ?', ['%' . $query . '%']);
504 }
505
506 // Apply scope and permissions
507 $isUserAdmin = PermissionManager::isAdmin($currentUserId);
508 if ($scope == 'all') {
509 if (!$isUserAdmin) {
510 $boardIds = PermissionManager::getBoardIdsForUser($currentUserId);
511 $boardQuery->whereIn('id', $boardIds);
512 $tasksQuery->whereIn('board_id', $boardIds);
513 }
514 } else {
515 // For 'current_board' scope, we don't search boards
516 $boardQuery->where('id', -1);
517
518 $inBoard = absint($scope);
519 if ($isUserAdmin || in_array($inBoard, PermissionManager::getBoardIdsForUser($currentUserId))) {
520 $tasksQuery->where('board_id', $inBoard);
521 } else {
522 $tasksQuery->where('id', -1); // Force no results
523 }
524 }
525
526 $allActiveBoardsIds = Board::query()->where('archived_at', null)->pluck('id')->toArray();
527
528 $boards = [];
529 $tasks = [];
530 $totalBoards = 0;
531 $totalTasks = 0;
532 $formattedBoards = [];
533 $formattedTasks = [];
534
535 // Fetch Boards if requested
536 if ($boardPage > 0) {
537 $totalBoards = $boardQuery->count();
538 $boardOffset = ($boardPage - 1) * $perPage;
539 $boards = $boardQuery->skip($boardOffset)->take($perPage)->get();
540 }
541
542 // Fetch Tasks if requested
543 if ($taskPage > 0) {
544 $totalTasks = $tasksQuery->count();
545 $taskOffset = ($taskPage - 1) * $perPage;
546 $tasks = $tasksQuery->skip($taskOffset)->take($perPage)->get();
547 }
548
549 foreach ($boards as $board) {
550 $formattedBoards[] = [
551 'type' => 'board',
552 'id' => $board->id,
553 'title' => $board->title,
554 'description' => $board->description,
555 ];
556 }
557 foreach ($tasks as $task) {
558 if (!in_array($task->board_id, $allActiveBoardsIds)) {
559 continue;
560 }
561
562 $board = $task->board;
563 $formattedTasks[] = [
564 'type' => 'task',
565 'id' => $task->id,
566 'title' => $task->title,
567 'description' => $task->description,
568 'board_id' => $task->board_id,
569 'board' => [
570 'id' => $board->id,
571 'title' => $board->title,
572 'url' => Helper::getBoardUrl($board->id)
573 ],
574 'stage' => [
575 'id' => $task->stage_id,
576 'title' => $task->stage->title ?? '',
577 ],
578 ];
579 }
580
581 return $this->sendSuccess([
582 'tasks' => [
583 'data' => $formattedTasks,
584 'current_page' => $taskPage,
585 'per_page' => $perPage,
586 'total' => $totalTasks,
587 'last_page' => (int) ceil($totalTasks / $perPage)
588 ],
589 'boards' => [
590 'data' => $formattedBoards,
591 'current_page' => $boardPage,
592 'per_page' => $perPage,
593 'total' => $totalBoards,
594 'last_page' => (int) ceil($totalBoards / $perPage)
595 ]
596 ], 200);
597 }
598
599 public function getDashboardViewSettings(Request $request)
600 {
601 $view = $request->getSafe('view', 'sanitize_text_field');
602
603 if ($view == 'kanbanview') {
604 $globalSettings = $this->optionService->getDashboardViewSettings();
605 } elseif ($view == 'listview') {
606 $globalSettings = $this->optionService->getListViewPreferences();
607 } elseif ($view == 'tableview') {
608 $globalSettings = $this->optionService->getTableViewPreferences();
609 } else {
610 // Handle invalid view or default to one
611 return $this->sendError(['message' => __('Invalid view type', 'fluent-boards')], 400);
612 }
613
614 if ($globalSettings->value)
615 $currentSettings = maybe_unserialize($globalSettings->value);
616
617 return $this->sendSuccess([
618 'currentSettings' => $currentSettings,
619 ], 200);
620 }
621
622 public function updateDashboardViewSettings(Request $request)
623 {
624 // updatedSettings is an array, sanitize each element
625 $rawSettings = $request->get('updatedSettings');
626 $newSettings = [];
627 if (is_array($rawSettings)) {
628 foreach ($rawSettings as $key => $value) {
629 $sanitizedKey = sanitize_text_field($key);
630 // Value could be string, boolean, or number - sanitize appropriately
631 if (is_string($value)) {
632 $sanitizedValue = sanitize_text_field($value);
633 } elseif (is_bool($value) || is_numeric($value)) {
634 $sanitizedValue = $value;
635 } else {
636 $sanitizedValue = sanitize_text_field((string)$value);
637 }
638 $newSettings[$sanitizedKey] = $sanitizedValue;
639 }
640 }
641 $view = $request->getSafe('view', 'sanitize_text_field');
642
643 $this->optionService->updateDashboardViewSettings($newSettings, $view);
644
645 if ($view == 'listview') {
646 $message = __("List view settings updated successfully", 'fluent-boards');
647 } elseif ($view == 'tableview') {
648 $message = __("Table view settings updated successfully", 'fluent-boards');
649 } else {
650 $message = __("Card view settings updated successfully", 'fluent-boards');
651 }
652
653 return $this->sendSuccess([
654 'message' => $message,
655 ], 201);
656 }
657
658
659 public function getAddonsSettings()
660 {
661 $addOns = [
662 'fluent-crm' => [
663 'title' => __('FluentCRM', 'fluent-boards'),
664 'logo' => fluent_boards_mix('images/addons/fluent-crm.svg'),
665 'is_installed' => defined('FLUENTCRM'),
666 'learn_more_url' => 'https://fluentcrm.com/',
667 'associate_doc' => 'https://fluentboards.com/docs/fluentboards-integration-with-fluentcrm/',
668 'action_text' => $this->isPluginInstalled('fluent-crm/fluent-crm.php') ? __('Activate FluentCRM', 'fluent-boards') : __('Install FluentCRM', 'fluent-boards'),
669 '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'),
670 'short_desc' => __('Email marketing automation', 'fluent-boards')
671 ],
672 'fluentform' => [
673 'title' => __('Fluent Forms', 'fluent-boards'),
674 'logo' => fluent_boards_mix('images/addons/fluentform.png'),
675 'is_installed' => defined('FLUENTFORM'),
676 'learn_more_url' => 'https://wordpress.org/plugins/fluentform/',
677 'associate_doc' => 'https://fluentboards.com/docs/fluentboards-integration-with-fluent-forms/',
678 'action_text' => $this->isPluginInstalled('fluent-form/fluent-form.php') ? __('Activate Fluent Forms', 'fluent-boards') : __('Install Fluent Forms', 'fluent-boards'),
679 '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'),
680 'short_desc' => __('Create forms and accept payments', 'fluent-boards')
681 ],
682 'fluent-support' => [
683 'title' => __('Fluent Support', 'fluent-boards'),
684 'logo' => fluent_boards_mix('images/addons/fluent-support.svg'),
685 'is_installed' => defined('FLUENT_SUPPORT_VERSION'),
686 'learn_more_url' => 'https://wordpress.org/plugins/fluent-connect/',
687 'settings_url' => admin_url('admin.php?page=fluent-support#/'),
688 'associate_doc' => 'https://fluentboards.com/docs/fluentboards-integration-with-fluentsupport/',
689 'action_text' => $this->isPluginInstalled('fluent-support/fluent-support.php') ? __('Activate Fluent Support', 'fluent-boards') : __('Install Fluent Support', 'fluent-boards'),
690 'description' => __('WordPress Helpdesk and Customer Support Ticket Plugin. Provide awesome support and manage customer queries right from your WordPress dashboard.', 'fluent-boards'),
691 'short_desc' => __('Customer support ticketing', 'fluent-boards')
692 ],
693 'fluent-smtp' => [
694 'title' => __('Fluent SMTP', 'fluent-boards'),
695 'logo' => fluent_boards_mix('images/addons/fluent-smtp.svg'),
696 'is_installed' => defined('FLUENTMAIL'),
697 'learn_more_url' => 'https://wordpress.org/plugins/fluent-smtp/',
698 'associate_doc' => admin_url('options-general.php?page=fluent-mail#/'),
699 'action_text' => $this->isPluginInstalled('fluent-smtp/fluent-smtp.php') ? __('Activate Fluent SMTP', 'fluent-boards') : __('Install Fluent SMTP', 'fluent-boards'),
700 'description' => __('The Ultimate SMTP and SES Plugin for WordPress. Connect with any SMTP, SendGrid, Mailgun, SES, Sendinblue, PepiPost, Google, Microsoft and more.', 'fluent-boards'),
701 'short_desc' => __('Reliable email delivery with SMTP', 'fluent-boards')
702 ],
703 ];
704
705 $addOns = apply_filters('fluent_boards/addons_settings', $addOns);
706
707 $modules = fluent_boards_get_pref_settings(false);
708
709 if (empty($modules['frontend']['render_type'])) {
710 $modules['frontend']['render_type'] = 'standalone';
711 }
712
713 $modules['panel_url'] = fluent_boards_page_url();
714
715 return [
716 'addons' => $addOns,
717 'featureModules' => $modules
718 ];
719 }
720
721 public function saveAddonsSettings(Request $request)
722 {
723 if (!defined('FLUENT_BOARDS_PRO')) {
724 return $this->sendError([
725 'message' => __('This feature is only available in Fluent Boards Pro', 'fluent-boards')
726 ]);
727 }
728
729 $rawSettings = $request->get('settings', []);
730
731 // Validate that settings is an array
732 if (!is_array($rawSettings)) {
733 return $this->sendError([
734 'message' => __('Invalid settings format', 'fluent-boards')
735 ], 400);
736 }
737
738 $prefSettings = fluent_boards_get_pref_settings(false);
739
740 $settings = wp_parse_args($rawSettings, $prefSettings);
741
742 $settings = Arr::only($settings, array_keys($prefSettings));
743
744 // Sanitize slug if it exists
745 if (isset($settings['frontend']['slug'])) {
746 $settings['frontend']['slug'] = sanitize_title($settings['frontend']['slug']);
747 }
748
749 if (empty($settings['frontend']['slug'])) {
750 $settings['frontend']['slug'] = 'projects';
751 }
752
753 if (defined('FLUENT_BOARDS_SLUG') && FLUENT_BOARDS_SLUG) {
754 $settings['frontend']['slug'] = FLUENT_BOARDS_SLUG;
755 }
756
757 do_action('fluent_boards/saving_addons', $settings, $prefSettings);
758
759 update_option('fluent_boards_modules', $settings, 'yes');
760
761 if (isset($settings['recurring_task']['enabled']) && $settings['recurring_task']['enabled'] == 'no') {
762 do_action('fluent_boards/recurring_task_disabled');
763 }
764
765 return $this->sendSuccess([
766 'message' => __('Settings are saved', 'fluent-boards'),
767 'featureModules' => $settings
768 ]);
769 }
770
771 public function installPlugin(Request $request)
772 {
773 if (!current_user_can('install_plugins')) {
774 return $this->sendError([
775 'message' => __('Sorry! you do not have permission to install plugin', 'fluent-boards')
776 ]);
777 }
778
779 $plugin = $request->getSafe('plugin', 'sanitize_text_field');
780
781 $acceptedFreePlugins = [
782 'fluent-crm' => 'fluent-crm.php',
783 'fluentform' => 'fluentform.php',
784 'fluent-support' => 'fluent-support.php',
785 'fluent-smtp' => 'fluent-smtp.php'
786 ];
787
788 $acceptedPlugins = apply_filters('fluent_boards/accepted_plugins', $acceptedFreePlugins);
789
790 if (!isset($acceptedPlugins[$plugin])) {
791 return $this->sendError([
792 'message' => __('Invalid plugin', 'fluent-boards')
793 ]);
794 }
795
796 $pluginToInstall = [
797 'name' => __('Fluent Plugin', 'fluent-boards'),
798 'repo-slug' => $plugin,
799 'file' => $acceptedPlugins[$plugin],
800 ];
801
802 // if plugin in free list then run background intaller otherwise call an action to install
803 if (isset($acceptedFreePlugins[$plugin])) {
804 $this->backgroundInstaller($pluginToInstall, $plugin);
805 } else {
806 do_action('fluent_boards/install_plugin', $pluginToInstall, $plugin);
807 }
808
809 return $this->sendSuccess([
810 'message' => __('Plugin is being installed', 'fluent-boards')
811 ]);
812 }
813
814 private function isPluginInstalled($plugin)
815 {
816 return file_exists(WP_PLUGIN_DIR . '/' . $plugin);
817 }
818
819 private function backgroundInstaller($plugin_to_install, $plugin_id)
820 {
821 if (!empty($plugin_to_install['repo-slug'])) {
822 require_once ABSPATH . 'wp-admin/includes/file.php';
823 require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
824 require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
825 require_once ABSPATH . 'wp-admin/includes/plugin.php';
826
827 WP_Filesystem();
828
829 $skin = new \Automatic_Upgrader_Skin();
830 $upgrader = new \WP_Upgrader($skin);
831 $installed_plugins = array_reduce(array_keys(\get_plugins()), array($this, 'associate_plugin_file'), array());
832 $plugin_slug = $plugin_to_install['repo-slug'];
833 $plugin_file = isset($plugin_to_install['file']) ? $plugin_to_install['file'] : $plugin_slug . '.php';
834 $installed = false;
835 $activate = false;
836
837 // See if the plugin is installed already.
838 if (isset($installed_plugins[$plugin_file])) {
839 $installed = true;
840 $activate = !is_plugin_active($installed_plugins[$plugin_file]);
841 }
842
843 // Install this thing!
844 if (!$installed) {
845 // Suppress feedback.
846 ob_start();
847
848 try {
849 $plugin_information = plugins_api(
850 'plugin_information',
851 array(
852 'slug' => $plugin_slug,
853 'fields' => array(
854 'short_description' => false,
855 'sections' => false,
856 'requires' => false,
857 'rating' => false,
858 'ratings' => false,
859 'downloaded' => false,
860 'last_updated' => false,
861 'added' => false,
862 'tags' => false,
863 'homepage' => false,
864 'donate_link' => false,
865 'author_profile' => false,
866 'author' => false,
867 ),
868 )
869 );
870
871 if (is_wp_error($plugin_information)) {
872 throw new \Exception(esc_html($plugin_information->get_error_message()));
873 }
874
875 $package = $plugin_information->download_link;
876 $download = $upgrader->download_package($package);
877
878 if (is_wp_error($download)) {
879 throw new \Exception(esc_html($download->get_error_message()));
880 }
881
882 $working_dir = $upgrader->unpack_package($download, true);
883
884 if (is_wp_error($working_dir)) {
885 throw new \Exception(esc_html($working_dir->get_error_message()));
886 }
887
888 $result = $upgrader->install_package(
889 array(
890 'source' => $working_dir,
891 'destination' => WP_PLUGIN_DIR,
892 'clear_destination' => false,
893 'abort_if_destination_exists' => false,
894 'clear_working' => true,
895 'hook_extra' => array(
896 'type' => 'plugin',
897 'action' => 'install',
898 ),
899 )
900 );
901
902 if (is_wp_error($result)) {
903 throw new \Exception(esc_html($result->get_error_message()));
904 }
905
906 $activate = true;
907
908 } catch (\Exception $e) {
909 }
910
911 // Discard feedback.
912 ob_end_clean();
913 }
914
915 wp_clean_plugins_cache();
916
917 // Activate this thing.
918 if ($activate) {
919 try {
920 $result = activate_plugin($installed ? $installed_plugins[$plugin_file] : $plugin_slug . '/' . $plugin_file);
921
922 if (is_wp_error($result)) {
923 throw new \Exception(esc_html($result->get_error_message()));
924 }
925 } catch (\Exception $e) {
926 }
927 }
928 }
929 }
930
931 private function associate_plugin_file($plugins, $key)
932 {
933 $path = explode('/', $key);
934 $filename = end($path);
935 $plugins[$filename] = $key;
936 return $plugins;
937 }
938
939 public function getBoards(Request $request)
940 {
941 $boards = Board::select(['id', 'title', 'type'])
942 ->byAccessUser(get_current_user_id())
943 ->orderBy('title', 'ASC')
944 ->get();
945
946 return $this->sendSuccess([
947 'boards' => $boards
948 ]);
949 }
950
951 public function getPages(Request $request)
952 {
953
954 $db = App::getInstance('db');
955
956 $allPages = $db->table('posts')->where('post_type', 'page')
957 ->where('post_status', 'publish')
958 ->select(['ID', 'post_title'])
959 ->orderBy('post_title', 'ASC')
960 ->get();
961
962 $pages = [];
963 foreach ($allPages as $page) {
964 $pages[] = [
965 'id' => $page->ID,
966 'title' => $page->post_title ? $page->post_title : __('(no title)', 'fluent-boards')
967 ];
968 }
969
970 return $this->sendSuccess([
971 'pages' => $pages
972 ]);
973 }
974
975 public function getGeneralSettings()
976 {
977 $settings = fluent_boards_get_option('general_settings', []);
978
979 return $this->sendSuccess([
980 'settings' => $settings,
981 'server_timezone' => \wp_timezone_string()
982 ]);
983
984 }
985
986 public function saveGeneralSettings(Request $request)
987 {
988 // check for pro version
989 if (!defined('FLUENT_BOARDS_PRO')) {
990 return $this->sendError([
991 'message' => __('This feature is only available in Fluent Boards Pro. Please upgrade.', 'fluent-boards')
992 ]);
993 }
994 // updatedSettings is an array, sanitize each element
995 $rawSettings = $request->get('updatedSettings', []);
996 $settings = [];
997 if (is_array($rawSettings)) {
998 foreach ($rawSettings as $key => $value) {
999 $sanitizedKey = sanitize_text_field($key);
1000 // Value could be string, boolean, or number - sanitize appropriately
1001 if (is_string($value)) {
1002 $sanitizedValue = sanitize_text_field($value);
1003 } elseif (is_bool($value) || is_numeric($value)) {
1004 $sanitizedValue = $value;
1005 } else {
1006 $sanitizedValue = sanitize_text_field((string)$value);
1007 }
1008 $settings[$sanitizedKey] = $sanitizedValue;
1009 }
1010 }
1011
1012 $settings = apply_filters('fluent_boards/save_general_settings', $settings);
1013
1014 $savedSettings = fluent_boards_update_option('general_settings', $settings);
1015 $savedGeneralSettings = \maybe_unserialize($savedSettings->value);
1016
1017 $scheduleHandler = new ProScheduleHandler();
1018
1019 if ($savedGeneralSettings['daily_reminder_enabled'] || $savedGeneralSettings['daily_reminder_enabled'] == 'true') {
1020 // force schedule from this settings update
1021 $scheduleHandler->clearDailyTaskReminderScheduler();
1022 $scheduleHandler->scheduleDailyTaskReminder();
1023 }
1024
1025 return $this->sendSuccess([
1026 'settings' => $savedGeneralSettings,
1027 'message' => __('Settings are saved', 'fluent-boards')
1028 ]);
1029
1030 }
1031
1032 }
1033