| @@ -1,73 +1,320 @@ | ||
| 1 | -<?php | |
| 2 | -function forumax_login_form_shortcode( $atts ) { | |
| 3 | - // If user already logged in, return a message or redirect | |
| 4 | - if ( is_user_logged_in() ) { | |
| 5 | - return '<div class="login-already" style="color: green; margin-bottom: 15px;">' . esc_html__( 'You are already logged in.', 'forumax' ) . '</div>'; | |
| 6 | - } | |
| 7 | - | |
| 8 | - // Extract shortcode attributes with defaults | |
| 9 | - $atts = shortcode_atts( array( | |
| 10 | - 'redirect' => '', | |
| 11 | - 'submit_label' => esc_html__( 'Sign in', 'forumax' ), | |
| 12 | - 'username_label' => esc_html__( 'Username', 'forumax' ), | |
| 13 | - 'password_label' => esc_html__( 'Password', 'forumax' ), | |
| 14 | - 'error_notice' => esc_html__( 'Invalid username or password. Please try again.', 'forumax' ), | |
| 15 | - 'username_placeholder' => esc_attr__( 'Enter your username', 'forumax' ), | |
| 16 | - 'password_placeholder' => esc_attr__( 'Enter your password', 'forumax' ), | |
| 17 | - 'button_color' => '#1e73be', // Default button background color | |
| 18 | - 'button_text_color' => '#ffffff', // default text color | |
| 19 | - 'button_position' => 'left', // left, center, right | |
| 20 | - 'signup_link' => '', // attribute for signup link | |
| 21 | - ), $atts ); | |
| 22 | - | |
| 23 | - // Set redirect URL | |
| 24 | - $redirect_url = ! empty( $atts['redirect'] ) ? $atts['redirect'] : get_permalink(); | |
| 25 | - | |
| 26 | - // Determine button alignment based on 'button_position' attribute | |
| 27 | - $button_alignment = 'text-align: center;'; // default | |
| 28 | - if ( $atts['button_position'] === 'left' ) { | |
| 29 | - $button_alignment = 'text-align: left;'; | |
| 30 | - } elseif ( $atts['button_position'] === 'right' ) { | |
| 31 | - $button_alignment = 'text-align: right;'; | |
| 32 | - } | |
| 33 | - | |
| 34 | - ob_start(); | |
| 35 | - | |
| 36 | - $form_args = array( | |
| 37 | - 'redirect' => $redirect_url, | |
| 38 | - 'label_username' => $atts['username_label'], | |
| 39 | - 'label_password' => $atts['password_label'], | |
| 40 | - 'label_remember' => esc_html__( 'Remember Me', 'forumax' ), | |
| 41 | - 'label_log_in' => $atts['submit_label'], | |
| 42 | - 'form_id' => 'forumax_login_form', | |
| 43 | - 'remember' => true, | |
| 44 | - '' | |
| 45 | - ); | |
| 46 | - | |
| 47 | - wp_login_form($form_args); | |
| 48 | - | |
| 49 | - if ( ! empty( $atts['signup_link'] ) ) : ?> | |
| 50 | - <div class="signup-text"> | |
| 51 | - <?php esc_html_e( 'Don’t have an account yet?', 'forumax' ); ?> | |
| 52 | - <a href="<?php echo esc_url( $atts['signup_link'] ); ?>"> | |
| 53 | - <?php esc_html_e( 'Sign up here', 'forumax' ); ?> | |
| 54 | - </a> | |
| 55 | - </div> | |
| 56 | - <?php endif; ?> | |
| 57 | - <?php | |
| 58 | - | |
| 59 | - return ob_get_clean(); | |
| 60 | -} | |
| 61 | - | |
| 62 | -add_shortcode( 'forumax_login_form', 'forumax_login_form_shortcode' ); | |
| 63 | - | |
| 64 | -// Redirect to the referrer page after login | |
| 65 | -add_action( 'wp_login_failed', 'forumax_login_failed_redirect' ); | |
| 66 | - | |
| 67 | -function forumax_login_failed_redirect( $username ) { | |
| 68 | - $referrer = wp_get_referer(); | |
| 69 | - if ( ! empty( $referrer ) && ! str_contains( $referrer, 'wp-login.php' ) ) { | |
| 70 | - wp_redirect( add_query_arg( 'login', 'failed', $referrer ) ); | |
| 71 | - exit; | |
| 72 | - } | |
| 73 | -} | |
| 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 | +/** | |
| 12 | + * Render the tabbed login and registration form. | |
| 13 | + * | |
| 14 | + * @param array $atts Shortcode attributes. | |
| 15 | + * | |
| 16 | + * @return string HTML output. | |
| 17 | + */ | |
| 18 | +function forumax_login_form_shortcode( $atts ) { | |
| 19 | + // If user already logged in, return a message. | |
| 20 | + if ( is_user_logged_in() ) { | |
| 21 | + return '<div class="frmx-auth-logged-in">' . esc_html__( 'You are already logged in.', 'forumax' ) . '</div>'; | |
| 22 | + } | |
| 23 | + | |
| 24 | + // Same shortcode is used on several spots of one page (reply prompt, topic | |
| 25 | + // prompt, subscribe modal, page content). Each instance needs its own IDs | |
| 26 | + // so a later form is not dropped and tabs/labels stay wired to that form. | |
| 27 | + static $frmx_login_form_instance = 0; | |
| 28 | + $frmx_login_form_instance++; | |
| 29 | + $uid = 'frmx-auth-' . $frmx_login_form_instance; | |
| 30 | + | |
| 31 | + // Extract shortcode attributes with defaults. | |
| 32 | + $atts = shortcode_atts( array( | |
| 33 | + 'redirect' => '', | |
| 34 | + ), $atts ); | |
| 35 | + | |
| 36 | + // Set redirect URL. | |
| 37 | + $redirect_url = ! empty( $atts['redirect'] ) ? $atts['redirect'] : get_permalink(); | |
| 38 | + | |
| 39 | + $can_register = get_option( 'users_can_register' ); | |
| 40 | + $wrapper_id = $uid; | |
| 41 | + | |
| 42 | + ob_start(); | |
| 43 | + ?> | |
| 44 | + <div class="frmx-auth-tabs" id="<?php echo esc_attr( $wrapper_id ); ?>"> | |
| 45 | + | |
| 46 | + <?php if ( $can_register ) : ?> | |
| 47 | + <div class="frmx-auth-tabs-header"> | |
| 48 | + <button type="button" class="frmx-auth-tab frmx-auth-tab-active" data-frmx-tab="login"> | |
| 49 | + <?php esc_html_e( 'Sign In', 'forumax' ); ?> | |
| 50 | + </button> | |
| 51 | + <button type="button" class="frmx-auth-tab" data-frmx-tab="register"> | |
| 52 | + <?php esc_html_e( 'Create Account', 'forumax' ); ?> | |
| 53 | + </button> | |
| 54 | + </div> | |
| 55 | + <?php endif; ?> | |
| 56 | + | |
| 57 | + <!-- Login Tab --> | |
| 58 | + <div class="frmx-auth-panel frmx-auth-panel-active" data-frmx-panel="login"> | |
| 59 | + <?php | |
| 60 | + $form_html = wp_login_form( array( | |
| 61 | + 'redirect' => $redirect_url, | |
| 62 | + 'label_username' => esc_html__( 'Username or Email', 'forumax' ), | |
| 63 | + 'label_password' => esc_html__( 'Password', 'forumax' ), | |
| 64 | + 'label_remember' => esc_html__( 'Remember Me', 'forumax' ), | |
| 65 | + 'label_log_in' => esc_html__( 'Sign In', 'forumax' ), | |
| 66 | + 'form_id' => $uid . '-login', | |
| 67 | + 'id_username' => $uid . '-user', | |
| 68 | + 'id_password' => $uid . '-pass', | |
| 69 | + 'id_remember' => $uid . '-remember', | |
| 70 | + 'id_submit' => $uid . '-submit', | |
| 71 | + 'remember' => true, | |
| 72 | + 'echo' => false, | |
| 73 | + ) ); | |
| 74 | + | |
| 75 | + // Class hook for styling (keeps a stable selector with form_id). | |
| 76 | + $form_html = preg_replace( | |
| 77 | + '/<form\b/i', | |
| 78 | + '<form class="forumax_login_form"', | |
| 79 | + $form_html, | |
| 80 | + 1 | |
| 81 | + ); | |
| 82 | + | |
| 83 | + // Add placeholders to core login form fields. | |
| 84 | + $username_placeholder = esc_attr__( 'Username or Email', 'forumax' ); | |
| 85 | + $password_placeholder = esc_attr__( 'Password', 'forumax' ); | |
| 86 | + | |
| 87 | + $form_html = preg_replace( | |
| 88 | + '/(<input\b(?![^>]*\bplaceholder=)[^>]*\bname=["\']log["\'][^>]*)(\/?\s*>)/i', | |
| 89 | + '$1 placeholder="' . $username_placeholder . '"$2', | |
| 90 | + $form_html | |
| 91 | + ); | |
| 92 | + $form_html = preg_replace( | |
| 93 | + '/(<input\b(?![^>]*\bplaceholder=)[^>]*\bname=["\']pwd["\'][^>]*)(\/?\s*>)/i', | |
| 94 | + '$1 placeholder="' . $password_placeholder . '"$2', | |
| 95 | + $form_html | |
| 96 | + ); | |
| 97 | + | |
| 98 | + echo $form_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped | |
| 99 | + ?> | |
| 100 | + | |
| 101 | + <?php | |
| 102 | + /** | |
| 103 | + * Hook to display social login buttons (e.g. Google Site Kit). | |
| 104 | + */ | |
| 105 | + do_action( 'forumax_login_form_social_buttons' ); | |
| 106 | + ?> | |
| 107 | + </div> | |
| 108 | + | |
| 109 | + <?php if ( $can_register ) : ?> | |
| 110 | + <!-- Registration Tab --> | |
| 111 | + <div class="frmx-auth-panel" data-frmx-panel="register"> | |
| 112 | + <form method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" class="frmx-auth-register-form"> | |
| 113 | + <input type="hidden" name="action" value="forumax_register" /> | |
| 114 | + <input type="hidden" name="redirect_to" value="<?php echo esc_url( $redirect_url ); ?>" /> | |
| 115 | + | |
| 116 | + <div class="frmx-auth-field"> | |
| 117 | + <label for="<?php echo esc_attr( $uid ); ?>-reg-login"><?php esc_html_e( 'Username', 'forumax' ); ?></label> | |
| 118 | + <input type="text" name="user_login" id="<?php echo esc_attr( $uid ); ?>-reg-login" placeholder="<?php esc_attr_e( 'Username', 'forumax' ); ?>" autocomplete="username" required /> | |
| 119 | + </div> | |
| 120 | + | |
| 121 | + <div class="frmx-auth-field"> | |
| 122 | + <label for="<?php echo esc_attr( $uid ); ?>-reg-email"><?php esc_html_e( 'Email', 'forumax' ); ?></label> | |
| 123 | + <input type="email" name="user_email" id="<?php echo esc_attr( $uid ); ?>-reg-email" placeholder="<?php esc_attr_e( 'Email', 'forumax' ); ?>" autocomplete="email" required /> | |
| 124 | + </div> | |
| 125 | + | |
| 126 | + <div class="frmx-auth-field"> | |
| 127 | + <label for="<?php echo esc_attr( $uid ); ?>-reg-pass"><?php esc_html_e( 'Password', 'forumax' ); ?></label> | |
| 128 | + <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 /> | |
| 129 | + </div> | |
| 130 | + | |
| 131 | + <div class="frmx-auth-field"> | |
| 132 | + <label for="<?php echo esc_attr( $uid ); ?>-reg-pass-confirm"><?php esc_html_e( 'Confirm Password', 'forumax' ); ?></label> | |
| 133 | + <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 /> | |
| 134 | + </div> | |
| 135 | + | |
| 136 | + <?php | |
| 137 | + /** | |
| 138 | + * Fires inside the registration form. | |
| 139 | + * | |
| 140 | + * This is the standard WordPress hook that plugins (e.g. CAPTCHA, | |
| 141 | + * custom fields) use to inject content into registration forms. | |
| 142 | + */ | |
| 143 | + do_action( 'register_form' ); | |
| 144 | + ?> | |
| 145 | + | |
| 146 | + <div class="frmx-auth-submit"> | |
| 147 | + <button type="submit" id="<?php echo esc_attr( $uid ); ?>-register" name="wp-submit" class="button submit fill-brand"> | |
| 148 | + <?php esc_html_e( 'Register', 'forumax' ); ?> | |
| 149 | + </button> | |
| 150 | + </div> | |
| 151 | + | |
| 152 | + <?php wp_nonce_field( 'forumax_register', 'forumax_register_nonce' ); ?> | |
| 153 | + </form> | |
| 154 | + </div> | |
| 155 | + <?php endif; ?> | |
| 156 | + | |
| 157 | + </div> | |
| 158 | + | |
| 159 | + <script> | |
| 160 | + ( function() { | |
| 161 | + var wrapper = document.getElementById( '<?php echo esc_js( $wrapper_id ); ?>' ); | |
| 162 | + if ( ! wrapper ) { | |
| 163 | + return; | |
| 164 | + } | |
| 165 | + | |
| 166 | + var tabs = wrapper.querySelectorAll( '.frmx-auth-tab' ); | |
| 167 | + var panels = wrapper.querySelectorAll( '.frmx-auth-panel' ); | |
| 168 | + | |
| 169 | + tabs.forEach( function( tab ) { | |
| 170 | + tab.addEventListener( 'click', function() { | |
| 171 | + var target = this.getAttribute( 'data-frmx-tab' ); | |
| 172 | + | |
| 173 | + tabs.forEach( function( t ) { | |
| 174 | + t.classList.remove( 'frmx-auth-tab-active' ); | |
| 175 | + } ); | |
| 176 | + this.classList.add( 'frmx-auth-tab-active' ); | |
| 177 | + | |
| 178 | + panels.forEach( function( p ) { | |
| 179 | + if ( p.getAttribute( 'data-frmx-panel' ) === target ) { | |
| 180 | + p.classList.add( 'frmx-auth-panel-active' ); | |
| 181 | + } else { | |
| 182 | + p.classList.remove( 'frmx-auth-panel-active' ); | |
| 183 | + } | |
| 184 | + } ); | |
| 185 | + } ); | |
| 186 | + } ); | |
| 187 | + | |
| 188 | + /* Password visibility toggle */ | |
| 189 | + 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>'; | |
| 190 | + 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>'; | |
| 191 | + | |
| 192 | + wrapper.querySelectorAll( 'input[type="password"]' ).forEach( function( input ) { | |
| 193 | + var container = document.createElement( 'div' ); | |
| 194 | + container.className = 'frmx-auth-password-wrap'; | |
| 195 | + input.parentNode.insertBefore( container, input ); | |
| 196 | + container.appendChild( input ); | |
| 197 | + | |
| 198 | + var toggleBtn = document.createElement( 'button' ); | |
| 199 | + toggleBtn.type = 'button'; | |
| 200 | + toggleBtn.className = 'frmx-auth-eye-toggle'; | |
| 201 | + toggleBtn.setAttribute( 'aria-label', 'Toggle password visibility' ); | |
| 202 | + toggleBtn.innerHTML = eyeShow; | |
| 203 | + container.appendChild( toggleBtn ); | |
| 204 | + | |
| 205 | + toggleBtn.addEventListener( 'click', function() { | |
| 206 | + var isPassword = input.type === 'password'; | |
| 207 | + input.type = isPassword ? 'text' : 'password'; | |
| 208 | + this.innerHTML = isPassword ? eyeHide : eyeShow; | |
| 209 | + input.focus(); | |
| 210 | + } ); | |
| 211 | + } ); | |
| 212 | + } )(); | |
| 213 | + </script> | |
| 214 | + <?php | |
| 215 | + | |
| 216 | + return ob_get_clean(); | |
| 217 | +} | |
| 218 | + | |
| 219 | +add_shortcode( 'forumax_login_form', 'forumax_login_form_shortcode' ); | |
| 220 | + | |
| 221 | +/** | |
| 222 | + * Redirect to the referrer page after a failed login attempt. | |
| 223 | + * | |
| 224 | + * @param string $username The username that failed login. | |
| 225 | + * | |
| 226 | + * @return void | |
| 227 | + */ | |
| 228 | +function forumax_login_failed_redirect( $username ) { | |
| 229 | + $referrer = wp_get_referer(); | |
| 230 | + if ( ! empty( $referrer ) && ! str_contains( $referrer, 'wp-login.php' ) ) { | |
| 231 | + wp_redirect( add_query_arg( 'login', 'failed', $referrer ) ); | |
| 232 | + exit; | |
| 233 | + } | |
| 234 | +} | |
| 235 | + | |
| 236 | +add_action( 'wp_login_failed', 'forumax_login_failed_redirect' ); | |
| 237 | + | |
| 238 | +/** | |
| 239 | + * Handle the custom registration form submission. | |
| 240 | + * | |
| 241 | + * Creates a new user with the provided username, email, and password, | |
| 242 | + * auto-logs them in, and redirects to the referring page. | |
| 243 | + * | |
| 244 | + * @return void | |
| 245 | + */ | |
| 246 | +function forumax_handle_registration() { | |
| 247 | + // Verify nonce. | |
| 248 | + if ( ! isset( $_POST['forumax_register_nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['forumax_register_nonce'] ) ), 'forumax_register' ) ) { | |
| 249 | + wp_die( esc_html__( 'Security check failed.', 'forumax' ), 403 ); | |
| 250 | + } | |
| 251 | + | |
| 252 | + // Check registration is allowed. | |
| 253 | + if ( ! get_option( 'users_can_register' ) ) { | |
| 254 | + wp_die( esc_html__( 'Registration is currently disabled.', 'forumax' ), 403 ); | |
| 255 | + } | |
| 256 | + | |
| 257 | + $redirect = ! empty( $_POST['redirect_to'] ) ? esc_url_raw( wp_unslash( $_POST['redirect_to'] ) ) : home_url(); | |
| 258 | + $referrer = wp_get_referer(); | |
| 259 | + if ( empty( $referrer ) ) { | |
| 260 | + $referrer = $redirect; | |
| 261 | + } | |
| 262 | + | |
| 263 | + $user_login = isset( $_POST['user_login'] ) ? sanitize_user( wp_unslash( $_POST['user_login'] ) ) : ''; | |
| 264 | + $user_email = isset( $_POST['user_email'] ) ? sanitize_email( wp_unslash( $_POST['user_email'] ) ) : ''; | |
| 265 | + $user_pass = isset( $_POST['user_pass'] ) ? $_POST['user_pass'] : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- password must not be sanitized. | |
| 266 | + $user_pass_confirm = isset( $_POST['user_pass_confirm'] ) ? $_POST['user_pass_confirm'] : ''; // phpcs:ignore | |
| 267 | + | |
| 268 | + // Validate fields. | |
| 269 | + $errors = array(); | |
| 270 | + | |
| 271 | + if ( empty( $user_login ) ) { | |
| 272 | + $errors[] = 'empty_username'; | |
| 273 | + } | |
| 274 | + | |
| 275 | + if ( empty( $user_email ) || ! is_email( $user_email ) ) { | |
| 276 | + $errors[] = 'invalid_email'; | |
| 277 | + } | |
| 278 | + | |
| 279 | + if ( empty( $user_pass ) ) { | |
| 280 | + $errors[] = 'empty_password'; | |
| 281 | + } | |
| 282 | + | |
| 283 | + if ( $user_pass !== $user_pass_confirm ) { | |
| 284 | + $errors[] = 'password_mismatch'; | |
| 285 | + } | |
| 286 | + | |
| 287 | + if ( ! empty( $errors ) ) { | |
| 288 | + wp_safe_redirect( add_query_arg( 'reg_error', implode( ',', $errors ), $referrer ) ); | |
| 289 | + exit; | |
| 290 | + } | |
| 291 | + | |
| 292 | + // Let external plugins validate registration (e.g. Envato license check). | |
| 293 | + $sbv_valid = apply_filters( 'sbv_validate_registration', true ); | |
| 294 | + if ( is_wp_error( $sbv_valid ) ) { | |
| 295 | + wp_safe_redirect( add_query_arg( 'reg_error', $sbv_valid->get_error_code(), $referrer ) ); | |
| 296 | + exit; | |
| 297 | + } | |
| 298 | + | |
| 299 | + // Create the user. | |
| 300 | + $user_id = wp_create_user( $user_login, $user_pass, $user_email ); | |
| 301 | + | |
| 302 | + if ( is_wp_error( $user_id ) ) { | |
| 303 | + $error_code = $user_id->get_error_code(); | |
| 304 | + wp_safe_redirect( add_query_arg( 'reg_error', $error_code, $referrer ) ); | |
| 305 | + exit; | |
| 306 | + } | |
| 307 | + | |
| 308 | + // Fire standard WordPress registration action. | |
| 309 | + do_action( 'user_register', $user_id ); | |
| 310 | + | |
| 311 | + // Auto-login the new user. | |
| 312 | + wp_set_current_user( $user_id ); | |
| 313 | + wp_set_auth_cookie( $user_id, true ); | |
| 314 | + | |
| 315 | + wp_safe_redirect( $redirect ); | |
| 316 | + exit; | |
| 317 | +} | |
| 318 | + | |
| 319 | +add_action( 'admin_post_nopriv_forumax_register', 'forumax_handle_registration' ); | |
| 320 | +add_action( 'admin_post_forumax_register', 'forumax_handle_registration' ); | |