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