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 +165 -84 1.112.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(
@@ -91,8 +113,10 @@
91 113 'fluent-boards#/boards',
92 114 [$this, 'render']
93 115 );
94 116
117 + do_action('fluent_boards/after_core_menu_items', $permissions = [], $isAdmin = true);
118 +
95 119 add_submenu_page(
96 120 'fluent-boards',
97 121 __('Reports', 'fluent-boards'),
98 122 __('Reports', 'fluent-boards'),
@@ -100,9 +124,16 @@
100 124 'fluent-boards#/reports',
101 125 [$this, 'render']
102 126 );
103 127
104 - do_action('fluent_boards/after_core_menu_items', $permissions = [], $isAdmin = true);
128 + add_submenu_page(
129 + 'fluent-boards',
130 + __('Addons', 'fluent-boards'),
131 + __('Addons', 'fluent-boards'),
132 + 'manage_options',
133 + 'fluent-boards#/add-ons',
134 + [$this, 'render']
135 + );
105 136
106 137 add_submenu_page(
107 138 'fluent-boards',
108 139 __('Settings', 'fluent-boards'),
@@ -107,9 +138,9 @@
107 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');
@@ -178,9 +190,8 @@
178 190 $baseUrl = $adminUrl;
179 191 }
180 192 }
181 193
182 -
183 194 $menuItems = [
184 195 'dashboard' => [
185 196 'key' => 'dashboard',
186 197 'label' => __('Dashboard', 'fluent-boards'),
@@ -189,36 +200,20 @@
189 200 'boards' => [
190 201 'key' => 'boards',
191 202 'label' => __('Boards', 'fluent-boards'),
192 203 'permalink' => $baseUrl . 'boards'
193 - ],
194 - 'reports' => [
195 - 'key' => 'reports',
196 - 'label' => __('Reports', 'fluent-boards'),
197 - 'permalink' => $baseUrl . 'reports'
198 204 ]
199 205 ];
200 -
201 - $isAdmin = PermissionManager::isAdmin();
202 -
206 +
203 207 $menuItems = apply_filters('fluent_boards/core_menu_items', $menuItems);
204 208
205 - if ($isAdmin) {
206 - $menuItems['settings'] = [
207 - 'key' => 'settings',
208 - 'label' => __('Settings', 'fluent-boards'),
209 - 'permalink' => $baseUrl . 'settings/members-role'
210 - ];
211 - }
209 + $menuItems['reports'] = [
210 + 'key' => 'reports',
211 + 'label' => __('Reports', 'fluent-boards'),
212 + 'permalink' => $baseUrl . 'reports'
213 + ];
212 214
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 - }
215 + $isAdmin = PermissionManager::isAdmin();
221 216
222 217 if ($isAdmin) {
223 218 $menuItems['help'] = [
224 219 'key' => 'help',
@@ -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,
@@ -378,12 +419,32 @@
378 419 'is_onboarded' => $onboardingValue,
379 420 'render_in' => is_admin() ? 'admin' : 'front',
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 - 'advanced_modules' => fluent_boards_get_pref_settings()
423 + 'advanced_modules' => fluent_boards_get_pref_settings(),
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() : [],
383 439 ]);
384 440 }
385 441
442 + protected function canManageCrmContacts()
443 + {
444 + return defined('FLUENTCRM') && \FluentCrm\App\Services\PermissionManager::currentUserCan('fcrm_manage_contacts');
445 + }
446 +
386 447 private function getOnboardingValue()
387 448 {
388 449 $onboarding = Meta::where('key', Constant::FBS_ONBOARDING)->first();
389 450 if ($onboarding) {
@@ -395,8 +456,19 @@
395 456 //
396 457 // return 'no';
397 458 }
398 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 +
399 471 /*
400 472 * TODO: This method should be moved to PermissionManager and Helper . Task for Masiur.
401 473 */
402 474 protected function getRoleAndPermissions($userId): array
@@ -418,9 +490,9 @@
418 490 $permissions = [];
419 491 foreach ($boardUserCollection as $boardWithPermission) {
420 492 $permissions[] = [
421 493 'board_id' => $boardWithPermission->object_id,
422 - 'role' => $boardWithPermission['settings']['is_admin'] ? 'board_admin' : 'board_member',
494 + 'role' => $this->boardUserRole($boardWithPermission),
423 495 'preferences' => $boardWithPermission->preferences,
424 496 'permissions' => [],
425 497 ];
426 498 }
@@ -430,8 +502,13 @@
430 502 'permissions' => $permissions,
431 503 ];
432 504 }
433 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 +
434 511 protected function getRestInfo($app)
435 512 {
436 513 $ns = $app->config->get('app.rest_namespace');
437 514 $ver = $app->config->get('app.rest_version');
@@ -458,8 +535,9 @@
458 535 add_filter('admin_footer_text', function ($content) {
459 536 $url = '#';
460 537 return '';
461 538
539 + // translators: %s is the URL for FluentBoards link
462 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>';
463 541 });
464 542
465 543 add_filter('update_footer', function ($text) {
@@ -467,25 +545,15 @@
467 545 });
468 546 }
469 547
470 548 /**
471 - * Retrieves the maximum upload limit based on PHP and WordPress configurations.
549 + * Retrieves the Fluent Boards upload limit exposed to the admin app.
472 550 *
473 - * This method calculates and returns the minimum value among the following:
474 - * 1. The PHP 'upload_max_filesize' configuration.
475 - * 2. The PHP 'post_max_size' configuration.
476 - * 3. The WordPress maximum upload size limit using the 'wp_max_upload_size' function.
477 - *
478 - * @return int The minimum of the mentioned upload size limits in bytes.
551 + * @return int Upload limit in bytes.
479 552 */
480 553 public function fileUploadLimit()
481 554 {
482 - // Calculate the minimum of 'upload_max_filesize', 'post_max_size', and WordPress maximum upload size.
483 - return min(
484 - wp_convert_hr_to_bytes(ini_get('upload_max_filesize')),
485 - wp_convert_hr_to_bytes(ini_get('post_max_size')),
486 - wp_max_upload_size()
487 - );
555 + return (new \FluentBoards\App\Services\UploadService())->getFileUploadLimit();
488 556 }
489 557
490 558 public function getInlineScript()
491 559 {
@@ -522,5 +590,18 @@
522 590 _.noConflict();
523 591 }
524 592 ";
525 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 +
526 607 }