PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 2.1.0
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v2.1.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 1.41 All 42 releases
← All changes | app/Hooks/Handlers/AdminMenuHandler.php +149 -77 1.212.1.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,13 +155,12 @@
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
161 + // For subtask sync
162 + UpdateHandler::maybeSubtaskGroupSync();
133 163
134 164 App::make('view')->render('admin.menu', [
135 165 'name' => $name,
136 166 'slug' => $slug,
@@ -142,29 +172,8 @@
142 172 'is_onboarded' => $this->getOnboardingValue()
143 173 ]);
144 174 }
145 175
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 176 public function getMenuItems($app)
168 177 {
169 178 $config = $app->config;
170 179 $slug = $config->get('app.slug');
@@ -205,25 +214,8 @@
205 214
206 215 $isAdmin = PermissionManager::isAdmin();
207 216
208 217 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 218 $menuItems['help'] = [
227 219 'key' => 'help',
228 220 'label' => __('Community', 'fluent-boards'),
229 221 'target' => '_blank',
@@ -233,9 +225,9 @@
233 225
234 226 if ($isDiffUrl) {
235 227 $menuItems['front'] = [
236 228 'key' => 'front',
237 - 'label' => __('Frontend Portal', 'fluent-boards'),
229 + 'label' => __('Front Portal', 'fluent-boards'),
238 230 'target' => '_blank',
239 231 'permalink' => fluent_boards_page_url()
240 232 ];
241 233 }
@@ -246,9 +238,9 @@
246 238 }
247 239
248 240 public function enqueueAssets()
249 241 {
250 - if (!PermissionManager::userHasAnyBoardAccess()) {
242 + if (!PermissionManager::hasAppAccess()) {
251 243 return;
252 244 }
253 245
254 246 add_action('wp_print_scripts', function () {
@@ -307,32 +299,58 @@
307 299 }
308 300
309 301 $app = App::getInstance();
310 302
311 - $assets = $app['url.assets'];
303 + $slug = $app->config->get('app.slug');
312 304
313 - $slug = $app->config->get('app.slug');
305 + // Inject Vite HMR client in dev mode
306 + Vite::injectViteClient();
314 307
315 - wp_enqueue_style(
316 - $slug . '_admin_app',
317 - $assets . 'admin/admin.css',
318 - [],
319 - FLUENT_BOARDS_PLUGIN_VERSION
320 - );
308 + $isRtl = fluent_boards_is_rtl();
309 + Vite::enqueueStyle($slug . '_admin_app', 'scss/admin.scss');
321 310
322 - do_action($slug . '_loading_app');
311 + // Enqueue merged chunk CSS (Vue SFC styles) in production
312 + if (!Vite::underDevelopment()) {
313 + $assets = $app['url.assets'];
314 + $styleCssPath = FLUENT_BOARDS_PLUGIN_PATH . 'assets/admin/style.css';
315 + if (file_exists($styleCssPath)) {
316 + wp_enqueue_style(
317 + $slug . '_vite_style',
318 + $assets . 'admin/style.css',
319 + [$slug . '_admin_app'],
320 + FLUENT_BOARDS_PLUGIN_VERSION
321 + );
322 + }
323 + }
323 324
324 - wp_enqueue_script(
325 + // RTL overrides must load after style.css so rules appended to the
326 + // element-plus layer win by source order, not only by specificity.
327 + if ($isRtl) {
328 + $rtlDeps = [$slug . '_admin_app'];
329 + if (wp_style_is($slug . '_vite_style', 'enqueued')) {
330 + $rtlDeps[] = $slug . '_vite_style';
331 + }
332 + Vite::enqueueStyle(
333 + $slug . '_admin_app_rtl',
334 + 'scss/admin_rtl.scss',
335 + $rtlDeps,
336 + FLUENT_BOARDS_PLUGIN_VERSION
337 + );
338 + }
339 +
340 + do_action('fluent-boards_loading_app');
341 +
342 + Vite::enqueueScript(
325 343 $slug . '_admin_app',
326 - $assets . 'admin/app.js',
344 + 'admin/app.js',
327 345 ['jquery'],
328 346 FLUENT_BOARDS_PLUGIN_VERSION,
329 347 true
330 348 );
331 349
332 - wp_enqueue_script(
350 + Vite::enqueueStaticScript(
333 351 $slug . '_global_admin',
334 - $assets . 'admin/global_admin.js',
352 + 'admin/global_admin.js',
335 353 [],
336 354 FLUENT_BOARDS_PLUGIN_VERSION,
337 355 true
338 356 );
@@ -352,8 +370,9 @@
352 370 $currentUser = get_user_by('ID', get_current_user_id());
353 371 $assets = $app['url.assets'];
354 372 $roleAndPermissions = $this->getRoleAndPermissions($currentUser->ID);
355 373 $onboardingValue = $this->getOnboardingValue();
374 + $generalSettings = fluent_boards_get_option('general_settings', []);
356 375
357 376 return apply_filters('fluent_boards/app_vars', [
358 377 'slug' => $slug = $app->config->get('app.slug'),
359 378 'nonce' => wp_create_nonce($slug),
@@ -361,13 +380,23 @@
361 380 'fluent_boards_file_upload_nonce' => wp_create_nonce('fluent_boards_file_upload_nonce'),
362 381 'ajaxurl' => admin_url('admin-ajax.php'),
363 382 'file_upload_limit' => $this->fileUploadLimit(),
364 383 'brand_logo' => $this->getMenuIcon(),
384 + 'business_name' => sanitize_text_field(Arr::get($generalSettings, 'business_name', '')),
365 385 'asset_url' => $assets,
366 386 'admin_url' => admin_url('admin.php'),
367 387 'fluent_crm_exists' => !defined('FLUENTCRM') ? false : true,
388 + 'fluent_support_exists' => !!defined('FLUENT_SUPPORT_VERSION'),
389 + 'can_manage_crm_contacts' => $this->canManageCrmContacts(),
368 390 'fluent_roadmap_exists' => !defined('FLUENT_ROADMAP') ? false : true,
369 391 'has_pro' => !!defined('FLUENT_BOARDS_PRO_VERSION'),
392 + 'upgrade_url' => fluent_boards_get_upgrade_url(),
393 + 'board_solid_colors' => Constant::BOARD_BACKGROUND_DEFAULT_SOLID_COLORS,
394 + 'board_gradient_colors' => Constant::BOARD_BACKGROUND_DEFAULT_GRADIENT_COLORS,
395 + 'label_color_presets' => Constant::LABEL_COLOR_PRESETS,
396 + 'ai_features' => [
397 + 'enabled' => (new \FluentBoards\App\Services\AiService())->isReady(),
398 + ],
370 399 'me' => [
371 400 'id' => $currentUser->ID,
372 401 'full_name' => trim($currentUser->first_name . ' ' . $currentUser->last_name),
373 402 'display_name' => $currentUser->display_name,
@@ -372,15 +401,19 @@
372 401 'full_name' => trim($currentUser->first_name . ' ' . $currentUser->last_name),
373 402 'display_name' => $currentUser->display_name,
374 403 'email' => $currentUser->user_email,
375 404 'photo' => fluent_boards_user_avatar($currentUser->user_email, $currentUser->display_name),
405 + 'logout_url' => wp_logout_url(),
376 406 'fluent_boards_role' => $roleAndPermissions['role'],
377 407 'fluent_boards_capabilities' => $roleAndPermissions['permissions'],
378 - 'is_wp_admin' => user_can($currentUser->ID, 'manage_options') ? 'yes' : 'no'
408 + 'is_wp_admin' => user_can($currentUser->ID, 'manage_options') ? 'yes' : 'no',
409 + 'can_create_board' => PermissionManager::userHasBoardCreationPermission($currentUser->ID)
379 410 ],
380 411 'base_url' => fluent_boards_page_url(),
381 412 'site_url' => site_url('/'),
382 - 'server_time' => current_time('mysql'),
413 + // 'server_time' => (new DateTime('now'))->format('Y-m-d H:i:s P'), // Server's default timezone
414 + 'server_time' => (new DateTime('now', wp_timezone()))->format('Y-m-d H:i:s P'), // wordpress site time
415 + 'server_time_zone' => (new DateTime('now', wp_timezone()))->format( 'P'),
383 416 'utc_offset' => current_time('timestamp') - strtotime(gmdate('Y-m-d H:i:s')),
384 417 'trans' => TransStrings::getStrings(),
385 418 'is_new' => Board::count() == 0 ? 'yes' : 'no',
386 419 'is_onboarded' => $onboardingValue,
@@ -388,11 +421,30 @@
388 421 'dashboard_notices' => apply_filters('fluent_boards/dashboard_notices', []),
389 422 'is_beta' => defined('FLUENT_BOARDS_PRO_VERSION') && !defined('FLUENT_BOARDS_PRO_LIVE'),
390 423 'advanced_modules' => fluent_boards_get_pref_settings(),
391 424 'crm_base_url' => defined('FLUENTCRM') ? fluentcrm_menu_url_base() : '',
425 + 'start_of_week' => intval(get_option('start_of_week', 0)),
426 + 'time_format' => get_option('time_format', 'g:i'),
427 + 'priorities' => apply_filters('fluent_boards/task_priorities', $this->getDefaultPriorities()),
428 + 'wpContentCss' => add_query_arg(
429 + 'ver', get_bloginfo('version'),
430 + site_url('/wp-includes/js/tinymce/skins/wordpress/wp-content.css')
431 + ),
432 + 'dashiconsCss' => add_query_arg(
433 + 'ver', get_bloginfo('version'),
434 + site_url('/wp-includes/css/dashicons.css')
435 + ),
436 + 'is_rtl' => fluent_boards_is_rtl(),
437 + 'board_menu_items' => BoardMenuHandler::getMenuItems(),
438 + 'reminder_types' => defined('FLUENT_BOARDS_PRO') ? Helper::taskReminderTypes() : [],
392 439 ]);
393 440 }
394 441
442 + protected function canManageCrmContacts()
443 + {
444 + return defined('FLUENTCRM') && \FluentCrm\App\Services\PermissionManager::currentUserCan('fcrm_manage_contacts');
445 + }
446 +
395 447 private function getOnboardingValue()
396 448 {
397 449 $onboarding = Meta::where('key', Constant::FBS_ONBOARDING)->first();
398 450 if ($onboarding) {
@@ -404,8 +456,19 @@
404 456 //
405 457 // return 'no';
406 458 }
407 459
460 + public function getDefaultPriorities()
461 + {
462 + return [
463 + '' => __('No priority', 'fluent-boards'),
464 + 'urgent' => __('Urgent', 'fluent-boards'),
465 + 'high' => __('High', 'fluent-boards'),
466 + 'medium' => __('Medium', 'fluent-boards'),
467 + 'low' => __('Low', 'fluent-boards')
468 + ];
469 + }
470 +
408 471 /*
409 472 * TODO: This method should be moved to PermissionManager and Helper . Task for Masiur.
410 473 */
411 474 protected function getRoleAndPermissions($userId): array
@@ -427,9 +490,9 @@
427 490 $permissions = [];
428 491 foreach ($boardUserCollection as $boardWithPermission) {
429 492 $permissions[] = [
430 493 'board_id' => $boardWithPermission->object_id,
431 - 'role' => $boardWithPermission['settings']['is_admin'] ? 'board_admin' : 'board_member',
494 + 'role' => $this->boardUserRole($boardWithPermission),
432 495 'preferences' => $boardWithPermission->preferences,
433 496 'permissions' => [],
434 497 ];
435 498 }
@@ -439,8 +502,13 @@
439 502 'permissions' => $permissions,
440 503 ];
441 504 }
442 505
506 + protected function boardUserRole($boardWithPermission)
507 + {
508 + return $boardWithPermission['settings']['is_admin'] ? 'board_admin' : (Arr::has($boardWithPermission, 'settings.is_viewer_only') && $boardWithPermission['settings']['is_viewer_only'] ? 'board_viewer' : 'board_member');
509 + }
510 +
443 511 protected function getRestInfo($app)
444 512 {
445 513 $ns = $app->config->get('app.rest_namespace');
446 514 $ver = $app->config->get('app.rest_version');
@@ -467,8 +535,9 @@
467 535 add_filter('admin_footer_text', function ($content) {
468 536 $url = '#';
469 537 return '';
470 538
539 + // translators: %s is the URL for FluentBoards link
471 540 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 541 });
473 542
474 543 add_filter('update_footer', function ($text) {
@@ -476,25 +545,15 @@
476 545 });
477 546 }
478 547
479 548 /**
480 - * Retrieves the maximum upload limit based on PHP and WordPress configurations.
549 + * Retrieves the Fluent Boards upload limit exposed to the admin app.
481 550 *
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.
551 + * @return int Upload limit in bytes.
488 552 */
489 553 public function fileUploadLimit()
490 554 {
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 - );
555 + return (new \FluentBoards\App\Services\UploadService())->getFileUploadLimit();
497 556 }
498 557
499 558 public function getInlineScript()
500 559 {
@@ -531,5 +590,18 @@
531 590 _.noConflict();
532 591 }
533 592 ";
534 593 }
594 + public function singleBoardRender()
595 + {
596 + $config = App::getInstance('config');
597 +
598 + $slug = $config->get('app.slug');
599 +
600 +
601 +
602 + App::make('view')->render('admin.single_board_shortcode', [
603 + 'slug' => $slug,
604 + ]);
605 + }
606 +
535 607 }