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

874 lines 38.5 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 <link rel="canonical" href="<?php echo esc_url(Helper::getAuthUrl()); ?>" />
295 <style>
296 .fcom_layout_side {
297 <?php foreach ($bannerColors as $colorKey => $colorValue): ?> --fcom_ <?php echo esc_html($colorKey); ?>: <?php echo esc_html($colorValue); ?>;
298 <?php endforeach; ?>
299 }
300 <?php echo esc_html($css); ?>
301 </style>
302 <?php
303 });
304
305 $pageVars['load_wp'] = 'yes';
306
307 // document title hook
308 add_filter('pre_get_document_title', function ($title) use ($frameData) {
309 return $frameData['title'];
310 }, 9999, 1);
311
312 status_header(200);
313 App::make('view')->render('headless_page', $pageVars);
314 exit(200);
315 }
316
317 public function handleUserSignup()
318 {
319 if (is_user_logged_in()) {
320 return $this->handleSignupCompleted(get_current_user_id());
321 }
322
323 $signupNonce = isset($_POST['_fcom_signup_nonce']) ? sanitize_text_field(wp_unslash($_POST['_fcom_signup_nonce'])) : '';
324 if (!$signupNonce || !wp_verify_nonce($signupNonce, 'fluent_auth_signup_nonce')) {
325 wp_send_json([
326 'message' => esc_html__('Invalid request. Please refresh the page and try again.', 'fluent-community')
327 ], 403);
328 }
329
330 $invitationToken = isset($_POST['invitation_token']) ? sanitize_text_field(wp_unslash($_POST['invitation_token'])) : '';
331 $hasValidInvitation = false;
332 if ($invitationToken) {
333 $pendingInvitation = Invitation::where('message_rendered', $invitationToken)->first();
334 $hasValidInvitation = $pendingInvitation && $pendingInvitation->isValid();
335 }
336
337 // A valid invitation must still allow signup even when public registration is disabled.
338 if (!$hasValidInvitation && !AuthHelper::isRegistrationEnabled()) {
339 wp_send_json([
340 'message' => esc_html__('Registration is disabled for this community', 'fluent-community')
341 ], 422);
342 }
343
344 $app = App::make('app');
345 $request = $app->make('request');
346 $fields = AuthHelper::getFormFields();
347
348 $authSettings = AuthenticationService::getAuthSettings();
349 $termsField = Arr::get($authSettings, 'signup.form.fields.terms');
350
351 $fields['terms'] = $termsField ?: $fields['terms'];
352
353 $requiredFields = array_filter($fields, function ($field) {
354 return ($field['required'] && empty($field['disabled'])) ?? false;
355 });
356
357 $keys = array_keys($fields);
358 $data = Arr::only($request->all(), $keys);
359
360 // remove space and special characters from username
361 $data['username'] = sanitize_user(strtolower(preg_replace('/[^A-Za-z0-9_]/', '', $data['username'])));
362
363 if (empty($data['username'])) {
364 wp_send_json([
365 'message' => esc_html__('Username is not valid', 'fluent-community'),
366 'errors' => [
367 'username' => __('Please provide a valid username', 'fluent-community')
368 ]
369 ], 422);
370 }
371
372 if (!ProfileHelper::isUsernameAvailable($data['username'])) {
373 wp_send_json([
374 'message' => esc_html__('Username is already taken', 'fluent-community'),
375 'errors' => [
376 'username' => __('Username is already taken. Please use a different username', 'fluent-community')
377 ]
378 ], 422);
379 }
380
381 $invitationToken = $request->get('invitation_token');
382 $invitation = null;
383 if ($invitationToken) {
384 $invitation = Invitation::where('message_rendered', $invitationToken)->first();
385 if (!$invitation) {
386 wp_send_json([
387 'message' => __('Invalid invitation token', 'fluent-community')
388 ], 422);
389 }
390
391 if ($invitation->message && $invitation->message != $data['email']) {
392 wp_send_json([
393 'message' => esc_html__('Email does not match with the invitation', 'fluent-community')
394 ], 422);
395 }
396
397 if ($invitation->message) {
398 add_filter('fluent_community/auth/two_factor_enabled', '__return_false');
399 add_filter('fluent_auth/verify_signup_email', '__return_false');
400 }
401 }
402
403 if (!$invitation && AuthenticationService::getCustomSignupPageUrl()) {
404 // we have custom signup page enabled
405 wp_send_json([
406 'message' => esc_html__('Direct Registration is disabled for this community', 'fluent-community')
407 ], 422);
408 }
409
410 $data['email'] = sanitize_email($data['email']);
411
412 $validations = [
413 'full_name' => 'required|max:100|string',
414 'username' => 'required|unique:users,user_login|unique:fcom_xprofile,username|min:4|max:30',
415 'email' => 'required|email|unique:users,user_email',
416 'password' => 'required|same:conf_password|max:50|string',
417 'conf_password' => 'required|same:password'
418 ];
419
420 if (!AuthHelper::isPasswordConfRequired()) {
421 unset($validations['conf_password']);
422 $validations['password'] = 'required|max:50|string';
423 }
424
425 foreach ($requiredFields as $key => $field) {
426 if (!isset($data[$key])) {
427 $validations[$key] = 'required';
428 }
429 }
430
431 $validator = $app->make('validator')->make($data, $validations, [
432 'username.required' => __('Username is required', 'fluent-community'),
433 'username.unique' => __('Username is already taken', 'fluent-community'),
434 'email.required' => __('Email is required', 'fluent-community'),
435 'email.email' => __('Email is not valid', 'fluent-community'),
436 'email.unique' => __('Email is already taken', 'fluent-community'),
437 'password.required' => __('Password is required', 'fluent-community'),
438 'password.same' => __('Password and confirmation password do not match', 'fluent-community'),
439 'conf_password.required' => __('Password confirmation is required', 'fluent-community'),
440 'conf_password.same' => __('Password and confirmation password do not match', 'fluent-community'),
441 'terms.required' => __('You must agree to the terms and conditions', 'fluent-community'),
442 'full_name.required' => __('Full name is required', 'fluent-community'),
443 ]);
444
445 if ($validator->fails()) {
446 wp_send_json([
447 'message' => __('Please fill in all required fields correctly.', 'fluent-community'),
448 'errors' => $validator->errors()
449 ], 422);
450 }
451
452 foreach ($data as $key => $value) {
453 // let's sanitize the data
454 $callBack = $fields[$key]['sanitize_callback'] ?? null;
455 if ($callBack) {
456 $data[$key] = call_user_func($callBack, $value);
457 }
458 }
459
460 // let's extract the full_name and set the first_name and last_name
461 if (!empty($data['full_name'])) {
462 $nameParts = explode(' ', $data['full_name']);
463 $data['first_name'] = $nameParts[0];
464 $data['last_name'] = implode(' ', array_slice($nameParts, 1));
465 unset($data['full_name']);
466 $data = array_filter($data);
467 }
468
469 $rateLimit = AuthHelper::isAuthRateLimit();
470
471 if (is_wp_error($rateLimit)) {
472 wp_send_json([
473 'message' => $rateLimit->get_error_message()
474 ], 422);
475 }
476
477 // We need two-factor authentication here
478 if (AuthHelper::isTwoFactorEnabled()) {
479 // Check if Two Factor code is given
480 $verificationToken = $request->get('__two_fa_signed_token');
481 if ($verificationToken) {
482 $code = $request->get('_email_verification_code');
483 if (!$code) {
484 wp_send_json([
485 'message' => __('Verification code is required', 'fluent-community')
486 ], 422);
487 }
488
489 $validated = AuthHelper::validateVerificationCode($code, $verificationToken, $data);
490 if (is_wp_error($validated)) {
491 wp_send_json([
492 'message' => $validated->get_error_message()
493 ], 422);
494 }
495 } else {
496 // Let's send the verification code
497 $htmlForm = AuthHelper::get2FaRegistrationCodeForm($data);
498 wp_send_json([
499 'verifcation_html' => $htmlForm
500 ]);
501 }
502 }
503
504 // let's create the user now
505 $userId = AuthHelper::registerNewUser($data['username'], $data['email'], $data['password'], [
506 'first_name' => Arr::get($data, 'first_name'),
507 'last_name' => Arr::get($data, 'last_name'),
508 'role' => get_option('default_role', 'subscriber')
509 ]);
510
511 if (is_wp_error($userId)) {
512 wp_send_json([
513 'message' => $userId->get_error_message()
514 ], 422);
515 }
516
517 $this->handleSignupCompleted($userId);
518 }
519
520 private function handleSignupViaFlentAuth($data)
521 {
522 add_action('fluent_auth/after_creating_user', function ($userId) {
523 $this->handleSignupCompleted($userId);
524 }, 1, 1);
525
526 add_filter('fluent_auth/signup_enabled', '__return_true');
527
528 (new CustomAuthHandler())->handleSignupAjax();
529 }
530
531 private function handleSignupCompleted($userId)
532 {
533 // We have the user now let's set the community membership
534 $user = User::find($userId);
535 $user->syncXProfile(true, true);
536
537 $redirectUrl = Helper::baseUrl();
538
539 if (!empty($_REQUEST['redirect_to'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
540 $redirectUrl = wp_validate_redirect(sanitize_url(wp_unslash($_REQUEST['redirect_to'])), Helper::baseUrl()); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
541 }
542
543 $redirectUrl = apply_filters('fluent_community/auth/after_signup_redirect_url', $redirectUrl, $user, $_REQUEST); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
544 $btnText = __('Continue to the community', 'fluent-community');
545
546 $html = '<div class="fcom_completed"><div class="fcom_complted_header"><h2>' . __('Congratulations!', 'fluent-community') . '</h2>';
547 $html .= '<p>' . __('You have successfully registered to the community', 'fluent-community') . '</p></div>';
548 $html .= '<a href="' . esc_url($redirectUrl) . '" class="fcom_btn fcom_btn_success">' . $btnText . '</a>';
549 $html .= '</div>';
550
551 if (!get_current_user_id()) {
552 $wpUser = get_user_by('ID', $userId);
553 AuthHelper::makeLogin($wpUser);
554 }
555
556 wp_send_json([
557 'success_html' => $html,
558 'redirect_url' => $redirectUrl
559 ]);
560 }
561
562 public function handleUserLogin()
563 {
564 if (is_user_logged_in()) {
565 $user = get_user_by('ID', get_current_user_id());
566 return $this->handleUserLoginSuccess($user);
567 }
568
569 if (AuthHelper::isFluentAuthAvailable()) {
570 wp_send_json([
571 'message' => __('This form cannot be used to log in. Please reload the page and try again.', 'fluent-community')
572 ], 422);
573 }
574
575 $loginNonce = isset($_POST['_fcom_login_nonce']) ? sanitize_text_field(wp_unslash($_POST['_fcom_login_nonce'])) : '';
576 if (!$loginNonce || !wp_verify_nonce($loginNonce, 'fcom_user_login_nonce')) {
577 wp_send_json([
578 'message' => esc_html__('Invalid request. Please refresh the page and try again.', 'fluent-community')
579 ], 403);
580 }
581
582 $app = App::make('app');
583 $request = $app->make('request');
584
585 $data = $request->all();
586
587 $validator = $app->make('validator')->make($data, [
588 'log' => 'required',
589 'pwd' => 'required'
590 ], [
591 'log.required' => __('Email is required', 'fluent-community'),
592 'pwd.required' => __('Password is required', 'fluent-community')
593 ]);
594
595 if ($validator->fails()) {
596 wp_send_json([
597 'message' => __('Please fill all the required fields correctly', 'fluent-community'),
598 'errors' => $validator->errors()
599 ], 422);
600 }
601
602 $rateLimit = AuthHelper::isAuthRateLimit();
603 if (is_wp_error($rateLimit)) {
604 wp_send_json([
605 'message' => $rateLimit->get_error_message()
606 ], 422);
607 }
608
609 $user = wp_authenticate($data['log'], $data['pwd']);
610
611 if (is_wp_error($user)) {
612 $enumerationCodes = ['invalid_username', 'invalid_email', 'incorrect_password'];
613 if (in_array($user->get_error_code(), $enumerationCodes, true)) {
614 $message = __('Email or password is incorrect.', 'fluent-community');
615 } else {
616 $message = $user->get_error_message();
617 }
618 wp_send_json([
619 'message' => $message
620 ], 422);
621 }
622
623 InvitationService::makeLogin($user);
624
625 $redirectUrl = null;
626 if (!empty($_REQUEST['redirect_to'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
627 $redirectUrl = wp_validate_redirect(sanitize_url(wp_unslash($_REQUEST['redirect_to'])), Helper::baseUrl()); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
628 }
629
630 if (!$redirectUrl) {
631 $redirectUrl = Helper::baseUrl();
632 }
633
634 if ($invitationToken = $request->get('invitation_token')) {
635 $maybeRedirectUrl = apply_filters('fluent_community/auth/after_login_with_invitation', null, $user, $invitationToken);
636 if ($maybeRedirectUrl && !is_wp_error($maybeRedirectUrl)) {
637 $redirectUrl = $maybeRedirectUrl;
638 }
639 }
640
641 $this->handleUserLoginSuccess($user, $redirectUrl);
642 }
643
644 private function handleUserLoginSuccess($user, $redirectUrl = null)
645 {
646 if (!$redirectUrl) {
647 $redirectUrl = Helper::baseUrl();
648 }
649
650 $redirectUrl = apply_filters('fluent_community/auth/after_login_redirect_url', $redirectUrl, $user);
651 $btnText = __('Continue to the community', 'fluent-community');
652
653 $html = '<div class="fcom_completed"><div class="fcom_complted_header"><h2>' . __('Welcome back!', 'fluent-community') . '</h2>';
654 $html .= '<p>' . __('You have successfully logged in to the community', 'fluent-community') . '</p></div>';
655 $html .= '<a href="' . esc_url($redirectUrl) . '" class="fcom_btn fcom_btn_success">' . $btnText . '</a>';
656 $html .= '</div>';
657
658 wp_send_json([
659 'success_html' => $html,
660 'redirect_url' => $redirectUrl
661 ]);
662 }
663
664 public function showLoginForm($frameData, $invitation = null)
665 {
666 $portalSettings = Helper::generalSettings();
667 $isFluentAuth = AuthHelper::isFluentAuthAvailable();
668 $loginSettings = AuthenticationService::getFormattedAuthSettings('login');
669 $formSettings = Arr::get($loginSettings, 'form');
670 $currentUrl = home_url(add_query_arg($_GET, $GLOBALS['wp']->request)); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
671 /* translators: %s is replaced by the title of the site */
672 $title = sprintf(__('Login to %s', 'fluent-community'), Arr::get($portalSettings, 'site_title'));
673
674 $description = '';
675 if ($invitation) {
676 $invitationBy = $invitation->xprofile ? $invitation->xprofile->display_name : __('Someone', 'fluent-community');
677 if ($invitation->post_id) {
678 $space = BaseSpace::find($invitation->post_id);
679 if ($space) {
680 $title = $space->title . ' - ' . Arr::get($portalSettings, 'site_title');
681 }
682 }
683 /* translators: %s is replaced by the name of the inviter */
684 $inviteDescription = \sprintf(__('%s has invited you to join this community. Please login to accept your invitation.', 'fluent-community'), $invitationBy);
685 add_action('fluent_community/before_auth_form_header', function ($formType) use ($inviteDescription) {
686 ?>
687 <div class="fcom_highlight_message">
688 <?php echo wp_kses_post($inviteDescription); ?>
689 </div>
690 <?php
691 });
692 }
693
694 $signupUrl = add_query_arg('form', 'register', $currentUrl);
695
696 if (!$invitation) {
697 if ($customSignupUrl = AuthenticationService::getCustomSignupPageUrl()) {
698 $signupUrl = $customSignupUrl;
699 }
700 }
701
702 add_filter('login_form_defaults', function ($defaults) use ($invitation, $frameData) {
703 $defaults['label_log_in'] = Arr::get($frameData, 'button_label');
704 return $defaults;
705 });
706
707 if ($isFluentAuth) {
708 add_filter('login_form_top', function () use ($invitation) {
709 $reditectUrl = Arr::get($_GET, 'redirect_to'); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
710 if (!$reditectUrl) {
711 $reditectUrl = apply_filters('fluent_community/default_redirect_url', Helper::baseUrl());
712 }
713 ob_start();
714 ?>
715 <?php if ($invitation) { ?>
716 <input type="hidden" name="invitation_token" value="<?php echo esc_attr($invitation->message_rendered); ?>"/>
717 <?php } ?>
718 <input name="is_fcom_auth" type="hidden" value="yes"/>
719 <input type="hidden" name="fcom_redirect" value="<?php echo esc_url($reditectUrl); ?>"/>
720 <?php
721 return ob_get_clean();
722 });
723 ?>
724 <div id="fcom_user_onboard_wrap" class="fcom_user_onboard">
725 <div class="fcom_onboard_header">
726 <?php do_action('fluent_community/before_auth_form_header', 'login'); ?>
727 <div class="fcom_onboard_header_title">
728 <?php if (!empty($formSettings['title'])): ?>
729 <h2>
730 <?php echo esc_html($formSettings['title']); ?>
731 </h2>
732 <?php endif; ?>
733 </div>
734 <?php if (!empty($formSettings['description'])): ?>
735 <div class="fcom_onboard_sub">
736 <?php echo wp_kses_post(trim($formSettings['description'])); ?>
737 </div>
738 <?php endif; ?>
739 </div>
740 <div class="fcom_onboard_body">
741 <div class="fcom_onboard_form">
742 <?php echo do_shortcode('[fluent_auth_login redirect_to="' . esc_url($currentUrl) . '"]'); ?>
743 <div class="fcom_spaced_divider">
744 <?php if ($invitation || AuthHelper::isRegistrationEnabled()): ?>
745 <div class="fcom_alt_auth_text">
746 <?php esc_html_e('Don\'t have an account?', 'fluent-community'); ?>
747 <a href="<?php echo esc_url($signupUrl); ?>">
748 <?php esc_html_e('Signup', 'fluent-community'); ?>
749 </a>
750 </div>
751 <?php endif; ?>
752 <p class="fcom_reset_pass_text">
753 <a href="<?php echo esc_url(AuthHelper::getLostPasswordUrl($currentUrl)); ?>">
754 <?php esc_html_e('Lost your password?', 'fluent-community'); ?>
755 </a>
756 </p>
757 </div>
758 </div>
759 </div>
760 </div>
761 <?php
762 return;
763 }
764
765 $frameData['redirect'] = $currentUrl;
766
767 $frameData['hiddenFields'] = [
768 'action' => 'fcom_user_login_form',
769 '_fcom_login_nonce' => wp_create_nonce('fcom_user_login_nonce'),
770 ];
771 if ($invitation) {
772 $frameData['button_label'] = __('Log In & Accept Invitation', 'fluent-community');
773 $frameData['hiddenFields']['invitation_token'] = $invitation->message_rendered;
774 }
775
776 $frameData['title'] = $title;
777 $frameData['description'] = $description;
778
779 $frameData['defaults'] = [
780 'email' => $invitation ? $invitation->message : ''
781 ];
782
783 if ($invitation || AuthHelper::isRegistrationEnabled()) {
784 $frameData['signupUrl'] = $signupUrl;
785 }
786
787 $frameData['settings'] = $formSettings;
788
789 if (isset($_GET['redirect_to'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
790 $frameData['redirect_to'] = sanitize_url(wp_unslash($_GET['redirect_to'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
791 }
792
793 App::make('view')->render('auth.login_form', $frameData);
794 }
795
796 public function renderRegistrationForm($frameData, $invitation = null)
797 {
798 $formFields = AuthHelper::getFormFields($invitation);
799
800 // Prefill the name from the invitation link's query param when present.
801 $inviteName = isset($_GET['invite_name']) ? sanitize_text_field(wp_unslash($_GET['invite_name'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
802 if ($inviteName && isset($formFields['full_name']) && empty($formFields['full_name']['value'])) {
803 $formFields['full_name']['value'] = $inviteName;
804 }
805
806 $authSettings = AuthenticationService::getAuthSettings();
807
808 $termsField = Arr::get($authSettings, 'signup.form.fields.terms');
809
810
811 if ($termsField) {
812 unset($termsField['label']);
813
814 // add new tab on the link for $termsField['inline_label']
815 $termsField['inline_label'] = FeedsHelper::addNewTabToLinks($termsField['inline_label']);
816
817 $formFields['terms'] = $termsField;
818 }
819
820 $frameData['formFields'] = $formFields;
821
822 if ($invitation) {
823 $frameData['hiddenFields'] = [
824 'invitation_token' => $invitation->message_rendered,
825 'action' => 'fcom_user_registration',
826 '_fcom_signup_nonce' => wp_create_nonce('fluent_auth_signup_nonce')
827 ];
828
829 $invitationBy = $invitation->xprofile ? $invitation->xprofile->display_name : __('Someone', 'fluent-community');
830 /* translators: %s is replaced by the name of the inviter */
831 $inviteDescription = sprintf(__('%s has invited you to join this community. Please create an account to accept your invitation.', 'fluent-community'), $invitationBy);
832
833 add_action('fluent_community/before_auth_form_header', function ($formType) use ($inviteDescription) {
834 ?>
835 <div class="fcom_highlight_message">
836 <?php echo wp_kses_post($inviteDescription); ?>
837 </div>
838 <?php
839 });
840
841 $frameData['button_label'] = __('Register & Accept invitation', 'fluent-community');
842 } else {
843 $frameData['hiddenFields'] = [
844 'register' => 'yes',
845 'action' => 'fcom_user_registration',
846 '_fcom_signup_nonce' => wp_create_nonce('fluent_auth_signup_nonce'),
847 ];
848
849 if (!empty($_GET['redirect_to'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
850 $frameData['hiddenFields']['redirect_to'] = sanitize_url(wp_unslash($_GET['redirect_to'])); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
851 }
852 }
853
854 add_action('fluent_community/before_registration_form', function ($frameData) {
855 if (AuthHelper::isFluentAuthAvailable()) {
856 $currentUrl = esc_url(home_url(add_query_arg($_GET, $GLOBALS['wp']->request))); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
857
858 ob_start();
859 $titlePrefix = __('Signup with', 'fluent-community');
860 do_shortcode('[fs_auth_buttons redirect="' . $currentUrl . '" title_prefix="' . $titlePrefix . ' " title=""]');
861 $html = ob_get_clean();
862
863 if ($html) {
864 echo '<div class="fcom_social_auth_wrap">';
865 echo $html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
866 echo '</div>';
867 }
868 }
869 });
870
871 App::make('view')->render('auth.user_invitation', $frameData);
872 }
873 }
874