PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.8.1
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.8.1
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
fluent-community / Modules / Auth / AuthModdule.php

AuthModdule.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 2.8.1, at Modules/Auth/AuthModdule.php

875 lines 38.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3
4 namespace FluentCommunity\Modules\Auth;
5
6 use FluentAuth\App\Hooks\Handlers\CustomAuthHandler;
7 use FluentCommunity\App\App;
8 use FluentCommunity\App\Functions\Utility;
9 use FluentCommunity\App\Services\AuthenticationService;
10 use FluentCommunity\App\Models\BaseSpace;
11 use FluentCommunity\App\Models\User;
12 use FluentCommunity\App\Services\FeedsHelper;
13 use FluentCommunity\App\Services\Helper;
14 use FluentCommunity\App\Services\ProfileHelper;
15 use FluentCommunity\App\Vite;
16 use FluentCommunity\Framework\Support\Arr;
17 use FluentCommunity\Modules\Auth\Classes\Invitation;
18 use FluentCommunity\Modules\Auth\Classes\InvitationHandler;
19 use FluentCommunity\Modules\Auth\Classes\InvitationService;
20
21 class AuthModdule
22 {
23 public function register($app)
24 {
25 add_action('fluent_community/portal_action_signed_url', [$this, 'maybeAutoLogin'], 10, 1);
26 add_action('fluent_community/portal_action_auth', [$this, 'viewAuthPage']);
27 add_action('wp_ajax_nopriv_fcom_user_registration', [$this, 'handleUserSignup']);
28 add_action('wp_ajax_fcom_user_registration', [$this, 'handleUserSignup']);
29 add_action('wp_ajax_nopriv_fcom_user_login_form', [$this, 'handleUserLogin']);
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);
43 }
44
45 public function maybeAutoLogin($requestData)
46 {
47 $urlHash = Arr::get($requestData, 'fcom_url_hash');
48 if ($urlHash && !get_current_user_id()) {
49 $tagetUser = ProfileHelper::getUserByUrlHash($urlHash);
50 if ($tagetUser) {
51 $willAtoLogin = apply_filters('fluent_community/allow_auto_login_by_url', !user_can($tagetUser, 'delete_pages'), $tagetUser);
52 if ($willAtoLogin) {
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 }
60 }
61 }
62 }
63
64 // Remove fcom_action and fcom_url_hash from the current url
65 $currentUrl = home_url(add_query_arg($_GET, $GLOBALS['wp']->request)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
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
68 exit();
69 }
70
71 public function viewAuthPage()
72 {
73
74 add_filter('login_form_defaults', function ($defaults) {
75 $defaults['label_username'] = __('Email Address', 'fluent-community');
76 return $defaults;
77 });
78
79 add_filter('fluent_community/has_color_scheme', '__return_false');
80
81 $currentUserId = get_current_user_id();
82 // check if there has any invitation token
83 $inivtationToken = Arr::get($_GET, 'invitation_token'); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
84
85 $inviation = null;
86 if ($inivtationToken) {
87 $inviation = apply_filters('fluent_community/auth/invitation', null, $inivtationToken);
88 if ($inviation && !$inviation->isValid()) {
89 $inviation = null;
90 }
91 }
92
93 if ($currentUserId && !$inviation) {
94 $redirectUrl = null;
95 if (!empty($_REQUEST['redirect_to'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
96 $redirectUrl = sanitize_url(wp_unslash($_REQUEST['redirect_to'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
97 }
98 if (!$redirectUrl) {
99 $redirectUrl = Helper::baseUrl();
100 }
101
102 wp_safe_redirect($redirectUrl);
103 exit();
104 }
105
106 if ($currentUserId && $inviation) {
107 /** @var BaseSpace|null $space */
108 $space = BaseSpace::withoutGlobalScopes()->find($inviation->post_id);
109 if ($space) {
110 if (Helper::isUserInSpace($currentUserId, $inviation->post_id)) {
111 // let's redirect the user to the space
112 $redirectUrl = $space->getPermalink();
113 wp_safe_redirect($redirectUrl);
114 exit();
115 }
116
117 if (!empty($_REQUEST['auto_accept'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
118 $redirectUrl = (new InvitationHandler())->handleInvitationLogin(Helper::baseUrl(), get_user_by('ID', $currentUserId), $inviation->message_rendered);
119 if (is_wp_error($redirectUrl) || !$redirectUrl) {
120 $redirectUrl = Helper::baseUrl();
121 }
122 wp_safe_redirect($redirectUrl);
123 exit();
124 }
125 }
126 }
127
128 do_action('fluent_community/auth/before_auth_page_process', $currentUserId, $inviation);
129
130 $acceptedForms = ['login', 'register', 'reset_password'];
131 $targetForm = Arr::get($_GET, 'form'); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
132 $explicitForm = in_array($targetForm, $acceptedForms, true);
133 if (!$explicitForm) {
134 $targetForm = 'login';
135 }
136
137 if ($inviation && !$explicitForm) {
138 if ($inviation->message) {
139 $isUserAvailable = get_user_by('email', $inviation->message);
140 $targetForm = $isUserAvailable ? 'login' : 'register';
141 } else {
142 $targetForm = 'register';
143 }
144 }
145
146 if ($inviation && $currentUserId && $inviation->isValid()) {
147 if ($inviation->message) {
148 $invitedUser = get_user_by('email', $inviation->message);
149 if ($invitedUser && $invitedUser->ID == $currentUserId) {
150 $targetForm = 'accept_invitation';
151 }
152 } else {
153 $targetForm = 'accept_invitation';
154 }
155 }
156
157 $isFluentAuth = AuthHelper::isFluentAuthAvailable();
158 if (!$isFluentAuth && $targetForm == 'reset_password') {
159 wp_safe_redirect(wp_lostpassword_url(Helper::baseUrl()));
160 exit();
161 }
162
163 $portalSettings = Helper::generalSettings();
164 $titleVar = Arr::get($portalSettings, 'site_title');
165
166 $frameData = [
167 'logo' => Arr::get($portalSettings, 'logo', ''),
168 /* translators: %s is replaced by the title of the site */
169 'title' => sprintf(__('Join %s', 'fluent-community'), $titleVar),
170 'description' => __('Login or Signup to join the community', 'fluent-community'),
171 'button_label' => __('Login', 'fluent-community'),
172 ];
173
174 if ($targetForm == 'register') {
175 $frameData['button_label'] = __('Signup', 'fluent-community');
176 if (!$inviation) {
177 $customSignupUrl = Arr::get($portalSettings, 'custom_signup_url');
178 if ($customSignupUrl) {
179 wp_safe_redirect($customSignupUrl);
180 exit();
181 }
182 }
183 }
184
185 $currentUrl = home_url(add_query_arg($_GET, $GLOBALS['wp']->request)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
186
187 do_action('fluent_community/enqueue_global_assets', true);
188 add_action('wp_enqueue_scripts', function () use ($isFluentAuth, $targetForm, $inviation) {
189 wp_enqueue_style('fluent_auth_styles', Vite::getStaticSrcUrl('user_registration.css'), [], FLUENT_COMMUNITY_PLUGIN_VERSION);
190 if(!$isFluentAuth || $targetForm == 'register' || $inviation) {
191 wp_enqueue_script('fluent_auth_scripts', Vite::getStaticSrcUrl('user_registration.js'), [], FLUENT_COMMUNITY_PLUGIN_VERSION, true);
192 wp_localize_script('fluent_auth_scripts', 'fluentComRegistration', array(
193 'ajax_url' => admin_url('admin-ajax.php'),
194 'is_logged_in' => is_user_logged_in(),
195 'redirecting_text' => __('Redirecting...', 'fluent-community')
196 ));
197 }
198 }, 10);
199
200 $pageVars = [
201 'title' => $frameData['title'],
202 'og_title' => $frameData['title'],
203 'description' => $frameData['description'],
204 'url' => $currentUrl,
205 'featured_image' => '',
206 'css_files' => [],
207 'js_files' => [],
208 'js_vars' => [],
209 'scope' => 'user_registration',
210 'layout' => 'signup',
211 'portal' => [
212 'logo' => Arr::get($portalSettings, 'logo', ''),
213 /* translators: %s is replaced by the title of the site */
214 'title' => \sprintf(__('Welcome to %s', 'fluent-community'), Arr::get($portalSettings, 'site_title')),
215 'description' => get_bloginfo('description')
216 ]
217 ];
218
219 if (Utility::isDev()) {
220 $pageVars['js_files'] = [
221 Vite::getStaticSrcUrl('public/js/user_registration.js')
222 ];
223 }
224
225 $formType = ($targetForm == 'register') ? 'signup' : 'login';
226
227 $formSettings = AuthenticationService::getFormattedAuthSettings($formType);
228
229 if ($formSettings) {
230 $pageVars['portal'] = Arr::get($formSettings, 'banner');
231 $pageVars['portal']['form'] = Arr::get($formSettings, 'form');
232 }
233
234 add_action('fluent_community/headless/content', function ($context) use ($targetForm, $currentUrl, $frameData, $inviation, $formSettings) {
235 $preContent = apply_filters('fluent_community/auth/pre_content', '', $context, $targetForm, $frameData);
236 if ($preContent) {
237 return;
238 }
239
240 if ($targetForm == 'login') {
241 $frameData['button_label'] = Arr::get($formSettings, 'form.button_label', __('Login', 'fluent-community'));
242 $this->showLoginForm($frameData, $inviation);
243 } else if ($targetForm == 'reset_password') {
244 $frameData['title'] = __('Reset your password', 'fluent-community');
245 ?>
246 <div id="fcom_user_onboard_wrap" class="fcom_user_onboard">
247 <div class="fcom_onboard_header">
248 <div class="fcom_onboard_header_title">
249 <h2><?php echo esc_html($frameData['title']); ?></h2>
250 </div>
251 <div class="fcom_onboard_sub">
252 <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>
253 </div>
254 </div>
255 <div class="fcom_onboard_body">
256 <div class="fcom_onboard_form">
257 <?php echo do_shortcode('[fluent_auth_reset_password redirect_to="' . esc_url($currentUrl) . '"]'); ?>
258 <div class="fcom_spaced_divider">
259 <div class="fcom_alt_auth_text">
260 <a href="<?php echo esc_url(add_query_arg('form', 'login', $currentUrl)); ?>">
261 <?php esc_html_e('Back to Login', 'fluent-community'); ?>
262 </a>
263 </div>
264 </div>
265 </div>
266 </div>
267 </div>
268 <?php
269 } else if ($targetForm == 'accept_invitation') {
270 do_action('fluent_community/auth/show_invitation_for_user', $inviation, $frameData);
271 } else {
272 //check if the registration is disabled (a valid invitation still allows signup)
273 if (!$inviation && !AuthHelper::isRegistrationEnabled()) {
274 echo '<div class="fcom_completed"><div class="fcom_complted_header"><h4>' . esc_html__('Registration is disabled for this community', 'fluent-community') . '</h4>';
275 return;
276 }
277
278 $frameData['hiddenFields'] = [
279 'register' => 'yes',
280 'action' => 'fcom_user_signup',
281 '_fcom_signup_nonce' => wp_create_nonce('fluent_auth_signup_nonce')
282 ];
283
284 $frameData['loginUrl'] = add_query_arg('form', 'login', $currentUrl);
285 $frameData = wp_parse_args(Arr::get($formSettings, 'form'), $frameData);
286
287 $this->renderRegistrationForm($frameData, $inviation);
288 }
289 }, 10, 1);
290
291 add_action('fluent_community/headless/head_early', function ($scope) use ($formSettings) {
292 $bannerColors = array_filter(Arr::only($formSettings['banner'], ['title_color', 'text_color', 'background_color']));
293 $css = Utility::getColorCssVariables();
294
295 $sideVars = '';
296 foreach ($bannerColors as $colorKey => $colorValue) {
297 $sideVars .= '--fcom_' . $colorKey . ': ' . $colorValue . ';';
298 }
299 ?>
300 <link rel="canonical" href="<?php echo esc_url(Helper::getAuthUrl()); ?>" />
301 <style>
302 .fcom_layout_side { <?php echo esc_html($sideVars); ?> }
303 <?php echo esc_html($css); ?>
304 </style>
305 <?php
306 });
307
308 $pageVars['load_wp'] = 'yes';
309
310 // document title hook
311 add_filter('pre_get_document_title', function ($title) use ($frameData) {
312 return $frameData['title'];
313 }, 9999, 1);
314
315 status_header(200);
316 App::make('view')->render('headless_page', $pageVars);
317 exit(200);
318 }
319
320 public function handleUserSignup()
321 {
322 if (is_user_logged_in()) {
323 return $this->handleSignupCompleted(get_current_user_id());
324 }
325
326 $signupNonce = isset($_POST['_fcom_signup_nonce']) ? sanitize_text_field(wp_unslash($_POST['_fcom_signup_nonce'])) : '';
327 if (!$signupNonce || !wp_verify_nonce($signupNonce, 'fluent_auth_signup_nonce')) {
328 wp_send_json([
329 'message' => esc_html__('Invalid request. Please refresh the page and try again.', 'fluent-community')
330 ], 403);
331 }
332
333 $invitationToken = isset($_POST['invitation_token']) ? sanitize_text_field(wp_unslash($_POST['invitation_token'])) : '';
334 $hasValidInvitation = false;
335 if ($invitationToken) {
336 $pendingInvitation = Invitation::where('message_rendered', $invitationToken)->first();
337 $hasValidInvitation = $pendingInvitation && $pendingInvitation->isValid();
338 }
339
340 // A valid invitation must still allow signup even when public registration is disabled.
341 if (!$hasValidInvitation && !AuthHelper::isRegistrationEnabled()) {
342 wp_send_json([
343 'message' => esc_html__('Registration is disabled for this community', 'fluent-community')
344 ], 422);
345 }
346
347 $app = App::make('app');
348 $request = $app->make('request');
349 $fields = AuthHelper::getFormFields();
350
351 $authSettings = AuthenticationService::getAuthSettings();
352 $termsField = Arr::get($authSettings, 'signup.form.fields.terms');
353
354 $fields['terms'] = $termsField ?: $fields['terms'];
355
356 $requiredFields = array_filter($fields, function ($field) {
357 return ($field['required'] && empty($field['disabled'])) ?? false;
358 });
359
360 $keys = array_keys($fields);
361 $data = Arr::only($request->all(), $keys);
362
363 // remove space and special characters from username
364 $data['username'] = sanitize_user(strtolower(preg_replace('/[^A-Za-z0-9_]/', '', $data['username'])));
365
366 if (empty($data['username'])) {
367 wp_send_json([
368 'message' => esc_html__('Username is not valid', 'fluent-community'),
369 'errors' => [
370 'username' => __('Please provide a valid username', 'fluent-community')
371 ]
372 ], 422);
373 }
374
375 if (!ProfileHelper::isUsernameAvailable($data['username'])) {
376 wp_send_json([
377 'message' => esc_html__('Username is already taken', 'fluent-community'),
378 'errors' => [
379 'username' => __('Username is already taken. Please use a different username', 'fluent-community')
380 ]
381 ], 422);
382 }
383
384 $invitationToken = $request->get('invitation_token');
385 $invitation = null;
386 if ($invitationToken) {
387 $invitation = Invitation::where('message_rendered', $invitationToken)->first();
388 if (!$invitation) {
389 wp_send_json([
390 'message' => __('Invalid invitation token', 'fluent-community')
391 ], 422);
392 }
393
394 if ($invitation->message && $invitation->message != $data['email']) {
395 wp_send_json([
396 'message' => esc_html__('Email does not match with the invitation', 'fluent-community')
397 ], 422);
398 }
399
400 if ($invitation->message) {
401 add_filter('fluent_community/auth/two_factor_enabled', '__return_false');
402 add_filter('fluent_auth/verify_signup_email', '__return_false');
403 }
404 }
405
406 if (!$invitation && AuthenticationService::getCustomSignupPageUrl()) {
407 // we have custom signup page enabled
408 wp_send_json([
409 'message' => esc_html__('Direct Registration is disabled for this community', 'fluent-community')
410 ], 422);
411 }
412
413 $data['email'] = sanitize_email($data['email']);
414
415 $validations = [
416 'full_name' => 'required|max:100|string',
417 'username' => 'required|unique:users,user_login|unique:fcom_xprofile,username|min:4|max:30',
418 'email' => 'required|email|unique:users,user_email',
419 'password' => 'required|same:conf_password|max:50|string',
420 'conf_password' => 'required|same:password'
421 ];
422
423 if (!AuthHelper::isPasswordConfRequired()) {
424 unset($validations['conf_password']);
425 $validations['password'] = 'required|max:50|string';
426 }
427
428 foreach ($requiredFields as $key => $field) {
429 if (!isset($data[$key])) {
430 $validations[$key] = 'required';
431 }
432 }
433
434 $validator = $app->make('validator')->make($data, $validations, [
435 'username.required' => __('Username is required', 'fluent-community'),
436 'username.unique' => __('Username is already taken', 'fluent-community'),
437 'email.required' => __('Email is required', 'fluent-community'),
438 'email.email' => __('Email is not valid', 'fluent-community'),
439 'email.unique' => __('Email is already taken', 'fluent-community'),
440 'password.required' => __('Password is required', 'fluent-community'),
441 'password.same' => __('Password and confirmation password do not match', 'fluent-community'),
442 'conf_password.required' => __('Password confirmation is required', 'fluent-community'),
443 'conf_password.same' => __('Password and confirmation password do not match', 'fluent-community'),
444 'terms.required' => __('You must agree to the terms and conditions', 'fluent-community'),
445 'full_name.required' => __('Full name is required', 'fluent-community'),
446 ]);
447
448 if ($validator->fails()) {
449 wp_send_json([
450 'message' => __('Please fill in all required fields correctly.', 'fluent-community'),
451 'errors' => $validator->errors()
452 ], 422);
453 }
454
455 foreach ($data as $key => $value) {
456 // let's sanitize the data
457 $callBack = $fields[$key]['sanitize_callback'] ?? null;
458 if ($callBack) {
459 $data[$key] = call_user_func($callBack, $value);
460 }
461 }
462
463 // let's extract the full_name and set the first_name and last_name
464 if (!empty($data['full_name'])) {
465 $nameParts = explode(' ', $data['full_name']);
466 $data['first_name'] = $nameParts[0];
467 $data['last_name'] = implode(' ', array_slice($nameParts, 1));
468 unset($data['full_name']);
469 $data = array_filter($data);
470 }
471
472 $rateLimit = AuthHelper::isAuthRateLimit();
473
474 if (is_wp_error($rateLimit)) {
475 wp_send_json([
476 'message' => $rateLimit->get_error_message()
477 ], 422);
478 }
479
480 // We need two-factor authentication here
481 if (AuthHelper::isTwoFactorEnabled()) {
482 // Check if Two Factor code is given
483 $verificationToken = $request->get('__two_fa_signed_token');
484 if ($verificationToken) {
485 $code = $request->get('_email_verification_code');
486 if (!$code) {
487 wp_send_json([
488 'message' => __('Verification code is required', 'fluent-community')
489 ], 422);
490 }
491
492 $validated = AuthHelper::validateVerificationCode($code, $verificationToken, $data);
493 if (is_wp_error($validated)) {
494 wp_send_json([
495 'message' => $validated->get_error_message()
496 ], 422);
497 }
498 } else {
499 // Let's send the verification code
500 $htmlForm = AuthHelper::get2FaRegistrationCodeForm($data);
501 wp_send_json([
502 'verifcation_html' => $htmlForm
503 ]);
504 }
505 }
506
507 // let's create the user now
508 $userId = AuthHelper::registerNewUser($data['username'], $data['email'], $data['password'], [
509 'first_name' => Arr::get($data, 'first_name'),
510 'last_name' => Arr::get($data, 'last_name'),
511 'role' => get_option('default_role', 'subscriber')
512 ]);
513
514 if (is_wp_error($userId)) {
515 wp_send_json([
516 'message' => $userId->get_error_message()
517 ], 422);
518 }
519
520 $this->handleSignupCompleted($userId);
521 }
522
523 private function handleSignupViaFlentAuth($data)
524 {
525 add_action('fluent_auth/after_creating_user', function ($userId) {
526 $this->handleSignupCompleted($userId);
527 }, 1, 1);
528
529 add_filter('fluent_auth/signup_enabled', '__return_true');
530
531 (new CustomAuthHandler())->handleSignupAjax();
532 }
533
534 private function handleSignupCompleted($userId)
535 {
536 // We have the user now let's set the community membership
537 $user = User::find($userId);
538 $user->syncXProfile(true, true);
539
540 $redirectUrl = Helper::baseUrl();
541
542 if (!empty($_REQUEST['redirect_to'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
543 $redirectUrl = wp_validate_redirect(sanitize_url(wp_unslash($_REQUEST['redirect_to'])), Helper::baseUrl()); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
544 }
545
546 $redirectUrl = apply_filters('fluent_community/auth/after_signup_redirect_url', $redirectUrl, $user, $_REQUEST); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
547 $btnText = __('Continue to the community', 'fluent-community');
548
549 $html = '<div class="fcom_completed"><div class="fcom_complted_header"><h2>' . __('Congratulations!', 'fluent-community') . '</h2>';
550 $html .= '<p>' . __('You have successfully registered to the community', 'fluent-community') . '</p></div>';
551 $html .= '<a href="' . esc_url($redirectUrl) . '" class="fcom_btn fcom_btn_success">' . $btnText . '</a>';
552 $html .= '</div>';
553
554 if (!get_current_user_id()) {
555 $wpUser = get_user_by('ID', $userId);
556 AuthHelper::makeLogin($wpUser);
557 }
558
559 wp_send_json([
560 'success_html' => $html,
561 'redirect_url' => $redirectUrl
562 ]);
563 }
564
565 public function handleUserLogin()
566 {
567 if (is_user_logged_in()) {
568 $user = get_user_by('ID', get_current_user_id());
569 return $this->handleUserLoginSuccess($user);
570 }
571
572 if (AuthHelper::isFluentAuthAvailable()) {
573 wp_send_json([
574 'message' => __('This form cannot be used to log in. Please reload the page and try again.', 'fluent-community')
575 ], 422);
576 }
577
578 $loginNonce = isset($_POST['_fcom_login_nonce']) ? sanitize_text_field(wp_unslash($_POST['_fcom_login_nonce'])) : '';
579 if (!$loginNonce || !wp_verify_nonce($loginNonce, 'fcom_user_login_nonce')) {
580 wp_send_json([
581 'message' => esc_html__('Invalid request. Please refresh the page and try again.', 'fluent-community')
582 ], 403);
583 }
584
585 $app = App::make('app');
586 $request = $app->make('request');
587
588 $data = $request->all();
589
590 $validator = $app->make('validator')->make($data, [
591 'log' => 'required',
592 'pwd' => 'required'
593 ], [
594 'log.required' => __('Email is required', 'fluent-community'),
595 'pwd.required' => __('Password is required', 'fluent-community')
596 ]);
597
598 if ($validator->fails()) {
599 wp_send_json([
600 'message' => __('Please fill all the required fields correctly', 'fluent-community'),
601 'errors' => $validator->errors()
602 ], 422);
603 }
604
605 $rateLimit = AuthHelper::isAuthRateLimit();
606 if (is_wp_error($rateLimit)) {
607 wp_send_json([
608 'message' => $rateLimit->get_error_message()
609 ], 422);
610 }
611
612 $user = wp_authenticate($data['log'], $data['pwd']);
613
614 if (is_wp_error($user)) {
615 $enumerationCodes = ['invalid_username', 'invalid_email', 'incorrect_password'];
616 if (in_array($user->get_error_code(), $enumerationCodes, true)) {
617 $message = __('Email or password is incorrect.', 'fluent-community');
618 } else {
619 $message = $user->get_error_message();
620 }
621 wp_send_json([
622 'message' => $message
623 ], 422);
624 }
625
626 InvitationService::makeLogin($user);
627
628 $redirectUrl = null;
629 if (!empty($_REQUEST['redirect_to'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
630 $redirectUrl = wp_validate_redirect(sanitize_url(wp_unslash($_REQUEST['redirect_to'])), Helper::baseUrl()); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
631 }
632
633 if (!$redirectUrl) {
634 $redirectUrl = Helper::baseUrl();
635 }
636
637 if ($invitationToken = $request->get('invitation_token')) {
638 $maybeRedirectUrl = apply_filters('fluent_community/auth/after_login_with_invitation', null, $user, $invitationToken);
639 if ($maybeRedirectUrl && !is_wp_error($maybeRedirectUrl)) {
640 $redirectUrl = $maybeRedirectUrl;
641 }
642 }
643
644 $this->handleUserLoginSuccess($user, $redirectUrl);
645 }
646
647 private function handleUserLoginSuccess($user, $redirectUrl = null)
648 {
649 if (!$redirectUrl) {
650 $redirectUrl = Helper::baseUrl();
651 }
652
653 $redirectUrl = apply_filters('fluent_community/auth/after_login_redirect_url', $redirectUrl, $user);
654 $btnText = __('Continue to the community', 'fluent-community');
655
656 $html = '<div class="fcom_completed"><div class="fcom_complted_header"><h2>' . __('Welcome back!', 'fluent-community') . '</h2>';
657 $html .= '<p>' . __('You have successfully logged in to the community', 'fluent-community') . '</p></div>';
658 $html .= '<a href="' . esc_url($redirectUrl) . '" class="fcom_btn fcom_btn_success">' . $btnText . '</a>';
659 $html .= '</div>';
660
661 wp_send_json([
662 'success_html' => $html,
663 'redirect_url' => $redirectUrl
664 ]);
665 }
666
667 public function showLoginForm($frameData, $invitation = null)
668 {
669 $portalSettings = Helper::generalSettings();
670 $isFluentAuth = AuthHelper::isFluentAuthAvailable();
671 $loginSettings = AuthenticationService::getFormattedAuthSettings('login');
672 $formSettings = Arr::get($loginSettings, 'form');
673 $currentUrl = home_url(add_query_arg($_GET, $GLOBALS['wp']->request)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
674 /* translators: %s is replaced by the title of the site */
675 $title = sprintf(__('Login to %s', 'fluent-community'), Arr::get($portalSettings, 'site_title'));
676
677 $description = '';
678 if ($invitation) {
679 $invitationBy = $invitation->xprofile ? $invitation->xprofile->display_name : __('Someone', 'fluent-community');
680 if ($invitation->post_id) {
681 $space = BaseSpace::find($invitation->post_id);
682 if ($space) {
683 $title = $space->title . ' - ' . Arr::get($portalSettings, 'site_title');
684 }
685 }
686 /* translators: %s is replaced by the name of the inviter */
687 $inviteDescription = \sprintf(__('%s has invited you to join this community. Please login to accept your invitation.', 'fluent-community'), $invitationBy);
688 add_action('fluent_community/before_auth_form_header', function ($formType) use ($inviteDescription) {
689 ?>
690 <div class="fcom_highlight_message">
691 <?php echo wp_kses_post($inviteDescription); ?>
692 </div>
693 <?php
694 });
695 }
696
697 $signupUrl = add_query_arg('form', 'register', $currentUrl);
698
699 if (!$invitation) {
700 if ($customSignupUrl = AuthenticationService::getCustomSignupPageUrl()) {
701 $signupUrl = $customSignupUrl;
702 }
703 }
704
705 add_filter('login_form_defaults', function ($defaults) use ($invitation, $frameData) {
706 $defaults['label_log_in'] = Arr::get($frameData, 'button_label');
707 return $defaults;
708 });
709
710 if ($isFluentAuth) {
711 add_filter('login_form_top', function () use ($invitation) {
712 $reditectUrl = Arr::get($_GET, 'redirect_to'); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
713 if (!$reditectUrl) {
714 $reditectUrl = apply_filters('fluent_community/default_redirect_url', Helper::baseUrl());
715 }
716 ob_start();
717 ?>
718 <?php if ($invitation) { ?>
719 <input type="hidden" name="invitation_token" value="<?php echo esc_attr($invitation->message_rendered); ?>"/>
720 <?php } ?>
721 <input name="is_fcom_auth" type="hidden" value="yes"/>
722 <input type="hidden" name="fcom_redirect" value="<?php echo esc_url($reditectUrl); ?>"/>
723 <?php
724 return ob_get_clean();
725 });
726 ?>
727 <div id="fcom_user_onboard_wrap" class="fcom_user_onboard">
728 <div class="fcom_onboard_header">
729 <?php do_action('fluent_community/before_auth_form_header', 'login'); ?>
730 <div class="fcom_onboard_header_title">
731 <?php if (!empty($formSettings['title'])): ?>
732 <h2>
733 <?php echo esc_html($formSettings['title']); ?>
734 </h2>
735 <?php endif; ?>
736 </div>
737 <?php if (!empty($formSettings['description'])): ?>
738 <div class="fcom_onboard_sub">
739 <?php echo wp_kses_post(trim($formSettings['description'])); ?>
740 </div>
741 <?php endif; ?>
742 </div>
743 <div class="fcom_onboard_body">
744 <div class="fcom_onboard_form">
745 <?php echo do_shortcode('[fluent_auth_login redirect_to="' . esc_url($currentUrl) . '"]'); ?>
746 <div class="fcom_spaced_divider">
747 <?php if ($invitation || AuthHelper::isRegistrationEnabled()): ?>
748 <div class="fcom_alt_auth_text">
749 <?php esc_html_e('Don\'t have an account?', 'fluent-community'); ?>
750 <a href="<?php echo esc_url($signupUrl); ?>">
751 <?php esc_html_e('Signup', 'fluent-community'); ?>
752 </a>
753 </div>
754 <?php endif; ?>
755 <p class="fcom_reset_pass_text">
756 <a href="<?php echo esc_url(AuthHelper::getLostPasswordUrl($currentUrl)); ?>">
757 <?php esc_html_e('Lost your password?', 'fluent-community'); ?>
758 </a>
759 </p>
760 </div>
761 </div>
762 </div>
763 </div>
764 <?php
765 return;
766 }
767
768 $frameData['redirect'] = $currentUrl;
769
770 $frameData['hiddenFields'] = [
771 'action' => 'fcom_user_login_form',
772 '_fcom_login_nonce' => wp_create_nonce('fcom_user_login_nonce'),
773 ];
774 if ($invitation) {
775 $frameData['button_label'] = __('Log In & Accept Invitation', 'fluent-community');
776 $frameData['hiddenFields']['invitation_token'] = $invitation->message_rendered;
777 }
778
779 $frameData['title'] = $title;
780 $frameData['description'] = $description;
781
782 $frameData['defaults'] = [
783 'email' => $invitation ? $invitation->message : ''
784 ];
785
786 if ($invitation || AuthHelper::isRegistrationEnabled()) {
787 $frameData['signupUrl'] = $signupUrl;
788 }
789
790 $frameData['settings'] = $formSettings;
791
792 if (isset($_GET['redirect_to'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
793 $frameData['redirect_to'] = sanitize_url(wp_unslash($_GET['redirect_to'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
794 }
795
796 App::make('view')->render('auth.login_form', $frameData);
797 }
798
799 public function renderRegistrationForm($frameData, $invitation = null)
800 {
801 $formFields = AuthHelper::getFormFields($invitation);
802
803 // Prefill the name from the invitation link's query param when present.
804 $inviteName = isset($_GET['invite_name']) ? sanitize_text_field(wp_unslash($_GET['invite_name'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
805 if ($inviteName && isset($formFields['full_name']) && empty($formFields['full_name']['value'])) {
806 $formFields['full_name']['value'] = $inviteName;
807 }
808
809 $authSettings = AuthenticationService::getAuthSettings();
810
811 $termsField = Arr::get($authSettings, 'signup.form.fields.terms');
812
813
814 if ($termsField) {
815 unset($termsField['label']);
816
817 // add new tab on the link for $termsField['inline_label']
818 $termsField['inline_label'] = FeedsHelper::addNewTabToLinks($termsField['inline_label']);
819
820 $formFields['terms'] = $termsField;
821 }
822
823 $frameData['formFields'] = $formFields;
824
825 if ($invitation) {
826 $frameData['hiddenFields'] = [
827 'invitation_token' => $invitation->message_rendered,
828 'action' => 'fcom_user_registration',
829 '_fcom_signup_nonce' => wp_create_nonce('fluent_auth_signup_nonce')
830 ];
831
832 $invitationBy = $invitation->xprofile ? $invitation->xprofile->display_name : __('Someone', 'fluent-community');
833 /* translators: %s is replaced by the name of the inviter */
834 $inviteDescription = sprintf(__('%s has invited you to join this community. Please create an account to accept your invitation.', 'fluent-community'), $invitationBy);
835
836 add_action('fluent_community/before_auth_form_header', function ($formType) use ($inviteDescription) {
837 ?>
838 <div class="fcom_highlight_message">
839 <?php echo wp_kses_post($inviteDescription); ?>
840 </div>
841 <?php
842 });
843
844 $frameData['button_label'] = __('Register & Accept invitation', 'fluent-community');
845 } else {
846 $frameData['hiddenFields'] = [
847 'register' => 'yes',
848 'action' => 'fcom_user_registration',
849 '_fcom_signup_nonce' => wp_create_nonce('fluent_auth_signup_nonce'),
850 ];
851
852 if (!empty($_GET['redirect_to'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
853 $frameData['hiddenFields']['redirect_to'] = sanitize_url(wp_unslash($_GET['redirect_to'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
854 }
855 }
856
857 add_action('fluent_community/before_registration_form', function ($frameData) {
858 if (AuthHelper::isFluentAuthAvailable()) {
859 $currentUrl = esc_url(home_url(add_query_arg($_GET, $GLOBALS['wp']->request))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
860
861 $titlePrefix = __('Signup with', 'fluent-community');
862 $html = do_shortcode('[fs_auth_buttons redirect="' . $currentUrl . '" title_prefix="' . $titlePrefix . ' " title=""]');
863
864 if ($html) {
865 echo '<div class="fcom_social_auth_wrap">';
866 echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
867 echo '</div>';
868 }
869 }
870 });
871
872 App::make('view')->render('auth.user_invitation', $frameData);
873 }
874 }
875