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 +155 -76 1.13 → 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(
@@ -104,12 +126,21 @@
104 126 );
105 127
106 128 add_submenu_page(
107 129 'fluent-boards',
130 + __('Addons', 'fluent-boards'),
131 + __('Addons', 'fluent-boards'),
132 + 'manage_options',
133 + 'fluent-boards#/add-ons',
134 + [$this, 'render']
135 + );
136 +
137 + add_submenu_page(
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');
@@ -202,25 +211,8 @@
202 211
203 212 $isAdmin = PermissionManager::isAdmin();
204 213
205 214 if ($isAdmin) {
206 - $menuItems['settings'] = [
207 - 'key' => 'settings',
208 - 'label' => __('Settings', 'fluent-boards'),
209 - 'permalink' => $baseUrl . 'settings/members-role'
210 - ];
211 - }
212 -
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 - }
221 -
222 - if ($isAdmin) {
223 215 $menuItems['help'] = [
224 216 'key' => 'help',
225 217 'label' => __('Community', 'fluent-boards'),
226 218 'target' => '_blank',
@@ -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,26 +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 422 'advanced_modules' => fluent_boards_get_pref_settings(),
383 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() : [],
384 438 ]);
385 439 }
386 440
441 + protected function canManageCrmContacts()
442 + {
443 + return defined('FLUENTCRM') && \FluentCrm\App\Services\PermissionManager::currentUserCan('fcrm_manage_contacts');
444 + }
445 +
387 446 private function getOnboardingValue()
388 447 {
389 448 $onboarding = Meta::where('key', Constant::FBS_ONBOARDING)->first();
390 449 if ($onboarding) {
@@ -396,8 +455,19 @@
396 455 //
397 456 // return 'no';
398 457 }
399 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 +
400 470 /*
401 471 * TODO: This method should be moved to PermissionManager and Helper . Task for Masiur.
402 472 */
403 473 protected function getRoleAndPermissions($userId): array
@@ -419,9 +489,9 @@
419 489 $permissions = [];
420 490 foreach ($boardUserCollection as $boardWithPermission) {
421 491 $permissions[] = [
422 492 'board_id' => $boardWithPermission->object_id,
423 - 'role' => $boardWithPermission['settings']['is_admin'] ? 'board_admin' : 'board_member',
493 + 'role' => $this->boardUserRole($boardWithPermission),
424 494 'preferences' => $boardWithPermission->preferences,
425 495 'permissions' => [],
426 496 ];
427 497 }
@@ -431,8 +501,13 @@
431 501 'permissions' => $permissions,
432 502 ];
433 503 }
434 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 +
435 510 protected function getRestInfo($app)
436 511 {
437 512 $ns = $app->config->get('app.rest_namespace');
438 513 $ver = $app->config->get('app.rest_version');
@@ -459,8 +534,9 @@
459 534 add_filter('admin_footer_text', function ($content) {
460 535 $url = '#';
461 536 return '';
462 537
538 + // translators: %s is the URL for FluentBoards link
463 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>';
464 540 });
465 541
466 542 add_filter('update_footer', function ($text) {
@@ -468,25 +544,15 @@
468 544 });
469 545 }
470 546
471 547 /**
472 - * Retrieves the maximum upload limit based on PHP and WordPress configurations.
548 + * Retrieves the Fluent Boards upload limit exposed to the admin app.
473 549 *
474 - * This method calculates and returns the minimum value among the following:
475 - * 1. The PHP 'upload_max_filesize' configuration.
476 - * 2. The PHP 'post_max_size' configuration.
477 - * 3. The WordPress maximum upload size limit using the 'wp_max_upload_size' function.
478 - *
479 - * @return int The minimum of the mentioned upload size limits in bytes.
550 + * @return int Upload limit in bytes.
480 551 */
481 552 public function fileUploadLimit()
482 553 {
483 - // Calculate the minimum of 'upload_max_filesize', 'post_max_size', and WordPress maximum upload size.
484 - return min(
485 - wp_convert_hr_to_bytes(ini_get('upload_max_filesize')),
486 - wp_convert_hr_to_bytes(ini_get('post_max_size')),
487 - wp_max_upload_size()
488 - );
554 + return (new \FluentBoards\App\Services\UploadService())->getFileUploadLimit();
489 555 }
490 556
491 557 public function getInlineScript()
492 558 {
@@ -523,5 +589,18 @@
523 589 _.noConflict();
524 590 }
525 591 ";
526 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 +
527 606 }