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 +134 -91 1.352.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-boards'),
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 {
@@ -108,12 +126,21 @@
108 126 );
109 127
110 128 add_submenu_page(
111 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',
112 139 __('Settings', 'fluent-boards'),
113 140 __('Settings', 'fluent-boards'),
114 141 'manage_options',
115 - 'fluent-boards#/settings/members-role',
142 + 'fluent-boards#/settings/general-settings',
116 143 [$this, 'render']
117 144 );
118 145 }
119 146
@@ -128,13 +155,12 @@
128 155 $assets = $app['url.assets'];
129 156 $slug = $config->get('app.slug');
130 157 $baseUrl = fluent_boards_page_url();
131 158
132 - $this->updateDatabase();
133 -
134 -
135 159 do_action('fluent_boards/rendering_app');
136 160
161 + // For subtask sync
162 + UpdateHandler::maybeSubtaskGroupSync();
137 163
138 164 App::make('view')->render('admin.menu', [
139 165 'name' => $name,
140 166 'slug' => $slug,
@@ -146,48 +172,8 @@
146 172 'is_onboarded' => $this->getOnboardingValue()
147 173 ]);
148 174 }
149 175
150 - private function updateDatabase($isForced = true)
151 - {
152 - global $wpdb;
153 -
154 - $table = $wpdb->prefix . 'fbs_board_terms';
155 -
156 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)) != $table) {
157 - return;
158 - } else {
159 - // change column type from int to decimal - for already installed sites
160 - $column_name = 'position';
161 - $preparedQuery = $wpdb->prepare("DESCRIBE $table %s", $column_name);
162 - $dataType = $wpdb->get_row($preparedQuery);
163 - if (strpos($dataType->Type, 'int') !== false) {
164 - $sql = $wpdb->prepare(
165 - "ALTER TABLE $table MODIFY $column_name decimal(10,2) NOT NULL DEFAULT '1' COMMENT 'Position: 1 = top/first, 2 = second/second in top, etc.';"
166 - );
167 - $wpdb->query($sql);
168 - }
169 - }
170 -
171 - $table = $wpdb->prefix . 'fbs_comments';
172 -
173 - if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)) != $table) {
174 - return;
175 - } else {
176 - $column_name = 'settings';
177 - // Check if the column already exists
178 - $column_exists = $wpdb->get_var($wpdb->prepare(
179 - "SHOW COLUMNS FROM $table LIKE %s", $column_name
180 - ));
181 -
182 - if (!$column_exists) {
183 - $sql = "ALTER TABLE $table ADD $column_name TEXT NULL COMMENT 'serialize array' AFTER `created_by`";
184 - $wpdb->query($sql);
185 - }
186 - }
187 -
188 - }
189 -
190 176 public function getMenuItems($app)
191 177 {
192 178 $config = $app->config;
193 179 $slug = $config->get('app.slug');
@@ -228,25 +214,8 @@
228 214
229 215 $isAdmin = PermissionManager::isAdmin();
230 216
231 217 if ($isAdmin) {
232 - $menuItems['settings'] = [
233 - 'key' => 'settings',
234 - 'label' => __('Settings', 'fluent-boards'),
235 - 'permalink' => $baseUrl . 'settings/members-role'
236 - ];
237 - }
238 -
239 - if (!defined('FLUENT_BOARDS_PRO')) {
240 - $menuItems['get_pro'] = [
241 - 'key' => 'get_pro',
242 - 'label' => __('Get Pro', 'fluent-boards'),
243 - 'permalink' => 'https://fluentboards.com?utm_source=menu&utm_medium=plugin&utm_campaign=pro&utm_id=wp',
244 - 'class' => 'pro_link'
245 - ];
246 - }
247 -
248 - if ($isAdmin) {
249 218 $menuItems['help'] = [
250 219 'key' => 'help',
251 220 'label' => __('Community', 'fluent-boards'),
252 221 'target' => '_blank',
@@ -256,9 +225,9 @@
256 225
257 226 if ($isDiffUrl) {
258 227 $menuItems['front'] = [
259 228 'key' => 'front',
260 - 'label' => __('Frontend Portal', 'fluent-boards'),
229 + 'label' => __('Front Portal', 'fluent-boards'),
261 230 'target' => '_blank',
262 231 'permalink' => fluent_boards_page_url()
263 232 ];
264 233 }
@@ -330,32 +299,58 @@
330 299 }
331 300
332 301 $app = App::getInstance();
333 302
334 - $assets = $app['url.assets'];
303 + $slug = $app->config->get('app.slug');
335 304
336 - $slug = $app->config->get('app.slug');
305 + // Inject Vite HMR client in dev mode
306 + Vite::injectViteClient();
337 307
338 - wp_enqueue_style(
339 - $slug . '_admin_app',
340 - $assets . 'admin/admin.css',
341 - [],
342 - FLUENT_BOARDS_PLUGIN_VERSION
343 - );
308 + $isRtl = fluent_boards_is_rtl();
309 + Vite::enqueueStyle($slug . '_admin_app', 'scss/admin.scss');
344 310
345 - 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 + }
346 324
347 - 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(
348 343 $slug . '_admin_app',
349 - $assets . 'admin/app.js',
344 + 'admin/app.js',
350 345 ['jquery'],
351 346 FLUENT_BOARDS_PLUGIN_VERSION,
352 347 true
353 348 );
354 349
355 - wp_enqueue_script(
350 + Vite::enqueueStaticScript(
356 351 $slug . '_global_admin',
357 - $assets . 'admin/global_admin.js',
352 + 'admin/global_admin.js',
358 353 [],
359 354 FLUENT_BOARDS_PLUGIN_VERSION,
360 355 true
361 356 );
@@ -375,8 +370,9 @@
375 370 $currentUser = get_user_by('ID', get_current_user_id());
376 371 $assets = $app['url.assets'];
377 372 $roleAndPermissions = $this->getRoleAndPermissions($currentUser->ID);
378 373 $onboardingValue = $this->getOnboardingValue();
374 + $generalSettings = fluent_boards_get_option('general_settings', []);
379 375
380 376 return apply_filters('fluent_boards/app_vars', [
381 377 'slug' => $slug = $app->config->get('app.slug'),
382 378 'nonce' => wp_create_nonce($slug),
@@ -384,13 +380,23 @@
384 380 'fluent_boards_file_upload_nonce' => wp_create_nonce('fluent_boards_file_upload_nonce'),
385 381 'ajaxurl' => admin_url('admin-ajax.php'),
386 382 'file_upload_limit' => $this->fileUploadLimit(),
387 383 'brand_logo' => $this->getMenuIcon(),
384 + 'business_name' => sanitize_text_field(Arr::get($generalSettings, 'business_name', '')),
388 385 'asset_url' => $assets,
389 386 'admin_url' => admin_url('admin.php'),
390 387 'fluent_crm_exists' => !defined('FLUENTCRM') ? false : true,
388 + 'fluent_support_exists' => !!defined('FLUENT_SUPPORT_VERSION'),
389 + 'can_manage_crm_contacts' => $this->canManageCrmContacts(),
391 390 'fluent_roadmap_exists' => !defined('FLUENT_ROADMAP') ? false : true,
392 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 + ],
393 399 'me' => [
394 400 'id' => $currentUser->ID,
395 401 'full_name' => trim($currentUser->first_name . ' ' . $currentUser->last_name),
396 402 'display_name' => $currentUser->display_name,
@@ -395,15 +401,19 @@
395 401 'full_name' => trim($currentUser->first_name . ' ' . $currentUser->last_name),
396 402 'display_name' => $currentUser->display_name,
397 403 'email' => $currentUser->user_email,
398 404 'photo' => fluent_boards_user_avatar($currentUser->user_email, $currentUser->display_name),
405 + 'logout_url' => wp_logout_url(),
399 406 'fluent_boards_role' => $roleAndPermissions['role'],
400 407 'fluent_boards_capabilities' => $roleAndPermissions['permissions'],
401 - '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)
402 410 ],
403 411 'base_url' => fluent_boards_page_url(),
404 412 'site_url' => site_url('/'),
405 - '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'),
406 416 'utc_offset' => current_time('timestamp') - strtotime(gmdate('Y-m-d H:i:s')),
407 417 'trans' => TransStrings::getStrings(),
408 418 'is_new' => Board::count() == 0 ? 'yes' : 'no',
409 419 'is_onboarded' => $onboardingValue,
@@ -412,11 +422,29 @@
412 422 'is_beta' => defined('FLUENT_BOARDS_PRO_VERSION') && !defined('FLUENT_BOARDS_PRO_LIVE'),
413 423 'advanced_modules' => fluent_boards_get_pref_settings(),
414 424 'crm_base_url' => defined('FLUENTCRM') ? fluentcrm_menu_url_base() : '',
415 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() : [],
416 439 ]);
417 440 }
418 441
442 + protected function canManageCrmContacts()
443 + {
444 + return defined('FLUENTCRM') && \FluentCrm\App\Services\PermissionManager::currentUserCan('fcrm_manage_contacts');
445 + }
446 +
419 447 private function getOnboardingValue()
420 448 {
421 449 $onboarding = Meta::where('key', Constant::FBS_ONBOARDING)->first();
422 450 if ($onboarding) {
@@ -428,8 +456,19 @@
428 456 //
429 457 // return 'no';
430 458 }
431 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 +
432 471 /*
433 472 * TODO: This method should be moved to PermissionManager and Helper . Task for Masiur.
434 473 */
435 474 protected function getRoleAndPermissions($userId): array
@@ -496,8 +535,9 @@
496 535 add_filter('admin_footer_text', function ($content) {
497 536 $url = '#';
498 537 return '';
499 538
539 + // translators: %s is the URL for FluentBoards link
500 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>';
501 541 });
502 542
503 543 add_filter('update_footer', function ($text) {
@@ -505,25 +545,15 @@
505 545 });
506 546 }
507 547
508 548 /**
509 - * Retrieves the maximum upload limit based on PHP and WordPress configurations.
549 + * Retrieves the Fluent Boards upload limit exposed to the admin app.
510 550 *
511 - * This method calculates and returns the minimum value among the following:
512 - * 1. The PHP 'upload_max_filesize' configuration.
513 - * 2. The PHP 'post_max_size' configuration.
514 - * 3. The WordPress maximum upload size limit using the 'wp_max_upload_size' function.
515 - *
516 - * @return int The minimum of the mentioned upload size limits in bytes.
551 + * @return int Upload limit in bytes.
517 552 */
518 553 public function fileUploadLimit()
519 554 {
520 - // Calculate the minimum of 'upload_max_filesize', 'post_max_size', and WordPress maximum upload size.
521 - return min(
522 - wp_convert_hr_to_bytes(ini_get('upload_max_filesize')),
523 - wp_convert_hr_to_bytes(ini_get('post_max_size')),
524 - wp_max_upload_size()
525 - );
555 + return (new \FluentBoards\App\Services\UploadService())->getFileUploadLimit();
526 556 }
527 557
528 558 public function getInlineScript()
529 559 {
@@ -560,5 +590,18 @@
560 590 _.noConflict();
561 591 }
562 592 ";
563 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 +
564 607 }