PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / trunk
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration vtrunk
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 1.45 All 41 releases
fluent-boards / app / Hooks / Handlers / AdminMenuHandler.php

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

607 lines 22.7 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 __('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',
139 __('Settings', 'fluent-boards'),
140 __('Settings', 'fluent-boards'),
141 'manage_options',
142 'fluent-boards#/settings/general-settings',
143 [$this, 'render']
144 );
145 }
146
147 public function render()
148 {
149 $this->changeFooter();
150
151 $config = App::getInstance('config');
152
153 $name = $config->get('app.name');
154 $app = App::getInstance();
155 $assets = $app['url.assets'];
156 $slug = $config->get('app.slug');
157 $baseUrl = fluent_boards_page_url();
158
159 do_action('fluent_boards/rendering_app');
160
161 // For subtask sync
162 UpdateHandler::maybeSubtaskGroupSync();
163
164 App::make('view')->render('admin.menu', [
165 'name' => $name,
166 'slug' => $slug,
167 'menuItems' => $this->getMenuItems($app),
168 'baseUrl' => $baseUrl,
169 'logo' => apply_filters('fluent_boards/app_logo', $assets . 'images/logo.svg'),
170 'icon' => apply_filters('fluent_boards/app_icon', $assets . 'images/icon.svg'),
171 'is_new' => Board::count() == 0 ? 'yes' : 'no',
172 'is_onboarded' => $this->getOnboardingValue()
173 ]);
174 }
175
176 public function getMenuItems($app)
177 {
178 $config = $app->config;
179 $slug = $config->get('app.slug');
180
181 $baseUrl = fluent_boards_page_url();
182
183 $isDiffUrl = false;
184
185 if (is_admin()) {
186 $adminUrl = admin_url('admin.php?page=fluent-boards#/');
187
188 if ($adminUrl != $baseUrl) {
189 $isDiffUrl = true;
190 $baseUrl = $adminUrl;
191 }
192 }
193
194 $menuItems = [
195 'dashboard' => [
196 'key' => 'dashboard',
197 'label' => __('Dashboard', 'fluent-boards'),
198 'permalink' => $baseUrl,
199 ],
200 'boards' => [
201 'key' => 'boards',
202 'label' => __('Boards', 'fluent-boards'),
203 'permalink' => $baseUrl . 'boards'
204 ]
205 ];
206
207 $menuItems = apply_filters('fluent_boards/core_menu_items', $menuItems);
208
209 $menuItems['reports'] = [
210 'key' => 'reports',
211 'label' => __('Reports', 'fluent-boards'),
212 'permalink' => $baseUrl . 'reports'
213 ];
214
215 $isAdmin = PermissionManager::isAdmin();
216
217 if ($isAdmin) {
218 $menuItems['help'] = [
219 'key' => 'help',
220 'label' => __('Community', 'fluent-boards'),
221 'target' => '_blank',
222 'permalink' => 'https://community.wpmanageninja.com/portal/community/fluent-boards/home'
223 ];
224 }
225
226 if ($isDiffUrl) {
227 $menuItems['front'] = [
228 'key' => 'front',
229 'label' => __('Front Portal', 'fluent-boards'),
230 'target' => '_blank',
231 'permalink' => fluent_boards_page_url()
232 ];
233 }
234
235 $menuItems = apply_filters('fluent_boards/menu_items', $menuItems);
236
237 return array_values($menuItems);
238 }
239
240 public function enqueueAssets()
241 {
242 if (!PermissionManager::hasAppAccess()) {
243 return;
244 }
245
246 add_action('wp_print_scripts', function () {
247
248 $isSkip = apply_filters('fluent_boards/skip_no_conflict', false);
249
250 if ($isSkip) {
251 return;
252 }
253
254 global $wp_scripts;
255 if (!$wp_scripts) {
256 return;
257 }
258
259 $approvedSlugs = apply_filters('fluent_boards/asset_listed_slugs', [
260 '\/fluent-crm\/'
261 ]);
262
263 $approvedSlugs[] = '\/fluent-boards\/';
264
265 $approvedSlugs = array_unique($approvedSlugs);
266
267 $approvedSlugs = implode('|', $approvedSlugs);
268
269 $pluginUrl = plugins_url();
270
271 $pluginUrl = str_replace(['http:', 'https:'], '', $pluginUrl);
272
273 foreach ($wp_scripts->queue as $script) {
274 if (empty($wp_scripts->registered[$script]) || empty($wp_scripts->registered[$script]->src)) {
275 continue;
276 }
277
278 $src = $wp_scripts->registered[$script]->src;
279 $isMatched = (strpos($src, $pluginUrl) !== false) && !preg_match('/' . $approvedSlugs . '/', $src);
280 if (!$isMatched) {
281 continue;
282 }
283 wp_dequeue_script($wp_scripts->registered[$script]->handle);
284 }
285 });
286
287 if (function_exists('wp_enqueue_media')) {
288 // Editor default styles.
289 add_filter('user_can_richedit', '__return_true');
290 if (is_admin()) {
291 wp_tinymce_inline_scripts();
292 }
293 wp_enqueue_editor();
294 wp_enqueue_script('thickbox');
295 wp_enqueue_script('editor');
296 }
297 if (function_exists('wp_enqueue_media')) {
298 wp_enqueue_media();
299 }
300
301 $app = App::getInstance();
302
303 $slug = $app->config->get('app.slug');
304
305 // Inject Vite HMR client in dev mode
306 Vite::injectViteClient();
307
308 $isRtl = fluent_boards_is_rtl();
309 Vite::enqueueStyle($slug . '_admin_app', 'scss/admin.scss');
310
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 }
324
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(
343 $slug . '_admin_app',
344 'admin/app.js',
345 ['jquery'],
346 FLUENT_BOARDS_PLUGIN_VERSION,
347 true
348 );
349
350 Vite::enqueueStaticScript(
351 $slug . '_global_admin',
352 'admin/global_admin.js',
353 [],
354 FLUENT_BOARDS_PLUGIN_VERSION,
355 true
356 );
357 /*
358 * This script only for resolve the conflict of lodash and underscore js
359 * Resolved the issue of media uploader specially for image upload
360 */
361 wp_add_inline_script($slug . '_global_admin', $this->getInlineScript(), 'after');
362
363 wp_localize_script($slug . '_admin_app', 'fluentAddonVars', $this->getAddonVars($app));
364
365 do_action('fluent_boards/after_enqueue_assets', $app);
366 }
367
368 public function getAddonVars($app)
369 {
370 $currentUser = get_user_by('ID', get_current_user_id());
371 $assets = $app['url.assets'];
372 $roleAndPermissions = $this->getRoleAndPermissions($currentUser->ID);
373 $onboardingValue = $this->getOnboardingValue();
374 $generalSettings = fluent_boards_get_option('general_settings', []);
375
376 return apply_filters('fluent_boards/app_vars', [
377 'slug' => $slug = $app->config->get('app.slug'),
378 'nonce' => wp_create_nonce($slug),
379 'rest' => $this->getRestInfo($app),
380 'fluent_boards_file_upload_nonce' => wp_create_nonce('fluent_boards_file_upload_nonce'),
381 'ajaxurl' => admin_url('admin-ajax.php'),
382 'file_upload_limit' => $this->fileUploadLimit(),
383 'brand_logo' => $this->getMenuIcon(),
384 'business_name' => sanitize_text_field(Arr::get($generalSettings, 'business_name', '')),
385 'asset_url' => $assets,
386 'admin_url' => admin_url('admin.php'),
387 'fluent_crm_exists' => !defined('FLUENTCRM') ? 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,
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 'ai_features' => [
396 'enabled' => (new \FluentBoards\App\Services\AiService())->isReady(),
397 ],
398 'me' => [
399 'id' => $currentUser->ID,
400 'full_name' => trim($currentUser->first_name . ' ' . $currentUser->last_name),
401 'display_name' => $currentUser->display_name,
402 'email' => $currentUser->user_email,
403 'photo' => fluent_boards_user_avatar($currentUser->user_email, $currentUser->display_name),
404 'logout_url' => wp_logout_url(),
405 'fluent_boards_role' => $roleAndPermissions['role'],
406 'fluent_boards_capabilities' => $roleAndPermissions['permissions'],
407 'is_wp_admin' => user_can($currentUser->ID, 'manage_options') ? 'yes' : 'no',
408 'can_create_board' => PermissionManager::userHasBoardCreationPermission($currentUser->ID)
409 ],
410 'base_url' => fluent_boards_page_url(),
411 'site_url' => site_url('/'),
412 // 'server_time' => (new DateTime('now'))->format('Y-m-d H:i:s P'), // Server's default timezone
413 'server_time' => (new DateTime('now', wp_timezone()))->format('Y-m-d H:i:s P'), // wordpress site time
414 'server_time_zone' => (new DateTime('now', wp_timezone()))->format( 'P'),
415 'utc_offset' => current_time('timestamp') - strtotime(gmdate('Y-m-d H:i:s')),
416 'trans' => TransStrings::getStrings(),
417 'is_new' => Board::count() == 0 ? 'yes' : 'no',
418 'is_onboarded' => $onboardingValue,
419 'render_in' => is_admin() ? 'admin' : 'front',
420 'dashboard_notices' => apply_filters('fluent_boards/dashboard_notices', []),
421 'is_beta' => defined('FLUENT_BOARDS_PRO_VERSION') && !defined('FLUENT_BOARDS_PRO_LIVE'),
422 'advanced_modules' => fluent_boards_get_pref_settings(),
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() : [],
438 ]);
439 }
440
441 protected function canManageCrmContacts()
442 {
443 return defined('FLUENTCRM') && \FluentCrm\App\Services\PermissionManager::currentUserCan('fcrm_manage_contacts');
444 }
445
446 private function getOnboardingValue()
447 {
448 $onboarding = Meta::where('key', Constant::FBS_ONBOARDING)->first();
449 if ($onboarding) {
450 return $onboarding->value;
451 }
452 // if (Board::first()) {
453 // return 'yes';
454 // }
455 //
456 // return 'no';
457 }
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
470 /*
471 * TODO: This method should be moved to PermissionManager and Helper . Task for Masiur.
472 */
473 protected function getRoleAndPermissions($userId): array
474 {
475 $role = 'fluent_boards_admin';
476 $boardsWithPermissions = Relation::query()->where('foreign_id', $userId)
477 ->where('object_type', Constant::OBJECT_TYPE_BOARD_USER)
478 ->get();
479 $boardUserCollection = Collection::make($boardsWithPermissions);
480
481 if (PermissionManager::isFluentBoardsAdmin($userId)) {
482 $role = 'fluent_boards_admin';
483 } elseif (user_can($userId, 'manage_options')) {
484 $role = 'wordpress_admin';
485 } else {
486 $role = 'member';
487 }
488
489 $permissions = [];
490 foreach ($boardUserCollection as $boardWithPermission) {
491 $permissions[] = [
492 'board_id' => $boardWithPermission->object_id,
493 'role' => $this->boardUserRole($boardWithPermission),
494 'preferences' => $boardWithPermission->preferences,
495 'permissions' => [],
496 ];
497 }
498
499 return [
500 'role' => $role,
501 'permissions' => $permissions,
502 ];
503 }
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
510 protected function getRestInfo($app)
511 {
512 $ns = $app->config->get('app.rest_namespace');
513 $ver = $app->config->get('app.rest_version');
514
515 return [
516 'base_url' => esc_url_raw(rest_url()),
517 'url' => rest_url($ns . '/' . $ver),
518 'nonce' => wp_create_nonce('wp_rest'),
519 'namespace' => $ns,
520 'version' => $ver,
521 ];
522 }
523
524 protected function getMenuIcon()
525 {
526 /*
527 * Left Sidebar Menu Icon
528 */
529 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>');
530 }
531
532 public function changeFooter()
533 {
534 add_filter('admin_footer_text', function ($content) {
535 $url = '#';
536 return '';
537
538 // translators: %s is the URL for FluentBoards link
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>';
540 });
541
542 add_filter('update_footer', function ($text) {
543 return FLUENT_BOARDS_PLUGIN_VERSION;
544 });
545 }
546
547 /**
548 * Retrieves the Fluent Boards upload limit exposed to the admin app.
549 *
550 * @return int Upload limit in bytes.
551 */
552 public function fileUploadLimit()
553 {
554 return (new \FluentBoards\App\Services\UploadService())->getFileUploadLimit();
555 }
556
557 public function getInlineScript()
558 {
559 return "
560 function isLodash () {
561
562 let isLodash = false;
563
564 // If _ is defined and the function _.forEach exists then we know underscore OR lodash are in place
565 if ( 'undefined' != typeof( _ ) && 'function' == typeof( _.forEach ) ) {
566
567 // A small sample of some of the functions that exist in lodash but not underscore
568 const funcs = [ 'get', 'set', 'at', 'cloneDeep' ];
569
570 // Simplest if assume exists to start
571 isLodash = true;
572
573 funcs.forEach( function ( func ) {
574 // If just one of the functions do not exist, then not lodash
575 isLodash = ( 'function' != typeof( _[ func ] ) ) ? false : isLodash;
576 } );
577 }
578
579 if ( isLodash ) {
580 // We know that lodash is loaded in the _ variable
581 return true;
582 } else {
583 // We know that lodash is NOT loaded
584 return false;
585 }
586 };
587
588 if ( isLodash() ) {
589 _.noConflict();
590 }
591 ";
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
606 }
607