PluginProbe
Forumax – AI Powered Advanced Community Forum Plugin / 2.5.1
Forumax – AI Powered Advanced Community Forum Plugin v2.5.1
2.5.1 2.5.0 2.4.4 2.4.3 2.4.2 2.4.1 2.4.0 trunk 1.0.8 1.1.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 All 31 releases
bbp-core / includes / login-form.php

login-form.php in Forumax – AI Powered Advanced Community Forum Plugin 2.5.1, at includes/login-form.php

394 lines 15.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Forumax Login & Registration Form Shortcode
4 *
5 * Renders a tabbed Login / Sign Up form. Uses wp_login_form() to ensure
6 * Google Site Kit (and any plugin that hooks into `login_form`) works.
7 *
8 * @package Forumax
9 */
10
11 defined( 'ABSPATH' ) || exit;
12
13 /**
14 * Render the tabbed login and registration form.
15 *
16 * @param array $atts Shortcode attributes.
17 *
18 * @return string HTML output.
19 */
20 function forumax_login_form_shortcode( $atts ) {
21 // If user already logged in, return a message.
22 if ( is_user_logged_in() ) {
23 return '<div class="frmx-auth-logged-in">' . esc_html__( 'You are already logged in.', 'forumax' ) . '</div>';
24 }
25
26 // Same shortcode is used on several spots of one page (reply prompt, topic
27 // prompt, subscribe modal, page content). Each instance needs its own IDs
28 // so a later form is not dropped and tabs/labels stay wired to that form.
29 static $frmx_login_form_instance = 0;
30 $frmx_login_form_instance++;
31 $uid = 'frmx-auth-' . $frmx_login_form_instance;
32
33 // Extract shortcode attributes with defaults.
34 $atts = shortcode_atts( array(
35 'redirect' => '',
36 ), $atts );
37
38 // Set redirect URL.
39 $redirect_url = ! empty( $atts['redirect'] ) ? $atts['redirect'] : get_permalink();
40
41 $can_register = get_option( 'users_can_register' );
42 $wrapper_id = $uid;
43
44 // Errors passed back by forumax_login_failed_redirect() / forumax_handle_registration().
45 // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only display flags.
46 $login_failed = isset( $_GET['login'] ) && 'failed' === sanitize_key( wp_unslash( $_GET['login'] ) );
47 $reg_errors = isset( $_GET['reg_error'] ) ? array_filter( array_map( 'sanitize_key', explode( ',', sanitize_text_field( wp_unslash( $_GET['reg_error'] ) ) ) ) ) : array();
48 // phpcs:enable
49 $active_tab = ( $can_register && ! empty( $reg_errors ) ) ? 'register' : 'login';
50
51 ob_start();
52 ?>
53 <div class="frmx-auth-tabs" id="<?php echo esc_attr( $wrapper_id ); ?>">
54
55 <?php if ( $can_register ) : ?>
56 <div class="frmx-auth-tabs-header">
57 <button type="button" class="frmx-auth-tab<?php echo 'login' === $active_tab ? ' frmx-auth-tab-active' : ''; ?>" data-frmx-tab="login">
58 <?php esc_html_e( 'Sign In', 'forumax' ); ?>
59 </button>
60 <button type="button" class="frmx-auth-tab<?php echo 'register' === $active_tab ? ' frmx-auth-tab-active' : ''; ?>" data-frmx-tab="register">
61 <?php esc_html_e( 'Create Account', 'forumax' ); ?>
62 </button>
63 </div>
64 <?php endif; ?>
65
66 <!-- Login Tab -->
67 <div class="frmx-auth-panel<?php echo 'login' === $active_tab ? ' frmx-auth-panel-active' : ''; ?>" data-frmx-panel="login">
68 <?php if ( $login_failed ) : ?>
69 <div class="frmx-auth-error bbp-template-notice error" role="alert">
70 <p><?php esc_html_e( 'Invalid username, email address or password. Please try again.', 'forumax' ); ?></p>
71 </div>
72 <?php endif; ?>
73 <?php
74 $form_html = wp_login_form( array(
75 'redirect' => $redirect_url,
76 'label_username' => esc_html__( 'Username or Email', 'forumax' ),
77 'label_password' => esc_html__( 'Password', 'forumax' ),
78 'label_remember' => esc_html__( 'Remember Me', 'forumax' ),
79 'label_log_in' => esc_html__( 'Sign In', 'forumax' ),
80 'form_id' => $uid . '-login',
81 'id_username' => $uid . '-user',
82 'id_password' => $uid . '-pass',
83 'id_remember' => $uid . '-remember',
84 'id_submit' => $uid . '-submit',
85 'remember' => true,
86 'echo' => false,
87 ) );
88
89 // Class hook for styling (keeps a stable selector with form_id).
90 $form_html = preg_replace(
91 '/<form\b/i',
92 '<form class="forumax_login_form"',
93 $form_html,
94 1
95 );
96
97 // Add placeholders to core login form fields.
98 $username_placeholder = esc_attr__( 'Username or Email', 'forumax' );
99 $password_placeholder = esc_attr__( 'Password', 'forumax' );
100
101 $form_html = preg_replace(
102 '/(<input\b(?![^>]*\bplaceholder=)[^>]*\bname=["\']log["\'][^>]*)(\/?\s*>)/i',
103 '$1 placeholder="' . $username_placeholder . '"$2',
104 $form_html
105 );
106 $form_html = preg_replace(
107 '/(<input\b(?![^>]*\bplaceholder=)[^>]*\bname=["\']pwd["\'][^>]*)(\/?\s*>)/i',
108 '$1 placeholder="' . $password_placeholder . '"$2',
109 $form_html
110 );
111
112 // Marks submissions from this form so only they are redirected back on failure.
113 $form_html = preg_replace( '/<\/form>/i', '<input type="hidden" name="forumax_login" value="1" /></form>', $form_html, 1 );
114
115 echo $form_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
116 ?>
117
118 <?php
119 /**
120 * Hook to display social login buttons (e.g. Google Site Kit).
121 */
122 do_action( 'forumax_login_form_social_buttons' );
123 ?>
124 </div>
125
126 <?php if ( $can_register ) : ?>
127 <!-- Registration Tab -->
128 <div class="frmx-auth-panel<?php echo 'register' === $active_tab ? ' frmx-auth-panel-active' : ''; ?>" data-frmx-panel="register">
129 <?php if ( ! empty( $reg_errors ) ) : ?>
130 <div class="frmx-auth-error bbp-template-notice error" role="alert">
131 <?php foreach ( $reg_errors as $reg_error ) : ?>
132 <p><?php echo esc_html( forumax_registration_error_message( $reg_error ) ); ?></p>
133 <?php endforeach; ?>
134 </div>
135 <?php endif; ?>
136 <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="frmx-auth-register-form">
137 <input type="hidden" name="action" value="forumax_register" />
138 <input type="hidden" name="redirect_to" value="<?php echo esc_url( $redirect_url ); ?>" />
139
140 <div class="frmx-auth-field">
141 <label for="<?php echo esc_attr( $uid ); ?>-reg-login"><?php esc_html_e( 'Username', 'forumax' ); ?></label>
142 <input type="text" name="user_login" id="<?php echo esc_attr( $uid ); ?>-reg-login" placeholder="<?php esc_attr_e( 'Username', 'forumax' ); ?>" autocomplete="username" required />
143 </div>
144
145 <div class="frmx-auth-field">
146 <label for="<?php echo esc_attr( $uid ); ?>-reg-email"><?php esc_html_e( 'Email', 'forumax' ); ?></label>
147 <input type="email" name="user_email" id="<?php echo esc_attr( $uid ); ?>-reg-email" placeholder="<?php esc_attr_e( 'Email', 'forumax' ); ?>" autocomplete="email" required />
148 </div>
149
150 <div class="frmx-auth-field">
151 <label for="<?php echo esc_attr( $uid ); ?>-reg-pass"><?php esc_html_e( 'Password', 'forumax' ); ?></label>
152 <input type="password" name="user_pass" id="<?php echo esc_attr( $uid ); ?>-reg-pass" placeholder="<?php esc_attr_e( 'Password', 'forumax' ); ?>" autocomplete="new-password" required />
153 </div>
154
155 <div class="frmx-auth-field">
156 <label for="<?php echo esc_attr( $uid ); ?>-reg-pass-confirm"><?php esc_html_e( 'Confirm Password', 'forumax' ); ?></label>
157 <input type="password" name="user_pass_confirm" id="<?php echo esc_attr( $uid ); ?>-reg-pass-confirm" placeholder="<?php esc_attr_e( 'Confirm Password', 'forumax' ); ?>" autocomplete="new-password" required />
158 </div>
159
160 <?php
161 /**
162 * Fires inside the registration form.
163 *
164 * This is the standard WordPress hook that plugins (e.g. CAPTCHA,
165 * custom fields) use to inject content into registration forms.
166 */
167 do_action( 'register_form' );
168 ?>
169
170 <div class="frmx-auth-submit">
171 <button type="submit" id="<?php echo esc_attr( $uid ); ?>-register" name="wp-submit" class="button submit fill-brand">
172 <?php esc_html_e( 'Register', 'forumax' ); ?>
173 </button>
174 </div>
175
176 <?php wp_nonce_field( 'forumax_register', 'forumax_register_nonce' ); ?>
177 </form>
178 </div>
179 <?php endif; ?>
180
181 </div>
182
183 <script>
184 ( function() {
185 var wrapper = document.getElementById( '<?php echo esc_js( $wrapper_id ); ?>' );
186 if ( ! wrapper ) {
187 return;
188 }
189
190 var tabs = wrapper.querySelectorAll( '.frmx-auth-tab' );
191 var panels = wrapper.querySelectorAll( '.frmx-auth-panel' );
192
193 tabs.forEach( function( tab ) {
194 tab.addEventListener( 'click', function() {
195 var target = this.getAttribute( 'data-frmx-tab' );
196
197 tabs.forEach( function( t ) {
198 t.classList.remove( 'frmx-auth-tab-active' );
199 } );
200 this.classList.add( 'frmx-auth-tab-active' );
201
202 panels.forEach( function( p ) {
203 if ( p.getAttribute( 'data-frmx-panel' ) === target ) {
204 p.classList.add( 'frmx-auth-panel-active' );
205 } else {
206 p.classList.remove( 'frmx-auth-panel-active' );
207 }
208 } );
209 } );
210 } );
211
212 /* Password visibility toggle */
213 var eyeShow = '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>';
214 var eyeHide = '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>';
215
216 wrapper.querySelectorAll( 'input[type="password"]' ).forEach( function( input ) {
217 var container = document.createElement( 'div' );
218 container.className = 'frmx-auth-password-wrap';
219 input.parentNode.insertBefore( container, input );
220 container.appendChild( input );
221
222 var toggleBtn = document.createElement( 'button' );
223 toggleBtn.type = 'button';
224 toggleBtn.className = 'frmx-auth-eye-toggle';
225 toggleBtn.setAttribute( 'aria-label', 'Toggle password visibility' );
226 toggleBtn.innerHTML = eyeShow;
227 container.appendChild( toggleBtn );
228
229 toggleBtn.addEventListener( 'click', function() {
230 var isPassword = input.type === 'password';
231 input.type = isPassword ? 'text' : 'password';
232 this.innerHTML = isPassword ? eyeHide : eyeShow;
233 input.focus();
234 } );
235 } );
236 } )();
237 </script>
238 <?php
239
240 return ob_get_clean();
241 }
242
243 add_shortcode( 'forumax_login_form', 'forumax_login_form_shortcode' );
244
245 /**
246 * Redirect to the referrer page after a failed login attempt.
247 *
248 * @param string $username The username that failed login.
249 *
250 * @return void
251 */
252 function forumax_login_failed_redirect( $username ) {
253 // Only handle the Forumax form; other login forms (WooCommerce, REST,
254 // XML-RPC, wp-login.php) keep their own error handling.
255 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- core login has no nonce; this only picks the redirect target.
256 if ( empty( $_POST['forumax_login'] ) ) {
257 return;
258 }
259
260 $referrer = wp_get_referer();
261 if ( ! empty( $referrer ) && ! str_contains( $referrer, 'wp-login.php' ) ) {
262 wp_safe_redirect( add_query_arg( 'login', 'failed', remove_query_arg( array( 'login', 'reg_error' ), $referrer ) ) );
263 exit;
264 }
265 }
266
267 add_action( 'wp_login_failed', 'forumax_login_failed_redirect' );
268
269 /**
270 * Human readable message for a registration error code.
271 *
272 * @param string $code Error code.
273 * @return string
274 */
275 function forumax_registration_error_message( $code ) {
276 $messages = array(
277 'empty_username' => __( 'Please enter a username.', 'forumax' ),
278 'invalid_username' => __( 'This username is invalid. Please use letters, numbers and - _ . @ only.', 'forumax' ),
279 'existing_user_login' => __( 'This username is already registered. Please choose another one.', 'forumax' ),
280 'invalid_email' => __( 'Please enter a valid email address.', 'forumax' ),
281 'email_exists' => __( 'This email address is already registered.', 'forumax' ),
282 'existing_user_email' => __( 'This email address is already registered.', 'forumax' ),
283 'empty_password' => __( 'Please enter a password.', 'forumax' ),
284 'password_mismatch' => __( 'The passwords do not match.', 'forumax' ),
285 );
286
287 return $messages[ $code ] ?? __( 'Registration failed. Please check the form and try again.', 'forumax' );
288 }
289
290 /**
291 * Handle the custom registration form submission.
292 *
293 * Creates a new user with the provided username, email, and password,
294 * auto-logs them in, and redirects to the referring page.
295 *
296 * @return void
297 */
298 function forumax_handle_registration() {
299 // Verify nonce.
300 if ( ! isset( $_POST['forumax_register_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['forumax_register_nonce'] ) ), 'forumax_register' ) ) {
301 wp_die( esc_html__( 'Security check failed.', 'forumax' ), 403 );
302 }
303
304 // Check registration is allowed.
305 if ( ! get_option( 'users_can_register' ) ) {
306 wp_die( esc_html__( 'Registration is currently disabled.', 'forumax' ), 403 );
307 }
308
309 $redirect = ! empty( $_POST['redirect_to'] ) ? esc_url_raw( wp_unslash( $_POST['redirect_to'] ) ) : home_url();
310 $referrer = wp_get_referer();
311 if ( empty( $referrer ) ) {
312 $referrer = $redirect;
313 }
314
315 $user_login = isset( $_POST['user_login'] ) ? sanitize_user( wp_unslash( $_POST['user_login'] ) ) : '';
316 $user_email = isset( $_POST['user_email'] ) ? sanitize_email( wp_unslash( $_POST['user_email'] ) ) : '';
317 $user_pass = isset( $_POST['user_pass'] ) ? $_POST['user_pass'] : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- password must not be sanitized.
318 $user_pass_confirm = isset( $_POST['user_pass_confirm'] ) ? $_POST['user_pass_confirm'] : ''; // phpcs:ignore
319
320 // Validate fields.
321 $errors = array();
322
323 if ( empty( $user_login ) ) {
324 $errors[] = 'empty_username';
325 }
326
327 if ( empty( $user_email ) || ! is_email( $user_email ) ) {
328 $errors[] = 'invalid_email';
329 }
330
331 if ( empty( $user_pass ) ) {
332 $errors[] = 'empty_password';
333 }
334
335 if ( $user_pass !== $user_pass_confirm ) {
336 $errors[] = 'password_mismatch';
337 }
338
339 if ( empty( $errors ) ) {
340 if ( ! validate_username( $user_login ) ) {
341 $errors[] = 'invalid_username';
342 } elseif ( username_exists( $user_login ) ) {
343 $errors[] = 'existing_user_login';
344 }
345
346 if ( email_exists( $user_email ) ) {
347 $errors[] = 'email_exists';
348 }
349 }
350
351 // Let CAPTCHA / anti-spam plugins that print into `register_form`
352 // validate the submission too, as core registration does.
353 if ( empty( $errors ) ) {
354 $wp_errors = new WP_Error();
355 do_action( 'register_post', $user_login, $user_email, $wp_errors );
356 $wp_errors = apply_filters( 'registration_errors', $wp_errors, $user_login, $user_email );
357
358 if ( is_wp_error( $wp_errors ) && $wp_errors->has_errors() ) {
359 $errors = array_merge( $errors, $wp_errors->get_error_codes() );
360 }
361 }
362
363 if ( ! empty( $errors ) ) {
364 wp_safe_redirect( add_query_arg( 'reg_error', implode( ',', array_unique( $errors ) ), remove_query_arg( array( 'login', 'reg_error' ), $referrer ) ) );
365 exit;
366 }
367
368 // Let external plugins validate registration (e.g. Envato license check).
369 $sbv_valid = apply_filters( 'sbv_validate_registration', true );
370 if ( is_wp_error( $sbv_valid ) ) {
371 wp_safe_redirect( add_query_arg( 'reg_error', $sbv_valid->get_error_code(), $referrer ) );
372 exit;
373 }
374
375 // Create the user.
376 $user_id = wp_create_user( $user_login, $user_pass, $user_email );
377
378 if ( is_wp_error( $user_id ) ) {
379 $error_code = $user_id->get_error_code();
380 wp_safe_redirect( add_query_arg( 'reg_error', $error_code, $referrer ) );
381 exit;
382 }
383
384 // Auto-login the new user.
385 wp_set_current_user( $user_id );
386 wp_set_auth_cookie( $user_id, true );
387
388 wp_safe_redirect( $redirect );
389 exit;
390 }
391
392 add_action( 'admin_post_nopriv_forumax_register', 'forumax_handle_registration' );
393 add_action( 'admin_post_forumax_register', 'forumax_handle_registration' );
394