PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.11.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.11.0
2.11.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 All 78 releases
← All changes | Modules/Auth/AuthHelper.php +312 -46 1.0.932.11.0 View file →
@@ -14,9 +14,9 @@
14 14 $errors = new \WP_Error();
15 15
16 16 $sanitized_user_login = sanitize_user($user_login);
17 17
18 - $user_email = apply_filters('user_registration_email', $user_email);
18 + $user_email = apply_filters('user_registration_email', $user_email); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
19 19
20 20 // Check the username.
21 21 if ('' === $sanitized_user_login) {
22 22 $errors->add('empty_username', __('<strong>Error</strong>: Please enter a username.', 'fluent-community'));
@@ -26,9 +26,9 @@
26 26 } elseif (username_exists($sanitized_user_login)) {
27 27 $errors->add('username_exists', __('<strong>Error</strong>: This username is already registered. Please choose another one.', 'fluent-community'));
28 28 } else {
29 29 /** This filter is documented in wp-includes/user.php */
30 - $illegal_user_logins = (array)apply_filters('illegal_user_logins', array());
30 + $illegal_user_logins = (array)apply_filters('illegal_user_logins', array()); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
31 31 if (in_array(strtolower($sanitized_user_login), array_map('strtolower', $illegal_user_logins), true)) {
32 32 $errors->add('invalid_username', __('<strong>Error</strong>: Sorry, that username is not allowed.', 'fluent-community'));
33 33 }
34 34 }
@@ -41,16 +41,24 @@
41 41 $user_email = '';
42 42 } elseif (email_exists($user_email)) {
43 43 $errors->add(
44 44 'email_exists',
45 - __('<strong>Error:</strong> This email address is already registered. Please login or try reset password', 'fluent-community')
45 + __('<strong>Error:</strong> This email address is already registered. Please login or try resetting your password.', 'fluent-community')
46 46 );
47 47 }
48 48
49 - do_action('register_post', $sanitized_user_login, $user_email, $errors);
49 + /**
50 + * MemberPress rejects every `register_post` while its "Disable WordPress registration form"
51 + * option is on (default on). That option targets wp-login.php, not the community portal, which has its own registration gate.
52 + */
53 + $hadMeprBlocker = remove_action('register_post', 'MeprUsersCtrl::maybe_disable_wp_registration_form', 10);
50 54
51 - $errors = apply_filters('registration_errors', $errors, $sanitized_user_login, $user_email);
55 + do_action('register_post', $sanitized_user_login, $user_email, $errors); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
52 56
57 + if ($hadMeprBlocker) {
58 + add_action('register_post', 'MeprUsersCtrl::maybe_disable_wp_registration_form', 10, 3);
59 + }
60 +
53 61 if ($errors->has_errors()) {
54 62 return $errors;
55 63 }
56 64
@@ -104,15 +112,15 @@
104 112 return $errors;
105 113 }
106 114
107 115 if (!empty($_COOKIE['wp_lang'])) {
108 - $wp_lang = sanitize_text_field($_COOKIE['wp_lang']);
116 + $wp_lang = sanitize_text_field(wp_unslash($_COOKIE['wp_lang']));
109 117 if (in_array($wp_lang, get_available_languages(), true)) {
110 118 update_user_meta($user_id, 'locale', $wp_lang); // Set user locale if defined on registration.
111 119 }
112 120 }
113 121
114 - do_action('register_new_user', $user_id);
122 + do_action('register_new_user', $user_id); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
115 123
116 124 return $user_id;
117 125 }
118 126
@@ -124,26 +132,112 @@
124 132
125 133 $user = get_user_by('ID', $user->ID);
126 134
127 135 if ($user) {
128 - do_action('wp_login', $user->user_login, $user);
136 + do_action('wp_login', $user->user_login, $user); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
129 137 }
130 138
131 139 return $user;
132 140 }
133 141
142 + /**
143 + * The slug this plugin is known by inside FluentAuth.
144 + */
145 + const FLUENT_AUTH_HOST = 'fluent-community';
146 +
147 + /**
148 + * Whether FluentAuth's login stack is usable on this screen.
149 + *
150 + * Note the order this has to be asked in: adoptFluentAuth() is what makes the answer
151 + * yes on a site whose shortcode setting is off, so the auth screen adopts first and
152 + * asks second. Everywhere else - a plugin wondering whether the portal is running
153 + * FluentAuth's forms - the question stands on its own.
154 + */
134 155 public static function isFluentAuthAvailable()
135 156 {
136 - if (defined('FLUENT_AUTH_VERSION') && FLUENT_AUTH_VERSION) {
137 - return (new \FluentAuth\App\Hooks\Handlers\CustomAuthHandler())->isEnabled();
157 + if (!defined('FLUENT_AUTH_VERSION') || !FLUENT_AUTH_VERSION) {
158 + return false;
138 159 }
139 160
140 - return false;
161 + return (new \FluentAuth\App\Hooks\Handlers\CustomAuthHandler())->isEnabled();
141 162 }
142 163
164 + /**
165 + * Tells FluentAuth this plugin exists, so it recognises the admin-ajax posts our
166 + * auth screen makes later. Cheap enough to run on every request, which is what it
167 + * has to do - the form post is a request of its own.
168 + *
169 + * @return void
170 + */
171 + public static function registerWithFluentAuth()
172 + {
173 + if (!self::hasFluentAuthBridge()) {
174 + return;
175 + }
176 +
177 + \FluentAuth\App\Services\LoginBridge::register(
178 + self::FLUENT_AUTH_HOST,
179 + 'is_fcom_auth',
180 + /*
181 + * Our own login endpoint. Unreachable while the portal renders FluentAuth's
182 + * form, but a site that filters the adoption back off falls through to it,
183 + * and this is what keeps the second factor arriving as a form there rather
184 + * than as the error message handleUserLogin() would otherwise print.
185 + */
186 + ['fcom_user_login_form']
187 + );
188 + }
189 +
190 + /**
191 + * Hands the screen being rendered to FluentAuth: its shortcodes render here even
192 + * where the site has the front end forms switched off, its assets load, and its
193 + * endpoints answer the posts this screen's forms make.
194 + *
195 + * @return bool whether FluentAuth took it
196 + */
197 + public static function adoptFluentAuth()
198 + {
199 + if (!self::hasFluentAuthBridge()) {
200 + return false;
201 + }
202 +
203 + \FluentAuth\App\Services\LoginBridge::adopt([
204 + 'host' => self::FLUENT_AUTH_HOST
205 + ]);
206 +
207 + return true;
208 + }
209 +
210 + /**
211 + * @return bool whether the installed FluentAuth is new enough to be adopted
212 + */
213 + private static function hasFluentAuthBridge()
214 + {
215 + // FluentAuth's autoloader requires the mapped file unconditionally, so probing
216 + // for a class an older release does not ship is a fatal error, not a false.
217 + return defined('FLUENT_AUTH_VERSION')
218 + && FLUENT_AUTH_VERSION
219 + && defined('FLUENT_AUTH_PLUGIN_PATH')
220 + && file_exists(FLUENT_AUTH_PLUGIN_PATH . 'app/Services/LoginBridge.php')
221 + && class_exists('\FluentAuth\App\Services\LoginBridge');
222 + }
223 +
224 + public static function getTermsText()
225 + {
226 + $policyUrl = apply_filters('fluent_community/terms_policy_url', get_privacy_policy_url());
227 +
228 + $termsText = __('I agree to the terms and conditions', 'fluent-community');
229 + if ($policyUrl) {
230 + /* translators: %1$s is replaced by the text "terms and conditions", %2$s is replaced by the text "to the terms and conditions" */
231 + $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>');
232 + }
233 +
234 + return $termsText;
235 + }
236 +
143 237 public static function getFormFields($invitation = null)
144 238 {
145 - $fields = apply_filters('fluent_communuty/auth/signup_fields', [
239 + $fields = apply_filters('fluent_community/auth/signup_fields', [
146 240 'full_name' => [
147 241 'label' => __('Full name', 'fluent-community'),
148 242 'placeholder' => __('Your first & last name', 'fluent-community'),
149 243 'type' => 'text',
@@ -156,15 +250,15 @@
156 250 'placeholder' => __('Your email address', 'fluent-community'),
157 251 'label' => __('Email Address', 'fluent-community'),
158 252 'required' => true,
159 253 'value' => $invitation ? $invitation->message : '',
160 - 'readonly' => !!$invitation,
254 + 'readonly' => $invitation && $invitation->message,
161 255 'sanitize_callback' => 'sanitize_email'
162 256 ],
163 257 'username' => [
164 258 'type' => 'text',
165 259 'placeholder' => __('No space or special characters', 'fluent-community'),
166 - 'label' => __('Space username', 'fluent-community'),
260 + 'label' => __('Username', 'fluent-community'),
167 261 'required' => true,
168 262 'sanitize_callback' => 'sanitize_user'
169 263 ],
170 264 'password' => [
@@ -182,9 +276,9 @@
182 276 'sanitize_callback' => 'sanitize_text_field'
183 277 ],
184 278 'terms' => [
185 279 'type' => 'inline_checkbox',
186 - 'inline_label' => __('I agree to the terms and conditions', 'fluent-community'),
280 + 'inline_label' => self::getTermsText(),
187 281 'required' => true
188 282 ]
189 283 ], $invitation);
190 284
@@ -194,21 +288,67 @@
194 288
195 289 return $fields;
196 290 }
197 291
292 + public static function getLostPasswordUrl($redirectUrl = '')
293 + {
294 + if (self::isFluentAuthAvailable()) {
295 + $url = add_query_arg([
296 + 'form' => 'reset_password'
297 + ], Helper::getAuthUrl());
298 + } else {
299 + $url = wp_lostpassword_url($redirectUrl);;
300 + }
301 +
302 + return apply_filters('fluent_community/auth/lost_password_url', $url);
303 + }
304 +
305 + public static function getLoginFormFields()
306 + {
307 + return apply_filters('fluent_community/auth/login_fields', [
308 + 'username' => [
309 + 'type' => 'text',
310 + 'placeholder' => __('Your account email address', 'fluent-community'),
311 + 'label' => __('Email Address', 'fluent-community'),
312 + 'required' => true,
313 + 'sanitize_callback' => 'sanitize_user'
314 + ],
315 + 'password' => [
316 + 'type' => 'password',
317 + 'placeholder' => __('Your account password', 'fluent-community'),
318 + 'label' => __('Password', 'fluent-community'),
319 + 'required' => true,
320 + 'sanitize_callback' => 'sanitize_text_field'
321 + ]
322 + ]);
323 + }
324 +
198 325 public static function isPasswordConfRequired()
199 326 {
200 - return apply_filters('fluent_community/autg/password_confirmation', true);
327 + $isRequired = apply_filters_deprecated('fluent_community/autg/password_confirmation', [true], '2.7.8', 'fluent_community/auth/password_confirmation');
328 +
329 + return apply_filters('fluent_community/auth/password_confirmation', $isRequired);
201 330 }
202 331
203 332 public static function isRegistrationEnabled()
204 333 {
205 - return apply_filters('fluent_community/auth/registration_enabled', get_option('users_can_register'));
334 +
335 + $enabled = !!get_option('users_can_register');
336 +
337 + if (!$enabled) {
338 + $generalSettinsg = Helper::generalSettings();
339 + $enabled = $generalSettinsg['explicit_registration'] !== 'no';
340 + }
341 +
342 + return apply_filters('fluent_community/auth/registration_enabled', $enabled);
206 343 }
207 344
208 345 public static function isTwoFactorEnabled()
209 346 {
210 - return apply_filters('fluent_community/auth/two_factor_enabled', true);
347 + // fluent_auth/verify_signup_email is kept for backward compatibility with FluentAuth-targeted snippets
348 + $enabled = apply_filters('fluent_auth/verify_signup_email', true);
349 +
350 + return apply_filters('fluent_community/auth/two_factor_enabled', $enabled);
211 351 }
212 352
213 353 public static function get2FaRegistrationCodeForm($formData)
214 354 {
@@ -213,34 +353,33 @@
213 353 public static function get2FaRegistrationCodeForm($formData)
214 354 {
215 355 $generalSettings = Helper::generalSettings();
216 356 try {
217 - $verifcationCode = str_pad(random_int(100123, 900987), 6, 0, STR_PAD_LEFT);
357 + $verifcationCode = str_pad((string) random_int(100123, 900987), 6, '0', STR_PAD_LEFT);
218 358 } catch (\Exception $e) {
219 - $verifcationCode = str_pad(mt_rand(100123, 900987), 6, 0, STR_PAD_LEFT);
359 + $verifcationCode = str_pad((string) wp_rand(100123, 900987), 6, '0', STR_PAD_LEFT);
220 360 }
221 361
222 - // Hash the code
362 + // Keep the code hash server-side, keyed by an opaque challenge id. The client only ever
363 + // receives the id, never the password verifier, so the code cannot be recovered offline.
223 364 $codeHash = wp_hash_password($verifcationCode);
224 -
225 - // Create a token with the email and code hash
226 - $data = [
365 + $signedToken = 'fcs_' . wp_generate_password(40, false);
366 + set_transient('fcom_signup_2fa_' . $signedToken, [
227 367 'email' => $formData['email'],
228 368 'code_hash' => $codeHash,
229 - 'expires' => time() + 600 // 10 minutes expiry
230 - ];
231 - $token = base64_encode(json_encode($data));
369 + 'expires' => time() + 600, // 10 minutes expiry
370 + 'attempts' => 0,
371 + ], 600);
232 372
233 - // Sign the token
234 - $signature = hash_hmac('sha256', $token, SECURE_AUTH_KEY);
235 - $signedToken = $token . '.' . $signature;
236 -
373 + /* translators: %s is replaced by the title of the site */
237 374 $mailSubject = apply_filters("fluent_community/auth/signup_verification_mail_subject", sprintf(__('Your registration verification code for %s', 'fluent-community'), Arr::get($generalSettings, 'site_title')));
238 375
239 376 $pStart = '<p style="font-family: Arial, sans-serif; font-size: 16px; font-weight: normal; margin: 0; margin-bottom: 16px;">';
240 377
378 + /* translators: %s is replaced by the name of the user */
241 379 $message = $pStart . sprintf(__('Hello %s,', 'fluent-community'), Arr::get($formData, 'first_name')) . '</p>' .
242 380 $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>' .
381 + /* translators: %s is replaced by the verification code */
243 382 $pStart . '<b>' . sprintf(__('Verification Code: %s', 'fluent-community'), $verifcationCode) . '</b></p>' .
244 383 '<br />' .
245 384 $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>';
246 385
@@ -255,9 +394,10 @@
255 394 'bodyContent' => $message,
256 395 'pre_header' => __('Activate your account', 'fluent-community'),
257 396 'footerLines' => [
258 397 __('If you did not initiate this request, please ignore this email.', 'fluent-community'),
259 - sprintf(__('This email has been sent from %1$s. Site: %2$s', 'fluent-community'), Arr::get($generalSettings, 'site_title'), site_url())
398 + /* translators: %1$s is replaced by the title of the site, %2$s is replaced by the home URL */
399 + sprintf(__('This email has been sent from %1$s. Site: %2$s', 'fluent-community'), Arr::get($generalSettings, 'site_title'), home_url())
260 400 ]
261 401 ]);
262 402
263 403 $mailer = new Mailer($formData['email'], $mailSubject, $message);
@@ -272,23 +412,35 @@
272 412 ob_start();
273 413 ?>
274 414 <div class="fls_signup_verification">
275 415 <input type="hidden" name="__two_fa_signed_token" value="<?php echo esc_attr($signedToken); ?>"/>
276 - <p><?php echo esc_html(sprintf(__('A verification code as been sent to %s. Please provide the code bellow: ', 'fluent-community'), $formData['email'])) ?></p>
277 - <div class="fcom_form-group fcom_field_vefication">
416 + <?php /* translators: %s is replaced by the email address */ ?>
417 + <p><?php echo esc_html(\sprintf(__('A verification code has been sent to %s. Please provide the code below: ', 'fluent-community'), $formData['email'])) ?></p>
418 + <div class="fcom_form-group fcom_field_verification">
278 419 <div class="fcom_form_label">
279 - <label for="fcom_field_vefication"><?php _e('Vefication Code', 'fluent-community'); ?></label>
420 + <label for="fcom_field_verification"><?php esc_html_e('Verification Code', 'fluent-community'); ?></label>
280 421 </div>
281 422 <div class="fs_input_wrap">
282 - <input type="text" id="fcom_field_vefication"
283 - placeholder="<?php _e('2FA Code', 'fluent-community'); ?>" name="_email_verification_code"
423 + <input type="text" id="fcom_field_verification"
424 + placeholder="<?php esc_html_e('2FA Code', 'fluent-community'); ?>" name="_email_verification_code"
284 425 required/>
285 426 </div>
286 427 </div>
287 428 <div class="fcom_form-group">
288 429 <div class="fcom_form_input">
289 - <button type="submit" class="fcom_btn fcom_btn_primary">
290 - <?php _e('Complete Signup', 'fluent-community'); ?>
430 + <button type="submit" class="fcom_btn has_svg_loader fcom_btn_primary">
431 + <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">
432 + <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">
433 + <animateTransform attributeType="xml"
434 + attributeName="transform"
435 + type="rotate"
436 + from="0 25 25"
437 + to="360 25 25"
438 + dur="0.6s"
439 + repeatCount="indefinite"/>
440 + </path>
441 + </svg>
442 + <span> <?php esc_html_e('Complete Signup', 'fluent-community'); ?></span>
291 443 </button>
292 444 </div>
293 445 </div>
294 446 </div>
@@ -298,28 +450,43 @@
298 450 }
299 451
300 452 public static function validateVerificationCode($code, $verificationToken, $formData)
301 453 {
302 - list($data, $signature) = explode('.', $verificationToken, 2);
303 - $expectedSignature = hash_hmac('sha256', $data, SECURE_AUTH_KEY);
454 + if (!is_string($verificationToken) || $verificationToken === '') {
455 + return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
456 + }
304 457
305 - if (!hash_equals($expectedSignature, $signature)) {
458 + $transientKey = 'fcom_signup_2fa_' . $verificationToken;
459 + $data = get_transient($transientKey);
460 +
461 + if (!is_array($data) || empty($data['expires']) || empty($data['email']) || empty($data['code_hash'])) {
306 462 return new \WP_Error('invalid_token', __('Invalid verification token. Please try again', 'fluent-community'));
307 463 }
308 464
309 - $data = json_decode(base64_decode($data), true);
310 - if ($data['expires'] < time()) {
311 - return new \WP_Error('expired_token', __('Verification token has expired. Please try again', 'fluent-community'));
465 + if ((int)$data['expires'] < time()) {
466 + delete_transient($transientKey);
467 + return new \WP_Error('expired_token', __('Verification token has expired. Please try again.', 'fluent-community'));
312 468 }
313 469
314 - if ($data['email'] !== $formData['email']) {
470 + if (!isset($formData['email']) || $data['email'] !== $formData['email']) {
315 471 return new \WP_Error('invalid_email', __('Invalid email address. Please try again', 'fluent-community'));
316 472 }
317 473
474 + // Cap online guesses per challenge: after too many wrong codes the challenge is burned.
475 + if ((int) Arr::get($data, 'attempts', 0) >= 10) {
476 + delete_transient($transientKey);
477 + return new \WP_Error('too_many_attempts', __('Too many invalid attempts. Please try again', 'fluent-community'));
478 + }
479 +
318 480 if (!wp_check_password($code, $data['code_hash'])) {
481 + $data['attempts'] = (int) Arr::get($data, 'attempts', 0) + 1;
482 + set_transient($transientKey, $data, max(1, (int) $data['expires'] - time()));
319 483 return new \WP_Error('invalid_code', __('Invalid verification code. Please try again', 'fluent-community'));
320 484 }
321 485
486 + // Single-use: consume the challenge on success.
487 + delete_transient($transientKey);
488 +
322 489 return true;
323 490 }
324 491
325 492 public static function isAuthRateLimit()
@@ -341,6 +508,105 @@
341 508
342 509 $rateLimit = $rateLimit + 1;
343 510 set_transient($transientKey, $rateLimit, 300); // per 5 minutes
344 511 return true;
512 + }
513 +
514 +
515 + public static function nativeLoginForm($args = array(), $hiddenFields = [])
516 + {
517 + $defaults = array(
518 + 'echo' => true,
519 + 'redirect' => (is_ssl() ? 'https://' : 'http://')
520 + . (isset($_SERVER['HTTP_HOST']) ? sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])) : '')
521 + . (isset($_SERVER['REQUEST_URI']) ? sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI'])) : ''),
522 + 'form_id' => 'loginform',
523 + 'label_username' => __('Email Address', 'fluent-community'),
524 + 'label_password' => __('Password', 'fluent-community'),
525 + 'label_remember' => __('Remember Me', 'fluent-community'),
526 + 'label_log_in' => __('Log In', 'fluent-community'),
527 + 'id_username' => 'user_login',
528 + 'id_password' => 'user_pass',
529 + 'id_remember' => 'rememberme',
530 + 'id_submit' => 'wp-submit',
531 + 'remember' => true,
532 + 'value_username' => '',
533 + 'username_placeholder' => __('Your account email address', 'fluent-community'),
534 + 'password_placeholder' => __('Your account password', 'fluent-community'),
535 + 'value_remember' => false,
536 + );
537 +
538 + $args = wp_parse_args($args, apply_filters('login_form_defaults', $defaults)); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
539 +
540 + $login_form_top = apply_filters('login_form_top', '', $args); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
541 +
542 + $login_form_middle = apply_filters('login_form_middle', '', $args); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
543 +
544 + $login_form_bottom = apply_filters('login_form_bottom', '', $args); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
545 +
546 + $actionUrl = esc_url(site_url('wp-login.php', 'login_post'));
547 +
548 + if (isset($args['action_url'])) {
549 + $actionUrl = esc_url($args['action_url']);
550 + }
551 +
552 + foreach ($hiddenFields as $key => $value) {
553 + $login_form_top .= \sprintf(
554 + '<input type="hidden" name="%1$s" value="%2$s" />',
555 + esc_attr($key),
556 + esc_attr($value)
557 + );
558 + }
559 +
560 + $form = \sprintf(
561 + '<form name="%1$s" id="%1$s" action="%2$s" method="post">',
562 + esc_attr($args['form_id']),
563 + $actionUrl
564 + ) .
565 + $login_form_top .
566 + \sprintf(
567 + '<p class="login-username fcom_form-group">
568 + <label for="%1$s">%2$s</label>
569 + <input type="text" name="log" id="%1$s" autocomplete="username" class="input" value="%3$s" placeholder="%4$s" size="20" />
570 + </p>',
571 + esc_attr($args['id_username']),
572 + esc_html($args['label_username']),
573 + esc_attr($args['value_username']),
574 + esc_attr($args['username_placeholder']),
575 + ) .
576 + \sprintf(
577 + '<p class="login-password fcom_form-group">
578 + <label for="%1$s">%2$s</label>
579 + <input type="password" name="pwd" id="%1$s" autocomplete="current-password" placeholder="%3$s" class="input" value="" size="20" />
580 + </p>',
581 + esc_attr($args['id_password']),
582 + esc_html($args['label_password']),
583 + esc_attr($args['password_placeholder'])
584 + ) .
585 + $login_form_middle .
586 + ($args['remember'] ?
587 + \sprintf(
588 + '<p class="login-remember fcom_form-group"><label><input name="rememberme" type="checkbox" id="%1$s" value="forever"%2$s /> %3$s</label></p>',
589 + esc_attr($args['id_remember']),
590 + ($args['value_remember'] ? ' checked="checked"' : ''),
591 + esc_html($args['label_remember'])
592 + ) : ''
593 + ) .
594 + \sprintf(
595 + '<p class="login-submit">
596 + <input type="submit" name="wp-submit" id="%1$s" class="button button-primary" value="%2$s" />
597 + <input type="hidden" name="redirect_to" value="%3$s" />
598 + </p>',
599 + esc_attr($args['id_submit']),
600 + esc_attr($args['label_log_in']),
601 + esc_url($args['redirect'])
602 + ) .
603 + $login_form_bottom .
604 + '</form>';
605 +
606 + if ($args['echo']) {
607 + echo $form; // @phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
608 + } else {
609 + return $form;
610 + }
345 611 }
346 612 }