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.1 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 All 44 releases
← All changes | app/Hooks/Handlers/AdminMenuHandler.php +150 -79 1.20 → 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,14 +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();
129 -
130 -
131 159 do_action('fluent_boards/rendering_app');
132 160
133 -
134 161 App::make('view')->render('admin.menu', [
135 162 'name' => $name,
136 163 'slug' => $slug,
137 164 'menuItems' => $this->getMenuItems($app),
@@ -142,29 +169,8 @@
142 169 'is_onboarded' => $this->getOnboardingValue()
143 170 ]);
144 171 }
145 172
146 - private function updateDatabase($isForced = true)
147 - {
148 - global $wpdb;
149 -
150 - $table = $wpdb->prefix . 'fbs_board_terms';
151 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)) != $table) {
152 - return;
153 - } else {
154 - // change column type from int to decimal - for already installed sites
155 - $column_name = 'position';
156 - $preparedQuery = $wpdb->prepare("DESCRIBE $table %s", $column_name);
157 - $dataType = $wpdb->get_row($preparedQuery);
158 - if (strpos($dataType->Type, 'int') !== false) {
159 - $sql = $wpdb->prepare(
160 - "ALTER TABLE $table MODIFY $column_name decimal(10,2) NOT NULL DEFAULT '1' COMMENT 'Position: 1 = top/first, 2 = second/second in top, etc.';"
161 - );
162 - $wpdb->query($sql);
163 - }
164 - }
165 - }
166 -
167 173 public function getMenuItems($app)
168 174 {
169 175 $config = $app->config;
170 176 $slug = $config->get('app.slug');
@@ -205,25 +211,8 @@
205 211
206 212 $isAdmin = PermissionManager::isAdmin();
207 213
208 214 if ($isAdmin) {
209 - $menuItems['settings'] = [
210 - 'key' => 'settings',
211 - 'label' => __('Settings', 'fluent-boards'),
212 - 'permalink' => $baseUrl . 'settings/members-role'
213 - ];
214 - }
215 -
216 - if (!defined('FLUENT_BOARDS_PRO')) {
217 - $menuItems['get_pro'] = [
218 - 'key' => 'get_pro',
219 - 'label' => __('Get Pro', 'fluent-boards'),
220 - 'permalink' => 'https://fluentboards.com?utm_source=menu&utm_medium=plugin&utm_campaign=pro&utm_id=wp',
221 - 'class' => 'pro_link'
222 - ];
223 - }
224 -
225 - if ($isAdmin) {
226 215 $menuItems['help'] = [
227 216 'key' => 'help',
228 217 'label' => __('Community', 'fluent-boards'),
229 218 'target' => '_blank',
@@ -233,9 +222,9 @@
233 222
234 223 if ($isDiffUrl) {
235 224 $menuItems['front'] = [
236 225 'key' => 'front',
237 - 'label' => __('Frontend Portal', 'fluent-boards'),
226 + 'label' => __('Front Portal', 'fluent-boards'),
238 227 'target' => '_blank',
239 228 'permalink' => fluent_boards_page_url()
240 229 ];
241 230 }
@@ -246,9 +235,9 @@
246 235 }
247 236
248 237 public function enqueueAssets()
249 238 {
250 - if (!PermissionManager::userHasAnyBoardAccess()) {
239 + if (!PermissionManager::hasAppAccess()) {
251 240 return;
252 241 }
253 242
254 243 add_action('wp_print_scripts', function () {
@@ -307,32 +296,58 @@
307 296 }
308 297
309 298 $app = App::getInstance();
310 299
311 - $assets = $app['url.assets'];
300 + $slug = $app->config->get('app.slug');
312 301
313 - $slug = $app->config->get('app.slug');
302 + // Inject Vite HMR client in dev mode
303 + Vite::injectViteClient();
314 304
315 - wp_enqueue_style(
316 - $slug . '_admin_app',
317 - $assets . 'admin/admin.css',
318 - [],
319 - FLUENT_BOARDS_PLUGIN_VERSION
320 - );
305 + $isRtl = fluent_boards_is_rtl();
306 + Vite::enqueueStyle($slug . '_admin_app', 'scss/admin.scss');
321 307
322 - 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 + }
323 321
324 - 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(
325 340 $slug . '_admin_app',
326 - $assets . 'admin/app.js',
341 + 'admin/app.js',
327 342 ['jquery'],
328 343 FLUENT_BOARDS_PLUGIN_VERSION,
329 344 true
330 345 );
331 346
332 - wp_enqueue_script(
347 + Vite::enqueueStaticScript(
333 348 $slug . '_global_admin',
334 - $assets . 'admin/global_admin.js',
349 + 'admin/global_admin.js',
335 350 [],
336 351 FLUENT_BOARDS_PLUGIN_VERSION,
337 352 true
338 353 );
@@ -352,8 +367,9 @@
352 367 $currentUser = get_user_by('ID', get_current_user_id());
353 368 $assets = $app['url.assets'];
354 369 $roleAndPermissions = $this->getRoleAndPermissions($currentUser->ID);
355 370 $onboardingValue = $this->getOnboardingValue();
371 + $generalSettings = fluent_boards_get_option('general_settings', []);
356 372
357 373 return apply_filters('fluent_boards/app_vars', [
358 374 'slug' => $slug = $app->config->get('app.slug'),
359 375 'nonce' => wp_create_nonce($slug),
@@ -361,13 +377,23 @@
361 377 'fluent_boards_file_upload_nonce' => wp_create_nonce('fluent_boards_file_upload_nonce'),
362 378 'ajaxurl' => admin_url('admin-ajax.php'),
363 379 'file_upload_limit' => $this->fileUploadLimit(),
364 380 'brand_logo' => $this->getMenuIcon(),
381 + 'business_name' => sanitize_text_field(Arr::get($generalSettings, 'business_name', '')),
365 382 'asset_url' => $assets,
366 383 'admin_url' => admin_url('admin.php'),
367 384 'fluent_crm_exists' => !defined('FLUENTCRM') ? false : true,
368 - '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,
369 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 + ],
370 396 'me' => [
371 397 'id' => $currentUser->ID,
372 398 'full_name' => trim($currentUser->first_name . ' ' . $currentUser->last_name),
373 399 'display_name' => $currentUser->display_name,
@@ -372,27 +398,52 @@
372 398 'full_name' => trim($currentUser->first_name . ' ' . $currentUser->last_name),
373 399 'display_name' => $currentUser->display_name,
374 400 'email' => $currentUser->user_email,
375 401 'photo' => fluent_boards_user_avatar($currentUser->user_email, $currentUser->display_name),
402 + 'logout_url' => wp_logout_url(),
376 403 'fluent_boards_role' => $roleAndPermissions['role'],
377 404 'fluent_boards_capabilities' => $roleAndPermissions['permissions'],
378 - 'is_wp_admin' => user_can($currentUser->ID, 'manage_options') ? 'yes' : 'no'
405 + 'is_wp_admin' => user_can($currentUser->ID, 'manage_options') ? 'yes' : 'no',
406 + 'can_create_board' => PermissionManager::userHasBoardCreationPermission($currentUser->ID)
379 407 ],
380 408 'base_url' => fluent_boards_page_url(),
381 409 'site_url' => site_url('/'),
382 - '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'),
383 413 'utc_offset' => current_time('timestamp') - strtotime(gmdate('Y-m-d H:i:s')),
384 414 'trans' => TransStrings::getStrings(),
385 415 'is_new' => Board::count() == 0 ? 'yes' : 'no',
386 416 'is_onboarded' => $onboardingValue,
387 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),
388 420 'dashboard_notices' => apply_filters('fluent_boards/dashboard_notices', []),
389 421 'is_beta' => defined('FLUENT_BOARDS_PRO_VERSION') && !defined('FLUENT_BOARDS_PRO_LIVE'),
390 422 'advanced_modules' => fluent_boards_get_pref_settings(),
391 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() : [],
392 438 ]);
393 439 }
394 440
441 + protected function canManageCrmContacts()
442 + {
443 + return defined('FLUENTCRM') && \FluentCrm\App\Services\PermissionManager::currentUserCan('fcrm_manage_contacts');
444 + }
445 +
395 446 private function getOnboardingValue()
396 447 {
397 448 $onboarding = Meta::where('key', Constant::FBS_ONBOARDING)->first();
398 449 if ($onboarding) {
@@ -404,8 +455,19 @@
404 455 //
405 456 // return 'no';
406 457 }
407 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 +
408 470 /*
409 471 * TODO: This method should be moved to PermissionManager and Helper . Task for Masiur.
410 472 */
411 473 protected function getRoleAndPermissions($userId): array
@@ -427,9 +489,9 @@
427 489 $permissions = [];
428 490 foreach ($boardUserCollection as $boardWithPermission) {
429 491 $permissions[] = [
430 492 'board_id' => $boardWithPermission->object_id,
431 - 'role' => $boardWithPermission['settings']['is_admin'] ? 'board_admin' : 'board_member',
493 + 'role' => $this->boardUserRole($boardWithPermission),
432 494 'preferences' => $boardWithPermission->preferences,
433 495 'permissions' => [],
434 496 ];
435 497 }
@@ -439,8 +501,13 @@
439 501 'permissions' => $permissions,
440 502 ];
441 503 }
442 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 +
443 510 protected function getRestInfo($app)
444 511 {
445 512 $ns = $app->config->get('app.rest_namespace');
446 513 $ver = $app->config->get('app.rest_version');
@@ -467,8 +534,9 @@
467 534 add_filter('admin_footer_text', function ($content) {
468 535 $url = '#';
469 536 return '';
470 537
538 + // translators: %s is the URL for FluentBoards link
471 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>';
472 540 });
473 541
474 542 add_filter('update_footer', function ($text) {
@@ -476,25 +544,15 @@
476 544 });
477 545 }
478 546
479 547 /**
480 - * Retrieves the maximum upload limit based on PHP and WordPress configurations.
548 + * Retrieves the Fluent Boards upload limit exposed to the admin app.
481 549 *
482 - * This method calculates and returns the minimum value among the following:
483 - * 1. The PHP 'upload_max_filesize' configuration.
484 - * 2. The PHP 'post_max_size' configuration.
485 - * 3. The WordPress maximum upload size limit using the 'wp_max_upload_size' function.
486 - *
487 - * @return int The minimum of the mentioned upload size limits in bytes.
550 + * @return int Upload limit in bytes.
488 551 */
489 552 public function fileUploadLimit()
490 553 {
491 - // Calculate the minimum of 'upload_max_filesize', 'post_max_size', and WordPress maximum upload size.
492 - return min(
493 - wp_convert_hr_to_bytes(ini_get('upload_max_filesize')),
494 - wp_convert_hr_to_bytes(ini_get('post_max_size')),
495 - wp_max_upload_size()
496 - );
554 + return (new \FluentBoards\App\Services\UploadService())->getFileUploadLimit();
497 555 }
498 556
499 557 public function getInlineScript()
500 558 {
@@ -531,5 +589,18 @@
531 589 _.noConflict();
532 590 }
533 591 ";
534 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 +
535 606 }