PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.95
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.95
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
fluent-boards / app / Hooks / Handlers / AdminMenuHandler.php

AdminMenuHandler.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 1.95, at app/Hooks/Handlers/AdminMenuHandler.php

625 lines 23.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBoards\App\Hooks\Handlers;
4
5 use FluentBoards\App\App;
6 use FluentBoards\App\Models\Board;
7 use FluentBoards\App\Models\Meta;
8 use FluentBoards\App\Models\Relation;
9 use FluentBoards\App\Services\Constant;
10 use FluentBoards\App\Services\Helper;
11 use FluentBoards\App\Services\TransStrings;
12 use FluentBoards\App\Vite;
13 use FluentBoards\Framework\Support\Arr;
14 use FluentBoards\Framework\Support\Collection;
15 use FluentBoards\App\Services\PermissionManager;
16 use FluentBoards\Framework\Support\DateTime;
17
18 class AdminMenuHandler
19 {
20
21 public function register()
22 {
23 add_action('admin_menu', [$this, 'add'], 11);
24
25 add_filter('fluent_crm/core_menu_items', function ($items) {
26 if (PermissionManager::userHasAnyBoardAccess()) {
27 $items['fluent-boards'] = [
28 'key' => 'fluent-boards',
29 'label' => __('Boards', 'fluent-boards'),
30 'permalink' => admin_url('admin.php?page=fluent-boards#/')
31 ];
32 }
33 return $items;
34 });
35
36 add_action('admin_enqueue_scripts', function () {
37 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Checking admin page context for asset enqueuing, no data modification
38 if (!isset($_REQUEST['page']) || $_REQUEST['page'] !== 'fluent-boards') {
39 return;
40 }
41
42 $this->enqueueAssets();
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);
58 }
59
60 public function add()
61 {
62 if (!PermissionManager::userHasAnyBoardAccess()) {
63 return;
64 }
65
66 $user = get_user_by('ID', get_current_user_id());
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
75 $settings = fluent_boards_get_pref_settings();
76
77 if (defined('FLUENTCRM') && \FluentCrm\App\Services\PermissionManager::currentUserPermissions() && Arr::get($settings, 'menu_settings.in_fluent_crm') === 'yes') {
78 add_submenu_page(
79 'fluentcrm-admin', //$parent_slug
80 __('Fluent Boards', 'fluent-boards'), //$page_title
81 __('Fluent Boards', 'fluent-boards'), //$menu_title
82 $capability,
83 'fluent-boards', //$menu_slug
84 [$this, 'render'],
85 );
86 return;
87 }
88
89 add_menu_page(
90 __('Fluent Boards', 'fluent-boards'),
91 __('Fluent Boards', 'fluent-boards'),
92 $capability,
93 'fluent-boards',
94 [$this, 'render'],
95 $this->getMenuIcon(),
96 Arr::get($settings, 'menu_settings.menu_position', 3)
97 );
98
99 add_submenu_page(
100 'fluent-boards',
101 __('Dashboard', 'fluent-boards'),
102 __('Dashboard', 'fluent-boards'),
103 $capability,
104 'fluent-boards',
105 [$this, 'render']
106 );
107
108 add_submenu_page(
109 'fluent-boards',
110 __('Boards', 'fluent-boards'),
111 __('Boards', 'fluent-boards'),
112 $capability,
113 'fluent-boards#/boards',
114 [$this, 'render']
115 );
116
117 do_action('fluent_boards/after_core_menu_items', $permissions = [], $isAdmin = true);
118
119 add_submenu_page(
120 'fluent-boards',
121 __('Reports', 'fluent-boards'),
122 __('Reports', 'fluent-boards'),
123 $capability,
124 'fluent-boards#/reports',
125 [$this, 'render']
126 );
127
128 add_submenu_page(
129 'fluent-boards',
130 __('Settings', 'fluent-boards'),
131 __('Settings', 'fluent-boards'),
132 'manage_options',
133 'fluent-boards#/settings/members-role',
134 [$this, 'render']
135 );
136 }
137
138 public function render()
139 {
140 $this->changeFooter();
141
142 $config = App::getInstance('config');
143
144 $name = $config->get('app.name');
145 $app = App::getInstance();
146 $assets = $app['url.assets'];
147 $slug = $config->get('app.slug');
148 $baseUrl = fluent_boards_page_url();
149
150 do_action('fluent_boards/rendering_app');
151
152 // For subtask sync
153 UpdateHandler::maybeSubtaskGroupSync();
154
155 App::make('view')->render('admin.menu', [
156 'name' => $name,
157 'slug' => $slug,
158 'menuItems' => $this->getMenuItems($app),
159 'baseUrl' => $baseUrl,
160 'logo' => apply_filters('fluent_boards/app_logo', $assets . 'images/logo.svg'),
161 'icon' => apply_filters('fluent_boards/app_icon', $assets . 'images/icon.svg'),
162 'is_new' => Board::count() == 0 ? 'yes' : 'no',
163 'is_onboarded' => $this->getOnboardingValue()
164 ]);
165 }
166
167 public function getMenuItems($app)
168 {
169 $config = $app->config;
170 $slug = $config->get('app.slug');
171
172 $baseUrl = fluent_boards_page_url();
173
174 $isDiffUrl = false;
175
176 if (is_admin()) {
177 $adminUrl = admin_url('admin.php?page=fluent-boards#/');
178
179 if ($adminUrl != $baseUrl) {
180 $isDiffUrl = true;
181 $baseUrl = $adminUrl;
182 }
183 }
184
185 $menuItems = [
186 'dashboard' => [
187 'key' => 'dashboard',
188 'label' => __('Dashboard', 'fluent-boards'),
189 'permalink' => $baseUrl,
190 ],
191 'boards' => [
192 'key' => 'boards',
193 'label' => __('Boards', 'fluent-boards'),
194 'permalink' => $baseUrl . 'boards'
195 ]
196 ];
197
198 $menuItems = apply_filters('fluent_boards/core_menu_items', $menuItems);
199
200 $menuItems['reports'] = [
201 'key' => 'reports',
202 'label' => __('Reports', 'fluent-boards'),
203 'permalink' => $baseUrl . 'reports'
204 ];
205
206 $isAdmin = PermissionManager::isAdmin();
207
208 if ($isAdmin) {
209 $menuItems['settings'] = [
210 'key' => 'settings',
211 'label' => __('Settings', 'fluent-boards'),
212 'permalink' => $baseUrl . 'settings/members-role'
213 ];
214 }
215
216 if (!defined('FLUENT_BOARDS_PRO')) {
217 $menuItems['get_pro'] = [
218 'key' => 'get_pro',
219 'label' => __('Get Pro', 'fluent-boards'),
220 'permalink' => 'https://fluentboards.com?utm_source=plugin&utm_medium=menu&utm_campaign=pro&utm_id=wp',
221 'class' => 'pro_link'
222 ];
223 }
224
225 if ($isAdmin) {
226 $menuItems['help'] = [
227 'key' => 'help',
228 'label' => __('Community', 'fluent-boards'),
229 'target' => '_blank',
230 'permalink' => 'https://community.wpmanageninja.com/portal/community/fluent-boards/home'
231 ];
232 }
233
234 if ($isDiffUrl) {
235 $menuItems['front'] = [
236 'key' => 'front',
237 'label' => __('Frontend Portal', 'fluent-boards'),
238 'target' => '_blank',
239 'permalink' => fluent_boards_page_url()
240 ];
241 }
242
243 $menuItems = apply_filters('fluent_boards/menu_items', $menuItems);
244
245 return array_values($menuItems);
246 }
247
248 public function enqueueAssets()
249 {
250 if (!PermissionManager::hasAppAccess()) {
251 return;
252 }
253
254 add_action('wp_print_scripts', function () {
255
256 $isSkip = apply_filters('fluent_boards/skip_no_conflict', false);
257
258 if ($isSkip) {
259 return;
260 }
261
262 global $wp_scripts;
263 if (!$wp_scripts) {
264 return;
265 }
266
267 $approvedSlugs = apply_filters('fluent_boards/asset_listed_slugs', [
268 '\/fluent-crm\/'
269 ]);
270
271 $approvedSlugs[] = '\/fluent-boards\/';
272
273 $approvedSlugs = array_unique($approvedSlugs);
274
275 $approvedSlugs = implode('|', $approvedSlugs);
276
277 $pluginUrl = plugins_url();
278
279 $pluginUrl = str_replace(['http:', 'https:'], '', $pluginUrl);
280
281 foreach ($wp_scripts->queue as $script) {
282 if (empty($wp_scripts->registered[$script]) || empty($wp_scripts->registered[$script]->src)) {
283 continue;
284 }
285
286 $src = $wp_scripts->registered[$script]->src;
287 $isMatched = (strpos($src, $pluginUrl) !== false) && !preg_match('/' . $approvedSlugs . '/', $src);
288 if (!$isMatched) {
289 continue;
290 }
291 wp_dequeue_script($wp_scripts->registered[$script]->handle);
292 }
293 });
294
295 if (function_exists('wp_enqueue_media')) {
296 // Editor default styles.
297 add_filter('user_can_richedit', '__return_true');
298 if (is_admin()) {
299 wp_tinymce_inline_scripts();
300 }
301 wp_enqueue_editor();
302 wp_enqueue_script('thickbox');
303 wp_enqueue_script('editor');
304 }
305 if (function_exists('wp_enqueue_media')) {
306 wp_enqueue_media();
307 }
308
309 $app = App::getInstance();
310
311 $slug = $app->config->get('app.slug');
312
313 // Inject Vite HMR client in dev mode
314 Vite::injectViteClient();
315
316 $isRtl = is_rtl();
317 $adminAppCss = $isRtl ? 'scss/admin.scss' : 'scss/admin.scss';
318 Vite::enqueueStyle($slug . '_admin_app', $adminAppCss);
319
320 // In production, load the RTL version if needed
321 if ($isRtl && !Vite::underDevelopment()) {
322 $assets = $app['url.assets'];
323 wp_enqueue_style(
324 $slug . '_admin_app_rtl',
325 $assets . 'admin/admin-rtl.css',
326 [$slug . '_admin_app'],
327 FLUENT_BOARDS_PLUGIN_VERSION
328 );
329 }
330
331 // Enqueue merged chunk CSS (Vue SFC styles) in production
332 if (!Vite::underDevelopment()) {
333 $assets = $app['url.assets'];
334 $styleCssPath = FLUENT_BOARDS_PLUGIN_PATH . 'assets/admin/style.css';
335 if (file_exists($styleCssPath)) {
336 wp_enqueue_style(
337 $slug . '_vite_style',
338 $assets . 'admin/style.css',
339 [$slug . '_admin_app'],
340 FLUENT_BOARDS_PLUGIN_VERSION
341 );
342 }
343 }
344
345 do_action('fluent-boards_loading_app');
346
347 Vite::enqueueScript(
348 $slug . '_admin_app',
349 'admin/app.js',
350 ['jquery'],
351 FLUENT_BOARDS_PLUGIN_VERSION,
352 true
353 );
354
355 Vite::enqueueStaticScript(
356 $slug . '_global_admin',
357 'admin/global_admin.js',
358 [],
359 FLUENT_BOARDS_PLUGIN_VERSION,
360 true
361 );
362 /*
363 * This script only for resolve the conflict of lodash and underscore js
364 * Resolved the issue of media uploader specially for image upload
365 */
366 wp_add_inline_script($slug . '_global_admin', $this->getInlineScript(), 'after');
367
368 wp_localize_script($slug . '_admin_app', 'fluentAddonVars', $this->getAddonVars($app));
369
370 do_action('fluent_boards/after_enqueue_assets', $app);
371 }
372
373 public function getAddonVars($app)
374 {
375 $currentUser = get_user_by('ID', get_current_user_id());
376 $assets = $app['url.assets'];
377 $roleAndPermissions = $this->getRoleAndPermissions($currentUser->ID);
378 $onboardingValue = $this->getOnboardingValue();
379
380 return apply_filters('fluent_boards/app_vars', [
381 'slug' => $slug = $app->config->get('app.slug'),
382 'nonce' => wp_create_nonce($slug),
383 'rest' => $this->getRestInfo($app),
384 'fluent_boards_file_upload_nonce' => wp_create_nonce('fluent_boards_file_upload_nonce'),
385 'ajaxurl' => admin_url('admin-ajax.php'),
386 'file_upload_limit' => $this->fileUploadLimit(),
387 'brand_logo' => $this->getMenuIcon(),
388 'asset_url' => $assets,
389 'admin_url' => admin_url('admin.php'),
390 'fluent_crm_exists' => !defined('FLUENTCRM') ? false : true,
391 'fluent_roadmap_exists' => !defined('FLUENT_ROADMAP') ? false : true,
392 'has_pro' => !!defined('FLUENT_BOARDS_PRO_VERSION'),
393 'me' => [
394 'id' => $currentUser->ID,
395 'full_name' => trim($currentUser->first_name . ' ' . $currentUser->last_name),
396 'display_name' => $currentUser->display_name,
397 'email' => $currentUser->user_email,
398 'photo' => fluent_boards_user_avatar($currentUser->user_email, $currentUser->display_name),
399 'fluent_boards_role' => $roleAndPermissions['role'],
400 'fluent_boards_capabilities' => $roleAndPermissions['permissions'],
401 'is_wp_admin' => user_can($currentUser->ID, 'manage_options') ? 'yes' : 'no',
402 'can_create_board' => PermissionManager::userHasBoardCreationPermission($currentUser->ID)
403 ],
404 'base_url' => fluent_boards_page_url(),
405 'site_url' => site_url('/'),
406 // 'server_time' => (new DateTime('now'))->format('Y-m-d H:i:s P'), // Server's default timezone
407 'server_time' => (new DateTime('now', wp_timezone()))->format('Y-m-d H:i:s P'), // wordpress site time
408 'server_time_zone' => (new DateTime('now', wp_timezone()))->format( 'P'),
409 'utc_offset' => current_time('timestamp') - strtotime(gmdate('Y-m-d H:i:s')),
410 'trans' => TransStrings::getStrings(),
411 'is_new' => Board::count() == 0 ? 'yes' : 'no',
412 'is_onboarded' => $onboardingValue,
413 'render_in' => is_admin() ? 'admin' : 'front',
414 'dashboard_notices' => apply_filters('fluent_boards/dashboard_notices', []),
415 'is_beta' => defined('FLUENT_BOARDS_PRO_VERSION') && !defined('FLUENT_BOARDS_PRO_LIVE'),
416 'advanced_modules' => fluent_boards_get_pref_settings(),
417 'crm_base_url' => defined('FLUENTCRM') ? fluentcrm_menu_url_base() : '',
418 'start_of_week' => intval(get_option('start_of_week', 0)),
419 'time_format' => get_option('time_format', 'g:i'),
420 'priorities' => apply_filters('fluent_boards/task_priorities', $this->getDefaultPriorities()),
421 'wpContentCss' => add_query_arg(
422 'ver', get_bloginfo('version'),
423 site_url('/wp-includes/js/tinymce/skins/wordpress/wp-content.css')
424 ),
425 'dashiconsCss' => add_query_arg(
426 'ver', get_bloginfo('version'),
427 site_url('/wp-includes/css/dashicons.css')
428 ),
429 'is_rtl' => is_rtl(),
430 'task_tabs' => apply_filters('fluent_boards/task_tabs', $this->getDefaultTaskTabs()),
431 'board_menu_items' => BoardMenuHandler::getMenuItems(),
432 'reminder_types' => defined('FLUENT_BOARDS_PRO') ? Helper::taskReminderTypes() : [],
433 ]);
434 }
435
436 private function getOnboardingValue()
437 {
438 $onboarding = Meta::where('key', Constant::FBS_ONBOARDING)->first();
439 if ($onboarding) {
440 return $onboarding->value;
441 }
442 // if (Board::first()) {
443 // return 'yes';
444 // }
445 //
446 // return 'no';
447 }
448
449 private function getDefaultTaskTabs()
450 {
451 return [
452 'all' => [
453 'key' => 'all',
454 'label' => __('All', 'fluent-boards'),
455 'component' => 'task-all-comments-and-activities'
456 ],
457 'comment' => [
458 'key' => 'comment',
459 'label' => __('Comments', 'fluent-boards'),
460 'component' => 'task-comments'
461 ],
462 'activity' => [
463 'key' => 'activity',
464 'label' => __('Activities', 'fluent-boards'),
465 'component' => 'task-activities'
466 ]
467 ];
468 }
469 public function getDefaultPriorities()
470 {
471 return [
472 'low' => __('Low', 'fluent-boards'),
473 'medium' => __('Medium', 'fluent-boards'),
474 'high' => __('High', 'fluent-boards')
475 ];
476 }
477
478 /*
479 * TODO: This method should be moved to PermissionManager and Helper . Task for Masiur.
480 */
481 protected function getRoleAndPermissions($userId): array
482 {
483 $role = 'fluent_boards_admin';
484 $boardsWithPermissions = Relation::query()->where('foreign_id', $userId)
485 ->where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
486 ->get();
487 $boardUserCollection = Collection::make($boardsWithPermissions);
488
489 if (PermissionManager::isFluentBoardsAdmin($userId)) {
490 $role = 'fluent_boards_admin';
491 } elseif (user_can($userId, 'manage_options')) {
492 $role = 'wordpress_admin';
493 } else {
494 $role = 'member';
495 }
496
497 $permissions = [];
498 foreach ($boardUserCollection as $boardWithPermission) {
499 $permissions[] = [
500 'board_id' => $boardWithPermission->object_id,
501 'role' => $this->boardUserRole($boardWithPermission),
502 'preferences' => $boardWithPermission->preferences,
503 'permissions' => [],
504 ];
505 }
506
507 return [
508 'role' => $role,
509 'permissions' => $permissions,
510 ];
511 }
512
513 protected function boardUserRole($boardWithPermission)
514 {
515 return $boardWithPermission['settings']['is_admin'] ? 'board_admin' : (Arr::has($boardWithPermission, 'settings.is_viewer_only') && $boardWithPermission['settings']['is_viewer_only'] ? 'board_viewer' : 'board_member');
516 }
517
518 protected function getRestInfo($app)
519 {
520 $ns = $app->config->get('app.rest_namespace');
521 $ver = $app->config->get('app.rest_version');
522
523 return [
524 'base_url' => esc_url_raw(rest_url()),
525 'url' => rest_url($ns . '/' . $ver),
526 'nonce' => wp_create_nonce('wp_rest'),
527 'namespace' => $ns,
528 'version' => $ver,
529 ];
530 }
531
532 protected function getMenuIcon()
533 {
534 /*
535 * Left Sidebar Menu Icon
536 */
537 return 'data:image/svg+xml;base64,' . base64_encode('<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M0 25.6C0 11.4615 11.4615 0 25.6 0H230.4C244.538 0 256 11.4615 256 25.6V230.4C256 244.538 244.538 256 230.4 256H25.6C11.4615 256 0 244.538 0 230.4V25.6ZM140.8 89.6C140.8 75.4615 152.262 64 166.4 64H186.88C189.708 64 192 66.2923 192 69.12V166.4C192 180.538 180.538 192 166.4 192H145.92C143.092 192 140.8 189.708 140.8 186.88V89.6ZM89.6 64C75.4615 64 64 75.4615 64 89.5999V148.48C64 151.308 66.2923 153.6 69.12 153.6H89.6C103.739 153.6 115.2 142.138 115.2 128V69.12C115.2 66.2923 112.908 64 110.08 64H89.6Z" fill="white"/></svg>');
538 }
539
540 public function changeFooter()
541 {
542 add_filter('admin_footer_text', function ($content) {
543 $url = '#';
544 return '';
545
546 // translators: %s is the URL for FluentBoards link
547 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>';
548 });
549
550 add_filter('update_footer', function ($text) {
551 return FLUENT_BOARDS_PLUGIN_VERSION;
552 });
553 }
554
555 /**
556 * Retrieves the maximum upload limit based on PHP and WordPress configurations.
557 *
558 * This method calculates and returns the minimum value among the following:
559 * 1. The PHP 'upload_max_filesize' configuration.
560 * 2. The PHP 'post_max_size' configuration.
561 * 3. The WordPress maximum upload size limit using the 'wp_max_upload_size' function.
562 *
563 * @return int The minimum of the mentioned upload size limits in bytes.
564 */
565 public function fileUploadLimit()
566 {
567 // Calculate the minimum of 'upload_max_filesize', 'post_max_size', and WordPress maximum upload size.
568 return min(
569 wp_convert_hr_to_bytes(ini_get('upload_max_filesize')),
570 wp_convert_hr_to_bytes(ini_get('post_max_size')),
571 wp_max_upload_size()
572 );
573 }
574
575 public function getInlineScript()
576 {
577 return "
578 function isLodash () {
579
580 let isLodash = false;
581
582 // If _ is defined and the function _.forEach exists then we know underscore OR lodash are in place
583 if ( 'undefined' != typeof( _ ) && 'function' == typeof( _.forEach ) ) {
584
585 // A small sample of some of the functions that exist in lodash but not underscore
586 const funcs = [ 'get', 'set', 'at', 'cloneDeep' ];
587
588 // Simplest if assume exists to start
589 isLodash = true;
590
591 funcs.forEach( function ( func ) {
592 // If just one of the functions do not exist, then not lodash
593 isLodash = ( 'function' != typeof( _[ func ] ) ) ? false : isLodash;
594 } );
595 }
596
597 if ( isLodash ) {
598 // We know that lodash is loaded in the _ variable
599 return true;
600 } else {
601 // We know that lodash is NOT loaded
602 return false;
603 }
604 };
605
606 if ( isLodash() ) {
607 _.noConflict();
608 }
609 ";
610 }
611 public function singleBoardRender()
612 {
613 $config = App::getInstance('config');
614
615 $slug = $config->get('app.slug');
616
617
618
619 App::make('view')->render('admin.single_board_shortcode', [
620 'slug' => $slug,
621 ]);
622 }
623
624 }
625