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 +105 -39 2.4.012.10.0 View file →
@@ -52,9 +52,11 @@
52 52 if ($willAtoLogin) {
53 53 try {
54 54 InvitationService::makeLogin($tagetUser);
55 55 } catch (\Throwable $e) {
56 - error_log('FluentCommunity: Auto-login failed for user #' . $tagetUser->ID . ': ' . $e->getMessage());
56 + if (defined('WP_DEBUG') && WP_DEBUG) {
57 + error_log('FluentCommunity: Auto-login failed for user #' . $tagetUser->ID . ': ' . $e->getMessage()); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
58 + }
57 59 }
58 60 }
59 61 }
60 62 }
@@ -61,12 +63,52 @@
61 63
62 64 // Remove fcom_action and fcom_url_hash from the current url
63 65 $currentUrl = home_url(add_query_arg($_GET, $GLOBALS['wp']->request)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
64 66 $url = remove_query_arg(['fcom_action', 'fcom_url_hash'], $currentUrl);
65 - 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
66 89 exit();
67 90 }
68 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 +
69 111 public function viewAuthPage()
70 112 {
71 113
72 114 add_filter('login_form_defaults', function ($defaults) {
@@ -96,20 +138,19 @@
96 138 if (!$redirectUrl) {
97 139 $redirectUrl = Helper::baseUrl();
98 140 }
99 141
100 - wp_safe_redirect($redirectUrl);
101 - exit();
142 + $this->safeRedirectAndExit($redirectUrl);
102 143 }
103 144
104 145 if ($currentUserId && $inviation) {
146 + /** @var BaseSpace|null $space */
105 147 $space = BaseSpace::withoutGlobalScopes()->find($inviation->post_id);
106 148 if ($space) {
107 149 if (Helper::isUserInSpace($currentUserId, $inviation->post_id)) {
108 150 // let's redirect the user to the space
109 151 $redirectUrl = $space->getPermalink();
110 - wp_safe_redirect($redirectUrl);
111 - exit();
152 + $this->safeRedirectAndExit($redirectUrl);
112 153 }
113 154
114 155 if (!empty($_REQUEST['auto_accept'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
115 156 $redirectUrl = (new InvitationHandler())->handleInvitationLogin(Helper::baseUrl(), get_user_by('ID', $currentUserId), $inviation->message_rendered);
@@ -115,10 +156,9 @@
115 156 $redirectUrl = (new InvitationHandler())->handleInvitationLogin(Helper::baseUrl(), get_user_by('ID', $currentUserId), $inviation->message_rendered);
116 157 if (is_wp_error($redirectUrl) || !$redirectUrl) {
117 158 $redirectUrl = Helper::baseUrl();
118 159 }
119 - wp_safe_redirect($redirectUrl);
120 - exit();
160 + $this->safeRedirectAndExit($redirectUrl);
121 161 }
122 162 }
123 163 }
124 164
@@ -125,16 +165,19 @@
125 165 do_action('fluent_community/auth/before_auth_page_process', $currentUserId, $inviation);
126 166
127 167 $acceptedForms = ['login', 'register', 'reset_password'];
128 168 $targetForm = Arr::get($_GET, 'form'); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
129 - if (!in_array($targetForm, $acceptedForms)) {
169 + $explicitForm = in_array($targetForm, $acceptedForms, true);
170 + if (!$explicitForm) {
130 171 $targetForm = 'login';
131 172 }
132 173
133 - if ($inviation && $targetForm != 'reset_password') {
174 + if ($inviation && !$explicitForm) {
134 175 if ($inviation->message) {
135 176 $isUserAvailable = get_user_by('email', $inviation->message);
136 177 $targetForm = $isUserAvailable ? 'login' : 'register';
178 + } else {
179 + $targetForm = 'register';
137 180 }
138 181 }
139 182
140 183 if ($inviation && $currentUserId && $inviation->isValid()) {
@@ -149,10 +192,9 @@
149 192 }
150 193
151 194 $isFluentAuth = AuthHelper::isFluentAuthAvailable();
152 195 if (!$isFluentAuth && $targetForm == 'reset_password') {
153 - wp_safe_redirect(wp_lostpassword_url(Helper::baseUrl()));
154 - exit();
196 + $this->safeRedirectAndExit(wp_lostpassword_url(Helper::baseUrl()));
155 197 }
156 198
157 199 $portalSettings = Helper::generalSettings();
158 200 $titleVar = Arr::get($portalSettings, 'site_title');
@@ -169,10 +211,9 @@
169 211 $frameData['button_label'] = __('Signup', 'fluent-community');
170 212 if (!$inviation) {
171 213 $customSignupUrl = Arr::get($portalSettings, 'custom_signup_url');
172 214 if ($customSignupUrl) {
173 - wp_safe_redirect($customSignupUrl);
174 - exit();
215 + $this->safeRedirectAndExit($customSignupUrl);
175 216 }
176 217 }
177 218 }
178 219
@@ -185,9 +226,13 @@
185 226 wp_enqueue_script('fluent_auth_scripts', Vite::getStaticSrcUrl('user_registration.js'), [], FLUENT_COMMUNITY_PLUGIN_VERSION, true);
186 227 wp_localize_script('fluent_auth_scripts', 'fluentComRegistration', array(
187 228 'ajax_url' => admin_url('admin-ajax.php'),
188 229 'is_logged_in' => is_user_logged_in(),
189 - '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 + ]
190 235 ));
191 236 }
192 237 }, 10);
193 238
@@ -262,10 +307,10 @@
262 307 <?php
263 308 } else if ($targetForm == 'accept_invitation') {
264 309 do_action('fluent_community/auth/show_invitation_for_user', $inviation, $frameData);
265 310 } else {
266 - //check if the registration is disabled
267 - if (!AuthHelper::isRegistrationEnabled()) {
311 + //check if the registration is disabled (a valid invitation still allows signup)
312 + if (!$inviation && !AuthHelper::isRegistrationEnabled()) {
268 313 echo '<div class="fcom_completed"><div class="fcom_complted_header"><h4>' . esc_html__('Registration is disabled for this community', 'fluent-community') . '</h4>';
269 314 return;
270 315 }
271 316
@@ -283,15 +328,20 @@
283 328 }, 10, 1);
284 329
285 330 add_action('fluent_community/headless/head_early', function ($scope) use ($formSettings) {
286 331 $bannerColors = array_filter(Arr::only($formSettings['banner'], ['title_color', 'text_color', 'background_color']));
287 - $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" />
288 341 <link rel="canonical" href="<?php echo esc_url(Helper::getAuthUrl()); ?>" />
289 342 <style>
290 - .fcom_layout_side {
291 - <?php foreach ($bannerColors as $colorKey => $colorValue): ?> --fcom_ <?php echo esc_html($colorKey); ?>: <?php echo esc_html($colorValue); ?>;
292 - <?php endforeach; ?>
293 - }
343 + .fcom_layout_side { <?php echo esc_html($sideVars); ?> }
294 344 <?php echo esc_html($css); ?>
295 345 </style>
296 346 <?php
297 347 });
@@ -302,11 +352,9 @@
302 352 add_filter('pre_get_document_title', function ($title) use ($frameData) {
303 353 return $frameData['title'];
304 354 }, 9999, 1);
305 355
306 - status_header(200);
307 - App::make('view')->render('headless_page', $pageVars);
308 - exit(200);
356 + $this->renderPageAndExit('headless_page', $pageVars);
309 357 }
310 358
311 359 public function handleUserSignup()
312 360 {
@@ -313,14 +361,8 @@
313 361 if (is_user_logged_in()) {
314 362 return $this->handleSignupCompleted(get_current_user_id());
315 363 }
316 364
317 - if (!AuthHelper::isRegistrationEnabled()) {
318 - wp_send_json([
319 - 'message' => esc_html__('Registration is disabled for this community', 'fluent-community')
320 - ], 422);
321 - }
322 -
323 365 $signupNonce = isset($_POST['_fcom_signup_nonce']) ? sanitize_text_field(wp_unslash($_POST['_fcom_signup_nonce'])) : '';
324 366 if (!$signupNonce || !wp_verify_nonce($signupNonce, 'fluent_auth_signup_nonce')) {
325 367 wp_send_json([
326 368 'message' => esc_html__('Invalid request. Please refresh the page and try again.', 'fluent-community')
@@ -326,8 +368,22 @@
326 368 'message' => esc_html__('Invalid request. Please refresh the page and try again.', 'fluent-community')
327 369 ], 403);
328 370 }
329 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 +
330 386 $app = App::make('app');
331 387 $request = $app->make('request');
332 388 $fields = AuthHelper::getFormFields();
333 389
@@ -522,9 +578,9 @@
522 578
523 579 $redirectUrl = Helper::baseUrl();
524 580
525 581 if (!empty($_REQUEST['redirect_to'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
526 - $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
527 583 }
528 584
529 585 $redirectUrl = apply_filters('fluent_community/auth/after_signup_redirect_url', $redirectUrl, $user, $_REQUEST); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
530 586 $btnText = __('Continue to the community', 'fluent-community');
@@ -594,10 +650,16 @@
594 650
595 651 $user = wp_authenticate($data['log'], $data['pwd']);
596 652
597 653 if (is_wp_error($user)) {
654 + $enumerationCodes = ['invalid_username', 'invalid_email', 'incorrect_password'];
655 + if (in_array($user->get_error_code(), $enumerationCodes, true)) {
656 + $message = __('Email or password is incorrect.', 'fluent-community');
657 + } else {
658 + $message = $user->get_error_message();
659 + }
598 660 wp_send_json([
599 - 'message' => $user->get_error_message()
661 + 'message' => $message
600 662 ], 422);
601 663 }
602 664
603 665 InvitationService::makeLogin($user);
@@ -720,9 +782,9 @@
720 782 <div class="fcom_onboard_body">
721 783 <div class="fcom_onboard_form">
722 784 <?php echo do_shortcode('[fluent_auth_login redirect_to="' . esc_url($currentUrl) . '"]'); ?>
723 785 <div class="fcom_spaced_divider">
724 - <?php if (AuthHelper::isRegistrationEnabled()): ?>
786 + <?php if ($invitation || AuthHelper::isRegistrationEnabled()): ?>
725 787 <div class="fcom_alt_auth_text">
726 788 <?php esc_html_e('Don\'t have an account?', 'fluent-community'); ?>
727 789 <a href="<?php echo esc_url($signupUrl); ?>">
728 790 <?php esc_html_e('Signup', 'fluent-community'); ?>
@@ -759,9 +821,9 @@
759 821 $frameData['defaults'] = [
760 822 'email' => $invitation ? $invitation->message : ''
761 823 ];
762 824
763 - if (AuthHelper::isRegistrationEnabled()) {
825 + if ($invitation || AuthHelper::isRegistrationEnabled()) {
764 826 $frameData['signupUrl'] = $signupUrl;
765 827 }
766 828
767 829 $frameData['settings'] = $formSettings;
@@ -776,8 +838,14 @@
776 838 public function renderRegistrationForm($frameData, $invitation = null)
777 839 {
778 840 $formFields = AuthHelper::getFormFields($invitation);
779 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 +
780 848 $authSettings = AuthenticationService::getAuthSettings();
781 849
782 850 $termsField = Arr::get($authSettings, 'signup.form.fields.terms');
783 851
@@ -826,14 +894,12 @@
826 894 }
827 895
828 896 add_action('fluent_community/before_registration_form', function ($frameData) {
829 897 if (AuthHelper::isFluentAuthAvailable()) {
830 - $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
831 899
832 - ob_start();
833 900 $titlePrefix = __('Signup with', 'fluent-community');
834 - do_shortcode('[fs_auth_buttons redirect="' . $currentUrl . '" title_prefix="' . $titlePrefix . ' " title=""]');
835 - $html = ob_get_clean();
901 + $html = do_shortcode('[fs_auth_buttons redirect="' . $currentUrl . '" title_prefix="' . $titlePrefix . ' " title=""]');
836 902
837 903 if ($html) {
838 904 echo '<div class="fcom_social_auth_wrap">';
839 905 echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped