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 / AuthHelper.php

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

534 lines 23.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentCommunity\Modules\Auth;
4
5 use FluentCommunity\App\App;
6 use FluentCommunity\App\Services\Helper;
7 use FluentCommunity\App\Services\Libs\Mailer;
8 use FluentCommunity\Framework\Support\Arr;
9
10 class AuthHelper
11 {
12 public static function registerNewUser($user_login, $user_email, $user_pass = '', $extraData = [])
13 {
14 $errors = new \WP_Error();
15
16 $sanitized_user_login = sanitize_user($user_login);
17
18 $user_email = apply_filters('user_registration_email', $user_email); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
19
20 // Check the username.
21 if ('' === $sanitized_user_login) {
22 $errors->add('empty_username', __('<strong>Error</strong>: Please enter a username.', 'fluent-community'));
23 } elseif (!validate_username($user_login)) {
24 $errors->add('invalid_username', __('<strong>Error</strong>: This username is invalid because it uses illegal characters. Please enter a valid username.', 'fluent-community'));
25 $sanitized_user_login = '';
26 } elseif (username_exists($sanitized_user_login)) {
27 $errors->add('username_exists', __('<strong>Error</strong>: This username is already registered. Please choose another one.', 'fluent-community'));
28 } else {
29 /** This filter is documented in wp-includes/user.php */
30 $illegal_user_logins = (array)apply_filters('illegal_user_logins', array()); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
31 if (in_array(strtolower($sanitized_user_login), array_map('strtolower', $illegal_user_logins), true)) {
32 $errors->add('invalid_username', __('<strong>Error</strong>: Sorry, that username is not allowed.', 'fluent-community'));
33 }
34 }
35
36 // Check the email address.
37 if ('' === $user_email) {
38 $errors->add('empty_email', __('<strong>Error</strong>: Please type your email address.', 'fluent-community'));
39 } elseif (!is_email($user_email)) {
40 $errors->add('invalid_email', __('<strong>Error</strong>: The email address is not correct.', 'fluent-community'));
41 $user_email = '';
42 } elseif (email_exists($user_email)) {
43 $errors->add(
44 'email_exists',
45 __('<strong>Error:</strong> This email address is already registered. Please login or try resetting your password.', 'fluent-community')
46 );
47 }
48
49 do_action('register_post', $sanitized_user_login, $user_email, $errors); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
50
51 if ($errors->has_errors()) {
52 return $errors;
53 }
54
55 if (!$user_pass) {
56 $user_pass = wp_generate_password(8, false);
57 }
58
59 $data = [
60 'user_login' => wp_slash($sanitized_user_login),
61 'user_email' => wp_slash($user_email),
62 'user_pass' => $user_pass
63 ];
64
65 if (!empty($extraData['first_name'])) {
66 $data['first_name'] = sanitize_text_field($extraData['first_name']);
67 }
68
69 if (!empty($extraData['last_name'])) {
70 $data['last_name'] = sanitize_text_field($extraData['last_name']);
71 }
72
73 if (!empty($extraData['full_name']) && empty($extraData['first_name']) && empty($extraData['last_name'])) {
74 $extraData['full_name'] = sanitize_text_field($extraData['full_name']);
75 // extract the names
76 $fullNameArray = explode(' ', $extraData['full_name']);
77 $data['first_name'] = array_shift($fullNameArray);
78 if ($fullNameArray) {
79 $data['last_name'] = implode(' ', $fullNameArray);
80 } else {
81 $data['last_name'] = '';
82 }
83 }
84
85 if (!empty($extraData['description'])) {
86 $data['description'] = sanitize_textarea_field($extraData['description']);
87 }
88
89 if (!empty($extraData['user_url']) && filter_var($extraData['user_url'], FILTER_VALIDATE_URL)) {
90 $data['user_url'] = sanitize_url($extraData['user_url']);
91 }
92
93 if (!empty($extraData['role'])) {
94 $data['role'] = $extraData['role'];
95 }
96
97 $user_id = wp_insert_user($data);
98
99 if (!$user_id || is_wp_error($user_id)) {
100 $errors->add('registerfail', __('<strong>Error</strong>: Could not register you. Please contact the site admin!', 'fluent-community')
101 );
102 return $errors;
103 }
104
105 if (!empty($_COOKIE['wp_lang'])) {
106 $wp_lang = sanitize_text_field(wp_unslash($_COOKIE['wp_lang']));
107 if (in_array($wp_lang, get_available_languages(), true)) {
108 update_user_meta($user_id, 'locale', $wp_lang); // Set user locale if defined on registration.
109 }
110 }
111
112 do_action('register_new_user', $user_id); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
113
114 return $user_id;
115 }
116
117 public static function makeLogin($user)
118 {
119 wp_clear_auth_cookie();
120 wp_set_current_user($user->ID, $user->user_login);
121 wp_set_auth_cookie($user->ID, true, is_ssl());
122
123 $user = get_user_by('ID', $user->ID);
124
125 if ($user) {
126 do_action('wp_login', $user->user_login, $user); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
127 }
128
129 return $user;
130 }
131
132 public static function isFluentAuthAvailable()
133 {
134 if (defined('FLUENT_AUTH_VERSION') && FLUENT_AUTH_VERSION) {
135 return (new \FluentAuth\App\Hooks\Handlers\CustomAuthHandler())->isEnabled();
136 }
137
138 return false;
139 }
140
141 public static function getTermsText()
142 {
143 $policyUrl = apply_filters('fluent_community/terms_policy_url', get_privacy_policy_url());
144
145 $termsText = __('I agree to the terms and conditions', 'fluent-community');
146 if ($policyUrl) {
147 /* translators: %1$s is replaced by the text "terms and conditions", %2$s is replaced by the text "to the terms and conditions" */
148 $termsText = sprintf(__('I agree to the %1$s terms and conditions %2$s', 'fluent-community'), '<a rel="noopener" href="' . esc_url($policyUrl) . '" target="_blank">', '</a>');
149 }
150
151 return $termsText;
152 }
153
154 public static function getFormFields($invitation = null)
155 {
156 $fields = apply_filters('fluent_community/auth/signup_fields', [
157 'full_name' => [
158 'label' => __('Full name', 'fluent-community'),
159 'placeholder' => __('Your first & last name', 'fluent-community'),
160 'type' => 'text',
161 'required' => true,
162 'value' => $invitation ? Arr::get($invitation->meta, 'invitee_name') : '',
163 'sanitize_callback' => 'sanitize_text_field'
164 ],
165 'email' => [
166 'type' => 'email',
167 'placeholder' => __('Your email address', 'fluent-community'),
168 'label' => __('Email Address', 'fluent-community'),
169 'required' => true,
170 'value' => $invitation ? $invitation->message : '',
171 'readonly' => $invitation && $invitation->message,
172 'sanitize_callback' => 'sanitize_email'
173 ],
174 'username' => [
175 'type' => 'text',
176 'placeholder' => __('No space or special characters', 'fluent-community'),
177 'label' => __('Username', 'fluent-community'),
178 'required' => true,
179 'sanitize_callback' => 'sanitize_user'
180 ],
181 'password' => [
182 'type' => 'password',
183 'placeholder' => __('Password', 'fluent-community'),
184 'label' => __('Account Password', 'fluent-community'),
185 'required' => true,
186 'sanitize_callback' => 'sanitize_text_field'
187 ],
188 'conf_password' => [
189 'type' => 'password',
190 'placeholder' => __('Password Confirmation', 'fluent-community'),
191 'label' => __('Re-type Account Password', 'fluent-community'),
192 'required' => true,
193 'sanitize_callback' => 'sanitize_text_field'
194 ],
195 'terms' => [
196 'type' => 'inline_checkbox',
197 'inline_label' => self::getTermsText(),
198 'required' => true
199 ]
200 ], $invitation);
201
202 if (!self::isPasswordConfRequired()) {
203 unset($fields['conf_password']);
204 }
205
206 return $fields;
207 }
208
209 public static function getLostPasswordUrl($redirectUrl = '')
210 {
211 if (self::isFluentAuthAvailable()) {
212 $url = add_query_arg([
213 'form' => 'reset_password'
214 ], Helper::getAuthUrl());
215 } else {
216 $url = wp_lostpassword_url($redirectUrl);;
217 }
218
219 return apply_filters('fluent_community/auth/lost_password_url', $url);
220 }
221
222 public static function getLoginFormFields()
223 {
224 return apply_filters('fluent_community/auth/login_fields', [
225 'username' => [
226 'type' => 'text',
227 'placeholder' => __('Your account email address', 'fluent-community'),
228 'label' => __('Email Address', 'fluent-community'),
229 'required' => true,
230 'sanitize_callback' => 'sanitize_user'
231 ],
232 'password' => [
233 'type' => 'password',
234 'placeholder' => __('Your account password', 'fluent-community'),
235 'label' => __('Password', 'fluent-community'),
236 'required' => true,
237 'sanitize_callback' => 'sanitize_text_field'
238 ]
239 ]);
240 }
241
242 public static function isPasswordConfRequired()
243 {
244 return apply_filters('fluent_community/autg/password_confirmation', true);
245 }
246
247 public static function isRegistrationEnabled()
248 {
249
250 $enabled = !!get_option('users_can_register');
251
252 if (!$enabled) {
253 $generalSettinsg = Helper::generalSettings();
254 $enabled = $generalSettinsg['explicit_registration'] !== 'no';
255 }
256
257 return apply_filters('fluent_community/auth/registration_enabled', $enabled);
258 }
259
260 public static function isTwoFactorEnabled()
261 {
262 // fluent_auth/verify_signup_email is kept for backward compatibility with FluentAuth-targeted snippets
263 $enabled = apply_filters('fluent_auth/verify_signup_email', true);
264
265 return apply_filters('fluent_community/auth/two_factor_enabled', $enabled);
266 }
267
268 public static function get2FaRegistrationCodeForm($formData)
269 {
270 $generalSettings = Helper::generalSettings();
271 try {
272 $verifcationCode = str_pad((string) random_int(100123, 900987), 6, '0', STR_PAD_LEFT);
273 } catch (\Exception $e) {
274 $verifcationCode = str_pad((string) wp_rand(100123, 900987), 6, '0', STR_PAD_LEFT);
275 }
276
277 // Hash the code
278 $codeHash = wp_hash_password($verifcationCode);
279
280 // Create a token with the email and code hash
281 $data = [
282 'email' => $formData['email'],
283 'code_hash' => $codeHash,
284 'expires' => time() + 600 // 10 minutes expiry
285 ];
286 $token = base64_encode(json_encode($data));
287
288 // Sign the token
289 $signature = hash_hmac('sha256', $token, SECURE_AUTH_KEY);
290 $signedToken = $token . '.' . $signature;
291
292 /* translators: %s is replaced by the title of the site */
293 $mailSubject = apply_filters("fluent_community/auth/signup_verification_mail_subject", sprintf(__('Your registration verification code for %s', 'fluent-community'), Arr::get($generalSettings, 'site_title')));
294
295 $pStart = '<p style="font-family: Arial, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">';
296
297 /* translators: %s is replaced by the name of the user */
298 $message = $pStart . sprintf(__('Hello %s,', 'fluent-community'), Arr::get($formData, 'first_name')) . '</p>' .
299 $pStart . __('Thank you for registering with us! To complete the setup of your account, please enter the verification code below on the registration page.', 'fluent-community') . '</p>' .
300 /* translators: %s is replaced by the verification code */
301 $pStart . '<b>' . sprintf(__('Verification Code: %s', 'fluent-community'), $verifcationCode) . '</b></p>' .
302 '<br />' .
303 $pStart . __('This code is valid for 10 minutes and is meant to ensure the security of your account. If you did not initiate this request, please ignore this email.', 'fluent-community') . '</p>';
304
305 $message = apply_filters('fluent_community/auth/signup_verification_email_body', $message, $verifcationCode, $formData);
306
307 $generalSettings = Helper::generalSettings();
308 $message = (string)App::make('view')->make('email.template', [
309 'logo' => [
310 'url' => $generalSettings['logo'],
311 'alt' => $generalSettings['site_title']
312 ],
313 'bodyContent' => $message,
314 'pre_header' => __('Activate your account', 'fluent-community'),
315 'footerLines' => [
316 __('If you did not initiate this request, please ignore this email.', 'fluent-community'),
317 /* translators: %1$s is replaced by the title of the site, %2$s is replaced by the home URL */
318 sprintf(__('This email has been sent from %1$s. Site: %2$s', 'fluent-community'), Arr::get($generalSettings, 'site_title'), home_url())
319 ]
320 ]);
321
322 $mailer = new Mailer($formData['email'], $mailSubject, $message);
323
324 if ($formData['first_name']) {
325 $toName = trim(Arr::get($formData, 'first_name') . ' ' . Arr::get($formData, 'last_name'));
326 $mailer = $mailer->to($formData['email'], $toName);
327 }
328
329 $mailer->send();
330
331 ob_start();
332 ?>
333 <div class="fls_signup_verification">
334 <input type="hidden" name="__two_fa_signed_token" value="<?php echo esc_attr($signedToken); ?>"/>
335 <?php /* translators: %s is replaced by the email address */ ?>
336 <p><?php echo esc_html(\sprintf(__('A verification code has been sent to %s. Please provide the code below: ', 'fluent-community'), $formData['email'])) ?></p>
337 <div class="fcom_form-group fcom_field_verification">
338 <div class="fcom_form_label">
339 <label for="fcom_field_verification"><?php esc_html_e('Verification Code', 'fluent-community'); ?></label>
340 </div>
341 <div class="fs_input_wrap">
342 <input type="text" id="fcom_field_verification"
343 placeholder="<?php esc_html_e('2FA Code', 'fluent-community'); ?>" name="_email_verification_code"
344 required/>
345 </div>
346 </div>
347 <div class="fcom_form-group">
348 <div class="fcom_form_input">
349 <button type="submit" class="fcom_btn has_svg_loader fcom_btn_primary">
350 <svg version="1.1" class="fls_loading_svg" x="0px" y="0px" width="40px" height="20px" viewBox="0 0 50 50" style="enable-background:new 0 0 50 50;" xml:space="preserve">
351 <path fill="currentColor" d="M43.935,25.145c0-10.318-8.364-18.683-18.683-18.683c-10.318,0-18.683,8.365-18.683,18.683h4.068c0-8.071,6.543-14.615,14.615-14.615c8.072,0,14.615,6.543,14.615,14.615H43.935z">
352 <animateTransform attributeType="xml"
353 attributeName="transform"
354 type="rotate"
355 from="0 25 25"
356 to="360 25 25"
357 dur="0.6s"
358 repeatCount="indefinite"/>
359 </path>
360 </svg>
361 <span> <?php esc_html_e('Complete Signup', 'fluent-community'); ?></span>
362 </button>
363 </div>
364 </div>
365 </div>
366
367 <?php
368 return ob_get_clean();
369 }
370
371 public static function validateVerificationCode($code, $verificationToken, $formData)
372 {
373 if (!is_string($verificationToken) || strpos($verificationToken, '.') === false) {
374 return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
375 }
376
377 list($data, $signature) = explode('.', $verificationToken, 2);
378 if (!$data || !$signature) {
379 return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
380 }
381
382 $expectedSignature = hash_hmac('sha256', $data, SECURE_AUTH_KEY);
383
384 if (!hash_equals($expectedSignature, $signature)) {
385 return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
386 }
387
388 $decodedData = base64_decode($data, true); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
389 if ($decodedData === false) {
390 return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
391 }
392
393 $data = json_decode($decodedData, true);
394 if (!is_array($data) || empty($data['expires']) || empty($data['email']) || empty($data['code_hash'])) {
395 return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
396 }
397
398 if ((int)$data['expires'] < time()) {
399 return new \WP_Error('expired_token', __('Verification token has expired. Please try again.', 'fluent-community'));
400 }
401
402 if (!isset($formData['email']) || $data['email'] !== $formData['email']) {
403 return new \WP_Error('invalid_email', __('Invalid email address. Please try again', 'fluent-community'));
404 }
405
406 if (!wp_check_password($code, $data['code_hash'])) {
407 return new \WP_Error('invalid_code', __('Invalid verification code. Please try again', 'fluent-community'));
408 }
409
410 return true;
411 }
412
413 public static function isAuthRateLimit()
414 {
415 if (apply_filters('fluent_community/auth/disable_rate_limit', false)) {
416 return true;
417 }
418
419 $transientKey = 'fluent_com_rate_limit_' . md5(Helper::getIp());
420 $rateLimit = get_transient($transientKey);
421
422 if (!$rateLimit) {
423 $rateLimit = 0;
424 }
425
426 if ($rateLimit >= 10) {
427 return new \WP_Error('rate_limit', __('Too many requests. Please try again later', 'fluent-community'));
428 }
429
430 $rateLimit = $rateLimit + 1;
431 set_transient($transientKey, $rateLimit, 300); // per 5 minutes
432 return true;
433 }
434
435
436 public static function nativeLoginForm($args = array(), $hiddenFields = [])
437 {
438 $defaults = array(
439 'echo' => true,
440 'redirect' => (is_ssl() ? 'https://' : 'http://')
441 . (isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : '')
442 . (isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : ''),
443 'form_id' => 'loginform',
444 'label_username' => __('Email Address', 'fluent-community'),
445 'label_password' => __('Password', 'fluent-community'),
446 'label_remember' => __('Remember Me', 'fluent-community'),
447 'label_log_in' => __('Log In', 'fluent-community'),
448 'id_username' => 'user_login',
449 'id_password' => 'user_pass',
450 'id_remember' => 'rememberme',
451 'id_submit' => 'wp-submit',
452 'remember' => true,
453 'value_username' => '',
454 'username_placeholder' => __('Your account email address', 'fluent-community'),
455 'password_placeholder' => __('Your account password', 'fluent-community'),
456 'value_remember' => false,
457 );
458
459 $args = wp_parse_args($args, apply_filters('login_form_defaults', $defaults)); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
460
461 $login_form_top = apply_filters('login_form_top', '', $args); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
462
463 $login_form_middle = apply_filters('login_form_middle', '', $args); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
464
465 $login_form_bottom = apply_filters('login_form_bottom', '', $args); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
466
467 $actionUrl = esc_url(site_url('wp-login.php', 'login_post'));
468
469 if (isset($args['action_url'])) {
470 $actionUrl = esc_url($args['action_url']);
471 }
472
473 foreach ($hiddenFields as $key => $value) {
474 $login_form_top .= \sprintf(
475 '<input type="hidden" name="%1$s" value="%2$s" />',
476 esc_attr($key),
477 esc_attr($value)
478 );
479 }
480
481 $form = \sprintf(
482 '<form name="%1$s" id="%1$s" action="%2$s" method="post">',
483 esc_attr($args['form_id']),
484 $actionUrl
485 ) .
486 $login_form_top .
487 \sprintf(
488 '<p class="login-username fcom_form-group">
489 <label for="%1$s">%2$s</label>
490 <input type="text" name="log" id="%1$s" autocomplete="username" class="input" value="%3$s" placeholder="%4$s" size="20" />
491 </p>',
492 esc_attr($args['id_username']),
493 esc_html($args['label_username']),
494 esc_attr($args['value_username']),
495 esc_attr($args['username_placeholder']),
496 ) .
497 \sprintf(
498 '<p class="login-password fcom_form-group">
499 <label for="%1$s">%2$s</label>
500 <input type="password" name="pwd" id="%1$s" autocomplete="current-password" placeholder="%3$s" class="input" value="" size="20" />
501 </p>',
502 esc_attr($args['id_password']),
503 esc_html($args['label_password']),
504 esc_attr($args['password_placeholder'])
505 ) .
506 $login_form_middle .
507 ($args['remember'] ?
508 \sprintf(
509 '<p class="login-remember fcom_form-group"><label><input name="rememberme" type="checkbox" id="%1$s" value="forever"%2$s /> %3$s</label></p>',
510 esc_attr($args['id_remember']),
511 ($args['value_remember'] ? ' checked="checked"' : ''),
512 esc_html($args['label_remember'])
513 ) : ''
514 ) .
515 \sprintf(
516 '<p class="login-submit">
517 <input type="submit" name="wp-submit" id="%1$s" class="button button-primary" value="%2$s" />
518 <input type="hidden" name="redirect_to" value="%3$s" />
519 </p>',
520 esc_attr($args['id_submit']),
521 esc_attr($args['label_log_in']),
522 esc_url($args['redirect'])
523 ) .
524 $login_form_bottom .
525 '</form>';
526
527 if ($args['echo']) {
528 echo $form; // @phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
529 } else {
530 return $form;
531 }
532 }
533 }
534