PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.6.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.6.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 / AuthHelper.php

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

531 lines 23.4 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 return apply_filters('fluent_auth/verify_signup_email', true);
263 }
264
265 public static function get2FaRegistrationCodeForm($formData)
266 {
267 $generalSettings = Helper::generalSettings();
268 try {
269 $verifcationCode = str_pad(random_int(100123, 900987), 6, 0, STR_PAD_LEFT);
270 } catch (\Exception $e) {
271 $verifcationCode = str_pad(wp_rand(100123, 900987), 6, 0, STR_PAD_LEFT);
272 }
273
274 // Hash the code
275 $codeHash = wp_hash_password($verifcationCode);
276
277 // Create a token with the email and code hash
278 $data = [
279 'email' => $formData['email'],
280 'code_hash' => $codeHash,
281 'expires' => time() + 600 // 10 minutes expiry
282 ];
283 $token = base64_encode(json_encode($data));
284
285 // Sign the token
286 $signature = hash_hmac('sha256', $token, SECURE_AUTH_KEY);
287 $signedToken = $token . '.' . $signature;
288
289 /* translators: %s is replaced by the title of the site */
290 $mailSubject = apply_filters("fluent_community/auth/signup_verification_mail_subject", sprintf(__('Your registration verification code for %s', 'fluent-community'), Arr::get($generalSettings, 'site_title')));
291
292 $pStart = '<p style="font-family: Arial, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">';
293
294 /* translators: %s is replaced by the name of the user */
295 $message = $pStart . sprintf(__('Hello %s,', 'fluent-community'), Arr::get($formData, 'first_name')) . '</p>' .
296 $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>' .
297 /* translators: %s is replaced by the verification code */
298 $pStart . '<b>' . sprintf(__('Verification Code: %s', 'fluent-community'), $verifcationCode) . '</b></p>' .
299 '<br />' .
300 $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>';
301
302 $message = apply_filters('fluent_community/auth/signup_verification_email_body', $message, $verifcationCode, $formData);
303
304 $generalSettings = Helper::generalSettings();
305 $message = (string)App::make('view')->make('email.template', [
306 'logo' => [
307 'url' => $generalSettings['logo'],
308 'alt' => $generalSettings['site_title']
309 ],
310 'bodyContent' => $message,
311 'pre_header' => __('Activate your account', 'fluent-community'),
312 'footerLines' => [
313 __('If you did not initiate this request, please ignore this email.', 'fluent-community'),
314 /* translators: %1$s is replaced by the title of the site, %2$s is replaced by the home URL */
315 sprintf(__('This email has been sent from %1$s. Site: %2$s', 'fluent-community'), Arr::get($generalSettings, 'site_title'), home_url())
316 ]
317 ]);
318
319 $mailer = new Mailer($formData['email'], $mailSubject, $message);
320
321 if ($formData['first_name']) {
322 $toName = trim(Arr::get($formData, 'first_name') . ' ' . Arr::get($formData, 'last_name'));
323 $mailer = $mailer->to($formData['email'], $toName);
324 }
325
326 $mailer->send();
327
328 ob_start();
329 ?>
330 <div class="fls_signup_verification">
331 <input type="hidden" name="__two_fa_signed_token" value="<?php echo esc_attr($signedToken); ?>"/>
332 <?php /* translators: %s is replaced by the email address */ ?>
333 <p><?php echo esc_html(\sprintf(__('A verification code has been sent to %s. Please provide the code below: ', 'fluent-community'), $formData['email'])) ?></p>
334 <div class="fcom_form-group fcom_field_verification">
335 <div class="fcom_form_label">
336 <label for="fcom_field_verification"><?php esc_html_e('Verification Code', 'fluent-community'); ?></label>
337 </div>
338 <div class="fs_input_wrap">
339 <input type="text" id="fcom_field_verification"
340 placeholder="<?php esc_html_e('2FA Code', 'fluent-community'); ?>" name="_email_verification_code"
341 required/>
342 </div>
343 </div>
344 <div class="fcom_form-group">
345 <div class="fcom_form_input">
346 <button type="submit" class="fcom_btn has_svg_loader fcom_btn_primary">
347 <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">
348 <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">
349 <animateTransform attributeType="xml"
350 attributeName="transform"
351 type="rotate"
352 from="0 25 25"
353 to="360 25 25"
354 dur="0.6s"
355 repeatCount="indefinite"/>
356 </path>
357 </svg>
358 <span> <?php esc_html_e('Complete Signup', 'fluent-community'); ?></span>
359 </button>
360 </div>
361 </div>
362 </div>
363
364 <?php
365 return ob_get_clean();
366 }
367
368 public static function validateVerificationCode($code, $verificationToken, $formData)
369 {
370 if (!is_string($verificationToken) || strpos($verificationToken, '.') === false) {
371 return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
372 }
373
374 list($data, $signature) = explode('.', $verificationToken, 2);
375 if (!$data || !$signature) {
376 return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
377 }
378
379 $expectedSignature = hash_hmac('sha256', $data, SECURE_AUTH_KEY);
380
381 if (!hash_equals($expectedSignature, $signature)) {
382 return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
383 }
384
385 $decodedData = base64_decode($data, true); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
386 if ($decodedData === false) {
387 return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
388 }
389
390 $data = json_decode($decodedData, true);
391 if (!is_array($data) || empty($data['expires']) || empty($data['email']) || empty($data['code_hash'])) {
392 return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
393 }
394
395 if ((int)$data['expires'] < time()) {
396 return new \WP_Error('expired_token', __('Verification token has expired. Please try again.', 'fluent-community'));
397 }
398
399 if (!isset($formData['email']) || $data['email'] !== $formData['email']) {
400 return new \WP_Error('invalid_email', __('Invalid email address. Please try again', 'fluent-community'));
401 }
402
403 if (!wp_check_password($code, $data['code_hash'])) {
404 return new \WP_Error('invalid_code', __('Invalid verification code. Please try again', 'fluent-community'));
405 }
406
407 return true;
408 }
409
410 public static function isAuthRateLimit()
411 {
412 if (apply_filters('fluent_community/auth/disable_rate_limit', false)) {
413 return true;
414 }
415
416 $transientKey = 'fluent_com_rate_limit_' . md5(Helper::getIp());
417 $rateLimit = get_transient($transientKey);
418
419 if (!$rateLimit) {
420 $rateLimit = 0;
421 }
422
423 if ($rateLimit >= 10) {
424 return new \WP_Error('rate_limit', __('Too many requests. Please try again later', 'fluent-community'));
425 }
426
427 $rateLimit = $rateLimit + 1;
428 set_transient($transientKey, $rateLimit, 300); // per 5 minutes
429 return true;
430 }
431
432
433 public static function nativeLoginForm($args = array(), $hiddenFields = [])
434 {
435 $defaults = array(
436 'echo' => true,
437 'redirect' => (is_ssl() ? 'https://' : 'http://')
438 . (isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : '')
439 . (isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : ''),
440 'form_id' => 'loginform',
441 'label_username' => __('Email Address', 'fluent-community'),
442 'label_password' => __('Password', 'fluent-community'),
443 'label_remember' => __('Remember Me', 'fluent-community'),
444 'label_log_in' => __('Log In', 'fluent-community'),
445 'id_username' => 'user_login',
446 'id_password' => 'user_pass',
447 'id_remember' => 'rememberme',
448 'id_submit' => 'wp-submit',
449 'remember' => true,
450 'value_username' => '',
451 'username_placeholder' => __('Your account email address', 'fluent-community'),
452 'password_placeholder' => __('Your account password', 'fluent-community'),
453 'value_remember' => false,
454 );
455
456 $args = wp_parse_args($args, apply_filters('login_form_defaults', $defaults)); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
457
458 $login_form_top = apply_filters('login_form_top', '', $args); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
459
460 $login_form_middle = apply_filters('login_form_middle', '', $args); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
461
462 $login_form_bottom = apply_filters('login_form_bottom', '', $args); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
463
464 $actionUrl = esc_url(site_url('wp-login.php', 'login_post'));
465
466 if (isset($args['action_url'])) {
467 $actionUrl = esc_url($args['action_url']);
468 }
469
470 foreach ($hiddenFields as $key => $value) {
471 $login_form_top .= \sprintf(
472 '<input type="hidden" name="%1$s" value="%2$s" />',
473 esc_attr($key),
474 esc_attr($value)
475 );
476 }
477
478 $form = \sprintf(
479 '<form name="%1$s" id="%1$s" action="%2$s" method="post">',
480 esc_attr($args['form_id']),
481 $actionUrl
482 ) .
483 $login_form_top .
484 \sprintf(
485 '<p class="login-username fcom_form-group">
486 <label for="%1$s">%2$s</label>
487 <input type="text" name="log" id="%1$s" autocomplete="username" class="input" value="%3$s" placeholder="%4$s" size="20" />
488 </p>',
489 esc_attr($args['id_username']),
490 esc_html($args['label_username']),
491 esc_attr($args['value_username']),
492 esc_attr($args['username_placeholder']),
493 ) .
494 \sprintf(
495 '<p class="login-password fcom_form-group">
496 <label for="%1$s">%2$s</label>
497 <input type="password" name="pwd" id="%1$s" autocomplete="current-password" placeholder="%3$s" class="input" value="" size="20" />
498 </p>',
499 esc_attr($args['id_password']),
500 esc_html($args['label_password']),
501 esc_attr($args['password_placeholder'])
502 ) .
503 $login_form_middle .
504 ($args['remember'] ?
505 \sprintf(
506 '<p class="login-remember fcom_form-group"><label><input name="rememberme" type="checkbox" id="%1$s" value="forever"%2$s /> %3$s</label></p>',
507 esc_attr($args['id_remember']),
508 ($args['value_remember'] ? ' checked="checked"' : ''),
509 esc_html($args['label_remember'])
510 ) : ''
511 ) .
512 \sprintf(
513 '<p class="login-submit">
514 <input type="submit" name="wp-submit" id="%1$s" class="button button-primary" value="%2$s" />
515 <input type="hidden" name="redirect_to" value="%3$s" />
516 </p>',
517 esc_attr($args['id_submit']),
518 esc_attr($args['label_log_in']),
519 esc_url($args['redirect'])
520 ) .
521 $login_form_bottom .
522 '</form>';
523
524 if ($args['echo']) {
525 echo $form; // @phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
526 } else {
527 return $form;
528 }
529 }
530 }
531