slug = Helper::getPortalSlug(); if ($this->slug !== '') { add_rewrite_rule('^' . $this->slug . '/?$', 'index.php?fcom_route=portal_home', 'top'); // For /hooks add_rewrite_rule('^' . $this->slug . '/(.+)/?', 'index.php?fcom_route=$matches[1]', 'top'); } }); add_filter('query_vars', function ($vars) { $vars[] = 'fcom_route'; return $vars; }); add_action('template_redirect', function () { $hook_path = get_query_var('fcom_route'); if ($hook_path) { $this->currentPath = $hook_path; $this->renderFullApp(); } if ($this->slug === '') { global $wp; $currentRequest = $wp->request; if ($hookPath = Helper::getPortalRequestPath($currentRequest)) { $this->currentPath = $hookPath; $this->renderFullApp(); } } }, 1); add_action('fluent_community/portal_html', function () { App::make('view')->render('portal.portal'); }); add_action('fluent_community/portal_header', function ($context) { $this->getPortalHeader(true, $context); }); add_action('fluent_community/portal_sidebar', function ($context) { $this->getPortalSidebar(true, $context); }); add_action('fluent_community/enqueue_global_assets', function ($useDefaultTheme = true) { $isRtl = Helper::isRtl(); wp_enqueue_style('fluent_community_global', Vite::getStaticSrcUrl('global.css', $isRtl), [], FLUENT_COMMUNITY_PLUGIN_VERSION, 'screen'); if ($useDefaultTheme) { wp_enqueue_style('fluent_community_default_theme', Vite::getStaticSrcUrl('theme-default.css', $isRtl), [], FLUENT_COMMUNITY_PLUGIN_VERSION, 'screen'); } $css = Utility::getColorCssVariables(); wp_add_inline_style('fluent_community_global', $css); wp_enqueue_script('portal_general', Vite::getStaticSrcUrl('portal_general.js'), [], FLUENT_COMMUNITY_PLUGIN_VERSION, [ 'in_footer' => true ]); wp_localize_script('portal_general', 'fcom_portal_general', $this->getGlobalScriptVars()); if (Utility::isCustomizationEnabled('enable_sidebar_toggle')) { add_action('fluent_community/before_portal_dom', [$this, 'renderSidebarCollapseFouc'], 1); } }); add_action('admin_bar_menu', function ($wp_admin_bar) { if (!\FluentCommunity\App\Services\Helper::canAccessPortal()) { return; } // Add custom link within site name dropdown $wp_admin_bar->add_node(array( 'id' => 'fluent-community-link', 'title' => __('Visit Community Portal', 'fluent-community'), 'href' => \FluentCommunity\App\Services\Helper::baseUrl('/'), 'parent' => 'site-name' )); }, 99); add_action('fluent_community/top_menu_right_items', [$this, 'renderTopMenuRightItems']); add_action('fluent_community/after_portal_sidebar', function () { if (did_action('fluent_community/after_header_right_menu_items')) { echo ''; }); add_action('fluent_community/portal_action_reactivate_account', function ($data) { $xprofile = Helper::getCurrentProfile(); if ($xprofile && $xprofile->status == '' && Utility::getPrivacySetting('can_deactive_account') === 'yes') { $nonce = Arr::get($data, '__nonce', ''); if (!wp_verify_nonce($nonce, 'fcom_reactivate_account')) { $this->viewErrorPage(__('Invalid request', 'fluent-community'), __('Security check failed. Please try again.', 'fluent-community')); } // we need to reactivate the account $xprofile->status = 'active'; $xprofile->save(); do_action('fluent_community/reactivate_account', $xprofile); delete_user_meta($xprofile->user_id, '_fcom_deactivated_at'); } // redirect to the portal home wp_safe_redirect(Helper::baseUrl('/')); exit; }); } public function renderTopMenuRightItems($context = 'headless') { $auth_url = $this->getAuthUrl(); $auth = Helper::getCurrentProfile(); $has_color_scheme = Helper::hasColorScheme(); $profileLinks = $this->getProfileLinks($auth); ?> first(); $xprofile = null; if ($userModel) { $xprofile = XProfile::where('user_id', get_current_user_id())->first(); if (!$xprofile) { $xprofile = $userModel->syncXProfile(); } } $authData = null; $spaceSlugs = []; $spaceGroups = []; if ($userModel && $xprofile) { $wpUser = get_user_by('ID', $userModel->ID); if ($userModel->isCommunityModerator()) { $userSpaces = Space::orderBy('serial', 'ASC')->get(); $spaceGroups = SpaceGroup::orderBy('serial', 'ASC') ->select(['id', 'title', 'slug', 'settings']) ->get(); } else { $userSpaces = Space::orderBy('serial', 'ASC') ->whereHas('members', function ($q) { $q->where('user_id', get_current_user_id()) ->where('status', 'active'); }) ->get(); $userSpaceParentIds = $userSpaces->pluck('parent_id')->filter()->unique()->toArray(); $spaceGroups = SpaceGroup::orderBy('serial', 'ASC') ->select(['id', 'title', 'slug', 'settings']) ->whereIn('id', $userSpaceParentIds) ->get(); } BaseSpace::preloadMemberships($userSpaces, $userModel->ID); $userSpaces->each(function ($space) use ($userModel) { $space = $space->formatSpaceData($userModel); do_action_ref_array('fluent_community/space', [&$space]); }); $userSpaces = $userSpaces->keyBy('slug'); $userModel->cacheAccessSpaces(); $spaceSlugs = $userSpaces->pluck('slug')->toArray(); $authData = [ 'id' => $xprofile->user_id, 'user_id' => $xprofile->user_id, 'username' => $xprofile->username, 'display_name' => $xprofile->display_name, 'first_name' => $wpUser->first_name ? $wpUser->first_name : $xprofile->getFirstName(), 'avatar' => $xprofile->avatar, 'total_points' => $xprofile->total_points, 'last_activity' => $xprofile->last_activity, 'email' => $userModel->user_email, 'spaces' => $userSpaces, 'community_roles' => $userModel->getCommunityRoles(), 'is_verified' => $xprofile->is_verified, 'compilation_score' => $xprofile->getCompletionScore(), 'meta' => $xprofile->meta ]; } else { $authData = [ 'id' => 0, 'user_id' => 0, 'spaces' => (object)[], 'community_roles' => [], 'compilation_score' => 100 ]; } $settings = Helper::generalSettings(); $onboardSettings = Helper::getOnboardingSettings(); global $wp_version; $isRtl = Helper::isRtl(); $isAbsoluteUrl = defined('FLUENT_COMMUNITY_PORTAL_SLUG') && FLUENT_COMMUNITY_PORTAL_SLUG == ''; $editorFrameUrl = site_url('?fluent_community_block_editor=1'); if (current_user_can('edit_posts')) { $editorFrameUrl = admin_url('edit.php?fluent_community_block_editor=1'); } $portalVars = apply_filters('fluent_community/portal_vars', [ 'portal_notices' => apply_filters('fluent_community/portal_notices', []), 'i18n' => TransStrings::getStrings(), 'auth' => $authData, 'ajaxurl' => admin_url('admin-ajax.php'), 'ajax_nonce' => wp_create_nonce('fluent_community_ajax_nonce'), 'slug' => 'fluent-community', 'rest' => $this->getRestInfo(), 'user_id' => $userModel ? $userModel->ID : null, 'assets_url' => FLUENT_COMMUNITY_PLUGIN_URL . 'assets/', 'permissions' => $userModel ? $userModel->getPermissions() : ['read' => true], 'logo' => Arr::get($settings, 'logo'), 'site_title' => Arr::get($settings, 'site_title'), 'access_level' => Arr::get($settings, 'access.acess_level'), 'user_membership_slugs' => $spaceSlugs, 'block_editor_assets' => [ 'scripts' => [ 'react' => Vite::getStaticSrcUrl('libs/isolated-editor/react.production.min.js'), 'react-dom' => Vite::getStaticSrcUrl( 'libs/isolated-editor/react-dom.production.min.js' ), 'isolated-block-editor' => Vite::getStaticSrcUrl('libs/isolated-editor/isolated-block-editor.min.js') ], 'styles' => [ $isRtl ? Vite::getStaticSrcUrl('libs/isolated-editor/core.rtl.css') : Vite::getStaticSrcUrl('libs/isolated-editor/core.css'), $isRtl ? Vite::getStaticSrcUrl('libs/isolated-editor/isolated-block-editor.rtl.css') : Vite::getStaticSrcUrl('libs/isolated-editor/isolated-block-editor.css'), includes_url('css/dist/block-editor/content.min.css?ver=' . $wp_version), ] ], 'features' => [ 'disable_global_posts' => Arr::get($settings, 'disable_global_posts', '') == 'yes', 'has_survey_poll' => true, 'is_onboarding_enabled' => Arr::get($onboardSettings, 'is_onboarding_enabled', 'no') == 'yes', 'can_switch_layout' => Utility::getCustomizationSetting('disable_feed_layout') !== 'yes', 'mention_mail' => Utility::hasEmailAnnouncementEnabled(), 'max_media_per_post' => apply_filters('fluent_community/max_media_per_post', Utility::getCustomizationSetting('max_media_per_post')), 'has_post_title' => Utility::postTitlePref(), 'has_course' => Helper::isFeatureEnabled('course_module'), 'followers_module' => Helper::isFeatureEnabled('followers_module'), 'skicky_sidebar' => Utility::isCustomizationEnabled('fixed_sidebar'), 'post_layout' => Utility::getCustomizationSetting('rich_post_layout'), 'member_list_layout' => Utility::getCustomizationSetting('member_list_layout'), 'default_feed_layout' => Utility::getCustomizationSetting('default_feed_layout'), 'disable_feed_sort_by' => Utility::getCustomizationSetting('disable_feed_sort_by'), 'default_feed_sort_by' => Utility::getCustomizationSetting('default_feed_sort_by'), 'video_embeder' => apply_filters('fluent_community/has_video_embeder', true), 'has_inline_image_upload' => apply_filters('fluent_community/has_inline_image_upload', 'yes'), 'has_topics' => !!Utility::getTopics(), 'show_post_modal' => Utility::isCustomizationEnabled('show_post_modal'), 'has_analytics' => Utility::hasAnalyticsEnabled(), 'can_deactivate_account' => Utility::getPrivacySetting('can_deactive_account') === 'yes', ], 'route_classes' => array_filter([ 'fcom_sticky_header' => Utility::isCustomizationEnabled('fixed_page_header'), 'fcom_sticky_sidebar' => Utility::isCustomizationEnabled('fixed_sidebar'), 'fcom_has_icon_on_header_menu' => Utility::isCustomizationEnabled('icon_on_header_menu') ]), 'urls' => [ 'site_url' => home_url(), 'portal_base' => Helper::baseUrl('/'), 'global_search' => Helper::baseUrl(), ], 'last_feed_id' => FeedsHelper::getLastFeedId(), 'unread_notification_count' => $userModel ? $userModel->getUnreadNotificationCount() : 0, 'unread_feed_ids' => $userModel ? $userModel->getUnreadNotificationFeedIds() : [], 'date_offset' => time() - current_time('timestamp'), 'date_formatter' => Helper::getDateFormatter(true), 'time_formatter' => Helper::getTimeFormatter(true), 'date_time_formatter' => Helper::getDateFormatter(true) . ' ' . Helper::getTimeFormatter(true), 'portal_slug' => Helper::getPortalSlug(true), 'socialLinkProviders' => ProfileHelper::socialLinkProviders(true), 'space_groups' => $spaceGroups, 'mobileMenuItems' => Helper::getMobileMenuItems(), 'feed_links' => Helper::getEnabledFeedLinks(), 'post_order_by_options' => Helper::getPostOrderOptions('feed'), 'comment_order_by_options' => Helper::getCommentOrderOptions('comment'), 'user_post_order_by_options' => Helper::getPostOrderOptions('user'), 'routing_system' => Helper::getPortalRouteType(), 'portal_url' => Helper::baseUrl('/'), 'upgrade_url' => Utility::getProUpgradeUrl('upgrade_page'), 'dateTime18n' => apply_filters('fluent_community/date_time_i18n', [ /* translators: weekday. Please keep the serial and format */ 'weekdays' => __('Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday', 'fluent-community'), /* translators: Months Please keep the serial and format*/ 'months' => __('January_February_March_April_May_June_July_August_September_October_November_December', 'fluent-community'), /* translators: weekday short Please keep the serial and format*/ 'weekdaysShort' => __('Sun_Mon_Tue_Wed_Thu_Fri_Sat', 'fluent-community'), /* translators: Months short Please keep the serial and format*/ 'monthsShort' => __('Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec', 'fluent-community'), /* translators: weekday min Please keep the serial and format*/ 'weekdaysMin' => __('Su_Mo_Tu_We_Th_Fr_Sa', 'fluent-community'), 'relativeTime' => [ /* translators: Relative Date Formats. Please do not alter %s*/ 'future' => __('in %s', 'fluent-community'), /* translators: Relative Date Formats. Please do not alter %s*/ 'past' => __('%s ago', 'fluent-community'), /* translators: Relative Date Formats.*/ 's' => __('a few seconds', 'fluent-community'), /* translators: Relative Date Formats.*/ 'm' => __('a minute', 'fluent-community'), /* translators: Relative Date Formats. Don't alter %d*/ 'mm' => __('%d minutes', 'fluent-community'), /* translators: Relative Date Formats*/ 'h' => __('an hour', 'fluent-community'), /* translators: Relative Date Formats. Don't alter %d*/ 'hh' => __('%d hours', 'fluent-community'), /* translators: Relative Date Formats*/ 'd' => __('a day', 'fluent-community'), /* translators: Relative Date Formats. Don't alter %d*/ 'dd' => __('%d days', 'fluent-community'), /* translators: Relative Date Formats*/ 'M' => __('a month', 'fluent-community'), /* translators: Relative Date Formats. Don't alter %d*/ 'MM' => __('%d months', 'fluent-community'), /* translators: Relative Date Formats*/ 'y' => __('a year', 'fluent-community'), /* translators: Relative Date Formats. Don't alter %d*/ 'yy' => __('%d years', 'fluent-community') ], 'relativeTimeMobile' => [ /* translators: Relative Date Formats. Please do not alter %s*/ 'future' => __('in %s', 'fluent-community'), /* translators: Relative Date Formats. Please do not alter %s*/ 'past' => __('%s ago', 'fluent-community'), /* translators: Relative Date Formats.*/ 's' => __('few sec', 'fluent-community'), /* translators: Relative Date Formats.*/ 'm' => __('1min', 'fluent-community'), /* translators: Relative Date Formats. Don't alter %d*/ 'mm' => __('%dmin', 'fluent-community'), /* translators: Relative Date Formats*/ 'h' => __('1h', 'fluent-community'), /* translators: Relative Date Formats. Don't alter %d*/ 'hh' => __('%dh', 'fluent-community'), /* translators: Relative Date Formats*/ 'd' => __('1d', 'fluent-community'), /* translators: Relative Date Formats. Don't alter %d*/ 'dd' => __('%dd', 'fluent-community'), /* translators: Relative Date Formats*/ 'M' => __('1mo', 'fluent-community'), /* translators: Relative Date Formats. Don't alter %d*/ 'MM' => __('%dmo', 'fluent-community'), /* translators: Relative Date Formats*/ 'y' => __('1y', 'fluent-community'), /* translators: Relative Date Formats. Don't alter %d*/ 'yy' => __('%dy', 'fluent-community') ] ]), 'topicsConfig' => Helper::getTopicsConfig(), 'moderationConfig' => Helper::getModerationConfig(), 'is_absolute_url' => $isAbsoluteUrl, 'portal_paths' => $isAbsoluteUrl ? Helper::portalRoutePaths() : [], 'suggestedColors' => Utility::getSuggestedColors(), 'view_leaderboard_members' => Utility::canViewLeaderboardMembers(), 'report_reasons' => Helper::getReportReasons(), 'el_i18n' => [ 'pagination' => [ /* translators: %s is replaced by the page number */ 'currentPage' => \sprintf(__('page %s', 'fluent-community'), '{pager}'), 'deprecationWarning' => 'Deprecated usages detected', 'goto' => __('Go to', 'fluent-community'), 'next' => __('Go to next page', 'fluent-community'), /* translators: %s is replaced by the number of pages */ 'nextPages' => \sprintf(__('Next %s pages', 'fluent-community'), ' {pager}'), 'page' => __('Page', 'fluent-community'), 'pageClassifier' => '', 'pagesize' => __('/page', 'fluent-community'), 'prev' => __('Go to previous page', 'fluent-community'), /* translators: %s is replaced by the number of pages */ 'prevPages' => \sprintf(__('Previous %s pages', 'fluent-community'), '{pager}'), /* translators: %s is replaced by the total number of items */ 'total' => \sprintf(__('Total %s', 'fluent-community'), '{total}'), ], 'table' => [ 'clearFilter' => __('All', 'fluent-community'), 'confirmFilter' => __('Confirm', 'fluent-community'), 'emptyText' => __('No Data', 'fluent-community'), 'resetFilter' => __('Reset', 'fluent-community'), 'sumText' => __('Sum', 'fluent-community'), ], 'image' => [ 'error' => __('Failed to Load', 'fluent-community'), ], 'upload' => [ 'continue' => __('Continue', 'fluent-community'), 'delete' => __('Delete', 'fluent-community'), 'deleteTip' => __('press delete to remove', 'fluent-community'), 'preview' => __('Preview', 'fluent-community'), ], 'select' => [ 'loading' => __('Loading', 'fluent-community'), 'noData' => __('No data', 'fluent-community'), 'noMatch' => __('No matching data', 'fluent-community'), 'placeholder' => __('Select', 'fluent-community'), ], 'datepicker' => [ 'now' => __('Now', 'fluent-community'), 'today' => __('Today', 'fluent-community'), 'cancel' => __('Cancel', 'fluent-community'), 'clear' => __('Clear', 'fluent-community'), 'confirm' => __('OK', 'fluent-community'), 'dateTablePrompt' => __('Use the arrow keys and enter to select the day of the month', 'fluent-community'), 'monthTablePrompt' => __('Use the arrow keys and enter to select the month', 'fluent-community'), 'yearTablePrompt' => __('Use the arrow keys and enter to select the year', 'fluent-community'), 'selectedDate' => __('Selected date', 'fluent-community'), 'selectDate' => __('Select date', 'fluent-community'), 'selectTime' => __('Select time', 'fluent-community'), 'startDate' => __('Start Date', 'fluent-community'), 'startTime' => __('Start Time', 'fluent-community'), 'endDate' => __('End Date', 'fluent-community'), 'endTime' => __('End Time', 'fluent-community'), 'prevYear' => __('Previous Year', 'fluent-community'), 'nextYear' => __('Next Year', 'fluent-community'), 'prevMonth' => __('Previous Month', 'fluent-community'), 'nextMonth' => __('Next Month', 'fluent-community'), 'year' => __('year', 'fluent-community'), 'month1' => __('January', 'fluent-community'), 'month2' => __('February', 'fluent-community'), 'month3' => __('March', 'fluent-community'), 'month4' => __('April', 'fluent-community'), 'month5' => __('May', 'fluent-community'), 'month6' => __('June', 'fluent-community'), 'month7' => __('July', 'fluent-community'), 'month8' => __('August', 'fluent-community'), 'month9' => __('September', 'fluent-community'), 'month10' => __('October', 'fluent-community'), 'month11' => __('November', 'fluent-community'), 'month12' => __('December', 'fluent-community'), 'week' => __('week', 'fluent-community'), 'weeks' => [ 'sun' => __('Sun', 'fluent-community'), 'mon' => __('Mon', 'fluent-community'), 'tue' => __('Tue', 'fluent-community'), 'wed' => __('Wed', 'fluent-community'), 'thu' => __('Thu', 'fluent-community'), 'fri' => __('Fri', 'fluent-community'), 'sat' => __('Sat', 'fluent-community'), ], 'weeksFull' => [ 'sun' => __('Sunday', 'fluent-community'), 'mon' => __('Monday', 'fluent-community'), 'tue' => __('Tuesday', 'fluent-community'), 'wed' => __('Wednesday', 'fluent-community'), 'thu' => __('Thursday', 'fluent-community'), 'fri' => __('Friday', 'fluent-community'), 'sat' => __('Saturday', 'fluent-community'), ], 'months' => [ 'jan' => __('Jan', 'fluent-community'), 'feb' => __('Feb', 'fluent-community'), 'mar' => __('Mar', 'fluent-community'), 'apr' => __('Apr', 'fluent-community'), 'may' => __('May', 'fluent-community'), 'jun' => __('Jun', 'fluent-community'), 'jul' => __('Jul', 'fluent-community'), 'aug' => __('Aug', 'fluent-community'), 'sep' => __('Sep', 'fluent-community'), 'oct' => __('Oct', 'fluent-community'), 'nov' => __('Nov', 'fluent-community'), 'dec' => __('Dec', 'fluent-community'), ], ] ], 'course_sections_collapsed' => apply_filters('fluent_community/course_section_collapse_default', 'no'), 'course_lesson_fullscreen' => apply_filters('fluent_community/course_lesson_fullscreen_default', 'no'), 'default_profile_tab' => apply_filters('fluent_community/default_profile_tab_route', ''), 'default_post_space' => apply_filters('fluent_community/create_post_default_space', ''), 'wp_lesson_editor_frame' => $editorFrameUrl, 'has_fluentcrm' => defined('FLUENTCRM'), 'lazy_styles' => [ 'wp-block-library-css' => includes_url('css/dist/block-library/style.min.css?version=' . $wp_version), 'fcom-block-content-styling-css' => FLUENT_COMMUNITY_PLUGIN_URL . 'Modules/Gutenberg/editor/content_styling.css?version=' . FLUENT_COMMUNITY_PLUGIN_VERSION ], 'debug_mode' => 1 ]); $portalVars['welcome_banner'] = Helper::getWelcomeBanner($userModel ? 'login' : 'logout'); if (!$xprofile) { $portalVars['auth_url'] = $this->getAuthUrl(); $portalVars['allow_signup'] = !!AuthHelper::isRegistrationEnabled(); } return $portalVars; } public function renderSidebarCollapseFouc() { ?> $scope, 'theme' => get_option('template'), 'default_color' => 'light', 'color_switch_cookie_name' => '', 'has_color_scheme' => Helper::hasColorScheme(), 'default_theme_mode' => Helper::getDefaultThemeMode(), 'collapse_sidebar_groups' => Utility::isCustomizationEnabled('collapse_sidebar_groups'), 'hide_header_on_scroll' => Utility::isCustomizationEnabled('hide_header_on_scroll'), 'enable_sidebar_toggle' => Utility::isCustomizationEnabled('enable_sidebar_toggle'), 'sidebar_toggle_label' => __('Toggle sidebar', 'fluent-community'), ]); } protected function getRestInfo() { $app = fluentCommunityApp(); $ns = $app->config->get('app.rest_namespace'); $v = $app->config->get('app.rest_version'); return [ 'base_url' => esc_url_raw(rest_url()), 'url' => rest_url($ns . '/' . $v), 'nonce' => wp_create_nonce('wp_rest'), 'namespace' => $ns, 'version' => $v, ]; } protected function renderFullApp() { do_action('litespeed_control_set_nocache', 'fluentcommunity api request'); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound // set no cache headers nocache_headers(); if (isset($_REQUEST['fcom_action'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $action = sanitize_text_field(wp_unslash($_REQUEST['fcom_action'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended do_action('fluent_community/portal_action_' . $action, $_GET); // phpcs:ignore WordPress.Security.NonceVerification.Recommended } $pathParts = explode('/', $this->currentPath); do_action('fluent_community/rendering_path_ssr_' . $pathParts[0], $pathParts); $userId = get_current_user_id(); if (!$userId && !Helper::isPublicAccessible()) { $adminAuthUrl = Arr::get(Helper::generalSettings(), 'auth_url', ''); $authPath = wp_parse_url($adminAuthUrl, PHP_URL_PATH) ?: ''; $portalPath = wp_parse_url(Helper::baseUrl(), PHP_URL_PATH) ?: ''; // Drop admin's URL if it points back into the portal (would infinite loop) $isPortalLoop = $authPath && $portalPath && strpos(trim($authPath, '/'), trim($portalPath, '/')) === 0; if ($adminAuthUrl && !$isPortalLoop) { $authUrl = $adminAuthUrl; } else { $adminAuthUrl = ''; $parsedUrl = wp_parse_url(home_url(add_query_arg($_REQUEST, $GLOBALS['wp']->request))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $redirectPath = ($parsedUrl['path'] ?? '/') . (!empty($parsedUrl['query']) ? '?' . $parsedUrl['query'] : ''); $internalUrl = $this->getAuthUrl(); $authUrl = $internalUrl ? add_query_arg(['redirect_to' => $redirectPath], $internalUrl) : wp_login_url($redirectPath); } do_action('fluent_community/portal/not_logged_in', $authUrl); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- intentional external admin auth redirect, followed by exit() $adminAuthUrl ? wp_redirect($authUrl) : wp_safe_redirect($authUrl); exit(); } do_action('fluent_community/portal/viewed'); $xprofile = Helper::getCurrentProfile(); if ($xprofile && $xprofile->status != 'active') { if ($xprofile->status == 'pending') { $this->viewErrorPage(__('Your request is pending', 'fluent-community'), __('An admin needs to approve your join request. Please contact an admin to get approval', 'fluent-community')); } else if ($xprofile->status == '') { // it's a self deactiavted account $this->viewErrorPage(__('Your account is deactivated', 'fluent-community'), __('You have deactivated your community profile.', 'fluent-community'), true, [ 'btn_url' => Helper::baseUrl('?fcom_action=reactivate_account&__nonce=' . wp_create_nonce('fcom_reactivate_account')), 'btn_text' => __('Activate my account', 'fluent-community') ]); } else { $this->viewErrorPage(__('Access denied', 'fluent-community'), __('Sorry, you cannot access this community', 'fluent-community')); } } // Gate by role before render: appVars() creates the profile lazily, so checking it // afterward let an unauthorized user see the portal once (and get a profile) before // being denied. Pass false to authorize by role without requiring a profile yet. if (!Helper::canAccessPortal($userId, false)) { $generalSettings = Helper::generalSettings(); $this->viewErrorPage(__('Access Denied', 'fluent-community'), Arr::get($generalSettings, 'restricted_role_content')); } if ($xprofile) { if ($xprofile->user) { $xprofile->user->cacheAccessSpaces(); } /* * Just in case if username is not sanitized */ if ($xprofile->username != CustomSanitizer::sanitizeUserName($xprofile->username)) { $xprofile->username = ProfileHelper::generateUserName($xprofile->user_id); $xprofile->save(); } do_action('fluent_community/portal_render_for_user', $xprofile); } $data = $this->getAppData(); $data = $this->maybePushDynamicMetaData($data); $data['isHeadless'] = Helper::isHeadless(); if (!$data['isHeadless']) { $this->loadClassicPortalAssets($data); } else { do_action('fluent_community/rendering_headless_portal', $data); } $data['html_class'] = ''; if (Utility::isCustomizationEnabled('enable_sidebar_toggle') && isset($_COOKIE['fcom_sidebar_collapsed']) && $_COOKIE['fcom_sidebar_collapsed'] === 'true') { $data['html_class'] = 'fcom_sidebar_collapsed_desktop'; } do_action('fluent_community/before_portal_rendered', $data); status_header(200); App::make('view')->render('portal_page', $data); exit(); } public function loadClassicPortalAssets($data) { $cssFiles = $data['css_files']; unset($cssFiles['fcom_theme_default']); foreach ($cssFiles as $fileName => $cssFile) { wp_enqueue_style($fileName, $cssFile['url'], [], FLUENT_COMMUNITY_PLUGIN_VERSION, 'screen'); } $jsFiles = $data['js_files']; $jsTags = array_keys($jsFiles); add_filter('script_loader_tag', function ($tag, $handle) use ($jsTags) { if (!in_array($handle, $jsTags)) { return $tag; } $tag = str_replace(' src', ' type="module" src', $tag); return $tag; }, 10, 2); foreach ($jsFiles as $fileName => $jsFile) { wp_enqueue_script($fileName, $jsFile['url'], $jsFile['deps'], FLUENT_COMMUNITY_PLUGIN_VERSION, [ 'in_footer' => true, 'strategy' => 'defer', 'type' => 'module' ]); } foreach ($data['js_vars'] as $varKey => $values) { wp_localize_script('fcom_start', $varKey, $values); } } public function getAppData() { $isRtl = Helper::isRtl(); $userId = get_current_user_id(); $url = home_url(add_query_arg($_REQUEST, $GLOBALS['wp']->request)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended $appVars = $this->appVars(); $generalSettings = Helper::generalSettings(); $isDev = Utility::isDev(); $siteTitle = Arr::get($generalSettings, 'site_title'); $dataVars = [ 'title' => $siteTitle, 'og_title' => $siteTitle, 'description' => get_bloginfo('description'), 'featured_image' => Arr::get($generalSettings, 'featured_image', ''), 'header_js_files' => [], 'js_vars' => [ 'fluentComAdmin' => $appVars, 'fcom_portal_general' => $this->getGlobalScriptVars('portal'), ], 'url' => $url, 'current_route' => $this->currentPath, 'contact' => $appVars['auth'], 'user' => $userId ? get_user_by('ID', $userId) : null, 'route_group' => Helper::getRouteNameByRequestPath($this->currentPath), 'is_dev' => $isDev, 'theme_color' => Utility::getThemeColor() ]; if ($isDev) { $dataVars['css_files'] = [ 'fcom_theme_default' => [ 'url' => Vite::getDynamicSrcUrl('theme-default.scss') ], 'fcom_global' => [ 'url' => Vite::getDynamicSrcUrl('global.scss') ], 'fcom_vendor' => [ 'url' => Vite::getDynamicSrcUrl('app_css.scss') ] ]; $dataVars['js_files'] = [ 'fcom_general' => [ 'url' => Vite::getDynamicSrcUrl('portal_general.js'), 'deps' => [] ], 'fcom_start' => [ 'url' => Vite::getDynamicSrcUrl('start.js'), 'deps' => [] ], 'fcom_app' => [ 'url' => Vite::getDynamicSrcUrl('app.js'), 'deps' => [] ] ]; } else { $dataVars['js_files'] = [ 'fcom_general' => [ 'url' => Vite::getStaticSrcUrl('portal_general.js'), 'deps' => [] ], 'fcom_start' => [ 'url' => Vite::getStaticSrcUrl('start.js'), 'deps' => [] ], 'fcom_app' => [ 'url' => Vite::getStaticSrcUrl('app.js'), 'deps' => [] ] ]; $dataVars['css_files'] = [ 'fcom_theme_default' => [ 'url' => Vite::getStaticSrcUrl('theme-default.css', $isRtl) ], 'fcom_global' => [ 'url' => Vite::getStaticSrcUrl('global.css', $isRtl) ], 'fcom_app_css' => [ 'url' => Vite::getStaticSrcUrl('app.css', $isRtl) ], 'fcom_vendor' => [ 'url' => Vite::getStaticSrcUrl('app_css.css', $isRtl) ], ]; } return apply_filters('fluent_community/portal_data_vars', $dataVars); } protected function maybePushDynamicMetaData($data) { $dynamicRoute = Arr::get($data, 'route_group'); $data['landing_route'] = $dynamicRoute; $validGroups = [ 'user_profile', 'community_view', 'course_view', 'feed_view', 'lesson_view' ]; if (!in_array($dynamicRoute, $validGroups)) { return $data; } if ($dynamicRoute == 'user_profile') { $uriParts = explode('/', $this->currentPath); if (count($uriParts) >= 2) { $userName = $uriParts[1]; $xProfile = XProfile::where('username', $userName)->first(); if ($xProfile && $xProfile->status === 'active' && Utility::canViewUserProfile($xProfile->user_id)) { $data['title'] = 'User ' . esc_html($xProfile->display_name) . ' - ' . $data['title']; $data['og_title'] = esc_html($xProfile->display_name) . ' - ' . $data['og_title']; if ($xProfile->short_description) { $data['description'] = Helper::getHumanExcerpt($xProfile->short_description, 100); } if ($coverPhoto = Arr::get($xProfile->meta, 'cover_photo')) { $data['featured_image'] = $coverPhoto; } } } return $data; } if ($dynamicRoute == 'community_view' || $dynamicRoute == 'course_view') { $uriParts = explode('/', $this->currentPath); if (count($uriParts) >= 2) { $paceSlug = $uriParts[1]; $space = BaseSpace::query()->onlyMain()->where('slug', $paceSlug)->first(); if ($space && $space->privacy != 'secret') { // matches the URL the sitemap submits, so tab variants consolidate onto it $data['canonical_url'] = $space->getPermalink(); if ($dynamicRoute == 'course_view') { /* translators: %s is replaced by the title of the space */ $data['title'] = sprintf(__('Enroll %s', 'fluent-community'), esc_html($space->title) . ' - ' . $data['title']); /* translators: %s is replaced by the title of the space */ $data['og_title'] = sprintf(__('Enroll %s', 'fluent-community'), esc_html($space->title)); } else { /* translators: %s is replaced by the title of the space */ $data['title'] = sprintf(__('Join %s', 'fluent-community'), esc_html($space->title) . ' - ' . $data['title']); /* translators: %s is replaced by the title of the space */ $data['og_title'] = sprintf(__('Join %s', 'fluent-community'), esc_html($space->title)); } if ($space->description) { $data['description'] = Helper::getHumanExcerpt($space->description, 100); } if ($ogImage = Arr::get($space->settings, 'og_image')) { $data['featured_image'] = $ogImage; } else if ($space->cover_photo) { $data['featured_image'] = $space->cover_photo; } if ($dynamicRoute == 'course_view') { $data['json_ld'] = apply_filters('fluent_community/course_view_json_ld', [], $space, $data); } } } return $data; } if ($dynamicRoute == 'feed_view') { $uriParts = explode('/', $this->currentPath); if (count($uriParts) >= 2) { $postSlug = end($uriParts); // Keeps the type scope, so course lessons sharing fcom_posts are not // described here. $feed = Feed::query()->where('slug', $postSlug) ->where('status', 'published') ->with([ 'xprofile' => function ($q) { $q->select(ProfileHelper::getXProfilePublicFields()); }, 'space' ]) ->whereDoesntHave('space', function ($q) { $q->whereIn('privacy', ['secret', 'private']); }) ->first(); if (!$feed) { return $data; } if ($feed->title) { $data['title'] = esc_html($feed->title) . ' - ' . $data['title']; $data['og_title'] = esc_html($feed->title); } else { $authorName = esc_html($feed->xprofile ? $feed->xprofile->display_name : 'Someone'); $data['title'] = $authorName . ' posted at ' . $data['title']; $data['og_title'] = $authorName . ' posted at ' . $data['og_title']; } $feedUrl = $feed->getPermalink(); $data['canonical_url'] = $feedUrl; if ($feed->message) { $data['description'] = esc_html(Helper::getHumanExcerpt($feed->message, 160)); } if ($mediaPreview = Arr::get($feed->meta, 'media_preview.image')) { $data['featured_image'] = esc_url($mediaPreview); } else if ($firstImage = Arr::get($feed->meta, 'media_items.0.url')) { $data['featured_image'] = esc_url($firstImage); } else if ($feed->space) { $space = $feed->space; if ($ogImage = Arr::get($space->settings, 'og_image')) { $data['featured_image'] = $ogImage; } else if ($space->cover_photo) { $data['featured_image'] = $space->cover_photo; } } $data['json_ld'] = apply_filters('fluent_community/feed_view_json_ld', [], $feed, $data); } return $data; } if ($dynamicRoute == 'lesson_view') { $uriParts = explode('/', $this->currentPath); $lessonSlug = Arr::get($uriParts, 3); $courseSlug = Arr::get($uriParts, 1); if (!$lessonSlug || !$courseSlug) { return $data; } $lesson = CourseLesson::query()->where('slug', $lessonSlug)->where('status', 'published')->first(); if (!$lesson) { return $data; } $course = BaseSpace::query()->onlyMain() ->where('id', $lesson->space_id) ->where('slug', $courseSlug) ->where('status', 'published') ->where('privacy', 'public') ->first(); if (!$course) { return $data; } $data['title'] = esc_html($lesson->title) . ' - ' . $data['title']; $data['og_title'] = esc_html($lesson->title); $data['description'] = esc_html(Helper::getHumanExcerpt($lesson->message, 120)); $data['canonical_url'] = $lesson->getPermalink(); return $data; } return $data; } protected function getMainMenuItems($scope = 'sidebar') { $menuGroups = Helper::getMenuItemsGroup('view'); $items = Arr::get($menuGroups, 'mainMenuItems', []); return apply_filters('fluent_community/main_menu_items', $items, $scope); } public function getPortalHeader($echo = false, $context = 'headless') { $settings = Helper::generalSettings(); $userId = get_current_user_id(); $xprofile = null; if ($userId && Helper::canAccessPortal($userId, false)) { $userModel = Helper::getCurrentUser(); if ($userModel) { $xprofile = $userModel->syncXProfile(false); } } $authUrl = $this->getAuthUrl(); if ($xprofile) { $xprofile->logout_url = wp_logout_url(Helper::baseUrl()); } $logo = Arr::get($settings, 'logo', ''); $whiteLogo = Arr::get($settings, 'white_logo', ''); if (!$whiteLogo) { $whiteLogo = $logo; } $logoPermalink = Helper::baseUrl('/'); $logoPermalinkType = Arr::get($settings, 'logo_permalink_type'); if ($logoPermalinkType == 'custom') { $logoPermalink = Arr::get($settings, 'logo_permalink'); } $data = apply_filters('fluent_community/header_vars', [ 'portal_url' => Helper::baseUrl('/'), 'logo' => $logo, 'white_logo' => $whiteLogo, 'logo_permalink' => $logoPermalink, 'site_title' => Arr::get($settings, 'site_title'), 'profile_url' => $xprofile ? Helper::baseUrl('u/' . $xprofile->username . '/') : '', 'auth' => $xprofile ? $xprofile : null, 'auth_url' => $authUrl, 'menuItems' => $this->getMainMenuItems('header'), 'context' => $context ]); if ($echo) { App::make('view')->render('portal.header', $data); return; } return (string)App::make('view')->make('portal.header', $data); } private function getProfileLinks($xprofile = null) { if (!$xprofile) { $xprofile = Helper::getCurrentProfile(); } $profileLinks = []; $menuGroups = Helper::getMenuItemsGroup('view'); if ($xprofile) { $profileLinks = Arr::get($menuGroups, 'profileDropdownItems', []); $replaces = [ '#{{user_url}}' => Helper::baseUrl('u/' . $xprofile->username), '#user_url' => Helper::baseUrl('u/' . $xprofile->username), '#{{logout_url}}' => wp_logout_url(Helper::baseUrl()), '#logout_url' => wp_logout_url(Helper::baseUrl()) ]; foreach ($profileLinks as &$link) { $slug = Arr::get($link, 'slug', ''); if ($slug == 'my_spaces') { $link['permalink'] = Helper::baseUrl('u/' . $xprofile->username . '/spaces'); } else if ($slug == 'logout') { $link['permalink'] = wp_logout_url(Helper::baseUrl()); } else if ($slug == 'profile') { $link['permalink'] = Helper::baseUrl('u/' . $xprofile->username); } $link['permalink'] = str_replace(array_keys($replaces), array_values($replaces), $link['permalink']); } } return $profileLinks; } public function getPortalSidebar($echo = false, $context = 'headless') { $data = Utility::getPortalSidebarData('sidebar'); $data['context'] = $context; if ($echo) { App::make('view')->render('portal.main_sidebar', $data); return; } return (string)App::make('view')->make('portal.main_sidebar', $data); } protected function getAuthUrl() { return apply_filters('fluent_community/auth/login_url', Helper::getAuthUrl()); } public function viewErrorPage($title, $message = '', $showBtn = true, $btnData = []) { $data = [ 'title' => $title, 'description' => $message, 'btn_txt' => Arr::get($btnData, 'btn_text', __('Go to home page', 'fluent-community')), 'url' => Arr::get($btnData, 'btn_url', home_url()) ]; if (!$showBtn) { $data['btn_txt'] = ''; } status_header(200); App::make('view')->render('error_page', $data); exit(); } }