PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.9.1
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.9.1
2.11.0 2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 All 78 releases
fluent-community / app / Hooks / Handlers / PortalHandler.php

PortalHandler.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 2.9.1, at app/Hooks/Handlers/PortalHandler.php

1,335 lines 71.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCommunity\App\Hooks\Handlers;
4
5 use FluentCommunity\App\App;
6 use FluentCommunity\App\Functions\Utility;
7 use FluentCommunity\App\Models\Space;
8 use FluentCommunity\App\Models\Feed;
9 use FluentCommunity\App\Models\BaseSpace;
10 use FluentCommunity\App\Models\SpaceGroup;
11 use FluentCommunity\App\Models\User;
12 use FluentCommunity\App\Models\XProfile;
13 use FluentCommunity\App\Services\CustomSanitizer;
14 use FluentCommunity\App\Services\Helper;
15 use FluentCommunity\App\Services\ProfileHelper;
16 use FluentCommunity\App\Services\TransStrings;
17 use FluentCommunity\App\Vite;
18 use FluentCommunity\App\Models\NotificationSubscriber;
19 use FluentCommunity\Framework\Support\Arr;
20 use FluentCommunity\App\Services\FeedsHelper;
21 use FluentCommunity\Modules\Auth\AuthHelper;
22 use FluentCommunity\Modules\Course\Model\CourseLesson;
23
24 class PortalHandler
25 {
26 protected $currentPath = '';
27
28 protected $slug = null;
29
30 public function register()
31 {
32 /**
33 * Register the portal route
34 */
35 add_action('init', function () {
36 $this->slug = Helper::getPortalSlug();
37 if ($this->slug !== '') {
38 add_rewrite_rule('^' . $this->slug . '/?$', 'index.php?fcom_route=portal_home', 'top'); // For /hooks
39 add_rewrite_rule('^' . $this->slug . '/(.+)/?', 'index.php?fcom_route=$matches[1]', 'top');
40 }
41 });
42
43 add_filter('query_vars', function ($vars) {
44 $vars[] = 'fcom_route';
45 return $vars;
46 });
47
48 add_action('template_redirect', function () {
49 $hook_path = get_query_var('fcom_route');
50
51 if ($hook_path) {
52 $this->currentPath = $hook_path;
53 $this->renderFullApp();
54 }
55
56 if ($this->slug === '') {
57 global $wp;
58 $currentRequest = $wp->request;
59 if ($hookPath = Helper::getPortalRequestPath($currentRequest)) {
60 $this->currentPath = $hookPath;
61 $this->renderFullApp();
62 }
63 }
64 }, 1);
65
66 add_action('fluent_community/portal_html', function () {
67 App::make('view')->render('portal.portal');
68 });
69
70 add_action('fluent_community/portal_header', function ($context) {
71 $this->getPortalHeader(true, $context);
72 });
73
74 add_action('fluent_community/portal_sidebar', function ($context) {
75 $this->getPortalSidebar(true, $context);
76 });
77
78 add_action('fluent_community/enqueue_global_assets', function ($useDefaultTheme = true) {
79 $isRtl = Helper::isRtl();
80
81 wp_enqueue_style('fluent_community_global', Vite::getStaticSrcUrl('global.css', $isRtl), [], FLUENT_COMMUNITY_PLUGIN_VERSION, 'screen');
82
83 if ($useDefaultTheme) {
84 wp_enqueue_style('fluent_community_default_theme', Vite::getStaticSrcUrl('theme-default.css', $isRtl), [], FLUENT_COMMUNITY_PLUGIN_VERSION, 'screen');
85 }
86 $css = Utility::getColorCssVariables();
87 wp_add_inline_style('fluent_community_global', $css);
88 wp_enqueue_script('portal_general', Vite::getStaticSrcUrl('portal_general.js'), [], FLUENT_COMMUNITY_PLUGIN_VERSION, [
89 'in_footer' => true
90 ]);
91 wp_localize_script('portal_general', 'fcom_portal_general', $this->getGlobalScriptVars());
92
93 if (Utility::isCustomizationEnabled('enable_sidebar_toggle')) {
94 add_action('fluent_community/before_portal_dom', [$this, 'renderSidebarCollapseFouc'], 1);
95 }
96 });
97
98 add_action('admin_bar_menu', function ($wp_admin_bar) {
99 if (!\FluentCommunity\App\Services\Helper::canAccessPortal()) {
100 return;
101 }
102
103 // Add custom link within site name dropdown
104 $wp_admin_bar->add_node(array(
105 'id' => 'fluent-community-link',
106 'title' => __('Visit Community Portal', 'fluent-community'),
107 'href' => \FluentCommunity\App\Services\Helper::baseUrl('/'),
108 'parent' => 'site-name'
109 ));
110 }, 99);
111
112 add_action('fluent_community/top_menu_right_items', [$this, 'renderTopMenuRightItems']);
113
114 add_action('fluent_community/after_portal_sidebar', function () {
115
116 if (did_action('fluent_community/after_header_right_menu_items')) {
117 echo '<div role="region" aria-label="Portal Settings" class="fcom_side_footer">';
118 if (Helper::isSiteAdmin()) {
119 ?>
120 <div style="display: flex;justify-content: space-between;" class="fcom_admin_menu">
121 <?php if (!defined('FLUENT_COMMUNITY_PRO')): ?>
122 <a title="Upgrade to Pro" target="_blank" rel="noopener" class="fcom_inline_icon_link_item"
123 href="<?php echo esc_url(Utility::getProUpgradeUrl('sidebar_cta')) ?>">
124 <span class="el-icon">
125 <svg width="126" height="125" viewBox="0 0 126 125" fill="none"><rect x="0.22139"
126 width="125"
127 height="125"
128 rx="17.8571"
129 fill="#4A5FE0"/>
130 <path
131 d="M47.9424 75.1832L61.6888 67.2467L69.6253 80.9931C62.0334 85.3763 52.3256 82.7751 47.9424 75.1832Z"
132 fill="white"/>
133 <path fill-rule="evenodd" clip-rule="evenodd"
134 d="M75.4348 59.3101L61.6884 67.2466L69.6249 80.993L83.3713 73.0565L75.4348 59.3101ZM89.1821 51.3734L75.4356 59.3099L83.3721 73.0564L97.1186 65.1199L89.1821 51.3734Z"
135 fill="white"/>
136 <path
137 d="M89.182 51.3736C92.978 49.182 97.8319 50.4826 100.023 54.2786L103.992 61.1518L97.1185 65.12L89.182 51.3736Z"
138 fill="white"/>
139 <path
140 d="M64.593 56.4052L50.8466 64.3417L42.9101 50.5953L56.6565 42.6588L64.593 56.4052Z"
141 fill="white"/>
142 <path
143 d="M78.3397 48.4683L64.5933 56.4048L56.6568 42.6584C64.2487 38.2752 73.9565 40.8764 78.3397 48.4683Z"
144 fill="white"/>
145 <path
146 d="M50.847 64.3418L37.1006 72.2783L29.1641 58.5318L42.9105 50.5953L50.847 64.3418Z"
147 fill="white"/>
148 <path
149 d="M37.1011 72.2783C33.3051 74.4699 28.4512 73.1693 26.2596 69.3733L22.2913 62.5001L29.1646 58.5319L37.1011 72.2783Z"
150 fill="white"/>
151 </svg>
152 </span>
153 <span><?php esc_html_e('Upgrade', 'fluent-community'); ?></span>
154 </a>
155 <?php else: ?>
156 <a title="Go to /wp-admin" aria-label="<?php echo esc_attr__('Go to WordPress admin', 'fluent-community'); ?>" class="fcom_inline_icon_link_item fcom_wp_admin_link"
157 href="<?php echo esc_url(admin_url()); ?>">
158 <span class="el-icon">
159 <svg viewBox="0 0 122.52 122.523"><g fill="currentColor">
160 <path
161 d="m8.708 61.26c0 20.802 12.089 38.779 29.619 47.298l-25.069-68.686c-2.916 6.536-4.55 13.769-4.55 21.388z"/>
162 <path
163 d="m96.74 58.608c0-6.495-2.333-10.993-4.334-14.494-2.664-4.329-5.161-7.995-5.161-12.324 0-4.831 3.664-9.328 8.825-9.328.233 0 .454.029.681.042-9.35-8.566-21.807-13.796-35.489-13.796-18.36 0-34.513 9.42-43.91 23.688 1.233.037 2.395.063 3.382.063 5.497 0 14.006-.667 14.006-.667 2.833-.167 3.167 3.994.337 4.329 0 0-2.847.335-6.015.501l19.138 56.925 11.501-34.493-8.188-22.434c-2.83-.166-5.511-.501-5.511-.501-2.832-.166-2.5-4.496.332-4.329 0 0 8.679.667 13.843.667 5.496 0 14.006-.667 14.006-.667 2.835-.167 3.168 3.994.337 4.329 0 0-2.853.335-6.015.501l18.992 56.494 5.242-17.517c2.272-7.269 4.001-12.49 4.001-16.989z"/>
164 <path
165 d="m62.184 65.857-15.768 45.819c4.708 1.384 9.687 2.141 14.846 2.141 6.12 0 11.989-1.058 17.452-2.979-.141-.225-.269-.464-.374-.724z"/>
166 <path
167 d="m107.376 36.046c.226 1.674.354 3.471.354 5.404 0 5.333-.996 11.328-3.996 18.824l-16.053 46.413c15.624-9.111 26.133-26.038 26.133-45.426.001-9.137-2.333-17.729-6.438-25.215z"/>
168 <path
169 d="m61.262 0c-33.779 0-61.262 27.481-61.262 61.26 0 33.783 27.483 61.263 61.262 61.263 33.778 0 61.265-27.48 61.265-61.263-.001-33.779-27.487-61.26-61.265-61.26zm0 119.715c-32.23 0-58.453-26.223-58.453-58.455 0-32.23 26.222-58.451 58.453-58.451 32.229 0 58.45 26.221 58.45 58.451 0 32.232-26.221 58.455-58.45 58.455z"/></g>
170 </svg>
171 </span>
172 </a>
173 <?php endif; ?>
174 <a title="Portal Settings" class="fcom_inline_icon_link_item"
175 href="<?php echo esc_url(Helper::baseUrl('admin/settings')); ?>">
176 <span class="el-icon">
177 <svg viewBox="0 0 1024 1024" data-v-d2e47025="">
178 <path fill="currentColor"
179 d="M600.704 64a32 32 0 0 1 30.464 22.208l35.2 109.376c14.784 7.232 28.928 15.36 42.432 24.512l112.384-24.192a32 32 0 0 1 34.432 15.36L944.32 364.8a32 32 0 0 1-4.032 37.504l-77.12 85.12a357.12 357.12 0 0 1 0 49.024l77.12 85.248a32 32 0 0 1 4.032 37.504l-88.704 153.6a32 32 0 0 1-34.432 15.296L708.8 803.904c-13.44 9.088-27.648 17.28-42.368 24.512l-35.264 109.376A32 32 0 0 1 600.704 960H423.296a32 32 0 0 1-30.464-22.208L357.696 828.48a351.616 351.616 0 0 1-42.56-24.64l-112.32 24.256a32 32 0 0 1-34.432-15.36L79.68 659.2a32 32 0 0 1 4.032-37.504l77.12-85.248a357.12 357.12 0 0 1 0-48.896l-77.12-85.248A32 32 0 0 1 79.68 364.8l88.704-153.6a32 32 0 0 1 34.432-15.296l112.32 24.256c13.568-9.152 27.776-17.408 42.56-24.64l35.2-109.312A32 32 0 0 1 423.232 64H600.64zm-23.424 64H446.72l-36.352 113.088-24.512 11.968a294.113 294.113 0 0 0-34.816 20.096l-22.656 15.36-116.224-25.088-65.28 113.152 79.68 88.192-1.92 27.136a293.12 293.12 0 0 0 0 40.192l1.92 27.136-79.808 88.192 65.344 113.152 116.224-25.024 22.656 15.296a294.113 294.113 0 0 0 34.816 20.096l24.512 11.968L446.72 896h130.688l36.48-113.152 24.448-11.904a288.282 288.282 0 0 0 34.752-20.096l22.592-15.296 116.288 25.024 65.28-113.152-79.744-88.192 1.92-27.136a293.12 293.12 0 0 0 0-40.256l-1.92-27.136 79.808-88.128-65.344-113.152-116.288 24.96-22.592-15.232a287.616 287.616 0 0 0-34.752-20.096l-24.448-11.904L577.344 128zM512 320a192 192 0 1 1 0 384 192 192 0 0 1 0-384m0 64a128 128 0 1 0 0 256 128 128 0 0 0 0-256"></path>
180 </svg>
181 </span>
182 <span><?php esc_html_e('Settings', 'fluent-community'); ?></span>
183 </a>
184 </div>
185 <?php
186 } else if (Utility::isCustomizationEnabled('show_powered_by')) {
187 ?>
188 <a target="_blank" rel="noopener" style="font-size: 13px; cursor: pointer;"
189 class="fcom_inline_icon_link_item" href="<?php echo esc_url(Utility::getProductUrl(true)) ?>">
190 <?php echo esc_html__('Powered by FluentCommunity', 'fluent-community'); ?>
191 </a>
192 <?php
193 }
194 echo '</div>';
195 return;
196 }
197
198 if (Helper::isSiteAdmin()) {
199 add_action('fluent_community/before_header_menu_items', function () {
200 ?>
201 <li class="top_menu_item fcom_icon_link">
202 <a aria-label="<?php echo esc_attr__('Settings', 'fluent-community'); ?>" href="<?php echo esc_url(Helper::baseUrl('admin/settings')); ?>">
203 <span class="el-icon">
204 <svg viewBox="0 0 1024 1024" data-v-d2e47025="">
205 <path fill="currentColor"
206 d="M600.704 64a32 32 0 0 1 30.464 22.208l35.2 109.376c14.784 7.232 28.928 15.36 42.432 24.512l112.384-24.192a32 32 0 0 1 34.432 15.36L944.32 364.8a32 32 0 0 1-4.032 37.504l-77.12 85.12a357.12 357.12 0 0 1 0 49.024l77.12 85.248a32 32 0 0 1 4.032 37.504l-88.704 153.6a32 32 0 0 1-34.432 15.296L708.8 803.904c-13.44 9.088-27.648 17.28-42.368 24.512l-35.264 109.376A32 32 0 0 1 600.704 960H423.296a32 32 0 0 1-30.464-22.208L357.696 828.48a351.616 351.616 0 0 1-42.56-24.64l-112.32 24.256a32 32 0 0 1-34.432-15.36L79.68 659.2a32 32 0 0 1 4.032-37.504l77.12-85.248a357.12 357.12 0 0 1 0-48.896l-77.12-85.248A32 32 0 0 1 79.68 364.8l88.704-153.6a32 32 0 0 1 34.432-15.296l112.32 24.256c13.568-9.152 27.776-17.408 42.56-24.64l35.2-109.312A32 32 0 0 1 423.232 64H600.64zm-23.424 64H446.72l-36.352 113.088-24.512 11.968a294.113 294.113 0 0 0-34.816 20.096l-22.656 15.36-116.224-25.088-65.28 113.152 79.68 88.192-1.92 27.136a293.12 293.12 0 0 0 0 40.192l1.92 27.136-79.808 88.192 65.344 113.152 116.224-25.024 22.656 15.296a294.113 294.113 0 0 0 34.816 20.096l24.512 11.968L446.72 896h130.688l36.48-113.152 24.448-11.904a288.282 288.282 0 0 0 34.752-20.096l22.592-15.296 116.288 25.024 65.28-113.152-79.744-88.192 1.92-27.136a293.12 293.12 0 0 0 0-40.256l-1.92-27.136 79.808-88.128-65.344-113.152-116.288 24.96-22.592-15.232a287.616 287.616 0 0 0-34.752-20.096l-24.448-11.904L577.344 128zM512 320a192 192 0 1 1 0 384 192 192 0 0 1 0-384m0 64a128 128 0 1 0 0 256 128 128 0 0 0 0-256"></path>
207 </svg>
208 </a>
209 </li>
210 <?php
211 });
212 }
213
214 // echo '<div class="fcom_side_footer">';
215 // $this->renderTopMenuRightItems();
216 // echo '</div>';
217 });
218
219 add_action('fluent_community/portal_action_reactivate_account', function ($data) {
220 $xprofile = Helper::getCurrentProfile();
221
222 if ($xprofile && $xprofile->status == '' && Utility::getPrivacySetting('can_deactive_account') === 'yes') {
223
224 $nonce = Arr::get($data, '__nonce', '');
225 if (!wp_verify_nonce($nonce, 'fcom_reactivate_account')) {
226 $this->viewErrorPage(__('Invalid request', 'fluent-community'), __('Security check failed. Please try again.', 'fluent-community'));
227 }
228
229 // we need to reactivate the account
230 $xprofile->status = 'active';
231 $xprofile->save();
232 do_action('fluent_community/reactivate_account', $xprofile);
233 delete_user_meta($xprofile->user_id, '_fcom_deactivated_at');
234 }
235
236 // redirect to the portal home
237 wp_safe_redirect(Helper::baseUrl('/'));
238 exit;
239 });
240 }
241
242 public function renderTopMenuRightItems($context = 'headless')
243 {
244 $auth_url = $this->getAuthUrl();
245 $auth = Helper::getCurrentProfile();
246 $has_color_scheme = Helper::hasColorScheme();
247 $profileLinks = $this->getProfileLinks($auth);
248 ?>
249 <ul class="fcom_user_context_menu_items">
250 <?php do_action('fluent_community/before_header_right_menu_items', $auth); ?>
251 <?php if ($has_color_scheme): ?>
252 <li class="top_menu_item fcom_color_mode">
253 <button type="button" aria-label="<?php echo esc_attr__('Toggle dark mode', 'fluent-community'); ?>" class="fcom_color_mode_core fcom_mode_switch fcom_menu_button">
254 <span class="fcom_color_mode_action el-icon">
255 <svg class="show_on_light" width="20" height="20" viewBox="0 0 20 20" fill="none"
256 xmlns="http://www.w3.org/2000/svg">
257 <path
258 d="M17.9163 11.7317C16.9166 12.2654 15.7748 12.5681 14.5623 12.5681C10.6239 12.5681 7.43128 9.37543 7.43128 5.43705C7.43128 4.22456 7.73388 3.08274 8.2677 2.08301C4.72272 2.91382 2.08301 6.09562 2.08301 9.89393C2.08301 14.3246 5.67476 17.9163 10.1054 17.9163C13.9038 17.9163 17.0855 15.2767 17.9163 11.7317Z"
259 stroke="currentColor" stroke-width="1.5" stroke-linecap="round"
260 stroke-linejoin="round"/>
261 </svg>
262 <svg class="show_on_dark" width="20" height="20" viewBox="0 0 20 20" fill="none"
263 xmlns="http://www.w3.org/2000/svg">
264 <g clip-path="url(#clip0_2906_20675)">
265 <path
266 d="M14.1663 10.0007C14.1663 12.3018 12.3009 14.1673 9.99967 14.1673C7.69849 14.1673 5.83301 12.3018 5.83301 10.0007C5.83301 7.69946 7.69849 5.83398 9.99967 5.83398C12.3009 5.83398 14.1663 7.69946 14.1663 10.0007Z"
267 stroke="currentColor" stroke-width="1.5"/>
268 <path
269 d="M9.99984 1.66699V2.91699M9.99984 17.0837V18.3337M15.8922 15.8931L15.0083 15.0092M4.99089 4.99137L4.107 4.10749M18.3332 10.0003H17.0832M2.9165 10.0003H1.6665M15.8926 4.10758L15.0087 4.99147M4.99129 15.0093L4.10741 15.8932"
270 stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
271 </g>
272 <defs>
273 <clipPath id="clip0_2906_20675">
274 <rect width="20" height="20" fill="currentColor"/>
275 </clipPath>
276 </defs>
277 </svg>
278 </span>
279 </button>
280 </li>
281 <?php endif; ?>
282 <li class="top_menu_item fcom_search_holder"></li>
283 <?php if ($auth): ?>
284 <li class="top_menu_item fcom_notification_holder fcom_countable_notification_holder">
285 <?php if ($context != 'headless'): ?>
286 <?php
287 $notificationCount = NotificationSubscriber::unread()
288 ->where('user_id', $auth->user_id)
289 ->count();
290 ?>
291 <a href="<?php echo esc_url(Helper::baseUrl('/notifications')); ?>" type="button"
292 aria-label="<?php echo esc_attr__('Notifications', 'fluent-community'); ?>"
293 class="fcom_menu_button fcom_theme_button">
294 <i class="el-icon">
295 <svg width="20" height="20" viewBox="0 0 20 20" fill="none"
296 xmlns="http://www.w3.org/2000/svg">
297 <path
298 d="M2.10794 11.9954C1.93073 13.1229 2.72301 13.9055 3.69305 14.2955C7.41201 15.7908 12.5873 15.7908 16.3063 14.2955C17.2763 13.9055 18.0686 13.1229 17.8914 11.9954C17.7825 11.3024 17.244 10.7254 16.845 10.162C16.3224 9.41493 16.2705 8.60009 16.2704 7.73317C16.2704 4.38291 13.4629 1.66699 9.99968 1.66699C6.53645 1.66699 3.72895 4.38291 3.72895 7.73317C3.72887 8.60009 3.67694 9.41493 3.15435 10.162C2.75537 10.7254 2.21685 11.3024 2.10794 11.9954Z"
299 stroke="currentColor" stroke-width="1.5" stroke-linecap="round"
300 stroke-linejoin="round"></path>
301 <path
302 d="M7.5 17.5C8.16345 18.0182 9.03956 18.3333 10 18.3333C10.9604 18.3333 11.8366 18.0182 12.5 17.5"
303 stroke="currentColor" stroke-width="1.5" stroke-linecap="round"
304 stroke-linejoin="round"></path>
305 </svg>
306 </i>
307 <?php if ($notificationCount): ?>
308 <sup
309 class="el-badge__content fcomc_unread_badge el-badge__content--danger is-fixed"><?php echo $notificationCount > 20 ? '20+' : esc_html($notificationCount); ?></sup>
310 <?php endif; ?>
311 </a>
312 <?php endif; ?>
313 </li>
314 <?php endif; ?>
315 <?php do_action('fluent_community/before_header_menu_items', $auth, $context); ?>
316 <?php if ($auth): ?>
317 <li class="top_menu_item fcom_menu_item_user">
318 <div class="fcom_user_menu_item">
319 <div class="fcom_profile_extend">
320 <div class="fcom_profile_menu">
321 <div class="user_avatar" role="button" tabindex="0" aria-expanded="false" aria-controls="fcom_profile_sub_menu" aria-label="<?php echo esc_attr__('Account menu', 'fluent-community'); ?>">
322 <img alt="<?php echo esc_attr($auth->display_name); ?>" src="<?php echo esc_url($auth->avatar); ?>"/>
323 <span class="avatar_icon">
324 <svg xmlns="http://www.w3.org/2000/svg" height="8" width="8"
325 viewBox="0 0 1024 1024">
326 <path fill="currentColor"
327 d="M104.704 338.752a64 64 0 0 1 90.496 0l316.8 316.8 316.8-316.8a64 64 0 0 1 90.496 90.496L557.248 791.296a64 64 0 0 1-90.496 0L104.704 429.248a64 64 0 0 1 0-90.496z"></path>
328 </svg>
329 </span>
330 </div>
331 <span class="user_name">
332 <?php echo esc_html($auth->display_name); ?>
333 </span>
334 <ul id="fcom_profile_sub_menu" class="fcom_profile_sub_menu">
335 <li class="fcom_profile_block">
336 <a href="<?php echo esc_url(Helper::baseUrl('/u/' . $auth->username)); ?>">
337 <div class="fcom_profile_avatar">
338 <img src="<?php echo esc_url($auth->avatar); ?>"
339 alt="<?php echo esc_attr($auth->display_name); ?>">
340 </div>
341 <div class="fcom_user_info">
342 <p class="fcom_user_name"><?php echo esc_html($auth->display_name); ?></p>
343 <p class="fcom_user_email"><?php echo esc_html($auth->user->user_email); ?></p>
344 </div>
345 </a>
346 </li>
347 <?php foreach ($profileLinks as $link): ?>
348 <li>
349 <?php Helper::renderLink($link, 'fcom_menu_link', ''); ?>
350 </li>
351 <?php endforeach; ?>
352 </ul>
353 </div>
354 </div>
355 </li>
356 <?php else: ?>
357 <?php
358 if (AuthHelper::isRegistrationEnabled()) {
359 $btnText = __('Login / Signup', 'fluent-community');
360 } else {
361 $btnText = __('Login', 'fluent-community');
362 }
363 ?>
364
365 <li style="margin-right: 5px" class="top_menu_item fcom_login_li">
366 <a class="fcom_login_btn el-button fcom_primary_button" href="<?php echo esc_url($auth_url); ?>">
367 <span><?php echo esc_html($btnText); ?></span>
368 </a>
369 </li>
370 <?php endif; ?>
371 <?php do_action('fluent_community/after_header_right_menu_items', $auth); ?>
372 </ul>
373 <?php
374 }
375
376 public function appVars()
377 {
378 $userModel = User::where('ID', get_current_user_id())->first();
379 $xprofile = null;
380
381 if ($userModel) {
382 $xprofile = XProfile::where('user_id', get_current_user_id())->first();
383 if (!$xprofile) {
384 $xprofile = $userModel->syncXProfile();
385 }
386 }
387
388 $authData = null;
389 $spaceSlugs = [];
390 $spaceGroups = [];
391
392 if ($userModel && $xprofile) {
393 $wpUser = get_user_by('ID', $userModel->ID);
394
395 if ($userModel->isCommunityModerator()) {
396 $userSpaces = Space::orderBy('serial', 'ASC')->get();
397 $spaceGroups = SpaceGroup::orderBy('serial', 'ASC')
398 ->select(['id', 'title', 'slug', 'settings'])
399 ->get();
400 } else {
401 $userSpaces = Space::orderBy('serial', 'ASC')
402 ->whereHas('members', function ($q) {
403 $q->where('user_id', get_current_user_id())
404 ->where('status', 'active');
405 })
406 ->get();
407
408 $userSpaceParentIds = $userSpaces->pluck('parent_id')->filter()->unique()->toArray();
409
410 $spaceGroups = SpaceGroup::orderBy('serial', 'ASC')
411 ->select(['id', 'title', 'slug', 'settings'])
412 ->whereIn('id', $userSpaceParentIds)
413 ->get();
414 }
415
416 BaseSpace::preloadMemberships($userSpaces, $userModel->ID);
417
418 $userSpaces->each(function ($space) use ($userModel) {
419 $space = $space->formatSpaceData($userModel);
420 do_action_ref_array('fluent_community/space', [&$space]);
421 });
422
423 $userSpaces = $userSpaces->keyBy('slug');
424
425 $userModel->cacheAccessSpaces();
426
427 $spaceSlugs = $userSpaces->pluck('slug')->toArray();
428
429 $authData = [
430 'id' => $xprofile->user_id,
431 'user_id' => $xprofile->user_id,
432 'username' => $xprofile->username,
433 'display_name' => $xprofile->display_name,
434 'first_name' => $wpUser->first_name ? $wpUser->first_name : $xprofile->getFirstName(),
435 'avatar' => $xprofile->avatar,
436 'total_points' => $xprofile->total_points,
437 'last_activity' => $xprofile->last_activity,
438 'email' => $userModel->user_email,
439 'spaces' => $userSpaces,
440 'community_roles' => $userModel->getCommunityRoles(),
441 'is_verified' => $xprofile->is_verified,
442 'compilation_score' => $xprofile->getCompletionScore(),
443 'meta' => $xprofile->meta
444 ];
445 } else {
446 $authData = [
447 'id' => 0,
448 'user_id' => 0,
449 'spaces' => (object)[],
450 'community_roles' => [],
451 'compilation_score' => 100
452 ];
453 }
454
455 $settings = Helper::generalSettings();
456
457 $onboardSettings = Helper::getOnboardingSettings();
458
459 global $wp_version;
460
461 $isRtl = Helper::isRtl();
462
463 $isAbsoluteUrl = defined('FLUENT_COMMUNITY_PORTAL_SLUG') && FLUENT_COMMUNITY_PORTAL_SLUG == '';
464
465 $editorFrameUrl = site_url('?fluent_community_block_editor=1');
466
467 if (current_user_can('edit_posts')) {
468 $editorFrameUrl = admin_url('edit.php?fluent_community_block_editor=1');
469 }
470
471 $portalVars = apply_filters('fluent_community/portal_vars', [
472 'portal_notices' => apply_filters('fluent_community/portal_notices', []),
473 'i18n' => TransStrings::getStrings(),
474 'auth' => $authData,
475 'ajaxurl' => admin_url('admin-ajax.php'),
476 'ajax_nonce' => wp_create_nonce('fluent_community_ajax_nonce'),
477 'slug' => 'fluent-community',
478 'rest' => $this->getRestInfo(),
479 'user_id' => $userModel ? $userModel->ID : null,
480 'assets_url' => FLUENT_COMMUNITY_PLUGIN_URL . 'assets/',
481 'permissions' => $userModel ? $userModel->getPermissions() : ['read' => true],
482 'logo' => Arr::get($settings, 'logo'),
483 'site_title' => Arr::get($settings, 'site_title'),
484 'access_level' => Arr::get($settings, 'access.acess_level'),
485 'user_membership_slugs' => $spaceSlugs,
486 'block_editor_assets' => [
487 'scripts' => [
488 'react' => Vite::getStaticSrcUrl('libs/isolated-editor/react.production.min.js'),
489 'react-dom' => Vite::getStaticSrcUrl(
490 'libs/isolated-editor/react-dom.production.min.js'
491 ),
492 'isolated-block-editor' => Vite::getStaticSrcUrl('libs/isolated-editor/isolated-block-editor.min.js')
493 ],
494 'styles' => [
495 $isRtl ? Vite::getStaticSrcUrl('libs/isolated-editor/core.rtl.css') : Vite::getStaticSrcUrl('libs/isolated-editor/core.css'),
496 $isRtl ? Vite::getStaticSrcUrl('libs/isolated-editor/isolated-block-editor.rtl.css') : Vite::getStaticSrcUrl('libs/isolated-editor/isolated-block-editor.css'),
497 includes_url('css/dist/block-editor/content.min.css?ver=' . $wp_version),
498 ]
499 ],
500 'features' => [
501 'disable_global_posts' => Arr::get($settings, 'disable_global_posts', '') == 'yes',
502 'has_survey_poll' => true,
503 'is_onboarding_enabled' => Arr::get($onboardSettings, 'is_onboarding_enabled', 'no') == 'yes',
504 'can_switch_layout' => Utility::getCustomizationSetting('disable_feed_layout') !== 'yes',
505 'mention_mail' => Utility::hasEmailAnnouncementEnabled(),
506 'max_media_per_post' => apply_filters('fluent_community/max_media_per_post', Utility::getCustomizationSetting('max_media_per_post')),
507 'has_post_title' => Utility::postTitlePref(),
508 'has_course' => Helper::isFeatureEnabled('course_module'),
509 'followers_module' => Helper::isFeatureEnabled('followers_module'),
510 'skicky_sidebar' => Utility::isCustomizationEnabled('fixed_sidebar'),
511 'post_layout' => Utility::getCustomizationSetting('rich_post_layout'),
512 'member_list_layout' => Utility::getCustomizationSetting('member_list_layout'),
513 'default_feed_layout' => Utility::getCustomizationSetting('default_feed_layout'),
514 'disable_feed_sort_by' => Utility::getCustomizationSetting('disable_feed_sort_by'),
515 'default_feed_sort_by' => Utility::getCustomizationSetting('default_feed_sort_by'),
516 'video_embeder' => apply_filters('fluent_community/has_video_embeder', true),
517 'has_inline_image_upload' => apply_filters('fluent_community/has_inline_image_upload', 'yes'),
518 'has_topics' => !!Utility::getTopics(),
519 'show_post_modal' => Utility::isCustomizationEnabled('show_post_modal'),
520 'has_analytics' => Utility::hasAnalyticsEnabled(),
521 'can_deactivate_account' => Utility::getPrivacySetting('can_deactive_account') === 'yes',
522 ],
523 'route_classes' => array_filter([
524 'fcom_sticky_header' => Utility::isCustomizationEnabled('fixed_page_header'),
525 'fcom_sticky_sidebar' => Utility::isCustomizationEnabled('fixed_sidebar'),
526 'fcom_has_icon_on_header_menu' => Utility::isCustomizationEnabled('icon_on_header_menu')
527 ]),
528 'urls' => [
529 'site_url' => home_url(),
530 'portal_base' => Helper::baseUrl('/'),
531 'global_search' => Helper::baseUrl(),
532 ],
533 'last_feed_id' => FeedsHelper::getLastFeedId(),
534 'unread_notification_count' => $userModel ? $userModel->getUnreadNotificationCount() : 0,
535 'unread_feed_ids' => $userModel ? $userModel->getUnreadNotificationFeedIds() : [],
536 'date_offset' => time() - current_time('timestamp'),
537 'date_formatter' => Helper::getDateFormatter(true),
538 'time_formatter' => Helper::getTimeFormatter(true),
539 'date_time_formatter' => Helper::getDateFormatter(true) . ' ' . Helper::getTimeFormatter(true),
540 'portal_slug' => Helper::getPortalSlug(true),
541 'socialLinkProviders' => ProfileHelper::socialLinkProviders(true),
542 'space_groups' => $spaceGroups,
543 'mobileMenuItems' => Helper::getMobileMenuItems(),
544 'feed_links' => Helper::getEnabledFeedLinks(),
545 'post_order_by_options' => Helper::getPostOrderOptions('feed'),
546 'comment_order_by_options' => Helper::getCommentOrderOptions('comment'),
547 'user_post_order_by_options' => Helper::getPostOrderOptions('user'),
548 'routing_system' => Helper::getPortalRouteType(),
549 'portal_url' => Helper::baseUrl('/'),
550 'upgrade_url' => Utility::getProUpgradeUrl('upgrade_page'),
551 'dateTime18n' => apply_filters('fluent_community/date_time_i18n', [
552 /* translators: weekday. Please keep the serial and format */
553 'weekdays' => __('Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday', 'fluent-community'),
554 /* translators: Months Please keep the serial and format*/
555 'months' => __('January_February_March_April_May_June_July_August_September_October_November_December', 'fluent-community'),
556 /* translators: weekday short Please keep the serial and format*/
557 'weekdaysShort' => __('Sun_Mon_Tue_Wed_Thu_Fri_Sat', 'fluent-community'),
558 /* translators: Months short Please keep the serial and format*/
559 'monthsShort' => __('Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec', 'fluent-community'),
560 /* translators: weekday min Please keep the serial and format*/
561 'weekdaysMin' => __('Su_Mo_Tu_We_Th_Fr_Sa', 'fluent-community'),
562 'relativeTime' => [
563 /* translators: Relative Date Formats. Please do not alter %s*/
564 'future' => __('in %s', 'fluent-community'),
565 /* translators: Relative Date Formats. Please do not alter %s*/
566 'past' => __('%s ago', 'fluent-community'),
567 /* translators: Relative Date Formats.*/
568 's' => __('a few seconds', 'fluent-community'),
569 /* translators: Relative Date Formats.*/
570 'm' => __('a minute', 'fluent-community'),
571 /* translators: Relative Date Formats. Don't alter %d*/
572 'mm' => __('%d minutes', 'fluent-community'),
573 /* translators: Relative Date Formats*/
574 'h' => __('an hour', 'fluent-community'),
575 /* translators: Relative Date Formats. Don't alter %d*/
576 'hh' => __('%d hours', 'fluent-community'),
577 /* translators: Relative Date Formats*/
578 'd' => __('a day', 'fluent-community'),
579 /* translators: Relative Date Formats. Don't alter %d*/
580 'dd' => __('%d days', 'fluent-community'),
581 /* translators: Relative Date Formats*/
582 'M' => __('a month', 'fluent-community'),
583 /* translators: Relative Date Formats. Don't alter %d*/
584 'MM' => __('%d months', 'fluent-community'),
585 /* translators: Relative Date Formats*/
586 'y' => __('a year', 'fluent-community'),
587 /* translators: Relative Date Formats. Don't alter %d*/
588 'yy' => __('%d years', 'fluent-community')
589 ],
590 'relativeTimeMobile' => [
591 /* translators: Relative Date Formats. Please do not alter %s*/
592 'future' => __('in %s', 'fluent-community'),
593 /* translators: Relative Date Formats. Please do not alter %s*/
594 'past' => __('%s ago', 'fluent-community'),
595 /* translators: Relative Date Formats.*/
596 's' => __('few sec', 'fluent-community'),
597 /* translators: Relative Date Formats.*/
598 'm' => __('1min', 'fluent-community'),
599 /* translators: Relative Date Formats. Don't alter %d*/
600 'mm' => __('%dmin', 'fluent-community'),
601 /* translators: Relative Date Formats*/
602 'h' => __('1h', 'fluent-community'),
603 /* translators: Relative Date Formats. Don't alter %d*/
604 'hh' => __('%dh', 'fluent-community'),
605 /* translators: Relative Date Formats*/
606 'd' => __('1d', 'fluent-community'),
607 /* translators: Relative Date Formats. Don't alter %d*/
608 'dd' => __('%dd', 'fluent-community'),
609 /* translators: Relative Date Formats*/
610 'M' => __('1mo', 'fluent-community'),
611 /* translators: Relative Date Formats. Don't alter %d*/
612 'MM' => __('%dmo', 'fluent-community'),
613 /* translators: Relative Date Formats*/
614 'y' => __('1y', 'fluent-community'),
615 /* translators: Relative Date Formats. Don't alter %d*/
616 'yy' => __('%dy', 'fluent-community')
617 ]
618 ]),
619 'topicsConfig' => Helper::getTopicsConfig(),
620 'moderationConfig' => Helper::getModerationConfig(),
621 'is_absolute_url' => $isAbsoluteUrl,
622 'portal_paths' => $isAbsoluteUrl ? Helper::portalRoutePaths() : [],
623 'suggestedColors' => Utility::getSuggestedColors(),
624 'view_leaderboard_members' => Utility::canViewLeaderboardMembers(),
625 'report_reasons' => Helper::getReportReasons(),
626 'el_i18n' => [
627 'pagination' => [
628 /* translators: %s is replaced by the page number */
629 'currentPage' => \sprintf(__('page %s', 'fluent-community'), '{pager}'),
630 'deprecationWarning' => 'Deprecated usages detected',
631 'goto' => __('Go to', 'fluent-community'),
632 'next' => __('Go to next page', 'fluent-community'),
633 /* translators: %s is replaced by the number of pages */
634 'nextPages' => \sprintf(__('Next %s pages', 'fluent-community'), ' {pager}'),
635 'page' => __('Page', 'fluent-community'),
636 'pageClassifier' => '',
637 'pagesize' => __('/page', 'fluent-community'),
638 'prev' => __('Go to previous page', 'fluent-community'),
639 /* translators: %s is replaced by the number of pages */
640 'prevPages' => \sprintf(__('Previous %s pages', 'fluent-community'), '{pager}'),
641 /* translators: %s is replaced by the total number of items */
642 'total' => \sprintf(__('Total %s', 'fluent-community'), '{total}'),
643 ],
644 'table' => [
645 'clearFilter' => __('All', 'fluent-community'),
646 'confirmFilter' => __('Confirm', 'fluent-community'),
647 'emptyText' => __('No Data', 'fluent-community'),
648 'resetFilter' => __('Reset', 'fluent-community'),
649 'sumText' => __('Sum', 'fluent-community'),
650 ],
651 'image' => [
652 'error' => __('Failed to Load', 'fluent-community'),
653 ],
654 'upload' => [
655 'continue' => __('Continue', 'fluent-community'),
656 'delete' => __('Delete', 'fluent-community'),
657 'deleteTip' => __('press delete to remove', 'fluent-community'),
658 'preview' => __('Preview', 'fluent-community'),
659 ],
660 'select' => [
661 'loading' => __('Loading', 'fluent-community'),
662 'noData' => __('No data', 'fluent-community'),
663 'noMatch' => __('No matching data', 'fluent-community'),
664 'placeholder' => __('Select', 'fluent-community'),
665 ],
666 'datepicker' => [
667 'now' => __('Now', 'fluent-community'),
668 'today' => __('Today', 'fluent-community'),
669 'cancel' => __('Cancel', 'fluent-community'),
670 'clear' => __('Clear', 'fluent-community'),
671 'confirm' => __('OK', 'fluent-community'),
672 'dateTablePrompt' => __('Use the arrow keys and enter to select the day of the month', 'fluent-community'),
673 'monthTablePrompt' => __('Use the arrow keys and enter to select the month', 'fluent-community'),
674 'yearTablePrompt' => __('Use the arrow keys and enter to select the year', 'fluent-community'),
675 'selectedDate' => __('Selected date', 'fluent-community'),
676 'selectDate' => __('Select date', 'fluent-community'),
677 'selectTime' => __('Select time', 'fluent-community'),
678 'startDate' => __('Start Date', 'fluent-community'),
679 'startTime' => __('Start Time', 'fluent-community'),
680 'endDate' => __('End Date', 'fluent-community'),
681 'endTime' => __('End Time', 'fluent-community'),
682 'prevYear' => __('Previous Year', 'fluent-community'),
683 'nextYear' => __('Next Year', 'fluent-community'),
684 'prevMonth' => __('Previous Month', 'fluent-community'),
685 'nextMonth' => __('Next Month', 'fluent-community'),
686 'year' => __('year', 'fluent-community'),
687 'month1' => __('January', 'fluent-community'),
688 'month2' => __('February', 'fluent-community'),
689 'month3' => __('March', 'fluent-community'),
690 'month4' => __('April', 'fluent-community'),
691 'month5' => __('May', 'fluent-community'),
692 'month6' => __('June', 'fluent-community'),
693 'month7' => __('July', 'fluent-community'),
694 'month8' => __('August', 'fluent-community'),
695 'month9' => __('September', 'fluent-community'),
696 'month10' => __('October', 'fluent-community'),
697 'month11' => __('November', 'fluent-community'),
698 'month12' => __('December', 'fluent-community'),
699 'week' => __('week', 'fluent-community'),
700 'weeks' => [
701 'sun' => __('Sun', 'fluent-community'),
702 'mon' => __('Mon', 'fluent-community'),
703 'tue' => __('Tue', 'fluent-community'),
704 'wed' => __('Wed', 'fluent-community'),
705 'thu' => __('Thu', 'fluent-community'),
706 'fri' => __('Fri', 'fluent-community'),
707 'sat' => __('Sat', 'fluent-community'),
708 ],
709 'weeksFull' => [
710 'sun' => __('Sunday', 'fluent-community'),
711 'mon' => __('Monday', 'fluent-community'),
712 'tue' => __('Tuesday', 'fluent-community'),
713 'wed' => __('Wednesday', 'fluent-community'),
714 'thu' => __('Thursday', 'fluent-community'),
715 'fri' => __('Friday', 'fluent-community'),
716 'sat' => __('Saturday', 'fluent-community'),
717 ],
718 'months' => [
719 'jan' => __('Jan', 'fluent-community'),
720 'feb' => __('Feb', 'fluent-community'),
721 'mar' => __('Mar', 'fluent-community'),
722 'apr' => __('Apr', 'fluent-community'),
723 'may' => __('May', 'fluent-community'),
724 'jun' => __('Jun', 'fluent-community'),
725 'jul' => __('Jul', 'fluent-community'),
726 'aug' => __('Aug', 'fluent-community'),
727 'sep' => __('Sep', 'fluent-community'),
728 'oct' => __('Oct', 'fluent-community'),
729 'nov' => __('Nov', 'fluent-community'),
730 'dec' => __('Dec', 'fluent-community'),
731 ],
732 ]
733 ],
734 'course_sections_collapsed' => apply_filters('fluent_community/course_section_collapse_default', 'no'),
735 'course_lesson_fullscreen' => apply_filters('fluent_community/course_lesson_fullscreen_default', 'no'),
736 'default_profile_tab' => apply_filters('fluent_community/default_profile_tab_route', ''),
737 'default_post_space' => apply_filters('fluent_community/create_post_default_space', ''),
738 'wp_lesson_editor_frame' => $editorFrameUrl,
739 'has_fluentcrm' => defined('FLUENTCRM'),
740 'lazy_styles' => [
741 'wp-block-library-css' => includes_url('css/dist/block-library/style.min.css?version=' . $wp_version),
742 'fcom-block-content-styling-css' => FLUENT_COMMUNITY_PLUGIN_URL . 'Modules/Gutenberg/editor/content_styling.css?version=' . FLUENT_COMMUNITY_PLUGIN_VERSION
743 ],
744 'debug_mode' => 1
745 ]);
746
747 $portalVars['welcome_banner'] = Helper::getWelcomeBanner($userModel ? 'login' : 'logout');
748
749 if (!$xprofile) {
750 $portalVars['auth_url'] = $this->getAuthUrl();
751 $portalVars['allow_signup'] = !!AuthHelper::isRegistrationEnabled();
752 }
753
754 return $portalVars;
755 }
756
757 public function renderSidebarCollapseFouc()
758 {
759 ?>
760 <script>
761 (function () {
762 var m = document.cookie.match(/(?:^|; )fcom_sidebar_collapsed=([^;]+)/);
763 if (m && m[1] === 'true') {
764 document.documentElement.classList.add('fcom_sidebar_collapsed_desktop');
765 }
766 })();
767 </script>
768 <?php
769 }
770
771 public function getGlobalScriptVars($scope = 'wp')
772 {
773 return apply_filters('fluent_community/general_portal_vars', [
774 'scope' => $scope,
775 'theme' => get_option('template'),
776 'default_color' => 'light',
777 'color_switch_cookie_name' => '',
778 'has_color_scheme' => Helper::hasColorScheme(),
779 'default_theme_mode' => Helper::getDefaultThemeMode(),
780 'collapse_sidebar_groups' => Utility::isCustomizationEnabled('collapse_sidebar_groups'),
781 'hide_header_on_scroll' => Utility::isCustomizationEnabled('hide_header_on_scroll'),
782 'enable_sidebar_toggle' => Utility::isCustomizationEnabled('enable_sidebar_toggle'),
783 'sidebar_toggle_label' => __('Toggle sidebar', 'fluent-community'),
784 ]);
785 }
786
787 protected function getRestInfo()
788 {
789 $app = fluentCommunityApp();
790 $ns = $app->config->get('app.rest_namespace');
791 $v = $app->config->get('app.rest_version');
792
793 return [
794 'base_url' => esc_url_raw(rest_url()),
795 'url' => rest_url($ns . '/' . $v),
796 'nonce' => wp_create_nonce('wp_rest'),
797 'namespace' => $ns,
798 'version' => $v,
799 ];
800 }
801
802 protected function renderFullApp()
803 {
804 do_action('litespeed_control_set_nocache', 'fluentcommunity api request'); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
805 // set no cache headers
806 nocache_headers();
807
808 if (isset($_REQUEST['fcom_action'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
809 $action = sanitize_text_field(wp_unslash($_REQUEST['fcom_action'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
810 do_action('fluent_community/portal_action_' . $action, $_GET); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
811 }
812
813 $pathParts = explode('/', $this->currentPath);
814 do_action('fluent_community/rendering_path_ssr_' . $pathParts[0], $pathParts);
815
816 $userId = get_current_user_id();
817
818 if (!$userId && !Helper::isPublicAccessible()) {
819 $adminAuthUrl = Arr::get(Helper::generalSettings(), 'auth_url', '');
820 $authPath = wp_parse_url($adminAuthUrl, PHP_URL_PATH) ?: '';
821 $portalPath = wp_parse_url(Helper::baseUrl(), PHP_URL_PATH) ?: '';
822
823 // Drop admin's URL if it points back into the portal (would infinite loop)
824 $isPortalLoop = $authPath && $portalPath && strpos(trim($authPath, '/'), trim($portalPath, '/')) === 0;
825
826 if ($adminAuthUrl && !$isPortalLoop) {
827 $authUrl = $adminAuthUrl;
828 } else {
829 $adminAuthUrl = '';
830 $parsedUrl = wp_parse_url(home_url(add_query_arg($_REQUEST, $GLOBALS['wp']->request))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
831 $redirectPath = ($parsedUrl['path'] ?? '/') . (!empty($parsedUrl['query']) ? '?' . $parsedUrl['query'] : '');
832 $internalUrl = $this->getAuthUrl();
833 $authUrl = $internalUrl ? add_query_arg(['redirect_to' => $redirectPath], $internalUrl) : wp_login_url($redirectPath);
834 }
835
836 do_action('fluent_community/portal/not_logged_in', $authUrl);
837 // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- intentional external admin auth redirect, followed by exit()
838 $adminAuthUrl ? wp_redirect($authUrl) : wp_safe_redirect($authUrl);
839 exit();
840 }
841
842 do_action('fluent_community/portal/viewed');
843 $xprofile = Helper::getCurrentProfile();
844
845 if ($xprofile && $xprofile->status != 'active') {
846
847 if ($xprofile->status == 'pending') {
848 $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'));
849 } else if ($xprofile->status == '') { // it's a self deactiavted account
850 $this->viewErrorPage(__('Your account is deactivated', 'fluent-community'), __('You have deactivated your community profile.', 'fluent-community'), true, [
851 'btn_url' => Helper::baseUrl('?fcom_action=reactivate_account&__nonce=' . wp_create_nonce('fcom_reactivate_account')),
852 'btn_text' => __('Activate my account', 'fluent-community')
853 ]);
854 } else {
855 $this->viewErrorPage(__('Access denied', 'fluent-community'), __('Sorry, you cannot access this community', 'fluent-community'));
856 }
857 }
858
859 // Gate by role before render: appVars() creates the profile lazily, so checking it
860 // afterward let an unauthorized user see the portal once (and get a profile) before
861 // being denied. Pass false to authorize by role without requiring a profile yet.
862 if (!Helper::canAccessPortal($userId, false)) {
863 $generalSettings = Helper::generalSettings();
864 $this->viewErrorPage(__('Access Denied', 'fluent-community'), Arr::get($generalSettings, 'restricted_role_content'));
865 }
866
867 if ($xprofile) {
868 if ($xprofile->user) {
869 $xprofile->user->cacheAccessSpaces();
870 }
871
872 /*
873 * Just in case if username is not sanitized
874 */
875 if ($xprofile->username != CustomSanitizer::sanitizeUserName($xprofile->username)) {
876 $xprofile->username = ProfileHelper::generateUserName($xprofile->user_id);
877 $xprofile->save();
878 }
879
880 do_action('fluent_community/portal_render_for_user', $xprofile);
881 }
882
883 $data = $this->getAppData();
884 $data = $this->maybePushDynamicMetaData($data);
885
886 $data['isHeadless'] = Helper::isHeadless();
887
888 if (!$data['isHeadless']) {
889 $this->loadClassicPortalAssets($data);
890 } else {
891 do_action('fluent_community/rendering_headless_portal', $data);
892 }
893
894 $data['html_class'] = '';
895 if (Utility::isCustomizationEnabled('enable_sidebar_toggle') && isset($_COOKIE['fcom_sidebar_collapsed']) && $_COOKIE['fcom_sidebar_collapsed'] === 'true') {
896 $data['html_class'] = 'fcom_sidebar_collapsed_desktop';
897 }
898
899 do_action('fluent_community/before_portal_rendered', $data);
900
901 status_header(200);
902 App::make('view')->render('portal_page', $data);
903 exit();
904 }
905
906 public function loadClassicPortalAssets($data)
907 {
908 $cssFiles = $data['css_files'];
909
910 unset($cssFiles['fcom_theme_default']);
911
912 foreach ($cssFiles as $fileName => $cssFile) {
913 wp_enqueue_style($fileName, $cssFile['url'], [], FLUENT_COMMUNITY_PLUGIN_VERSION, 'screen');
914 }
915
916 $jsFiles = $data['js_files'];
917 $jsTags = array_keys($jsFiles);
918 add_filter('script_loader_tag', function ($tag, $handle) use ($jsTags) {
919 if (!in_array($handle, $jsTags)) {
920 return $tag;
921 }
922 $tag = str_replace(' src', ' type="module" src', $tag);
923 return $tag;
924 }, 10, 2);
925 foreach ($jsFiles as $fileName => $jsFile) {
926 wp_enqueue_script($fileName, $jsFile['url'], $jsFile['deps'], FLUENT_COMMUNITY_PLUGIN_VERSION, [
927 'in_footer' => true,
928 'strategy' => 'defer',
929 'type' => 'module'
930 ]);
931 }
932
933 foreach ($data['js_vars'] as $varKey => $values) {
934 wp_localize_script('fcom_start', $varKey, $values);
935 }
936 }
937
938 public function getAppData()
939 {
940 $isRtl = Helper::isRtl();
941
942 $userId = get_current_user_id();
943 $url = home_url(add_query_arg($_REQUEST, $GLOBALS['wp']->request)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
944
945 $appVars = $this->appVars();
946 $generalSettings = Helper::generalSettings();
947 $isDev = Utility::isDev();
948
949 $siteTitle = Arr::get($generalSettings, 'site_title');
950
951 $dataVars = [
952 'title' => $siteTitle,
953 'og_title' => $siteTitle,
954 'description' => get_bloginfo('description'),
955 'featured_image' => Arr::get($generalSettings, 'featured_image', ''),
956 'header_js_files' => [],
957 'js_vars' => [
958 'fluentComAdmin' => $appVars,
959 'fcom_portal_general' => $this->getGlobalScriptVars('portal'),
960 ],
961 'url' => $url,
962 'current_route' => $this->currentPath,
963 'contact' => $appVars['auth'],
964 'user' => $userId ? get_user_by('ID', $userId) : null,
965 'route_group' => Helper::getRouteNameByRequestPath($this->currentPath),
966 'is_dev' => $isDev,
967 'theme_color' => Utility::getThemeColor()
968 ];
969
970 if ($isDev) {
971 $dataVars['css_files'] = [
972 'fcom_theme_default' => [
973 'url' => Vite::getDynamicSrcUrl('theme-default.scss')
974 ],
975 'fcom_global' => [
976 'url' => Vite::getDynamicSrcUrl('global.scss')
977 ],
978 'fcom_vendor' => [
979 'url' => Vite::getDynamicSrcUrl('app_css.scss')
980 ]
981 ];
982 $dataVars['js_files'] = [
983 'fcom_general' => [
984 'url' => Vite::getDynamicSrcUrl('portal_general.js'),
985 'deps' => []
986 ],
987 'fcom_start' => [
988 'url' => Vite::getDynamicSrcUrl('start.js'),
989 'deps' => []
990 ],
991 'fcom_app' => [
992 'url' => Vite::getDynamicSrcUrl('app.js'),
993 'deps' => []
994 ]
995 ];
996
997 } else {
998 $dataVars['js_files'] = [
999 'fcom_general' => [
1000 'url' => Vite::getStaticSrcUrl('portal_general.js'),
1001 'deps' => []
1002 ],
1003 'fcom_start' => [
1004 'url' => Vite::getStaticSrcUrl('start.js'),
1005 'deps' => []
1006 ],
1007 'fcom_app' => [
1008 'url' => Vite::getStaticSrcUrl('app.js'),
1009 'deps' => []
1010 ]
1011 ];
1012 $dataVars['css_files'] = [
1013 'fcom_theme_default' => [
1014 'url' => Vite::getStaticSrcUrl('theme-default.css', $isRtl)
1015 ],
1016 'fcom_global' => [
1017 'url' => Vite::getStaticSrcUrl('global.css', $isRtl)
1018 ],
1019 'fcom_app_css' => [
1020 'url' => Vite::getStaticSrcUrl('app.css', $isRtl)
1021 ],
1022 'fcom_vendor' => [
1023 'url' => Vite::getStaticSrcUrl('app_css.css', $isRtl)
1024 ],
1025 ];
1026 }
1027
1028 return apply_filters('fluent_community/portal_data_vars', $dataVars);
1029 }
1030
1031 protected function maybePushDynamicMetaData($data)
1032 {
1033 $dynamicRoute = Arr::get($data, 'route_group');
1034
1035 $data['landing_route'] = $dynamicRoute;
1036
1037 $validGroups = [
1038 'user_profile',
1039 'community_view',
1040 'course_view',
1041 'feed_view',
1042 'lesson_view'
1043 ];
1044
1045 if (!in_array($dynamicRoute, $validGroups)) {
1046 return $data;
1047 }
1048
1049 if ($dynamicRoute == 'user_profile') {
1050 $uriParts = explode('/', $this->currentPath);
1051 if (count($uriParts) >= 2) {
1052 $userName = $uriParts[1];
1053 $xProfile = XProfile::where('username', $userName)->first();
1054 if ($xProfile && $xProfile->status === 'active' && Utility::canViewUserProfile($xProfile->user_id)) {
1055 $data['title'] = 'User ' . esc_html($xProfile->display_name) . ' - ' . $data['title'];
1056 $data['og_title'] = esc_html($xProfile->display_name) . ' - ' . $data['og_title'];
1057 if ($xProfile->short_description) {
1058 $data['description'] = Helper::getHumanExcerpt($xProfile->short_description, 100);
1059 }
1060 if ($coverPhoto = Arr::get($xProfile->meta, 'cover_photo')) {
1061 $data['featured_image'] = $coverPhoto;
1062 }
1063 }
1064 }
1065 return $data;
1066 }
1067
1068 if ($dynamicRoute == 'community_view' || $dynamicRoute == 'course_view') {
1069 $uriParts = explode('/', $this->currentPath);
1070 if (count($uriParts) >= 2) {
1071 $paceSlug = $uriParts[1];
1072 $space = BaseSpace::query()->onlyMain()->where('slug', $paceSlug)->first();
1073 if ($space && $space->privacy != 'secret') {
1074
1075 // matches the URL the sitemap submits, so tab variants consolidate onto it
1076 $data['canonical_url'] = $space->getPermalink();
1077
1078 if ($dynamicRoute == 'course_view') {
1079 /* translators: %s is replaced by the title of the space */
1080 $data['title'] = sprintf(__('Enroll %s', 'fluent-community'), esc_html($space->title) . ' - ' . $data['title']);
1081 /* translators: %s is replaced by the title of the space */
1082 $data['og_title'] = sprintf(__('Enroll %s', 'fluent-community'), esc_html($space->title));
1083 } else {
1084 /* translators: %s is replaced by the title of the space */
1085 $data['title'] = sprintf(__('Join %s', 'fluent-community'), esc_html($space->title) . ' - ' . $data['title']);
1086 /* translators: %s is replaced by the title of the space */
1087 $data['og_title'] = sprintf(__('Join %s', 'fluent-community'), esc_html($space->title));
1088 }
1089
1090 if ($space->description) {
1091 $data['description'] = Helper::getHumanExcerpt($space->description, 100);
1092 }
1093 if ($ogImage = Arr::get($space->settings, 'og_image')) {
1094 $data['featured_image'] = $ogImage;
1095 } else if ($space->cover_photo) {
1096 $data['featured_image'] = $space->cover_photo;
1097 }
1098
1099 if ($dynamicRoute == 'course_view') {
1100 $data['json_ld'] = apply_filters('fluent_community/course_view_json_ld', [], $space, $data);
1101 }
1102 }
1103 }
1104 return $data;
1105 }
1106
1107 if ($dynamicRoute == 'feed_view') {
1108 $uriParts = explode('/', $this->currentPath);
1109 if (count($uriParts) >= 2) {
1110 $postSlug = end($uriParts);
1111 // Keeps the type scope, so course lessons sharing fcom_posts are not
1112 // described here.
1113 $feed = Feed::query()->where('slug', $postSlug)
1114 ->where('status', 'published')
1115 ->with([
1116 'xprofile' => function ($q) {
1117 $q->select(ProfileHelper::getXProfilePublicFields());
1118 },
1119 'space'
1120 ])
1121 ->whereDoesntHave('space', function ($q) {
1122 $q->whereIn('privacy', ['secret', 'private']);
1123 })
1124 ->first();
1125
1126 if (!$feed) {
1127 return $data;
1128 }
1129
1130 if ($feed->title) {
1131 $data['title'] = esc_html($feed->title) . ' - ' . $data['title'];
1132 $data['og_title'] = esc_html($feed->title);
1133 } else {
1134 $authorName = esc_html($feed->xprofile ? $feed->xprofile->display_name : 'Someone');
1135 $data['title'] = $authorName . ' posted at ' . $data['title'];
1136 $data['og_title'] = $authorName . ' posted at ' . $data['og_title'];
1137 }
1138
1139 $feedUrl = $feed->getPermalink();
1140 $data['canonical_url'] = $feedUrl;
1141
1142 if ($feed->message) {
1143 $data['description'] = esc_html(Helper::getHumanExcerpt($feed->message, 160));
1144 }
1145
1146 if ($mediaPreview = Arr::get($feed->meta, 'media_preview.image')) {
1147 $data['featured_image'] = esc_url($mediaPreview);
1148 } else if ($firstImage = Arr::get($feed->meta, 'media_items.0.url')) {
1149 $data['featured_image'] = esc_url($firstImage);
1150 } else if ($feed->space) {
1151 $space = $feed->space;
1152 if ($ogImage = Arr::get($space->settings, 'og_image')) {
1153 $data['featured_image'] = $ogImage;
1154 } else if ($space->cover_photo) {
1155 $data['featured_image'] = $space->cover_photo;
1156 }
1157 }
1158
1159 $data['json_ld'] = apply_filters('fluent_community/feed_view_json_ld', [], $feed, $data);
1160 }
1161 return $data;
1162 }
1163
1164 if ($dynamicRoute == 'lesson_view') {
1165 $uriParts = explode('/', $this->currentPath);
1166
1167 $lessonSlug = Arr::get($uriParts, 3);
1168 $courseSlug = Arr::get($uriParts, 1);
1169 if (!$lessonSlug || !$courseSlug) {
1170 return $data;
1171 }
1172
1173 $lesson = CourseLesson::query()->where('slug', $lessonSlug)->where('status', 'published')->first();
1174
1175 if (!$lesson) {
1176 return $data;
1177 }
1178
1179 $course = BaseSpace::query()->onlyMain()
1180 ->where('id', $lesson->space_id)
1181 ->where('slug', $courseSlug)
1182 ->where('status', 'published')
1183 ->where('privacy', 'public')
1184 ->first();
1185
1186 if (!$course) {
1187 return $data;
1188 }
1189
1190 $data['title'] = esc_html($lesson->title) . ' - ' . $data['title'];
1191 $data['og_title'] = esc_html($lesson->title);
1192 $data['description'] = esc_html(Helper::getHumanExcerpt($lesson->message, 120));
1193 $data['canonical_url'] = $lesson->getPermalink();
1194
1195 return $data;
1196 }
1197
1198 return $data;
1199 }
1200
1201 protected function getMainMenuItems($scope = 'sidebar')
1202 {
1203 $menuGroups = Helper::getMenuItemsGroup('view');
1204
1205 $items = Arr::get($menuGroups, 'mainMenuItems', []);
1206
1207 return apply_filters('fluent_community/main_menu_items', $items, $scope);
1208 }
1209
1210 public function getPortalHeader($echo = false, $context = 'headless')
1211 {
1212 $settings = Helper::generalSettings();
1213 $userId = get_current_user_id();
1214
1215 $xprofile = null;
1216 if ($userId && Helper::canAccessPortal($userId, false)) {
1217 $userModel = Helper::getCurrentUser();
1218 if ($userModel) {
1219 $xprofile = $userModel->syncXProfile(false);
1220 }
1221 }
1222
1223 $authUrl = $this->getAuthUrl();
1224
1225 if ($xprofile) {
1226 $xprofile->logout_url = wp_logout_url(Helper::baseUrl());
1227 }
1228
1229 $logo = Arr::get($settings, 'logo', '');
1230 $whiteLogo = Arr::get($settings, 'white_logo', '');
1231 if (!$whiteLogo) {
1232 $whiteLogo = $logo;
1233 }
1234
1235 $logoPermalink = Helper::baseUrl('/');
1236 $logoPermalinkType = Arr::get($settings, 'logo_permalink_type');
1237 if ($logoPermalinkType == 'custom') {
1238 $logoPermalink = Arr::get($settings, 'logo_permalink');
1239 }
1240
1241 $data = apply_filters('fluent_community/header_vars', [
1242 'portal_url' => Helper::baseUrl('/'),
1243 'logo' => $logo,
1244 'white_logo' => $whiteLogo,
1245 'logo_permalink' => $logoPermalink,
1246 'site_title' => Arr::get($settings, 'site_title'),
1247 'profile_url' => $xprofile ? Helper::baseUrl('u/' . $xprofile->username . '/') : '',
1248 'auth' => $xprofile ? $xprofile : null,
1249 'auth_url' => $authUrl,
1250 'menuItems' => $this->getMainMenuItems('header'),
1251 'context' => $context
1252 ]);
1253
1254 if ($echo) {
1255 App::make('view')->render('portal.header', $data);
1256 return;
1257 }
1258
1259 return (string)App::make('view')->make('portal.header', $data);
1260 }
1261
1262 private function getProfileLinks($xprofile = null)
1263 {
1264 if (!$xprofile) {
1265 $xprofile = Helper::getCurrentProfile();
1266 }
1267
1268 $profileLinks = [];
1269 $menuGroups = Helper::getMenuItemsGroup('view');
1270 if ($xprofile) {
1271 $profileLinks = Arr::get($menuGroups, 'profileDropdownItems', []);
1272
1273 $replaces = [
1274 '#{{user_url}}' => Helper::baseUrl('u/' . $xprofile->username),
1275 '#user_url' => Helper::baseUrl('u/' . $xprofile->username),
1276 '#{{logout_url}}' => wp_logout_url(Helper::baseUrl()),
1277 '#logout_url' => wp_logout_url(Helper::baseUrl())
1278 ];
1279
1280 foreach ($profileLinks as &$link) {
1281 $slug = Arr::get($link, 'slug', '');
1282
1283 if ($slug == 'my_spaces') {
1284 $link['permalink'] = Helper::baseUrl('u/' . $xprofile->username . '/spaces');
1285 } else if ($slug == 'logout') {
1286 $link['permalink'] = wp_logout_url(Helper::baseUrl());
1287 } else if ($slug == 'profile') {
1288 $link['permalink'] = Helper::baseUrl('u/' . $xprofile->username);
1289 }
1290
1291 $link['permalink'] = str_replace(array_keys($replaces), array_values($replaces), $link['permalink']);
1292 }
1293 }
1294
1295 return $profileLinks;
1296 }
1297
1298 public function getPortalSidebar($echo = false, $context = 'headless')
1299 {
1300 $data = Utility::getPortalSidebarData('sidebar');
1301
1302 $data['context'] = $context;
1303
1304 if ($echo) {
1305 App::make('view')->render('portal.main_sidebar', $data);
1306 return;
1307 }
1308
1309 return (string)App::make('view')->make('portal.main_sidebar', $data);
1310 }
1311
1312 protected function getAuthUrl()
1313 {
1314 return apply_filters('fluent_community/auth/login_url', Helper::getAuthUrl());
1315 }
1316
1317 public function viewErrorPage($title, $message = '', $showBtn = true, $btnData = [])
1318 {
1319 $data = [
1320 'title' => $title,
1321 'description' => $message,
1322 'btn_txt' => Arr::get($btnData, 'btn_text', __('Go to home page', 'fluent-community')),
1323 'url' => Arr::get($btnData, 'btn_url', home_url())
1324 ];
1325
1326 if (!$showBtn) {
1327 $data['btn_txt'] = '';
1328 }
1329
1330 status_header(200);
1331 App::make('view')->render('error_page', $data);
1332 exit();
1333 }
1334 }
1335