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

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

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