PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / trunk
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration vtrunk
2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 1.45 All 41 releases
← All changes | app/Http/Controllers/OptionsController.php +76 -13 1.95.2trunk View file →
@@ -8,8 +8,9 @@
8 8 use FluentBoards\App\Models\User;
9 9 use FluentBoards\App\Models\Board;
10 10 use FluentBoards\App\Models\Relation;
11 11 use FluentBoards\App\Services\BoardService;
12 +use FluentBoards\App\Services\DescriptionMarkdownConverter;
12 13 use FluentBoards\App\Services\Helper;
13 14 use FluentBoards\App\Services\OptionService;
14 15 use FluentBoards\App\Services\Constant;
15 16 use FluentBoards\App\Services\PermissionManager;
@@ -56,10 +57,28 @@
56 57 }
57 58
58 59 $options = $this->addUserDataAsSelectorOption($users);
59 60
61 + } elseif ('board_create_users' === $optionKey) {
62 + if (!PermissionManager::userHasBoardCreationPermission()) {
63 + throw new \Exception(esc_html__('You do not have permission to access this route', 'fluent-boards'));
64 + }
65 +
66 + if (!defined('FLUENT_BOARDS_PRO')) {
67 + // Bound the lookup: this popover searches on focus (empty query too).
68 + $users = PermissionManager::getAll_WP_Admins($search, 20);
69 + } else {
70 + $users = Helper::searchWordPressUsers($search);
71 + }
72 +
73 + $options = $this->addUserDataAsSelectorOption($users);
74 + // Board creation can be delegated to members without `list_users`;
75 + // don't leak full account emails to them.
76 + $options = $this->maskSelectorEmailsForViewer($options);
77 +
60 78 } elseif ('boards' === $optionKey) {
61 79 $boards = Board::query()
80 + ->byAccessUser(get_current_user_id())
62 81 ->when($search, function ($query) use ($search) {
63 82 // $search is already sanitized with sanitize_text_field above
64 83 return $query->where('title', 'LIKE', '%' . $search . '%');
65 84 })->take(20)->get();
@@ -128,8 +147,42 @@
128 147 }
129 148 return $options;
130 149 }
131 150
151 + /**
152 + * Obfuscate emails in selector options for viewers who lack the `list_users`
153 + * capability, keeping the current user's own email visible.
154 + */
155 + private function maskSelectorEmailsForViewer($options)
156 + {
157 + if (current_user_can('list_users')) {
158 + return $options;
159 + }
160 +
161 + $currentUser = wp_get_current_user();
162 + $currentUserEmail = ($currentUser && isset($currentUser->user_email)) ? $currentUser->user_email : '';
163 +
164 + foreach ($options as $index => $option) {
165 + $email = $option['email'] ?? '';
166 +
167 + if ($email === '' || $email === $currentUserEmail) {
168 + continue;
169 + }
170 +
171 + $maskedEmail = Helper::obfuscateEmail($email);
172 +
173 + // When there's no display name the raw email doubles as the name.
174 + if (($option['name'] ?? '') === $email) {
175 + $options[$index]['name'] = $maskedEmail;
176 + }
177 +
178 + $options[$index]['email'] = $maskedEmail;
179 + $options[$index]['title'] = ($options[$index]['name'] ?? $maskedEmail) . ' (' . $maskedEmail . ')';
180 + }
181 +
182 + return $options;
183 + }
184 +
132 185 public function getCurrentUserPermissions()
133 186 {
134 187 try {
135 188 $currentUserBoards = Relation::query()
@@ -412,21 +465,29 @@
412 465 }
413 466
414 467 $boardId = $request->getSafe('boardId', 'intval');
415 468
416 - $memberUserIds = Relation::where('object_type', 'board_user')
469 + if ($boardId && !PermissionManager::userHasPermission($boardId)) {
470 + return $this->sendError([
471 + 'message' => __('You do not have permission to access this route', 'fluent-boards')
472 + ], 403);
473 + }
474 +
475 + $memberUserIdsQuery = Relation::where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
417 476 ->select(['foreign_id'])
418 477 ->groupBy('foreign_id');
419 478
420 479 if ($boardId) {
421 - $memberUserIds = $memberUserIds->where('object_id', $boardId);
480 + $memberUserIdsQuery->where('object_id', $boardId);
481 + } elseif (!PermissionManager::isAdmin()) {
482 + $boardIds = array_filter(array_map('intval', PermissionManager::getBoardIdsForUser()));
483 + $memberUserIdsQuery->whereIn('object_id', $boardIds);
422 484 }
423 485
424 - $members = [];
425 -
426 - $memberUserIds = $memberUserIds->get()
486 + $memberUserIds = $memberUserIdsQuery->get()
427 487 ->pluck('foreign_id')->toArray();
428 488
489 + $members = [];
429 490
430 491 if ($memberUserIds) {
431 492 $memberUsers = get_users([
432 493 'include' => $memberUserIds
@@ -476,9 +537,9 @@
476 537 'members' => $members
477 538 ];
478 539 }
479 540
480 - public function quickSearch()
541 + public function globalSearch()
481 542 {
482 543 $currentUserId = get_current_user_id();
483 544
484 545 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- REST API endpoint, nonce verification handled by WordPress REST API
@@ -492,8 +553,9 @@
492 553 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- REST API endpoint, nonce verification handled by WordPress REST API
493 554 $boardPage = isset($_REQUEST['board_page']) ? max(1, (int)$_REQUEST['board_page']) : 0;
494 555 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- REST API endpoint, nonce verification handled by WordPress REST API
495 556 $perPage = isset($_REQUEST['per_page']) ? (int)$_REQUEST['per_page'] : 20;
557 + $perPage = max(1, min(100, $perPage));
496 558
497 559 // Build base queries
498 560 $firstThreeChars = substr($query, 0, 3);
499 561 $firstNineChars = substr($query, 0, 9);
@@ -558,9 +620,9 @@
558 620 $formattedBoards[] = [
559 621 'type' => 'board',
560 622 'id' => $board->id,
561 623 'title' => $board->title,
562 - 'description' => $board->description,
624 + 'description' => DescriptionMarkdownConverter::normalize($board->description),
563 625 ];
564 626 }
565 627 foreach ($tasks as $task) {
566 628 if (!in_array($task->board_id, $allActiveBoardsIds)) {
@@ -571,9 +633,9 @@
571 633 $formattedTasks[] = [
572 634 'type' => 'task',
573 635 'id' => $task->id,
574 636 'title' => $task->title,
575 - 'description' => $task->description,
637 + 'description' => DescriptionMarkdownConverter::normalize($task->description),
576 638 'board_id' => $task->board_id,
577 639 'board' => [
578 640 'id' => $board->id,
579 641 'title' => $board->title,
@@ -669,12 +731,12 @@
669 731 $canAutoInstallKit = $this->canAutoInstallFluentKit();
670 732 $kitPluginFile = 'fluent-toolkit/fluent-toolkit.php';
671 733 $kitLoaded = defined('FLUENT_TOOLKIT_VERSION');
672 734 $kitPluginExists = $this->isPluginInstalled($kitPluginFile);
673 - $kitActionText = __('Get FluentKit from GitHub', 'fluent-boards');
735 + $kitActionText = __('Get FluentHub from GitHub', 'fluent-boards');
674 736
675 737 if ($canAutoInstallKit) {
676 - $kitActionText = $kitPluginExists ? __('Activate FluentKit', 'fluent-boards') : __('Install FluentKit', 'fluent-boards');
738 + $kitActionText = $kitPluginExists ? __('Activate FluentHub', 'fluent-boards') : __('Install FluentHub', 'fluent-boards');
677 739 }
678 740
679 741 $addOns = [
680 742 'fluent-crm' => [
@@ -718,9 +780,9 @@
718 780 'description' => __('The Ultimate SMTP and SES Plugin for WordPress. Connect with any SMTP, SendGrid, Mailgun, SES, Sendinblue, PepiPost, Google, Microsoft and more.', 'fluent-boards'),
719 781 'short_desc' => __('Reliable email delivery with SMTP', 'fluent-boards')
720 782 ],
721 783 'fluent-toolkit' => [
722 - 'title' => __('FluentKit', 'fluent-boards'),
784 + 'title' => __('FluentHub', 'fluent-boards'),
723 785 'logo' => fluent_boards_mix('images/addons/fluent-toolkit.svg'),
724 786 'is_installed' => $kitLoaded,
725 787 'learn_more_url' => 'https://github.com/WPManageNinja/fluent-toolkit',
726 788 'settings_url' => admin_url('admin.php?page=fluent-toolkit'),
@@ -727,9 +789,9 @@
727 789 'associate_doc' => 'https://github.com/WPManageNinja/fluent-toolkit',
728 790 'action_text' => $kitActionText,
729 791 'install_route' => $canAutoInstallKit ? 'admin/mcp/install-adapter' : '',
730 792 'install_url' => $canAutoInstallKit ? '' : 'https://github.com/WPManageNinja/fluent-toolkit',
731 - 'description' => __('Fluent Boards MCP tools become available after FluentKit is installed and active.', 'fluent-boards'),
793 + 'description' => __('Fluent Boards MCP tools become available after FluentHub is installed and active.', 'fluent-boards'),
732 794 'short_desc' => __('AI agent tools for Fluent Boards', 'fluent-boards')
733 795 ],
734 796 ];
735 797
@@ -1004,9 +1066,10 @@
1004 1066 $pages = [];
1005 1067 foreach ($allPages as $page) {
1006 1068 $pages[] = [
1007 1069 'id' => $page->ID,
1008 - 'title' => $page->post_title ? $page->post_title : __('(no title)', 'fluent-boards')
1070 + 'title' => $page->post_title ? $page->post_title : __('(no title)', 'fluent-boards'),
1071 + 'url' => esc_url_raw(get_permalink($page->ID))
1009 1072 ];
1010 1073 }
1011 1074
1012 1075 return $this->sendSuccess([