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