PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.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 1.1.0 All 77 releases
← All changes | Modules/Auth/AuthModdule.php +90 -35 2.6.012.10.0 View file →
@@ -63,12 +63,52 @@
63 63
64 64 // Remove fcom_action and fcom_url_hash from the current url
65 65 $currentUrl = home_url(add_query_arg($_GET, $GLOBALS['wp']->request)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
66 66 $url = remove_query_arg(['fcom_action', 'fcom_url_hash'], $currentUrl);
67 - wp_redirect($url, 302); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect
67 + $this->redirectAndExit($url);
68 + }
69 +
70 + /**
71 + * Send a redirect and stop.
72 + *
73 + * Extracted only so it can be observed: a bare `exit()` terminates the PHP
74 + * process, which in a test run kills the whole suite with no result (see
75 + * FIX-PLAN item 22 for the same problem on PortalHandler). A test subclass
76 + * overrides this and the two methods below to record what was about to
77 + * happen and throw instead. Behaviour in production is unchanged — this is
78 + * the original call, moved.
79 + *
80 + * This one keeps the UNSAFE variant its single caller already used. That
81 + * caller builds its target with home_url(), so it is same-host by
82 + * construction rather than by validation. Kept as a separate method from
83 + * safeRedirectAndExit(), rather than a $safe flag, so the distinction stays
84 + * visible to anyone grepping for wp_redirect.
85 + */
86 + protected function redirectAndExit($url, $status = 302)
87 + {
88 + wp_redirect($url, $status); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect
68 89 exit();
69 90 }
70 91
92 + /**
93 + * Send a host-confined redirect and stop. See redirectAndExit().
94 + */
95 + protected function safeRedirectAndExit($url)
96 + {
97 + wp_safe_redirect($url);
98 + exit();
99 + }
100 +
101 + /**
102 + * Render the headless page and stop. See redirectAndExit().
103 + */
104 + protected function renderPageAndExit($template, $pageVars)
105 + {
106 + status_header(200);
107 + App::make('view')->render($template, $pageVars);
108 + exit(200);
109 + }
110 +
71 111 public function viewAuthPage()
72 112 {
73 113
74 114 add_filter('login_form_defaults', function ($defaults) {
@@ -98,20 +138,19 @@
98 138 if (!$redirectUrl) {
99 139 $redirectUrl = Helper::baseUrl();
100 140 }
101 141
102 - wp_safe_redirect($redirectUrl);
103 - exit();
142 + $this->safeRedirectAndExit($redirectUrl);
104 143 }
105 144
106 145 if ($currentUserId && $inviation) {
146 + /** @var BaseSpace|null $space */
107 147 $space = BaseSpace::withoutGlobalScopes()->find($inviation->post_id);
108 148 if ($space) {
109 149 if (Helper::isUserInSpace($currentUserId, $inviation->post_id)) {
110 150 // let's redirect the user to the space
111 151 $redirectUrl = $space->getPermalink();
112 - wp_safe_redirect($redirectUrl);
113 - exit();
152 + $this->safeRedirectAndExit($redirectUrl);
114 153 }
115 154
116 155 if (!empty($_REQUEST['auto_accept'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
117 156 $redirectUrl = (new InvitationHandler())->handleInvitationLogin(Helper::baseUrl(), get_user_by('ID', $currentUserId), $inviation->message_rendered);
@@ -117,10 +156,9 @@
117 156 $redirectUrl = (new InvitationHandler())->handleInvitationLogin(Helper::baseUrl(), get_user_by('ID', $currentUserId), $inviation->message_rendered);
118 157 if (is_wp_error($redirectUrl) || !$redirectUrl) {
119 158 $redirectUrl = Helper::baseUrl();
120 159 }
121 - wp_safe_redirect($redirectUrl);
122 - exit();
160 + $this->safeRedirectAndExit($redirectUrl);
123 161 }
124 162 }
125 163 }
126 164
@@ -154,10 +192,9 @@
154 192 }
155 193
156 194 $isFluentAuth = AuthHelper::isFluentAuthAvailable();
157 195 if (!$isFluentAuth && $targetForm == 'reset_password') {
158 - wp_safe_redirect(wp_lostpassword_url(Helper::baseUrl()));
159 - exit();
196 + $this->safeRedirectAndExit(wp_lostpassword_url(Helper::baseUrl()));
160 197 }
161 198
162 199 $portalSettings = Helper::generalSettings();
163 200 $titleVar = Arr::get($portalSettings, 'site_title');
@@ -174,10 +211,9 @@
174 211 $frameData['button_label'] = __('Signup', 'fluent-community');
175 212 if (!$inviation) {
176 213 $customSignupUrl = Arr::get($portalSettings, 'custom_signup_url');
177 214 if ($customSignupUrl) {
178 - wp_safe_redirect($customSignupUrl);
179 - exit();
215 + $this->safeRedirectAndExit($customSignupUrl);
180 216 }
181 217 }
182 218 }
183 219
@@ -190,9 +226,13 @@
190 226 wp_enqueue_script('fluent_auth_scripts', Vite::getStaticSrcUrl('user_registration.js'), [], FLUENT_COMMUNITY_PLUGIN_VERSION, true);
191 227 wp_localize_script('fluent_auth_scripts', 'fluentComRegistration', array(
192 228 'ajax_url' => admin_url('admin-ajax.php'),
193 229 'is_logged_in' => is_user_logged_in(),
194 - 'redirecting_text' => __('Redirecting...', 'fluent-community')
230 + 'redirecting_text' => __('Redirecting...', 'fluent-community'),
231 + 'i18n' => [
232 + 'generic_error' => esc_html__('Something went wrong. Please try again later', 'fluent-community'),
233 + 'network_error' => esc_html__('Could not reach the server. Please check your connection and try again.', 'fluent-community'),
234 + ]
195 235 ));
196 236 }
197 237 }, 10);
198 238
@@ -267,10 +307,10 @@
267 307 <?php
268 308 } else if ($targetForm == 'accept_invitation') {
269 309 do_action('fluent_community/auth/show_invitation_for_user', $inviation, $frameData);
270 310 } else {
271 - //check if the registration is disabled
272 - if (!AuthHelper::isRegistrationEnabled()) {
311 + //check if the registration is disabled (a valid invitation still allows signup)
312 + if (!$inviation && !AuthHelper::isRegistrationEnabled()) {
273 313 echo '<div class="fcom_completed"><div class="fcom_complted_header"><h4>' . esc_html__('Registration is disabled for this community', 'fluent-community') . '</h4>';
274 314 return;
275 315 }
276 316
@@ -288,15 +328,20 @@
288 328 }, 10, 1);
289 329
290 330 add_action('fluent_community/headless/head_early', function ($scope) use ($formSettings) {
291 331 $bannerColors = array_filter(Arr::only($formSettings['banner'], ['title_color', 'text_color', 'background_color']));
292 - $css = Utility::getColorCssVariables(); ?>
332 + $css = Utility::getColorCssVariables();
333 +
334 + $sideVars = '';
335 + foreach ($bannerColors as $colorKey => $colorValue) {
336 + $sideVars .= '--fcom_' . $colorKey . ': ' . $colorValue . ';';
337 + }
338 + ?>
339 + <?php // the auth screen renders with load_wp, which skips headless_page's noindex ?>
340 + <meta name="robots" content="noindex, noarchive" />
293 341 <link rel="canonical" href="<?php echo esc_url(Helper::getAuthUrl()); ?>" />
294 342 <style>
295 - .fcom_layout_side {
296 - <?php foreach ($bannerColors as $colorKey => $colorValue): ?> --fcom_ <?php echo esc_html($colorKey); ?>: <?php echo esc_html($colorValue); ?>;
297 - <?php endforeach; ?>
298 - }
343 + .fcom_layout_side { <?php echo esc_html($sideVars); ?> }
299 344 <?php echo esc_html($css); ?>
300 345 </style>
301 346 <?php
302 347 });
@@ -307,11 +352,9 @@
307 352 add_filter('pre_get_document_title', function ($title) use ($frameData) {
308 353 return $frameData['title'];
309 354 }, 9999, 1);
310 355
311 - status_header(200);
312 - App::make('view')->render('headless_page', $pageVars);
313 - exit(200);
356 + $this->renderPageAndExit('headless_page', $pageVars);
314 357 }
315 358
316 359 public function handleUserSignup()
317 360 {
@@ -318,14 +361,8 @@
318 361 if (is_user_logged_in()) {
319 362 return $this->handleSignupCompleted(get_current_user_id());
320 363 }
321 364
322 - if (!AuthHelper::isRegistrationEnabled()) {
323 - wp_send_json([
324 - 'message' => esc_html__('Registration is disabled for this community', 'fluent-community')
325 - ], 422);
326 - }
327 -
328 365 $signupNonce = isset($_POST['_fcom_signup_nonce']) ? sanitize_text_field(wp_unslash($_POST['_fcom_signup_nonce'])) : '';
329 366 if (!$signupNonce || !wp_verify_nonce($signupNonce, 'fluent_auth_signup_nonce')) {
330 367 wp_send_json([
331 368 'message' => esc_html__('Invalid request. Please refresh the page and try again.', 'fluent-community')
@@ -331,8 +368,22 @@
331 368 'message' => esc_html__('Invalid request. Please refresh the page and try again.', 'fluent-community')
332 369 ], 403);
333 370 }
334 371
372 + $invitationToken = isset($_POST['invitation_token']) ? sanitize_text_field(wp_unslash($_POST['invitation_token'])) : '';
373 + $hasValidInvitation = false;
374 + if ($invitationToken) {
375 + $pendingInvitation = Invitation::where('message_rendered', $invitationToken)->first();
376 + $hasValidInvitation = $pendingInvitation && $pendingInvitation->isValid();
377 + }
378 +
379 + // A valid invitation must still allow signup even when public registration is disabled.
380 + if (!$hasValidInvitation && !AuthHelper::isRegistrationEnabled()) {
381 + wp_send_json([
382 + 'message' => esc_html__('Registration is disabled for this community', 'fluent-community')
383 + ], 422);
384 + }
385 +
335 386 $app = App::make('app');
336 387 $request = $app->make('request');
337 388 $fields = AuthHelper::getFormFields();
338 389
@@ -527,9 +578,9 @@
527 578
528 579 $redirectUrl = Helper::baseUrl();
529 580
530 581 if (!empty($_REQUEST['redirect_to'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
531 - $redirectUrl = sanitize_url(wp_unslash($_REQUEST['redirect_to'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
582 + $redirectUrl = wp_validate_redirect(sanitize_url(wp_unslash($_REQUEST['redirect_to'])), Helper::baseUrl()); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
532 583 }
533 584
534 585 $redirectUrl = apply_filters('fluent_community/auth/after_signup_redirect_url', $redirectUrl, $user, $_REQUEST); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
535 586 $btnText = __('Continue to the community', 'fluent-community');
@@ -731,9 +782,9 @@
731 782 <div class="fcom_onboard_body">
732 783 <div class="fcom_onboard_form">
733 784 <?php echo do_shortcode('[fluent_auth_login redirect_to="' . esc_url($currentUrl) . '"]'); ?>
734 785 <div class="fcom_spaced_divider">
735 - <?php if (AuthHelper::isRegistrationEnabled()): ?>
786 + <?php if ($invitation || AuthHelper::isRegistrationEnabled()): ?>
736 787 <div class="fcom_alt_auth_text">
737 788 <?php esc_html_e('Don\'t have an account?', 'fluent-community'); ?>
738 789 <a href="<?php echo esc_url($signupUrl); ?>">
739 790 <?php esc_html_e('Signup', 'fluent-community'); ?>
@@ -770,9 +821,9 @@
770 821 $frameData['defaults'] = [
771 822 'email' => $invitation ? $invitation->message : ''
772 823 ];
773 824
774 - if (AuthHelper::isRegistrationEnabled()) {
825 + if ($invitation || AuthHelper::isRegistrationEnabled()) {
775 826 $frameData['signupUrl'] = $signupUrl;
776 827 }
777 828
778 829 $frameData['settings'] = $formSettings;
@@ -787,8 +838,14 @@
787 838 public function renderRegistrationForm($frameData, $invitation = null)
788 839 {
789 840 $formFields = AuthHelper::getFormFields($invitation);
790 841
842 + // Prefill the name from the invitation link's query param when present.
843 + $inviteName = isset($_GET['invite_name']) ? sanitize_text_field(wp_unslash($_GET['invite_name'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
844 + if ($inviteName && isset($formFields['full_name']) && empty($formFields['full_name']['value'])) {
845 + $formFields['full_name']['value'] = $inviteName;
846 + }
847 +
791 848 $authSettings = AuthenticationService::getAuthSettings();
792 849
793 850 $termsField = Arr::get($authSettings, 'signup.form.fields.terms');
794 851
@@ -837,14 +894,12 @@
837 894 }
838 895
839 896 add_action('fluent_community/before_registration_form', function ($frameData) {
840 897 if (AuthHelper::isFluentAuthAvailable()) {
841 - $currentUrl = home_url(add_query_arg($_GET, $GLOBALS['wp']->request)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
898 + $currentUrl = esc_url(home_url(add_query_arg($_GET, $GLOBALS['wp']->request))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
842 899
843 - ob_start();
844 900 $titlePrefix = __('Signup with', 'fluent-community');
845 - do_shortcode('[fs_auth_buttons redirect="' . $currentUrl . '" title_prefix="' . $titlePrefix . ' " title=""]');
846 - $html = ob_get_clean();
901 + $html = do_shortcode('[fs_auth_buttons redirect="' . $currentUrl . '" title_prefix="' . $titlePrefix . ' " title=""]');
847 902
848 903 if ($html) {
849 904 echo '<div class="fcom_social_auth_wrap">';
850 905 echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped