PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.2.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.2.0
2.2.0 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 All 43 releases
← All changes | app/Hooks/Handlers/AdminMenuHandler.php +165 -85 1.11 → 2.2.0 View file →
@@ -6,12 +6,15 @@
6 6 use FluentBoards\App\Models\Board;
7 7 use FluentBoards\App\Models\Meta;
8 8 use FluentBoards\App\Models\Relation;
9 9 use FluentBoards\App\Services\Constant;
10 +use FluentBoards\App\Services\Helper;
10 11 use FluentBoards\App\Services\TransStrings;
12 +use FluentBoards\App\Vite;
11 13 use FluentBoards\Framework\Support\Arr;
12 14 use FluentBoards\Framework\Support\Collection;
13 15 use FluentBoards\App\Services\PermissionManager;
16 +use FluentBoards\Framework\Support\DateTime;
14 17
15 18 class AdminMenuHandler
16 19 {
17 20
@@ -22,9 +25,9 @@
22 25 add_filter('fluent_crm/core_menu_items', function ($items) {
23 26 if (PermissionManager::userHasAnyBoardAccess()) {
24 27 $items['fluent-boards'] = [
25 28 'key' => 'fluent-boards',
26 - 'label' => __('Fluent Boards', 'fluent-crm'),
29 + 'label' => __('Boards', 'fluent-boards'),
27 30 'permalink' => admin_url('admin.php?page=fluent-boards#/')
28 31 ];
29 32 }
30 33 return $items;
@@ -30,8 +33,9 @@
30 33 return $items;
31 34 });
32 35
33 36 add_action('admin_enqueue_scripts', function () {
37 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Checking admin page context for asset enqueuing, no data modification
34 38 if (!isset($_REQUEST['page']) || $_REQUEST['page'] !== 'fluent-boards') {
35 39 return;
36 40 }
37 41
@@ -36,8 +40,22 @@
36 40 }
37 41
38 42 $this->enqueueAssets();
39 43 });
44 +
45 + add_filter('fluent_crm/sidebar_core_menu_items', function($menuItems, $permissions) {
46 + $settings = fluent_boards_get_pref_settings();
47 + if (Arr::get($settings, 'menu_settings.in_fluent_crm') === 'yes') {
48 + $menuItems[] = [
49 + 'key' => 'fluent-boards',
50 + 'page_title' => __('Fluent Boards', 'fluent-boards'),
51 + 'menu_title' => __('Fluent Boards', 'fluent-boards'),
52 + 'capability' => 'manage_options',
53 + 'uri' => admin_url('admin.php?page=fluent-boards')
54 + ];
55 + }
56 + return $menuItems;
57 + }, 10, 2);
40 58 }
41 59
42 60 public function add()
43 61 {
@@ -45,12 +63,16 @@
45 63 return;
46 64 }
47 65
48 66 $user = get_user_by('ID', get_current_user_id());
49 - $caps = $user->allcaps;
50 - // get the first key
51 - $capability = key($caps);
52 67
68 + if (current_user_can('manage_options')) {
69 + $capability = 'manage_options';
70 + } else {
71 + $roles = array_values((array)$user->roles);
72 + $capability = Arr::get($roles, 0);
73 + }
74 +
53 75 $settings = fluent_boards_get_pref_settings();
54 76
55 77 if (defined('FLUENTCRM') && \FluentCrm\App\Services\PermissionManager::currentUserPermissions() && Arr::get($settings, 'menu_settings.in_fluent_crm') === 'yes') {
56 78 add_submenu_page(
@@ -91,8 +113,10 @@
91 113 'fluent-boards#/boards',
92 114 [$this, 'render']
93 115 );
94 116
117 + do_action('fluent_boards/after_core_menu_items', $permissions = [], $isAdmin = true);
118 +
95 119 add_submenu_page(
96 120 'fluent-boards',
97 121 __('Reports', 'fluent-boards'),
98 122 __('Reports', 'fluent-boards'),
@@ -100,9 +124,16 @@
100 124 'fluent-boards#/reports',
101 125 [$this, 'render']
102 126 );
103 127
104 - do_action('fluent_boards/after_core_menu_items', $permissions = [], $isAdmin = true);
128 + add_submenu_page(
129 + 'fluent-boards',
130 + __('Addons', 'fluent-boards'),
131 + __('Addons', 'fluent-boards'),
132 + 'manage_options',
133 + 'fluent-boards#/add-ons',
134 + [$this, 'render']
135 + );
105 136
106 137 add_submenu_page(
107 138 'fluent-boards',
108 139 __('Settings', 'fluent-boards'),
@@ -107,9 +138,9 @@
107 138 'fluent-boards',
108 139 __('Settings', 'fluent-boards'),
109 140 __('Settings', 'fluent-boards'),
110 141 'manage_options',
111 - 'fluent-boards#/settings/members-role',
142 + 'fluent-boards#/settings/general-settings',
112 143 [$this, 'render']
113 144 );
114 145 }
115 146
@@ -124,11 +155,10 @@
124 155 $assets = $app['url.assets'];
125 156 $slug = $config->get('app.slug');
126 157 $baseUrl = fluent_boards_page_url();
127 158
128 - $this->updateDatabase();
159 + do_action('fluent_boards/rendering_app');
129 160
130 -
131 161 App::make('view')->render('admin.menu', [
132 162 'name' => $name,
133 163 'slug' => $slug,
134 164 'menuItems' => $this->getMenuItems($app),
@@ -139,29 +169,8 @@
139 169 'is_onboarded' => $this->getOnboardingValue()
140 170 ]);
141 171 }
142 172
143 - private function updateDatabase($isForced = true)
144 - {
145 - global $wpdb;
146 -
147 - $table = $wpdb->prefix . 'fbs_board_terms';
148 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)) != $table) {
149 - return;
150 - } else {
151 - // change column type from int to decimal - for already installed sites
152 - $column_name = 'position';
153 - $preparedQuery = $wpdb->prepare("DESCRIBE $table %s", $column_name);
154 - $dataType = $wpdb->get_row($preparedQuery);
155 - if (strpos($dataType->Type, 'int') !== false) {
156 - $sql = $wpdb->prepare(
157 - "ALTER TABLE $table MODIFY $column_name decimal(10,2) NOT NULL DEFAULT '1' COMMENT 'Position: 1 = top/first, 2 = second/second in top, etc.';"
158 - );
159 - $wpdb->query($sql);
160 - }
161 - }
162 - }
163 -
164 173 public function getMenuItems($app)
165 174 {
166 175 $config = $app->config;
167 176 $slug = $config->get('app.slug');
@@ -178,9 +187,8 @@
178 187 $baseUrl = $adminUrl;
179 188 }
180 189 }
181 190
182 -
183 191 $menuItems = [
184 192 'dashboard' => [
185 193 'key' => 'dashboard',
186 194 'label' => __('Dashboard', 'fluent-boards'),
@@ -189,36 +197,20 @@
189 197 'boards' => [
190 198 'key' => 'boards',
191 199 'label' => __('Boards', 'fluent-boards'),
192 200 'permalink' => $baseUrl . 'boards'
193 - ],
194 - 'reports' => [
195 - 'key' => 'reports',
196 - 'label' => __('Reports', 'fluent-boards'),
197 - 'permalink' => $baseUrl . 'reports'
198 201 ]
199 202 ];
200 -
201 - $isAdmin = PermissionManager::isAdmin();
202 -
203 +
203 204 $menuItems = apply_filters('fluent_boards/core_menu_items', $menuItems);
204 205
205 - if ($isAdmin) {
206 - $menuItems['settings'] = [
207 - 'key' => 'settings',
208 - 'label' => __('Settings', 'fluent-boards'),
209 - 'permalink' => $baseUrl . 'settings/members-role'
210 - ];
211 - }
206 + $menuItems['reports'] = [
207 + 'key' => 'reports',
208 + 'label' => __('Reports', 'fluent-boards'),
209 + 'permalink' => $baseUrl . 'reports'
210 + ];
212 211
213 - if (!defined('FLUENT_BOARDS_PRO')) {
214 - $menuItems['get_pro'] = [
215 - 'key' => 'get_pro',
216 - 'label' => __('Get Pro', 'fluent-boards'),
217 - 'permalink' => 'https://fluentboards.com?utm_source=menu&utm_medium=plugin&utm_campaign=pro&utm_id=wp',
218 - 'class' => 'pro_link'
219 - ];
220 - }
212 + $isAdmin = PermissionManager::isAdmin();
221 213
222 214 if ($isAdmin) {
223 215 $menuItems['help'] = [
224 216 'key' => 'help',
@@ -230,9 +222,9 @@
230 222
231 223 if ($isDiffUrl) {
232 224 $menuItems['front'] = [
233 225 'key' => 'front',
234 - 'label' => __('Frontend Portal', 'fluent-boards'),
226 + 'label' => __('Front Portal', 'fluent-boards'),
235 227 'target' => '_blank',
236 228 'permalink' => fluent_boards_page_url()
237 229 ];
238 230 }
@@ -243,9 +235,9 @@
243 235 }
244 236
245 237 public function enqueueAssets()
246 238 {
247 - if (!PermissionManager::userHasAnyBoardAccess()) {
239 + if (!PermissionManager::hasAppAccess()) {
248 240 return;
249 241 }
250 242
251 243 add_action('wp_print_scripts', function () {
@@ -295,37 +287,67 @@
295 287 if (is_admin()) {
296 288 wp_tinymce_inline_scripts();
297 289 }
298 290 wp_enqueue_editor();
291 + wp_enqueue_script('thickbox');
292 + wp_enqueue_script('editor');
293 + }
294 + if (function_exists('wp_enqueue_media')) {
299 295 wp_enqueue_media();
300 296 }
301 297
302 298 $app = App::getInstance();
303 299
304 - $assets = $app['url.assets'];
300 + $slug = $app->config->get('app.slug');
305 301
306 - $slug = $app->config->get('app.slug');
302 + // Inject Vite HMR client in dev mode
303 + Vite::injectViteClient();
307 304
308 - wp_enqueue_style(
309 - $slug . '_admin_app',
310 - $assets . 'admin/admin.css',
311 - [],
312 - FLUENT_BOARDS_PLUGIN_VERSION
313 - );
305 + $isRtl = fluent_boards_is_rtl();
306 + Vite::enqueueStyle($slug . '_admin_app', 'scss/admin.scss');
314 307
315 - do_action($slug . '_loading_app');
308 + // Enqueue merged chunk CSS (Vue SFC styles) in production
309 + if (!Vite::underDevelopment()) {
310 + $assets = $app['url.assets'];
311 + $styleCssPath = FLUENT_BOARDS_PLUGIN_PATH . 'assets/admin/style.css';
312 + if (file_exists($styleCssPath)) {
313 + wp_enqueue_style(
314 + $slug . '_vite_style',
315 + $assets . 'admin/style.css',
316 + [$slug . '_admin_app'],
317 + FLUENT_BOARDS_PLUGIN_VERSION
318 + );
319 + }
320 + }
316 321
317 - wp_enqueue_script(
322 + // RTL overrides must load after style.css so rules appended to the
323 + // element-plus layer win by source order, not only by specificity.
324 + if ($isRtl) {
325 + $rtlDeps = [$slug . '_admin_app'];
326 + if (wp_style_is($slug . '_vite_style', 'enqueued')) {
327 + $rtlDeps[] = $slug . '_vite_style';
328 + }
329 + Vite::enqueueStyle(
330 + $slug . '_admin_app_rtl',
331 + 'scss/admin_rtl.scss',
332 + $rtlDeps,
333 + FLUENT_BOARDS_PLUGIN_VERSION
334 + );
335 + }
336 +
337 + do_action('fluent-boards_loading_app');
338 +
339 + Vite::enqueueScript(
318 340 $slug . '_admin_app',
319 - $assets . 'admin/app.js',
341 + 'admin/app.js',
320 342 ['jquery'],
321 343 FLUENT_BOARDS_PLUGIN_VERSION,
322 344 true
323 345 );
324 346
325 - wp_enqueue_script(
347 + Vite::enqueueStaticScript(
326 348 $slug . '_global_admin',
327 - $assets . 'admin/global_admin.js',
349 + 'admin/global_admin.js',
328 350 [],
329 351 FLUENT_BOARDS_PLUGIN_VERSION,
330 352 true
331 353 );
@@ -345,8 +367,9 @@
345 367 $currentUser = get_user_by('ID', get_current_user_id());
346 368 $assets = $app['url.assets'];
347 369 $roleAndPermissions = $this->getRoleAndPermissions($currentUser->ID);
348 370 $onboardingValue = $this->getOnboardingValue();
371 + $generalSettings = fluent_boards_get_option('general_settings', []);
349 372
350 373 return apply_filters('fluent_boards/app_vars', [
351 374 'slug' => $slug = $app->config->get('app.slug'),
352 375 'nonce' => wp_create_nonce($slug),
@@ -354,13 +377,23 @@
354 377 'fluent_boards_file_upload_nonce' => wp_create_nonce('fluent_boards_file_upload_nonce'),
355 378 'ajaxurl' => admin_url('admin-ajax.php'),
356 379 'file_upload_limit' => $this->fileUploadLimit(),
357 380 'brand_logo' => $this->getMenuIcon(),
381 + 'business_name' => sanitize_text_field(Arr::get($generalSettings, 'business_name', '')),
358 382 'asset_url' => $assets,
359 383 'admin_url' => admin_url('admin.php'),
360 384 'fluent_crm_exists' => !defined('FLUENTCRM') ? false : true,
361 - 'fluent_roadmap_exists' => !defined('FLUENTROADMAP') ? false : true,
385 + 'fluent_support_exists' => !!defined('FLUENT_SUPPORT_VERSION'),
386 + 'can_manage_crm_contacts' => $this->canManageCrmContacts(),
387 + 'fluent_roadmap_exists' => !defined('FLUENT_ROADMAP') ? false : true,
362 388 'has_pro' => !!defined('FLUENT_BOARDS_PRO_VERSION'),
389 + 'upgrade_url' => fluent_boards_get_upgrade_url(),
390 + 'board_solid_colors' => Constant::BOARD_BACKGROUND_DEFAULT_SOLID_COLORS,
391 + 'board_gradient_colors' => Constant::BOARD_BACKGROUND_DEFAULT_GRADIENT_COLORS,
392 + 'label_color_presets' => Constant::LABEL_COLOR_PRESETS,
393 + 'ai_features' => [
394 + 'enabled' => (new \FluentBoards\App\Services\AiService())->isReady(),
395 + ],
363 396 'me' => [
364 397 'id' => $currentUser->ID,
365 398 'full_name' => trim($currentUser->first_name . ' ' . $currentUser->last_name),
366 399 'display_name' => $currentUser->display_name,
@@ -365,25 +398,52 @@
365 398 'full_name' => trim($currentUser->first_name . ' ' . $currentUser->last_name),
366 399 'display_name' => $currentUser->display_name,
367 400 'email' => $currentUser->user_email,
368 401 'photo' => fluent_boards_user_avatar($currentUser->user_email, $currentUser->display_name),
402 + 'logout_url' => wp_logout_url(),
369 403 'fluent_boards_role' => $roleAndPermissions['role'],
370 404 'fluent_boards_capabilities' => $roleAndPermissions['permissions'],
405 + 'is_wp_admin' => user_can($currentUser->ID, 'manage_options') ? 'yes' : 'no',
406 + 'can_create_board' => PermissionManager::userHasBoardCreationPermission($currentUser->ID)
371 407 ],
372 408 'base_url' => fluent_boards_page_url(),
373 409 'site_url' => site_url('/'),
374 - 'server_time' => current_time('mysql'),
410 + // 'server_time' => (new DateTime('now'))->format('Y-m-d H:i:s P'), // Server's default timezone
411 + 'server_time' => (new DateTime('now', wp_timezone()))->format('Y-m-d H:i:s P'), // wordpress site time
412 + 'server_time_zone' => (new DateTime('now', wp_timezone()))->format( 'P'),
375 413 'utc_offset' => current_time('timestamp') - strtotime(gmdate('Y-m-d H:i:s')),
376 414 'trans' => TransStrings::getStrings(),
377 415 'is_new' => Board::count() == 0 ? 'yes' : 'no',
378 416 'is_onboarded' => $onboardingValue,
379 417 'render_in' => is_admin() ? 'admin' : 'front',
418 + // Gates wp-admin links (WP profile, CRM, application passwords) so the frontend portal shows no admin traces.
419 + 'can_access_wp_admin' => (bool) apply_filters('fluent_boards/can_access_wp_admin', is_admin() || current_user_can('manage_options'), $currentUser),
380 420 'dashboard_notices' => apply_filters('fluent_boards/dashboard_notices', []),
381 421 'is_beta' => defined('FLUENT_BOARDS_PRO_VERSION') && !defined('FLUENT_BOARDS_PRO_LIVE'),
382 - 'advanced_modules' => fluent_boards_get_pref_settings()
422 + 'advanced_modules' => fluent_boards_get_pref_settings(),
423 + 'crm_base_url' => defined('FLUENTCRM') ? fluentcrm_menu_url_base() : '',
424 + 'start_of_week' => intval(get_option('start_of_week', 0)),
425 + 'time_format' => get_option('time_format', 'g:i'),
426 + 'priorities' => apply_filters('fluent_boards/task_priorities', $this->getDefaultPriorities()),
427 + 'wpContentCss' => add_query_arg(
428 + 'ver', get_bloginfo('version'),
429 + site_url('/wp-includes/js/tinymce/skins/wordpress/wp-content.css')
430 + ),
431 + 'dashiconsCss' => add_query_arg(
432 + 'ver', get_bloginfo('version'),
433 + site_url('/wp-includes/css/dashicons.css')
434 + ),
435 + 'is_rtl' => fluent_boards_is_rtl(),
436 + 'board_menu_items' => BoardMenuHandler::getMenuItems(),
437 + 'reminder_types' => defined('FLUENT_BOARDS_PRO') ? Helper::taskReminderTypes() : [],
383 438 ]);
384 439 }
385 440
441 + protected function canManageCrmContacts()
442 + {
443 + return defined('FLUENTCRM') && \FluentCrm\App\Services\PermissionManager::currentUserCan('fcrm_manage_contacts');
444 + }
445 +
386 446 private function getOnboardingValue()
387 447 {
388 448 $onboarding = Meta::where('key', Constant::FBS_ONBOARDING)->first();
389 449 if ($onboarding) {
@@ -395,8 +455,19 @@
395 455 //
396 456 // return 'no';
397 457 }
398 458
459 + public function getDefaultPriorities()
460 + {
461 + return [
462 + '' => __('No priority', 'fluent-boards'),
463 + 'urgent' => __('Urgent', 'fluent-boards'),
464 + 'high' => __('High', 'fluent-boards'),
465 + 'medium' => __('Medium', 'fluent-boards'),
466 + 'low' => __('Low', 'fluent-boards')
467 + ];
468 + }
469 +
399 470 /*
400 471 * TODO: This method should be moved to PermissionManager and Helper . Task for Masiur.
401 472 */
402 473 protected function getRoleAndPermissions($userId): array
@@ -418,9 +489,9 @@
418 489 $permissions = [];
419 490 foreach ($boardUserCollection as $boardWithPermission) {
420 491 $permissions[] = [
421 492 'board_id' => $boardWithPermission->object_id,
422 - 'role' => $boardWithPermission['settings']['is_admin'] ? 'board_admin' : 'board_member',
493 + 'role' => $this->boardUserRole($boardWithPermission),
423 494 'preferences' => $boardWithPermission->preferences,
424 495 'permissions' => [],
425 496 ];
426 497 }
@@ -430,8 +501,13 @@
430 501 'permissions' => $permissions,
431 502 ];
432 503 }
433 504
505 + protected function boardUserRole($boardWithPermission)
506 + {
507 + return $boardWithPermission['settings']['is_admin'] ? 'board_admin' : (Arr::has($boardWithPermission, 'settings.is_viewer_only') && $boardWithPermission['settings']['is_viewer_only'] ? 'board_viewer' : 'board_member');
508 + }
509 +
434 510 protected function getRestInfo($app)
435 511 {
436 512 $ns = $app->config->get('app.rest_namespace');
437 513 $ver = $app->config->get('app.rest_version');
@@ -458,8 +534,9 @@
458 534 add_filter('admin_footer_text', function ($content) {
459 535 $url = '#';
460 536 return '';
461 537
538 + // translators: %s is the URL for FluentBoards link
462 539 return sprintf(wp_kses(__('Thank you for using <a href="%s">FluentBoards</a>', 'fluent-boards'), ['a' => ['href' => []]]), esc_url($url)) . '<span title="based on your WP timezone settings" style="margin-left: 10px;" data-timestamp="' . current_time('timestamp') . '" id="fc_server_timestamp"></span>';
463 540 });
464 541
465 542 add_filter('update_footer', function ($text) {
@@ -467,25 +544,15 @@
467 544 });
468 545 }
469 546
470 547 /**
471 - * Retrieves the maximum upload limit based on PHP and WordPress configurations.
548 + * Retrieves the Fluent Boards upload limit exposed to the admin app.
472 549 *
473 - * This method calculates and returns the minimum value among the following:
474 - * 1. The PHP 'upload_max_filesize' configuration.
475 - * 2. The PHP 'post_max_size' configuration.
476 - * 3. The WordPress maximum upload size limit using the 'wp_max_upload_size' function.
477 - *
478 - * @return int The minimum of the mentioned upload size limits in bytes.
550 + * @return int Upload limit in bytes.
479 551 */
480 552 public function fileUploadLimit()
481 553 {
482 - // Calculate the minimum of 'upload_max_filesize', 'post_max_size', and WordPress maximum upload size.
483 - return min(
484 - wp_convert_hr_to_bytes(ini_get('upload_max_filesize')),
485 - wp_convert_hr_to_bytes(ini_get('post_max_size')),
486 - wp_max_upload_size()
487 - );
554 + return (new \FluentBoards\App\Services\UploadService())->getFileUploadLimit();
488 555 }
489 556
490 557 public function getInlineScript()
491 558 {
@@ -522,5 +589,18 @@
522 589 _.noConflict();
523 590 }
524 591 ";
525 592 }
593 + public function singleBoardRender()
594 + {
595 + $config = App::getInstance('config');
596 +
597 + $slug = $config->get('app.slug');
598 +
599 +
600 +
601 + App::make('view')->render('admin.single_board_shortcode', [
602 + 'slug' => $slug,
603 + ]);
604 + }
605 +
526 606 }