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 +419 -105 1.0.992.10.0 View file →
@@ -5,14 +5,18 @@
5 5
6 6 use FluentAuth\App\Hooks\Handlers\CustomAuthHandler;
7 7 use FluentCommunity\App\App;
8 8 use FluentCommunity\App\Functions\Utility;
9 +use FluentCommunity\App\Services\AuthenticationService;
9 10 use FluentCommunity\App\Models\BaseSpace;
10 11 use FluentCommunity\App\Models\User;
12 +use FluentCommunity\App\Services\FeedsHelper;
11 13 use FluentCommunity\App\Services\Helper;
12 14 use FluentCommunity\App\Services\ProfileHelper;
13 15 use FluentCommunity\App\Vite;
14 16 use FluentCommunity\Framework\Support\Arr;
17 +use FluentCommunity\Modules\Auth\Classes\Invitation;
18 +use FluentCommunity\Modules\Auth\Classes\InvitationHandler;
15 19 use FluentCommunity\Modules\Auth\Classes\InvitationService;
16 20
17 21 class AuthModdule
18 22 {
@@ -23,64 +27,167 @@
23 27 add_action('wp_ajax_nopriv_fcom_user_registration', [$this, 'handleUserSignup']);
24 28 add_action('wp_ajax_fcom_user_registration', [$this, 'handleUserSignup']);
25 29 add_action('wp_ajax_nopriv_fcom_user_login_form', [$this, 'handleUserLogin']);
26 30 add_action('wp_ajax_fcom_user_login_form', [$this, 'handleUserLogin']);
31 +
32 + add_filter('fluent_auth/login_redirect_url', function ($redirectUrl, $user) {
33 + if (empty($_REQUEST['is_fcom_auth']) || empty($_REQUEST['fcom_redirect'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
34 + return $redirectUrl;
35 + }
36 +
37 + // validate the url
38 + $redirectUrl = wp_validate_redirect(sanitize_url(wp_unslash($_REQUEST['fcom_redirect'])), Helper::baseUrl()); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
39 +
40 + $redirectUrl = apply_filters('fluent_community/auth/after_login_redirect_url', $redirectUrl, $user);
41 + return $redirectUrl;
42 + }, 10, 2);
27 43 }
28 44
29 45 public function maybeAutoLogin($requestData)
30 46 {
31 47 $urlHash = Arr::get($requestData, 'fcom_url_hash');
32 - if ($urlHash) {
48 + if ($urlHash && !get_current_user_id()) {
33 49 $tagetUser = ProfileHelper::getUserByUrlHash($urlHash);
34 -
35 50 if ($tagetUser) {
36 51 $willAtoLogin = apply_filters('fluent_community/allow_auto_login_by_url', !user_can($tagetUser, 'delete_pages'), $tagetUser);
37 - // $willAtoLogin = true;
38 52 if ($willAtoLogin) {
39 - InvitationService::makeLogin($tagetUser);
53 + try {
54 + InvitationService::makeLogin($tagetUser);
55 + } catch (\Throwable $e) {
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 + }
59 + }
40 60 }
41 61 }
42 62 }
43 63
44 64 // Remove fcom_action and fcom_url_hash from the current url
45 - $currentUrl = home_url(add_query_arg($_GET, $GLOBALS['wp']->request));
65 + $currentUrl = home_url(add_query_arg($_GET, $GLOBALS['wp']->request)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
46 66 $url = remove_query_arg(['fcom_action', 'fcom_url_hash'], $currentUrl);
47 - wp_redirect($url);
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
48 89 exit();
49 90 }
50 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 +
51 111 public function viewAuthPage()
52 112 {
113 +
114 + add_filter('login_form_defaults', function ($defaults) {
115 + $defaults['label_username'] = __('Email Address', 'fluent-community');
116 + return $defaults;
117 + });
118 +
119 + add_filter('fluent_community/has_color_scheme', '__return_false');
120 +
53 121 $currentUserId = get_current_user_id();
54 122 // check if there has any invitation token
55 - $inivtationToken = Arr::get($_GET, 'invitation_token');
123 + $inivtationToken = Arr::get($_GET, 'invitation_token'); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
56 124
57 125 $inviation = null;
58 126 if ($inivtationToken) {
59 127 $inviation = apply_filters('fluent_community/auth/invitation', null, $inivtationToken);
128 + if ($inviation && !$inviation->isValid()) {
129 + $inviation = null;
130 + }
60 131 }
61 132
62 133 if ($currentUserId && !$inviation) {
63 - wp_redirect(Helper::baseUrl());
64 - exit();
134 + $redirectUrl = null;
135 + if (!empty($_REQUEST['redirect_to'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
136 + $redirectUrl = sanitize_url(wp_unslash($_REQUEST['redirect_to'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
137 + }
138 + if (!$redirectUrl) {
139 + $redirectUrl = Helper::baseUrl();
140 + }
141 +
142 + $this->safeRedirectAndExit($redirectUrl);
65 143 }
66 144
145 + if ($currentUserId && $inviation) {
146 + /** @var BaseSpace|null $space */
147 + $space = BaseSpace::withoutGlobalScopes()->find($inviation->post_id);
148 + if ($space) {
149 + if (Helper::isUserInSpace($currentUserId, $inviation->post_id)) {
150 + // let's redirect the user to the space
151 + $redirectUrl = $space->getPermalink();
152 + $this->safeRedirectAndExit($redirectUrl);
153 + }
154 +
155 + if (!empty($_REQUEST['auto_accept'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
156 + $redirectUrl = (new InvitationHandler())->handleInvitationLogin(Helper::baseUrl(), get_user_by('ID', $currentUserId), $inviation->message_rendered);
157 + if (is_wp_error($redirectUrl) || !$redirectUrl) {
158 + $redirectUrl = Helper::baseUrl();
159 + }
160 + $this->safeRedirectAndExit($redirectUrl);
161 + }
162 + }
163 + }
164 +
67 165 do_action('fluent_community/auth/before_auth_page_process', $currentUserId, $inviation);
68 166
69 167 $acceptedForms = ['login', 'register', 'reset_password'];
70 - $targetForm = Arr::get($_GET, 'form');
71 - if (!in_array($targetForm, $acceptedForms)) {
168 + $targetForm = Arr::get($_GET, 'form'); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
169 + $explicitForm = in_array($targetForm, $acceptedForms, true);
170 + if (!$explicitForm) {
72 171 $targetForm = 'login';
73 172 }
74 173
75 - if ($inviation && $targetForm != 'reset_password') {
76 - $isUserAvailable = get_user_by('email', $inviation->message);
77 - $targetForm = $isUserAvailable ? 'login' : 'register';
174 + if ($inviation && !$explicitForm) {
175 + if ($inviation->message) {
176 + $isUserAvailable = get_user_by('email', $inviation->message);
177 + $targetForm = $isUserAvailable ? 'login' : 'register';
178 + } else {
179 + $targetForm = 'register';
180 + }
78 181 }
79 182
80 - if ($inviation && $currentUserId) {
81 - $invitedUser = get_user_by('email', $inviation->message);
82 - if ($invitedUser && $invitedUser->ID == $currentUserId) {
183 + if ($inviation && $currentUserId && $inviation->isValid()) {
184 + if ($inviation->message) {
185 + $invitedUser = get_user_by('email', $inviation->message);
186 + if ($invitedUser && $invitedUser->ID == $currentUserId) {
187 + $targetForm = 'accept_invitation';
188 + }
189 + } else {
83 190 $targetForm = 'accept_invitation';
84 191 }
85 192 }
86 193
@@ -85,10 +192,9 @@
85 192 }
86 193
87 194 $isFluentAuth = AuthHelper::isFluentAuthAvailable();
88 195 if (!$isFluentAuth && $targetForm == 'reset_password') {
89 - wp_redirect(wp_lostpassword_url(Helper::baseUrl()));
90 - exit();
196 + $this->safeRedirectAndExit(wp_lostpassword_url(Helper::baseUrl()));
91 197 }
92 198
93 199 $portalSettings = Helper::generalSettings();
94 200 $titleVar = Arr::get($portalSettings, 'site_title');
@@ -93,60 +199,86 @@
93 199 $portalSettings = Helper::generalSettings();
94 200 $titleVar = Arr::get($portalSettings, 'site_title');
95 201
96 202 $frameData = [
97 - 'logo' => Arr::get($portalSettings, 'logo', ''),
98 - 'title' => sprintf(__('Join %s', 'fluent-community'), $titleVar),
99 - 'description' => __('Login or Signup to join the community', 'fluent-community'),
100 - 'loginBtnText' => __('Login', 'fluent-community'),
101 - 'signupBtnText' => __('Signup', 'fluent-community'),
203 + 'logo' => Arr::get($portalSettings, 'logo', ''),
204 + /* translators: %s is replaced by the title of the site */
205 + 'title' => sprintf(__('Join %s', 'fluent-community'), $titleVar),
206 + 'description' => __('Login or Signup to join the community', 'fluent-community'),
207 + 'button_label' => __('Login', 'fluent-community'),
102 208 ];
103 209
104 - $currentUrl = home_url(add_query_arg($_GET, $GLOBALS['wp']->request));
210 + if ($targetForm == 'register') {
211 + $frameData['button_label'] = __('Signup', 'fluent-community');
212 + if (!$inviation) {
213 + $customSignupUrl = Arr::get($portalSettings, 'custom_signup_url');
214 + if ($customSignupUrl) {
215 + $this->safeRedirectAndExit($customSignupUrl);
216 + }
217 + }
218 + }
105 219
220 + $currentUrl = home_url(add_query_arg($_GET, $GLOBALS['wp']->request)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
221 +
222 + do_action('fluent_community/enqueue_global_assets', true);
223 + add_action('wp_enqueue_scripts', function () use ($isFluentAuth, $targetForm, $inviation) {
224 + wp_enqueue_style('fluent_auth_styles', Vite::getStaticSrcUrl('user_registration.css'), [], FLUENT_COMMUNITY_PLUGIN_VERSION);
225 + if(!$isFluentAuth || $targetForm == 'register' || $inviation) {
226 + wp_enqueue_script('fluent_auth_scripts', Vite::getStaticSrcUrl('user_registration.js'), [], FLUENT_COMMUNITY_PLUGIN_VERSION, true);
227 + wp_localize_script('fluent_auth_scripts', 'fluentComRegistration', array(
228 + 'ajax_url' => admin_url('admin-ajax.php'),
229 + 'is_logged_in' => is_user_logged_in(),
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 + ]
235 + ));
236 + }
237 + }, 10);
238 +
106 239 $pageVars = [
107 240 'title' => $frameData['title'],
241 + 'og_title' => $frameData['title'],
108 242 'description' => $frameData['description'],
109 243 'url' => $currentUrl,
110 244 'featured_image' => '',
111 - 'css_files' => [
112 - Vite::getDynamicSrcUrl('theme-default.scss'),
113 - Vite::getStaticSrcUrl('user_registration.css')
114 - ],
115 - 'js_files' => [
116 - Vite::getStaticSrcUrl('user_registration.js')
117 - ],
118 - 'js_vars' => [
119 - 'fluentComRegistration' => [
120 - 'ajax_url' => admin_url('admin-ajax.php'),
121 - 'is_logged_in' => is_user_logged_in(),
122 - ]
123 - ],
245 + 'css_files' => [],
246 + 'js_files' => [],
247 + 'js_vars' => [],
124 248 'scope' => 'user_registration',
125 249 'layout' => 'signup',
126 250 'portal' => [
127 251 'logo' => Arr::get($portalSettings, 'logo', ''),
128 - 'title' => \sprintf( __('Welcome to %s', 'fluent-community'), Arr::get($portalSettings, 'site_title') ),
252 + /* translators: %s is replaced by the title of the site */
253 + 'title' => \sprintf(__('Welcome to %s', 'fluent-community'), Arr::get($portalSettings, 'site_title')),
129 254 'description' => get_bloginfo('description')
130 255 ]
131 256 ];
132 257
133 - if ($isFluentAuth) {
134 - $pageVars['js_files'][] = FLUENT_AUTH_PLUGIN_URL . 'dist/public/login_helper.js';
135 - $pageVars['js_vars']['fluentAuthPublic'] = [
136 - 'hide' => false,
137 - 'redirect_fallback' => site_url(),
138 - 'fls_login_nonce' => wp_create_nonce('fsecurity_login_nonce'),
139 - 'ajax_url' => admin_url('admin-ajax.php'),
140 - 'i18n' => [
141 - 'Username_or_Email' => __('Email Address', 'fluent-community'),
142 - 'Password' => __('Password', 'fluent-community')
143 - ]
258 + if (Utility::isDev()) {
259 + $pageVars['js_files'] = [
260 + Vite::getStaticSrcUrl('public/js/user_registration.js')
144 261 ];
145 262 }
146 263
147 - add_action('fluent_community/headless/content', function ($context) use ($targetForm, $currentUrl, $frameData, $inviation) {
264 + $formType = ($targetForm == 'register') ? 'signup' : 'login';
265 +
266 + $formSettings = AuthenticationService::getFormattedAuthSettings($formType);
267 +
268 + if ($formSettings) {
269 + $pageVars['portal'] = Arr::get($formSettings, 'banner');
270 + $pageVars['portal']['form'] = Arr::get($formSettings, 'form');
271 + }
272 +
273 + add_action('fluent_community/headless/content', function ($context) use ($targetForm, $currentUrl, $frameData, $inviation, $formSettings) {
274 + $preContent = apply_filters('fluent_community/auth/pre_content', '', $context, $targetForm, $frameData);
275 + if ($preContent) {
276 + return;
277 + }
278 +
148 279 if ($targetForm == 'login') {
280 + $frameData['button_label'] = Arr::get($formSettings, 'form.button_label', __('Login', 'fluent-community'));
149 281 $this->showLoginForm($frameData, $inviation);
150 282 } else if ($targetForm == 'reset_password') {
151 283 $frameData['title'] = __('Reset your password', 'fluent-community');
152 284 ?>
@@ -154,8 +286,11 @@
154 286 <div class="fcom_onboard_header">
155 287 <div class="fcom_onboard_header_title">
156 288 <h2><?php echo esc_html($frameData['title']); ?></h2>
157 289 </div>
290 + <div class="fcom_onboard_sub">
291 + <p><?php esc_html_e('Please enter your email address. You will receive an email message with instructions on how to reset your password.', 'fluent-community'); ?></p>
292 + </div>
158 293 </div>
159 294 <div class="fcom_onboard_body">
160 295 <div class="fcom_onboard_form">
161 296 <?php echo do_shortcode('[fluent_auth_reset_password redirect_to="' . esc_url($currentUrl) . '"]'); ?>
@@ -161,9 +296,9 @@
161 296 <?php echo do_shortcode('[fluent_auth_reset_password redirect_to="' . esc_url($currentUrl) . '"]'); ?>
162 297 <div class="fcom_spaced_divider">
163 298 <div class="fcom_alt_auth_text">
164 299 <a href="<?php echo esc_url(add_query_arg('form', 'login', $currentUrl)); ?>">
165 - <?php _e('Back to Login', 'fluent-community'); ?>
300 + <?php esc_html_e('Back to Login', 'fluent-community'); ?>
166 301 </a>
167 302 </div>
168 303 </div>
169 304 </div>
@@ -170,13 +305,13 @@
170 305 </div>
171 306 </div>
172 307 <?php
173 308 } else if ($targetForm == 'accept_invitation') {
174 - do_action('fluent_community/auth/show_inviration_for_user', $inviation, $frameData);
309 + do_action('fluent_community/auth/show_invitation_for_user', $inviation, $frameData);
175 310 } else {
176 - //check if the registration is disabled
177 - if (!AuthHelper::isRegistrationEnabled()) {
178 - echo '<div class="fcom_completed"><div class="fcom_complted_header"><h4>' . __('Registration is disabled for this community', 'fluent-community') . '</h4>';
311 + //check if the registration is disabled (a valid invitation still allows signup)
312 + if (!$inviation && !AuthHelper::isRegistrationEnabled()) {
313 + echo '<div class="fcom_completed"><div class="fcom_complted_header"><h4>' . esc_html__('Registration is disabled for this community', 'fluent-community') . '</h4>';
179 314 return;
180 315 }
181 316
182 317 $frameData['hiddenFields'] = [
@@ -181,20 +316,45 @@
181 316
182 317 $frameData['hiddenFields'] = [
183 318 'register' => 'yes',
184 319 'action' => 'fcom_user_signup',
185 - '_fls_signup_nonce' => wp_create_nonce('fluent_auth_signup_nonce')
320 + '_fcom_signup_nonce' => wp_create_nonce('fluent_auth_signup_nonce')
186 321 ];
187 322
188 323 $frameData['loginUrl'] = add_query_arg('form', 'login', $currentUrl);
189 - $frameData['description'] = __('Create an account to join the community', 'fluent-community');
324 + $frameData = wp_parse_args(Arr::get($formSettings, 'form'), $frameData);
325 +
190 326 $this->renderRegistrationForm($frameData, $inviation);
191 327 }
192 328 }, 10, 1);
193 329
194 - status_header(200);
195 - App::make('view')->render('headless_page', $pageVars);
196 - exit(200);
330 + add_action('fluent_community/headless/head_early', function ($scope) use ($formSettings) {
331 + $bannerColors = array_filter(Arr::only($formSettings['banner'], ['title_color', 'text_color', 'background_color']));
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" />
341 + <link rel="canonical" href="<?php echo esc_url(Helper::getAuthUrl()); ?>" />
342 + <style>
343 + .fcom_layout_side { <?php echo esc_html($sideVars); ?> }
344 + <?php echo esc_html($css); ?>
345 + </style>
346 + <?php
347 + });
348 +
349 + $pageVars['load_wp'] = 'yes';
350 +
351 + // document title hook
352 + add_filter('pre_get_document_title', function ($title) use ($frameData) {
353 + return $frameData['title'];
354 + }, 9999, 1);
355 +
356 + $this->renderPageAndExit('headless_page', $pageVars);
197 357 }
198 358
199 359 public function handleUserSignup()
200 360 {
@@ -201,11 +361,26 @@
201 361 if (is_user_logged_in()) {
202 362 return $this->handleSignupCompleted(get_current_user_id());
203 363 }
204 364
205 - if (!AuthHelper::isRegistrationEnabled()) {
365 + $signupNonce = isset($_POST['_fcom_signup_nonce']) ? sanitize_text_field(wp_unslash($_POST['_fcom_signup_nonce'])) : '';
366 + if (!$signupNonce || !wp_verify_nonce($signupNonce, 'fluent_auth_signup_nonce')) {
206 367 wp_send_json([
207 - 'message' => __('Registration is disabled for this community', 'fluent-community')
368 + 'message' => esc_html__('Invalid request. Please refresh the page and try again.', 'fluent-community')
369 + ], 403);
370 + }
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')
208 383 ], 422);
209 384 }
210 385
211 386 $app = App::make('app');
@@ -211,10 +386,15 @@
211 386 $app = App::make('app');
212 387 $request = $app->make('request');
213 388 $fields = AuthHelper::getFormFields();
214 389
390 + $authSettings = AuthenticationService::getAuthSettings();
391 + $termsField = Arr::get($authSettings, 'signup.form.fields.terms');
392 +
393 + $fields['terms'] = $termsField ?: $fields['terms'];
394 +
215 395 $requiredFields = array_filter($fields, function ($field) {
216 - return $field['required'] ?? false;
396 + return ($field['required'] && empty($field['disabled'])) ?? false;
217 397 });
218 398
219 399 $keys = array_keys($fields);
220 400 $data = Arr::only($request->all(), $keys);
@@ -223,9 +403,9 @@
223 403 $data['username'] = sanitize_user(strtolower(preg_replace('/[^A-Za-z0-9_]/', '', $data['username'])));
224 404
225 405 if (empty($data['username'])) {
226 406 wp_send_json([
227 - 'message' => __('Username is not valid', 'fluent-community'),
407 + 'message' => esc_html__('Username is not valid', 'fluent-community'),
228 408 'errors' => [
229 409 'username' => __('Please provide a valid username', 'fluent-community')
230 410 ]
231 411 ], 422);
@@ -232,9 +412,9 @@
232 412 }
233 413
234 414 if (!ProfileHelper::isUsernameAvailable($data['username'])) {
235 415 wp_send_json([
236 - 'message' => __('Username is already taken', 'fluent-community'),
416 + 'message' => esc_html__('Username is already taken', 'fluent-community'),
237 417 'errors' => [
238 418 'username' => __('Username is already taken. Please use a different username', 'fluent-community')
239 419 ]
240 420 ], 422);
@@ -239,8 +419,37 @@
239 419 ]
240 420 ], 422);
241 421 }
242 422
423 + $invitationToken = $request->get('invitation_token');
424 + $invitation = null;
425 + if ($invitationToken) {
426 + $invitation = Invitation::where('message_rendered', $invitationToken)->first();
427 + if (!$invitation) {
428 + wp_send_json([
429 + 'message' => __('Invalid invitation token', 'fluent-community')
430 + ], 422);
431 + }
432 +
433 + if ($invitation->message && $invitation->message != $data['email']) {
434 + wp_send_json([
435 + 'message' => esc_html__('Email does not match with the invitation', 'fluent-community')
436 + ], 422);
437 + }
438 +
439 + if ($invitation->message) {
440 + add_filter('fluent_community/auth/two_factor_enabled', '__return_false');
441 + add_filter('fluent_auth/verify_signup_email', '__return_false');
442 + }
443 + }
444 +
445 + if (!$invitation && AuthenticationService::getCustomSignupPageUrl()) {
446 + // we have custom signup page enabled
447 + wp_send_json([
448 + 'message' => esc_html__('Direct Registration is disabled for this community', 'fluent-community')
449 + ], 422);
450 + }
451 +
243 452 $data['email'] = sanitize_email($data['email']);
244 453
245 454 $validations = [
246 455 'full_name' => 'required|max:100|string',
@@ -298,16 +507,8 @@
298 507 unset($data['full_name']);
299 508 $data = array_filter($data);
300 509 }
301 510
302 - if (AuthHelper::isFluentAuthAvailable()) {
303 - $data = wp_parse_args($data, $request->all());
304 - $this->handleSignupViaFlentAuth($data);
305 - wp_send_json([
306 - 'message' => __('Something is not working! Please try again', 'fluent-community')
307 - ], 422);
308 - }
309 -
310 511 $rateLimit = AuthHelper::isAuthRateLimit();
311 512
312 513 if (is_wp_error($rateLimit)) {
313 514 wp_send_json([
@@ -359,16 +560,14 @@
359 560 }
360 561
361 562 private function handleSignupViaFlentAuth($data)
362 563 {
363 - add_filter('fluent_auth/signup_form_data', function ($requestData) use ($data) {
364 - return $data;
365 - });
366 -
367 564 add_action('fluent_auth/after_creating_user', function ($userId) {
368 565 $this->handleSignupCompleted($userId);
369 566 }, 1, 1);
370 567
568 + add_filter('fluent_auth/signup_enabled', '__return_true');
569 +
371 570 (new CustomAuthHandler())->handleSignupAjax();
372 571 }
373 572
374 573 private function handleSignupCompleted($userId)
@@ -378,14 +577,18 @@
378 577 $user->syncXProfile(true, true);
379 578
380 579 $redirectUrl = Helper::baseUrl();
381 580
382 - $redirectUrl = apply_filters('fluent_community/auth/after_signup_redirect_url', $redirectUrl, $user, $_REQUEST);
581 + if (!empty($_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
583 + }
584 +
585 + $redirectUrl = apply_filters('fluent_community/auth/after_signup_redirect_url', $redirectUrl, $user, $_REQUEST); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
383 586 $btnText = __('Continue to the community', 'fluent-community');
384 587
385 588 $html = '<div class="fcom_completed"><div class="fcom_complted_header"><h2>' . __('Congratulations!', 'fluent-community') . '</h2>';
386 589 $html .= '<p>' . __('You have successfully registered to the community', 'fluent-community') . '</p></div>';
387 - $html .= '<a href="' . $redirectUrl . '" class="fcom_btn fcom_btn_success">' . $btnText . '</a>';
590 + $html .= '<a href="' . esc_url($redirectUrl) . '" class="fcom_btn fcom_btn_success">' . $btnText . '</a>';
388 591 $html .= '</div>';
389 592
390 593 if (!get_current_user_id()) {
391 594 $wpUser = get_user_by('ID', $userId);
@@ -410,8 +613,15 @@
410 613 'message' => __('This form cannot be used to log in. Please reload the page and try again.', 'fluent-community')
411 614 ], 422);
412 615 }
413 616
617 + $loginNonce = isset($_POST['_fcom_login_nonce']) ? sanitize_text_field(wp_unslash($_POST['_fcom_login_nonce'])) : '';
618 + if (!$loginNonce || !wp_verify_nonce($loginNonce, 'fcom_user_login_nonce')) {
619 + wp_send_json([
620 + 'message' => esc_html__('Invalid request. Please refresh the page and try again.', 'fluent-community')
621 + ], 403);
622 + }
623 +
414 624 $app = App::make('app');
415 625 $request = $app->make('request');
416 626
417 627 $data = $request->all();
@@ -440,10 +650,16 @@
440 650
441 651 $user = wp_authenticate($data['log'], $data['pwd']);
442 652
443 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 + }
444 660 wp_send_json([
445 - 'message' => $user->get_error_message()
661 + 'message' => $message
446 662 ], 422);
447 663 }
448 664
449 665 InvitationService::makeLogin($user);
@@ -448,10 +664,10 @@
448 664
449 665 InvitationService::makeLogin($user);
450 666
451 667 $redirectUrl = null;
452 - if (!empty($_REQUEST['redirect_to'])) {
453 - $redirectUrl = sanitize_url($_REQUEST['redirect_to']);
668 + if (!empty($_REQUEST['redirect_to'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
669 + $redirectUrl = wp_validate_redirect(sanitize_url(wp_unslash($_REQUEST['redirect_to'])), Helper::baseUrl()); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
454 670 }
455 671
456 672 if (!$redirectUrl) {
457 673 $redirectUrl = Helper::baseUrl();
@@ -477,9 +693,9 @@
477 693 $btnText = __('Continue to the community', 'fluent-community');
478 694
479 695 $html = '<div class="fcom_completed"><div class="fcom_complted_header"><h2>' . __('Welcome back!', 'fluent-community') . '</h2>';
480 696 $html .= '<p>' . __('You have successfully logged in to the community', 'fluent-community') . '</p></div>';
481 - $html .= '<a href="' . $redirectUrl . '" class="fcom_btn fcom_btn_success">' . $btnText . '</a>';
697 + $html .= '<a href="' . esc_url($redirectUrl) . '" class="fcom_btn fcom_btn_success">' . $btnText . '</a>';
482 698 $html .= '</div>';
483 699
484 700 wp_send_json([
485 701 'success_html' => $html,
@@ -490,9 +706,12 @@
490 706 public function showLoginForm($frameData, $invitation = null)
491 707 {
492 708 $portalSettings = Helper::generalSettings();
493 709 $isFluentAuth = AuthHelper::isFluentAuthAvailable();
494 - $currentUrl = home_url(add_query_arg($_GET, $GLOBALS['wp']->request));
710 + $loginSettings = AuthenticationService::getFormattedAuthSettings('login');
711 + $formSettings = Arr::get($loginSettings, 'form');
712 + $currentUrl = home_url(add_query_arg($_GET, $GLOBALS['wp']->request)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
713 + /* translators: %s is replaced by the title of the site */
495 714 $title = sprintf(__('Login to %s', 'fluent-community'), Arr::get($portalSettings, 'site_title'));
496 715
497 716 $description = '';
498 717 if ($invitation) {
@@ -502,21 +721,62 @@
502 721 if ($space) {
503 722 $title = $space->title . ' - ' . Arr::get($portalSettings, 'site_title');
504 723 }
505 724 }
506 - $description = sprintf(__('%s has invited you to join this community. Please login to accept your invitation.', 'fluent-community'), $invitationBy);
725 + /* translators: %s is replaced by the name of the inviter */
726 + $inviteDescription = \sprintf(__('%s has invited you to join this community. Please login to accept your invitation.', 'fluent-community'), $invitationBy);
727 + add_action('fluent_community/before_auth_form_header', function ($formType) use ($inviteDescription) {
728 + ?>
729 + <div class="fcom_highlight_message">
730 + <?php echo wp_kses_post($inviteDescription); ?>
731 + </div>
732 + <?php
733 + });
507 734 }
508 735
736 + $signupUrl = add_query_arg('form', 'register', $currentUrl);
737 +
738 + if (!$invitation) {
739 + if ($customSignupUrl = AuthenticationService::getCustomSignupPageUrl()) {
740 + $signupUrl = $customSignupUrl;
741 + }
742 + }
743 +
744 + add_filter('login_form_defaults', function ($defaults) use ($invitation, $frameData) {
745 + $defaults['label_log_in'] = Arr::get($frameData, 'button_label');
746 + return $defaults;
747 + });
748 +
509 749 if ($isFluentAuth) {
750 + add_filter('login_form_top', function () use ($invitation) {
751 + $reditectUrl = Arr::get($_GET, 'redirect_to'); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
752 + if (!$reditectUrl) {
753 + $reditectUrl = apply_filters('fluent_community/default_redirect_url', Helper::baseUrl());
754 + }
755 + ob_start();
756 + ?>
757 + <?php if ($invitation) { ?>
758 + <input type="hidden" name="invitation_token" value="<?php echo esc_attr($invitation->message_rendered); ?>"/>
759 + <?php } ?>
760 + <input name="is_fcom_auth" type="hidden" value="yes"/>
761 + <input type="hidden" name="fcom_redirect" value="<?php echo esc_url($reditectUrl); ?>"/>
762 + <?php
763 + return ob_get_clean();
764 + });
510 765 ?>
511 766 <div id="fcom_user_onboard_wrap" class="fcom_user_onboard">
512 767 <div class="fcom_onboard_header">
768 + <?php do_action('fluent_community/before_auth_form_header', 'login'); ?>
513 769 <div class="fcom_onboard_header_title">
514 - <h2><?php echo esc_html($title); ?></h2>
770 + <?php if (!empty($formSettings['title'])): ?>
771 + <h2>
772 + <?php echo esc_html($formSettings['title']); ?>
773 + </h2>
774 + <?php endif; ?>
515 775 </div>
516 - <?php if ($description): ?>
776 + <?php if (!empty($formSettings['description'])): ?>
517 777 <div class="fcom_onboard_sub">
518 - <p><?php echo wp_kses_post($description); ?></p>
778 + <?php echo wp_kses_post(trim($formSettings['description'])); ?>
519 779 </div>
520 780 <?php endif; ?>
521 781 </div>
522 782 <div class="fcom_onboard_body">
@@ -522,19 +782,19 @@
522 782 <div class="fcom_onboard_body">
523 783 <div class="fcom_onboard_form">
524 784 <?php echo do_shortcode('[fluent_auth_login redirect_to="' . esc_url($currentUrl) . '"]'); ?>
525 785 <div class="fcom_spaced_divider">
526 - <?php if (AuthHelper::isRegistrationEnabled()): ?>
786 + <?php if ($invitation || AuthHelper::isRegistrationEnabled()): ?>
527 787 <div class="fcom_alt_auth_text">
528 - <?php _e('Don\'t have an account?', 'fluent-community'); ?>
529 - <a href="<?php echo esc_url(add_query_arg('form', 'register', $currentUrl)); ?>">
530 - <?php _e('Signup', 'fluent-community'); ?>
788 + <?php esc_html_e('Don\'t have an account?', 'fluent-community'); ?>
789 + <a href="<?php echo esc_url($signupUrl); ?>">
790 + <?php esc_html_e('Signup', 'fluent-community'); ?>
531 791 </a>
532 792 </div>
533 793 <?php endif; ?>
534 794 <p class="fcom_reset_pass_text">
535 - <a href="<?php echo esc_url(add_query_arg('form', 'reset_password', $currentUrl)); ?>">
536 - <?php _e('Lost your password?', 'fluent-community'); ?>
795 + <a href="<?php echo esc_url(AuthHelper::getLostPasswordUrl($currentUrl)); ?>">
796 + <?php esc_html_e('Lost your password?', 'fluent-community'); ?>
537 797 </a>
538 798 </p>
539 799 </div>
540 800 </div>
@@ -545,11 +805,14 @@
545 805 }
546 806
547 807 $frameData['redirect'] = $currentUrl;
548 808
549 - $frameData['hiddenFields'] = [];
809 + $frameData['hiddenFields'] = [
810 + 'action' => 'fcom_user_login_form',
811 + '_fcom_login_nonce' => wp_create_nonce('fcom_user_login_nonce'),
812 + ];
550 813 if ($invitation) {
551 - $frameData['loginBtnText'] = __('Log In & Accept Invitation', 'fluent-community');
814 + $frameData['button_label'] = __('Log In & Accept Invitation', 'fluent-community');
552 815 $frameData['hiddenFields']['invitation_token'] = $invitation->message_rendered;
553 816 }
554 817
555 818 $frameData['title'] = $title;
@@ -558,14 +821,16 @@
558 821 $frameData['defaults'] = [
559 822 'email' => $invitation ? $invitation->message : ''
560 823 ];
561 824
562 - if (AuthHelper::isRegistrationEnabled()) {
563 - $frameData['signupUrl'] = add_query_arg('form', 'register', $currentUrl);
825 + if ($invitation || AuthHelper::isRegistrationEnabled()) {
826 + $frameData['signupUrl'] = $signupUrl;
564 827 }
565 828
566 - if (isset($_GET['redirect_to'])) {
567 - $frameData['redirect_to'] = sanitize_url($_GET['redirect_to']);
829 + $frameData['settings'] = $formSettings;
830 +
831 + if (isset($_GET['redirect_to'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
832 + $frameData['redirect_to'] = sanitize_url(wp_unslash($_GET['redirect_to'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
568 833 }
569 834
570 835 App::make('view')->render('auth.login_form', $frameData);
571 836 }
@@ -573,8 +838,28 @@
573 838 public function renderRegistrationForm($frameData, $invitation = null)
574 839 {
575 840 $formFields = AuthHelper::getFormFields($invitation);
576 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 +
848 + $authSettings = AuthenticationService::getAuthSettings();
849 +
850 + $termsField = Arr::get($authSettings, 'signup.form.fields.terms');
851 +
852 +
853 + if ($termsField) {
854 + unset($termsField['label']);
855 +
856 + // add new tab on the link for $termsField['inline_label']
857 + $termsField['inline_label'] = FeedsHelper::addNewTabToLinks($termsField['inline_label']);
858 +
859 + $formFields['terms'] = $termsField;
860 + }
861 +
577 862 $frameData['formFields'] = $formFields;
578 863
579 864 if ($invitation) {
580 865 $frameData['hiddenFields'] = [
@@ -579,21 +864,50 @@
579 864 if ($invitation) {
580 865 $frameData['hiddenFields'] = [
581 866 'invitation_token' => $invitation->message_rendered,
582 867 'action' => 'fcom_user_registration',
583 - '_fls_signup_nonce' => wp_create_nonce('fluent_auth_signup_nonce')
868 + '_fcom_signup_nonce' => wp_create_nonce('fluent_auth_signup_nonce')
584 869 ];
585 870
586 871 $invitationBy = $invitation->xprofile ? $invitation->xprofile->display_name : __('Someone', 'fluent-community');
587 - $frameData['description'] = sprintf(__('%s has invited you to join this community. Please create an account to accept your invitation.', 'fluent-community'), $invitationBy);
588 - $frameData['signupBtnText'] = __('Register & Accept invitation', 'fluent-community');
872 + /* translators: %s is replaced by the name of the inviter */
873 + $inviteDescription = sprintf(__('%s has invited you to join this community. Please create an account to accept your invitation.', 'fluent-community'), $invitationBy);
874 +
875 + add_action('fluent_community/before_auth_form_header', function ($formType) use ($inviteDescription) {
876 + ?>
877 + <div class="fcom_highlight_message">
878 + <?php echo wp_kses_post($inviteDescription); ?>
879 + </div>
880 + <?php
881 + });
882 +
883 + $frameData['button_label'] = __('Register & Accept invitation', 'fluent-community');
589 884 } else {
590 885 $frameData['hiddenFields'] = [
591 886 'register' => 'yes',
592 887 'action' => 'fcom_user_registration',
593 - '_fls_signup_nonce' => wp_create_nonce('fluent_auth_signup_nonce')
888 + '_fcom_signup_nonce' => wp_create_nonce('fluent_auth_signup_nonce'),
594 889 ];
890 +
891 + if (!empty($_GET['redirect_to'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
892 + $frameData['hiddenFields']['redirect_to'] = sanitize_url(wp_unslash($_GET['redirect_to'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
893 + }
595 894 }
895 +
896 + add_action('fluent_community/before_registration_form', function ($frameData) {
897 + if (AuthHelper::isFluentAuthAvailable()) {
898 + $currentUrl = esc_url(home_url(add_query_arg($_GET, $GLOBALS['wp']->request))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
899 +
900 + $titlePrefix = __('Signup with', 'fluent-community');
901 + $html = do_shortcode('[fs_auth_buttons redirect="' . $currentUrl . '" title_prefix="' . $titlePrefix . ' " title=""]');
902 +
903 + if ($html) {
904 + echo '<div class="fcom_social_auth_wrap">';
905 + echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
906 + echo '</div>';
907 + }
908 + }
909 + });
596 910
597 911 App::make('view')->render('auth.user_invitation', $frameData);
598 912 }
599 913 }