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 +141 -93 1.32 → 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 {
@@ -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,14 +155,10 @@
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
137 -
138 161 App::make('view')->render('admin.menu', [
139 162 'name' => $name,
140 163 'slug' => $slug,
141 164 'menuItems' => $this->getMenuItems($app),
@@ -146,48 +169,8 @@
146 169 'is_onboarded' => $this->getOnboardingValue()
147 170 ]);
148 171 }
149 172
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 173 public function getMenuItems($app)
191 174 {
192 175 $config = $app->config;
193 176 $slug = $config->get('app.slug');
@@ -228,25 +211,8 @@
228 211
229 212 $isAdmin = PermissionManager::isAdmin();
230 213
231 214 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 215 $menuItems['help'] = [
250 216 'key' => 'help',
251 217 'label' => __('Community', 'fluent-boards'),
252 218 'target' => '_blank',
@@ -256,9 +222,9 @@
256 222
257 223 if ($isDiffUrl) {
258 224 $menuItems['front'] = [
259 225 'key' => 'front',
260 - 'label' => __('Frontend Portal', 'fluent-boards'),
226 + 'label' => __('Front Portal', 'fluent-boards'),
261 227 'target' => '_blank',
262 228 'permalink' => fluent_boards_page_url()
263 229 ];
264 230 }
@@ -330,32 +296,58 @@
330 296 }
331 297
332 298 $app = App::getInstance();
333 299
334 - $assets = $app['url.assets'];
300 + $slug = $app->config->get('app.slug');
335 301
336 - $slug = $app->config->get('app.slug');
302 + // Inject Vite HMR client in dev mode
303 + Vite::injectViteClient();
337 304
338 - wp_enqueue_style(
339 - $slug . '_admin_app',
340 - $assets . 'admin/admin.css',
341 - [],
342 - FLUENT_BOARDS_PLUGIN_VERSION
343 - );
305 + $isRtl = fluent_boards_is_rtl();
306 + Vite::enqueueStyle($slug . '_admin_app', 'scss/admin.scss');
344 307
345 - 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 + }
346 321
347 - 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(
348 340 $slug . '_admin_app',
349 - $assets . 'admin/app.js',
341 + 'admin/app.js',
350 342 ['jquery'],
351 343 FLUENT_BOARDS_PLUGIN_VERSION,
352 344 true
353 345 );
354 346
355 - wp_enqueue_script(
347 + Vite::enqueueStaticScript(
356 348 $slug . '_global_admin',
357 - $assets . 'admin/global_admin.js',
349 + 'admin/global_admin.js',
358 350 [],
359 351 FLUENT_BOARDS_PLUGIN_VERSION,
360 352 true
361 353 );
@@ -375,8 +367,9 @@
375 367 $currentUser = get_user_by('ID', get_current_user_id());
376 368 $assets = $app['url.assets'];
377 369 $roleAndPermissions = $this->getRoleAndPermissions($currentUser->ID);
378 370 $onboardingValue = $this->getOnboardingValue();
371 + $generalSettings = fluent_boards_get_option('general_settings', []);
379 372
380 373 return apply_filters('fluent_boards/app_vars', [
381 374 'slug' => $slug = $app->config->get('app.slug'),
382 375 'nonce' => wp_create_nonce($slug),
@@ -384,13 +377,23 @@
384 377 'fluent_boards_file_upload_nonce' => wp_create_nonce('fluent_boards_file_upload_nonce'),
385 378 'ajaxurl' => admin_url('admin-ajax.php'),
386 379 'file_upload_limit' => $this->fileUploadLimit(),
387 380 'brand_logo' => $this->getMenuIcon(),
381 + 'business_name' => sanitize_text_field(Arr::get($generalSettings, 'business_name', '')),
388 382 'asset_url' => $assets,
389 383 'admin_url' => admin_url('admin.php'),
390 384 'fluent_crm_exists' => !defined('FLUENTCRM') ? false : true,
385 + 'fluent_support_exists' => !!defined('FLUENT_SUPPORT_VERSION'),
386 + 'can_manage_crm_contacts' => $this->canManageCrmContacts(),
391 387 'fluent_roadmap_exists' => !defined('FLUENT_ROADMAP') ? false : true,
392 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 + ],
393 396 'me' => [
394 397 'id' => $currentUser->ID,
395 398 'full_name' => trim($currentUser->first_name . ' ' . $currentUser->last_name),
396 399 'display_name' => $currentUser->display_name,
@@ -395,27 +398,52 @@
395 398 'full_name' => trim($currentUser->first_name . ' ' . $currentUser->last_name),
396 399 'display_name' => $currentUser->display_name,
397 400 'email' => $currentUser->user_email,
398 401 'photo' => fluent_boards_user_avatar($currentUser->user_email, $currentUser->display_name),
402 + 'logout_url' => wp_logout_url(),
399 403 'fluent_boards_role' => $roleAndPermissions['role'],
400 404 'fluent_boards_capabilities' => $roleAndPermissions['permissions'],
401 - '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)
402 407 ],
403 408 'base_url' => fluent_boards_page_url(),
404 409 'site_url' => site_url('/'),
405 - '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'),
406 413 'utc_offset' => current_time('timestamp') - strtotime(gmdate('Y-m-d H:i:s')),
407 414 'trans' => TransStrings::getStrings(),
408 415 'is_new' => Board::count() == 0 ? 'yes' : 'no',
409 416 'is_onboarded' => $onboardingValue,
410 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),
411 420 'dashboard_notices' => apply_filters('fluent_boards/dashboard_notices', []),
412 421 'is_beta' => defined('FLUENT_BOARDS_PRO_VERSION') && !defined('FLUENT_BOARDS_PRO_LIVE'),
413 422 'advanced_modules' => fluent_boards_get_pref_settings(),
414 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() : [],
415 438 ]);
416 439 }
417 440
441 + protected function canManageCrmContacts()
442 + {
443 + return defined('FLUENTCRM') && \FluentCrm\App\Services\PermissionManager::currentUserCan('fcrm_manage_contacts');
444 + }
445 +
418 446 private function getOnboardingValue()
419 447 {
420 448 $onboarding = Meta::where('key', Constant::FBS_ONBOARDING)->first();
421 449 if ($onboarding) {
@@ -427,8 +455,19 @@
427 455 //
428 456 // return 'no';
429 457 }
430 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 +
431 470 /*
432 471 * TODO: This method should be moved to PermissionManager and Helper . Task for Masiur.
433 472 */
434 473 protected function getRoleAndPermissions($userId): array
@@ -450,9 +489,9 @@
450 489 $permissions = [];
451 490 foreach ($boardUserCollection as $boardWithPermission) {
452 491 $permissions[] = [
453 492 'board_id' => $boardWithPermission->object_id,
454 - 'role' => $boardWithPermission['settings']['is_admin'] ? 'board_admin' : 'board_member',
493 + 'role' => $this->boardUserRole($boardWithPermission),
455 494 'preferences' => $boardWithPermission->preferences,
456 495 'permissions' => [],
457 496 ];
458 497 }
@@ -462,8 +501,13 @@
462 501 'permissions' => $permissions,
463 502 ];
464 503 }
465 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 +
466 510 protected function getRestInfo($app)
467 511 {
468 512 $ns = $app->config->get('app.rest_namespace');
469 513 $ver = $app->config->get('app.rest_version');
@@ -490,8 +534,9 @@
490 534 add_filter('admin_footer_text', function ($content) {
491 535 $url = '#';
492 536 return '';
493 537
538 + // translators: %s is the URL for FluentBoards link
494 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>';
495 540 });
496 541
497 542 add_filter('update_footer', function ($text) {
@@ -499,25 +544,15 @@
499 544 });
500 545 }
501 546
502 547 /**
503 - * Retrieves the maximum upload limit based on PHP and WordPress configurations.
548 + * Retrieves the Fluent Boards upload limit exposed to the admin app.
504 549 *
505 - * This method calculates and returns the minimum value among the following:
506 - * 1. The PHP 'upload_max_filesize' configuration.
507 - * 2. The PHP 'post_max_size' configuration.
508 - * 3. The WordPress maximum upload size limit using the 'wp_max_upload_size' function.
509 - *
510 - * @return int The minimum of the mentioned upload size limits in bytes.
550 + * @return int Upload limit in bytes.
511 551 */
512 552 public function fileUploadLimit()
513 553 {
514 - // Calculate the minimum of 'upload_max_filesize', 'post_max_size', and WordPress maximum upload size.
515 - return min(
516 - wp_convert_hr_to_bytes(ini_get('upload_max_filesize')),
517 - wp_convert_hr_to_bytes(ini_get('post_max_size')),
518 - wp_max_upload_size()
519 - );
554 + return (new \FluentBoards\App\Services\UploadService())->getFileUploadLimit();
520 555 }
521 556
522 557 public function getInlineScript()
523 558 {
@@ -554,5 +589,18 @@
554 589 _.noConflict();
555 590 }
556 591 ";
557 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 +
558 606 }