true ) ); Ajax::register( 'register', array( $this, 'ajax_register' ), array( 'public' => true ) ); Ajax::register( 'lost', array( $this, 'ajax_lost' ), array( 'public' => true ) ); Ajax::register( 'reset', array( $this, 'ajax_reset' ), array( 'public' => true ) ); Ajax::register( 'resend_verify', array( $this, 'ajax_resend' ), array( 'public' => true ) ); Ajax::register( 'profile_save', array( $this, 'ajax_profile' ) ); Ajax::register( 'password_change', array( $this, 'ajax_password' ) ); Ajax::register( 'privacy_request', array( $this, 'ajax_privacy' ) ); Ajax::register( 'user_approve', array( $this, 'ajax_approve' ), array( 'cap' => 'frontglut_manage_settings' ) ); // Security. add_filter( 'authenticate', array( $this, 'guard_lockout' ), 5, 3 ); add_filter( 'authenticate', array( $this, 'guard_status' ), 40, 3 ); add_action( 'wp_login_failed', array( $this, 'on_failed' ) ); add_action( 'wp_login', array( $this, 'on_success' ), 10, 2 ); add_action( 'admin_init', array( $this, 'guard_admin' ) ); add_filter( 'show_admin_bar', array( $this, 'admin_bar' ) ); add_filter( 'login_redirect', array( $this, 'login_redirect' ), 20, 3 ); add_filter( 'logout_redirect', function ( $to, $req, $user ) { return self::logout_redirect_url( $to ); }, 20, 3 ); add_filter( 'retrieve_password_message', array( $this, 'reset_message' ), 20, 4 ); add_filter( 'lostpassword_url', array( $this, 'lost_url' ), 20 ); add_filter( 'register_url', array( $this, 'register_url' ), 20 ); add_filter( 'login_url', array( $this, 'login_url' ), 20, 3 ); // Avatar. add_filter( 'pre_get_avatar_data', array( $this, 'avatar' ), 10, 2 ); // Flows via URL. add_action( 'template_redirect', array( $this, 'handle_urls' ) ); add_action( 'init', array( $this, 'rewrites' ) ); add_filter( 'query_vars', function ( $v ) { $v[] = 'frontglut_member'; return $v; } ); add_action( 'delete_user', function ( $id ) { global $wpdb; $wpdb->delete( $wpdb->prefix . 'frontglut_follows', array( 'user_id' => $id ) ); } ); // Dashboard integration. add_filter( 'frontglut_menu_items', function ( $i ) { $i['profile'] = array( 'label' => __( 'My profile', 'frontglut' ), 'icon' => 'admin-users', 'group' => 'account', 'order' => 90, 'render' => array( $this, 'render_profile_section' ) ); if ( current_user_can( 'manage_options' ) ) { $i['users'] = array( 'label' => __( 'Users', 'frontglut' ), 'icon' => 'groups', 'group' => 'account', 'order' => 91, 'cap' => 'frontglut_manage_settings', 'render' => array( $this, 'render_users' ) ); } return $i; } ); add_action( 'show_user_profile', array( $this, 'admin_user_fields' ) ); add_action( 'edit_user_profile', array( $this, 'admin_user_fields' ) ); add_filter( 'manage_users_columns', function ( $c ) { $c['frontglut_status'] = __( 'FrontGlut status', 'frontglut' ); return $c; } ); add_filter( 'manage_users_custom_column', function ( $v, $col, $uid ) { if ( 'frontglut_status' === $col ) { return get_user_meta( $uid, 'frontglut_pending_approval', true ) ? __( 'Awaiting approval', 'frontglut' ) : ( get_user_meta( $uid, 'frontglut_verify_token', true ) ? __( 'Unverified email', 'frontglut' ) : '—' ); } return $v; }, 10, 3 ); } /* ---------------- settings ---------------- */ public function defaults( $d ) { $d['auth'] = array( 'default_role' => 'subscriber', 'approval' => 0, 'verify_email' => 0, 'redirect_login' => '', 'redirect_register' => '', 'redirect_logout' => '', 'redirect_roles' => '', 'restrict_roles' => array( 'subscriber', 'customer' ), 'hide_admin_bar' => 1, 'max_attempts' => 5, 'lockout_min' => 15, 'reg_per_hour' => 5, 'allow_delete' => 1, 'google_id' => '', 'google_secret' => '', 'facebook_id' => '', 'facebook_secret' => '', 'registration_open' => 0, // Off until the administrator enables frontend registration. ); return $d; } public function schema( $s ) { $roles = Caps::registration_roles(); // Only low-privilege roles can be given to self-registered users. $s['auth'] = array( 'label' => __( 'Accounts & Login', 'frontglut' ), 'order' => 25, 'fields' => array( 'auth.registration_open' => array( 'type' => 'checkbox', 'label' => __( 'Allow frontend registration', 'frontglut' ) ), 'auth.default_role' => array( 'type' => 'select', 'label' => __( 'Default role for new users', 'frontglut' ), 'options' => $roles, 'help' => __( 'Only roles without publishing, editing or management rights are listed.', 'frontglut' ) ), 'auth.approval' => array( 'type' => 'checkbox', 'label' => __( 'New accounts require admin approval', 'frontglut' ) ), 'auth.verify_email' => array( 'type' => 'checkbox', 'label' => __( 'Require email verification', 'frontglut' ) ), 'auth.reg_per_hour' => array( 'type' => 'number', 'label' => __( 'Registrations per IP per hour', 'frontglut' ), 'min' => 1 ), 'auth.max_attempts' => array( 'type' => 'number', 'label' => __( 'Failed logins before lockout', 'frontglut' ), 'min' => 1 ), 'auth.lockout_min' => array( 'type' => 'number', 'label' => __( 'Lockout minutes', 'frontglut' ), 'min' => 1 ), 'auth.redirect_login' => array( 'type' => 'url', 'label' => __( 'After login redirect (URL)', 'frontglut' ), 'help' => __( 'Empty = dashboard for users with access, otherwise home.', 'frontglut' ) ), 'auth.redirect_register' => array( 'type' => 'url', 'label' => __( 'After registration redirect (URL)', 'frontglut' ) ), 'auth.redirect_logout' => array( 'type' => 'url', 'label' => __( 'After logout redirect (URL)', 'frontglut' ) ), 'auth.redirect_roles' => array( 'type' => 'textarea', 'label' => __( 'Per-role login redirects', 'frontglut' ), 'help' => __( 'One per line: role|https://example.com/page', 'frontglut' ) ), 'auth.restrict_roles' => array( 'type' => 'roles', 'label' => __( 'Block wp-admin for roles', 'frontglut' ), 'help' => __( 'These roles are redirected to the frontend dashboard and have the admin bar hidden.', 'frontglut' ) ), 'auth.hide_admin_bar' => array( 'type' => 'checkbox', 'label' => __( 'Hide admin bar for restricted roles', 'frontglut' ) ), 'auth.allow_delete' => array( 'type' => 'checkbox', 'label' => __( 'Let users request export / deletion of their data', 'frontglut' ) ), /* translators: %s: value. */ 'auth.google_id' => array( 'type' => 'text', 'label' => __( 'Google OAuth client ID', 'frontglut' ), 'help' => sprintf( __( 'Redirect URI: %s', 'frontglut' ), esc_url( add_query_arg( 'frontglut_social_cb', 'google', home_url( '/' ) ) ) ) ), 'auth.google_secret' => array( 'type' => 'text', 'label' => __( 'Google OAuth client secret', 'frontglut' ) ), /* translators: %s: value. */ 'auth.facebook_id' => array( 'type' => 'text', 'label' => __( 'Facebook app ID', 'frontglut' ), 'help' => sprintf( __( 'Redirect URI: %s', 'frontglut' ), esc_url( add_query_arg( 'frontglut_social_cb', 'facebook', home_url( '/' ) ) ) ) ), 'auth.facebook_secret' => array( 'type' => 'text', 'label' => __( 'Facebook app secret', 'frontglut' ) ), ) ); return $s; } /* ---------------- URL helpers ---------------- */ public static function page_url( $key, $fallback = '' ) { $id = (int) Settings::get( 'general.' . $key, 0 ); return $id && 'publish' === get_post_status( $id ) ? get_permalink( $id ) : $fallback; } public function lost_url( $url ) { return self::page_url( 'lost_page', $url ); } public function register_url( $url ) { return self::page_url( 'register_page', $url ); } public function login_url( $url, $redirect = '', $force = false ) { $p = self::page_url( 'login_page' ); if ( ! $p || is_admin() || false !== strpos( (string) $url, 'action=' ) ) { return $url; } return $redirect ? add_query_arg( 'redirect_to', rawurlencode( $redirect ), $p ) : $p; } public static function logout_redirect_url( $default = '' ) { $u = Settings::get( 'auth.redirect_logout' ); return $u ? $u : ( $default ? $default : home_url( '/' ) ); } private static function role_redirect( $user, $key ) { $lines = preg_split( '/\r\n|\r|\n/', (string) Settings::get( 'auth.redirect_roles', '' ) ); foreach ( $lines as $l ) { $p = array_map( 'trim', explode( '|', $l, 2 ) ); if ( 2 === count( $p ) && in_array( $p[0], (array) $user->roles, true ) && 'redirect_login' === $key ) { return esc_url_raw( $p[1] ); } } $u = Settings::get( 'auth.' . $key ); return $u ? $u : ''; } public static function after_login_url( $user, $requested = '' ) { $role = self::role_redirect( $user, 'redirect_login' ); if ( $role ) { return $role; } if ( $requested && wp_validate_redirect( $requested, '' ) ) { return $requested; } return \FrontGlut\Core\Caps::can( 'frontglut_access_dashboard', $user->ID ) ? Dashboard::url() : home_url( '/' ); } public function login_redirect( $to, $requested, $user ) { if ( ! $user || is_wp_error( $user ) ) { return $to; } $role = self::role_redirect( $user, 'redirect_login' ); if ( $role ) { return $role; } if ( $this->is_restricted( $user ) ) { return self::after_login_url( $user, $requested ); } return $to; } /* ---------------- guards ---------------- */ private function is_restricted( $user ) { if ( ! $user || user_can( $user, 'manage_options' ) ) { return false; } return (bool) array_intersect( (array) $user->roles, (array) Settings::get( 'auth.restrict_roles', array() ) ); } public function guard_admin() { if ( wp_doing_ajax() || ( defined( 'DOING_CRON' ) && DOING_CRON ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) || ! is_user_logged_in() ) { return; } global $pagenow; if ( in_array( $pagenow, array( 'admin-post.php', 'async-upload.php', 'admin-ajax.php' ), true ) ) { return; } if ( $this->is_restricted( wp_get_current_user() ) && ! apply_filters( 'frontglut_allow_admin_request', false, $pagenow ) ) { wp_safe_redirect( Dashboard::url() ); exit; } } public function admin_bar( $show ) { if ( $show && Settings::get( 'auth.hide_admin_bar' ) && is_user_logged_in() && $this->is_restricted( wp_get_current_user() ) ) { return false; } return $show; } private function ident( $login ) { return strtolower( (string) $login ) . '|' . Audit::ip(); } public function guard_lockout( $user, $login, $pass ) { if ( $login && RateLimiter::blocked( 'login', (int) Settings::get( 'auth.max_attempts', 5 ), $this->ident( $login ) ) ) { /* translators: %s: value. */ return new \WP_Error( 'frontglut_locked', sprintf( __( 'Too many failed attempts. Try again in %d minutes.', 'frontglut' ), (int) Settings::get( 'auth.lockout_min', 15 ) ) ); } return $user; } public function on_failed( $login ) { RateLimiter::hit( 'login', 9999, max( 1, (int) Settings::get( 'auth.lockout_min', 15 ) ) * MINUTE_IN_SECONDS, $this->ident( $login ) ); } public function on_success( $login, $user ) { RateLimiter::reset( 'login', $this->ident( $login ) ); } public function guard_status( $user, $login, $pass ) { if ( $user instanceof \WP_User ) { if ( get_user_meta( $user->ID, 'frontglut_pending_approval', true ) ) { return new \WP_Error( 'frontglut_pending', __( 'Your account is awaiting administrator approval.', 'frontglut' ) ); } if ( get_user_meta( $user->ID, 'frontglut_verify_token', true ) ) { return new \WP_Error( 'frontglut_unverified', __( 'Please verify your email address first. Check your inbox.', 'frontglut' ) ); } } return $user; } /* ---------------- forms (HTML) ---------------- */ private function social_buttons() { $h = ''; foreach ( apply_filters( 'frontglut_social_buttons', array( 'google' => 'Google', 'facebook' => 'Facebook' ) ) as $k => $l ) { if ( Settings::get( 'auth.' . $k . '_id' ) && Settings::get( 'auth.' . $k . '_secret' ) ) { /* translators: %s: value. */ $h .= '' . esc_html( sprintf( __( 'Continue with %s', 'frontglut' ), $l ) ) . ''; // phpcs:ignore WordPress.Security.NonceVerification } } return $h ? '
' . $h . '
' : ''; } public static function login_form( $a = array() ) { return ( new self() )->login_html( $a ); } public function login_html( $a = array() ) { $redirect = isset( $a['redirect'] ) ? $a['redirect'] : Request::route( 'redirect_to', 'url', '' ); $h = ''; $err = Request::route( 'frontglut_social_err', 'int', 0 ); $msgs = array( 1 => __( 'Social login failed. Please try again.', 'frontglut' ), 2 => __( 'Your social account did not share a verified email address.', 'frontglut' ), 3 => __( 'For security, this account must log in with its username and password.', 'frontglut' ), ); if ( isset( $msgs[ $err ] ) ) { $h .= Ui::notice( esc_html( $msgs[ $err ] ), 'warn' ); } elseif ( Request::route( 'frontglut_pending', 'bool', false ) ) { $h .= Ui::notice( esc_html__( 'Your account is awaiting administrator approval.', 'frontglut' ), 'warn' ); } $h .= Ui::form_open( 'login', array( 'after' => 'reload' ) ) . Ui::hidden( 'redirect_to', $redirect ) . Forms::honeypot(); $h .= Fields::render( array( 'key' => 'log', 'type' => 'text', 'label' => __( 'Username or email', 'frontglut' ), 'required' => 1 ), '', 'log' ); $h .= Fields::render( array( 'key' => 'pwd', 'type' => 'password', 'label' => __( 'Password', 'frontglut' ), 'required' => 1 ), '', 'pwd' ); $h .= '
'; if ( Captcha::active_for( 'login' ) ) { $h .= Captcha::render(); } $h .= $this->social_buttons(); $lost = self::page_url( 'lost_page', wp_lostpassword_url() ); $reg = Settings::get( 'auth.registration_open' ) ? self::page_url( 'register_page' ) : ''; $h .= Ui::form_close( __( 'Log in', 'frontglut' ), '' ); $h .= ''; return $h; } public function sc_login( $atts ) { Assets::need(); if ( is_user_logged_in() ) { /* translators: %s: value. */ return '
' . Ui::notice( sprintf( esc_html__( 'You are logged in as %s.', 'frontglut' ), esc_html( wp_get_current_user()->display_name ) ) ) . '

' . Ui::btn( __( 'Go to dashboard', 'frontglut' ), Dashboard::url(), 'frontglut-primary' ) . ' ' . Ui::btn( __( 'Log out', 'frontglut' ), wp_logout_url( self::logout_redirect_url() ) ) . '

'; } return '

' . esc_html__( 'Log in', 'frontglut' ) . '

' . $this->login_html() . '
'; } public function sc_register( $atts ) { Assets::need(); if ( is_user_logged_in() ) { return '
' . Ui::notice( esc_html__( 'You are already registered and logged in.', 'frontglut' ) ) . '
'; } if ( ! Settings::get( 'auth.registration_open' ) ) { return '
' . Ui::notice( esc_html__( 'Registration is currently closed.', 'frontglut' ), 'warn' ) . '
'; } $form = Forms::default_form( 'register' ); $h = '

' . esc_html__( 'Create an account', 'frontglut' ) . '

' . Ui::form_open( 'register', array( 'after' => 'reload' ) ) . Forms::honeypot(); $h .= Forms::render_fields( $form['fields'], array(), 'frontglut_f', 0 ); if ( Captcha::active_for( 'register' ) ) { $h .= Captcha::render(); } $h .= $this->social_buttons(); $h .= Ui::form_close( __( 'Register', 'frontglut' ) ); return $h . '
'; } public function sc_lost( $atts ) { Assets::need(); $key = isset( $_GET['frontglut_key'] ) ? sanitize_text_field( wp_unslash( $_GET['frontglut_key'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification -- password-reset key, verified by check_password_reset_key() below. $log = isset( $_GET['frontglut_login'] ) ? sanitize_user( wp_unslash( $_GET['frontglut_login'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification -- password-reset link from email: the key is verified by check_password_reset_key() below. if ( $key && $log ) { $u = check_password_reset_key( $key, $log ); if ( is_wp_error( $u ) ) { return '
' . Ui::notice( esc_html__( 'This reset link is invalid or has expired.', 'frontglut' ), 'bad' ) . '

' . esc_html__( 'Request a new link', 'frontglut' ) . '

'; } $h = '

' . esc_html__( 'Choose a new password', 'frontglut' ) . '

' . Ui::form_open( 'reset', array( 'after' => 'none' ) ) . Ui::hidden( 'key', $key ) . Ui::hidden( 'login', $log ); $h .= Fields::render( array( 'key' => 'pass1', 'type' => 'password', 'label' => __( 'New password', 'frontglut' ), 'required' => 1, 'minlength' => 8 ), '', 'pass1' ); $h .= Fields::render( array( 'key' => 'pass2', 'type' => 'password', 'label' => __( 'Confirm password', 'frontglut' ), 'required' => 1 ), '', 'pass2' ); return $h . Ui::form_close( __( 'Save password', 'frontglut' ) ) . '
'; } $h = '

' . esc_html__( 'Lost your password?', 'frontglut' ) . '

' . esc_html__( 'Enter your username or email and we will send you a reset link.', 'frontglut' ) . '

' . Ui::form_open( 'lost' ) . Forms::honeypot(); $h .= Fields::render( array( 'key' => 'user_login', 'type' => 'text', 'label' => __( 'Username or email', 'frontglut' ), 'required' => 1 ), '', 'user_login' ); if ( Captcha::active_for( 'lost' ) ) { $h .= Captcha::render(); } return $h . Ui::form_close( __( 'Email me a link', 'frontglut' ) ) . '
'; } /* ---------------- AJAX: login ---------------- */ public function ajax_login( $req ) { if ( ! Forms::check_honeypot( $req ) ) { Ajax::fail( __( 'Spam detected.', 'frontglut' ) ); } if ( Captcha::active_for( 'login' ) ) { $c = Captcha::verify( $req ); if ( is_wp_error( $c ) ) { Ajax::fail( $c->get_error_message() ); } } $user = wp_signon( array( 'user_login' => isset( $req['log'] ) ? sanitize_text_field( $req['log'] ) : '', 'user_password' => isset( $req['pwd'] ) ? (string) $req['pwd'] : '', 'remember' => ! empty( $req['rememberme'] ) ), is_ssl() ); if ( is_wp_error( $user ) ) { $code = $user->get_error_code(); $msg = in_array( $code, array( 'invalid_username', 'invalid_email', 'incorrect_password', 'empty_username', 'empty_password' ), true ) ? __( 'Incorrect username or password.', 'frontglut' ) : wp_strip_all_tags( $user->get_error_message() ); Ajax::fail( $msg, 401, array( 'code' => $code ) ); } $to = self::after_login_url( $user, isset( $req['redirect_to'] ) ? esc_url_raw( $req['redirect_to'] ) : '' ); return array( 'message' => __( 'Welcome back!', 'frontglut' ), 'redirect' => $to ); } /* ---------------- AJAX: register ---------------- */ public function ajax_register( $req ) { if ( ! Settings::get( 'auth.registration_open' ) ) { Ajax::fail( __( 'Registration is closed.', 'frontglut' ), 403 ); } if ( ! Forms::check_honeypot( $req ) ) { Ajax::fail( __( 'Spam detected.', 'frontglut' ) ); } if ( Captcha::active_for( 'register' ) ) { $c = Captcha::verify( $req ); if ( is_wp_error( $c ) ) { Ajax::fail( $c->get_error_message() ); } } if ( ! RateLimiter::hit( 'register', max( 1, (int) Settings::get( 'auth.reg_per_hour', 5 ) ), HOUR_IN_SECONDS ) ) { Ajax::fail( __( 'Too many registrations from your network. Try later.', 'frontglut' ), 429 ); } $form = Forms::default_form( 'register' ); $post = isset( $req['frontglut_f'] ) && is_array( $req['frontglut_f'] ) ? $req['frontglut_f'] : array(); list( $v, $errors ) = Fields::process( $form['fields'], $post ); $login = isset( $v['user_login'] ) ? sanitize_user( $v['user_login'], true ) : ''; $email = isset( $v['user_email'] ) ? $v['user_email'] : ''; $pass = isset( $post['user_pass'] ) && is_string( $post['user_pass'] ) ? $post['user_pass'] : ''; if ( $login && ( username_exists( $login ) || ! validate_username( $login ) ) ) { $errors['user_login'] = __( 'This username is not available.', 'frontglut' ); } if ( $email && email_exists( $email ) ) { $errors['user_email'] = __( 'This email is already registered.', 'frontglut' ); } if ( isset( $post['user_pass2'] ) && $pass !== (string) $post['user_pass2'] ) { $errors['user_pass2'] = __( 'Passwords do not match.', 'frontglut' ); } if ( strlen( $pass ) < 8 ) { $errors['user_pass'] = __( 'Password must be at least 8 characters.', 'frontglut' ); } if ( $errors ) { Ajax::fail( __( 'Please fix the highlighted fields.', 'frontglut' ), 400, array( 'errors' => $errors ) ); } $role = Caps::registration_role( Settings::get( 'auth.default_role', 'subscriber' ) ); $id = wp_insert_user( array( 'user_login' => $login, 'user_email' => $email, 'user_pass' => $pass, 'role' => $role, 'first_name' => isset( $v['first_name'] ) ? $v['first_name'] : '', 'last_name' => isset( $v['last_name'] ) ? $v['last_name'] : '' ) ); if ( is_wp_error( $id ) ) { Ajax::fail( $id->get_error_message() ); } $this->save_extra( $id, $form['fields'], $v, array( 'user_login', 'user_email', 'user_pass', 'user_pass2', 'first_name', 'last_name' ) ); do_action( 'frontglut_user_registered', $id, $v ); $user = get_userdata( $id ); list( $message, $auto ) = self::account_gate( $id, $login, $email ); Audit::log( 'registered', $login, '', $id ); $out = array( 'message' => $message ); if ( $auto ) { wp_set_current_user( $id ); wp_set_auth_cookie( $id, true ); $out['redirect'] = Settings::get( 'auth.redirect_register' ) ? Settings::get( 'auth.redirect_register' ) : self::after_login_url( $user ); } else { $out['redirect'] = ''; } return $out; } /** * Applies the site's email-verification and admin-approval settings to a newly self-registered * account and sends the matching emails. Shared by every self-registration path. * * @return array [ message, bool $may_log_in_now ] */ public static function account_gate( $id, $login, $email ) { $user = get_userdata( $id ); $vars = array( 'name' => $user->display_name, 'username' => $login, 'email' => $email, 'login_url' => self::page_url( 'login_page', wp_login_url() ), 'review_url' => admin_url( 'users.php?s=' . rawurlencode( $login ) ) ); $message = __( 'Account created.', 'frontglut' ); $auto = true; if ( Settings::get( 'auth.verify_email' ) ) { $token = wp_generate_password( 32, false ); update_user_meta( $id, 'frontglut_verify_token', wp_hash( $token ) ); $vars['verify_url'] = add_query_arg( array( 'frontglut_verify' => $token, 'uid' => $id ), home_url( '/' ) ); Mailer::send_template( 'verify_email', $email, $vars ); $message = __( 'Almost done! Check your inbox to verify your email.', 'frontglut' ); $auto = false; } if ( Settings::get( 'auth.approval' ) ) { update_user_meta( $id, 'frontglut_pending_approval', 1 ); $message = __( 'Thanks! Your account is awaiting administrator approval.', 'frontglut' ); $auto = false; } if ( ! Settings::get( 'auth.verify_email' ) ) { Mailer::send_template( 'welcome', $email, $vars ); } Mailer::send_template( 'admin_new_user', get_option( 'admin_email' ), $vars ); return array( $message, $auto ); } /** Store non-core fields as user meta (respecting storage = meta only). */ private function save_extra( $user_id, array $fields, array $values, array $skip ) { foreach ( $fields as $f ) { $k = $f['key']; if ( in_array( $k, $skip, true ) || ! array_key_exists( $k, $values ) || in_array( $f['type'], array( 'password', 'heading', 'label' ), true ) ) { continue; } if ( preg_match( '/capabilit|user_level|session_tokens|^wp_|^frontglut_(vendor|pending|verify|store)|^_/i', $k ) ) { continue; // never let a form write privilege / system meta. } $val = $values[ $k ]; if ( in_array( $f['type'], array( 'file', 'image', 'gallery' ), true ) ) { $val = Uploads::filter_owned( (array) $val ); Uploads::claim( $val, 0 ); $val = 'gallery' === $f['type'] || ! empty( $f['multiple'] ) ? $val : ( $val ? $val[0] : '' ); } update_user_meta( $user_id, sanitize_key( $k ), $val ); } } /* ---------------- verify / approve ---------------- */ public function handle_urls() { // Email-verification link: authorised by the single-use token (hash_equals against the stored hash), not a nonce, because it is opened from an email. if ( ! empty( $_GET['frontglut_verify'] ) && ! empty( $_GET['uid'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification -- single-use token checked below. $uid = (int) $_GET['uid']; // phpcs:ignore WordPress.Security.NonceVerification -- single-use token checked below. $stored = (string) get_user_meta( $uid, 'frontglut_verify_token', true ); $tok = sanitize_text_field( wp_unslash( $_GET['frontglut_verify'] ) ); // phpcs:ignore WordPress.Security.NonceVerification -- this is the token being verified. $ok = $stored && hash_equals( $stored, wp_hash( $tok ) ); if ( $ok ) { delete_user_meta( $uid, 'frontglut_verify_token' ); $u = get_userdata( $uid ); Mailer::send_template( 'welcome', $u->user_email, array( 'name' => $u->display_name, 'username' => $u->user_login, 'login_url' => self::page_url( 'login_page', wp_login_url() ) ) ); } wp_safe_redirect( add_query_arg( 'frontglut_verified', $ok ? 1 : 0, self::page_url( 'login_page', home_url( '/' ) ) ) ); exit; } // Social login start/callback: protected by the OAuth "state" value bound to this browser's cookie (see social_callback()). if ( ! empty( $_GET['frontglut_social'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification -- starts the OAuth flow; nothing is changed. $this->social_start( sanitize_key( $_GET['frontglut_social'] ) ); // phpcs:ignore WordPress.Security.NonceVerification -- see above. } if ( ! empty( $_GET['frontglut_social_cb'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification -- OAuth callback, verified by the state check in social_callback(). $this->social_callback( sanitize_key( $_GET['frontglut_social_cb'] ) ); // phpcs:ignore WordPress.Security.NonceVerification -- see above. } $m = get_query_var( 'frontglut_member' ); if ( $m ) { $this->render_member_page( $m ); } } public function ajax_resend( $req ) { $login = isset( $req['user'] ) ? sanitize_text_field( $req['user'] ) : ''; $u = get_user_by( is_email( $login ) ? 'email' : 'login', $login ); if ( $u && get_user_meta( $u->ID, 'frontglut_verify_token', true ) && RateLimiter::hit( 'resend', 3, HOUR_IN_SECONDS, (string) $u->ID ) ) { $token = wp_generate_password( 32, false ); update_user_meta( $u->ID, 'frontglut_verify_token', wp_hash( $token ) ); Mailer::send_template( 'verify_email', $u->user_email, array( 'name' => $u->display_name, 'verify_url' => add_query_arg( array( 'frontglut_verify' => $token, 'uid' => $u->ID ), home_url( '/' ) ) ) ); } return array( 'message' => __( 'If the account needs verification, a new email was sent.', 'frontglut' ) ); } public function ajax_approve( $req ) { $uid = isset( $req['id'] ) ? (int) $req['id'] : 0; $u = get_userdata( $uid ); if ( ! $u ) { Ajax::fail( __( 'User not found.', 'frontglut' ), 404 ); } delete_user_meta( $uid, 'frontglut_pending_approval' ); Mailer::send_template( 'account_approved', $u->user_email, array( 'name' => $u->display_name, 'login_url' => self::page_url( 'login_page', wp_login_url() ) ) ); Audit::log( 'user_approved', (string) $uid ); return array( 'message' => __( 'User approved.', 'frontglut' ) ); } /* ---------------- lost / reset ---------------- */ public function ajax_lost( $req ) { if ( ! Forms::check_honeypot( $req ) ) { Ajax::fail( __( 'Spam detected.', 'frontglut' ) ); } if ( Captcha::active_for( 'lost' ) ) { $c = Captcha::verify( $req ); if ( is_wp_error( $c ) ) { Ajax::fail( $c->get_error_message() ); } } if ( ! RateLimiter::hit( 'lost', 5, HOUR_IN_SECONDS ) ) { Ajax::fail( __( 'Too many requests. Try again later.', 'frontglut' ), 429 ); } $_POST['user_login'] = isset( $req['user_login'] ) ? sanitize_text_field( $req['user_login'] ) : ''; retrieve_password(); // Result intentionally not revealed (prevents user enumeration). return array( 'message' => __( 'If that account exists, a reset link is on its way.', 'frontglut' ) ); } public function reset_message( $message, $key, $user_login, $user_data ) { $page = self::page_url( 'lost_page' ); if ( ! $page ) { return $message; } $url = add_query_arg( array( 'frontglut_key' => $key, 'frontglut_login' => rawurlencode( $user_login ) ), $page ); return sprintf( __( "Someone requested a password reset for %1\$s.\n\nIf this was you, open this link:\n%2\$s\n\nIf not, ignore this email.", 'frontglut' ), $user_login, $url ); } public function ajax_reset( $req ) { $u = check_password_reset_key( isset( $req['key'] ) ? sanitize_text_field( $req['key'] ) : '', isset( $req['login'] ) ? sanitize_user( $req['login'] ) : '' ); if ( is_wp_error( $u ) ) { Ajax::fail( __( 'This reset link is invalid or has expired.', 'frontglut' ) ); } $p1 = isset( $req['pass1'] ) ? (string) $req['pass1'] : ''; if ( strlen( $p1 ) < 8 ) { Ajax::fail( __( 'Password must be at least 8 characters.', 'frontglut' ), 400, array( 'errors' => array( 'pass1' => __( 'Too short.', 'frontglut' ) ) ) ); } if ( $p1 !== ( isset( $req['pass2'] ) ? (string) $req['pass2'] : '' ) ) { Ajax::fail( __( 'Passwords do not match.', 'frontglut' ), 400, array( 'errors' => array( 'pass2' => __( 'Does not match.', 'frontglut' ) ) ) ); } reset_password( $u, $p1 ); Audit::log( 'password_reset', $u->user_login, '', $u->ID ); return array( 'message' => __( 'Password updated. You can log in now.', 'frontglut' ), 'redirect' => self::page_url( 'login_page', wp_login_url() ) ); } /* ---------------- profile ---------------- */ public function render_profile_section() { return $this->profile_html(); } public function sc_profile( $atts ) { Assets::need(); if ( ! is_user_logged_in() ) { return '
' . Ui::notice( esc_html__( 'Please log in to edit your profile.', 'frontglut' ), 'warn' ) . $this->login_html() . '
'; } return '
' . $this->profile_html() . '
'; } private function user_values( $user, array $fields ) { $vals = array(); foreach ( $fields as $f ) { $k = $f['key']; if ( in_array( $f['storage'], array( 'user_email', 'user_url', 'display_name' ), true ) ) { $vals[ $k ] = $user->{ $f['storage'] }; } elseif ( in_array( $f['storage'], array( 'first_name', 'last_name', 'description' ), true ) ) { $vals[ $k ] = get_user_meta( $user->ID, $f['storage'], true ); } else { $vals[ $k ] = get_user_meta( $user->ID, $k, true ); } } return $vals; } public function profile_html() { $user = wp_get_current_user(); $form = Forms::default_form( 'profile' ); $tabs = apply_filters( 'frontglut_profile_tabs', array( 'profile' => array( 'label' => __( 'Profile', 'frontglut' ), 'render' => function () use ( $user, $form ) { $h = Ui::form_open( 'profile_save', array( 'after' => 'reload' ) ); $h .= Forms::render_fields( $form['fields'], $this->user_values( $user, $form['fields'] ) ); return $h . Ui::form_close( __( 'Save profile', 'frontglut' ) ); } ), 'password' => array( 'label' => __( 'Password', 'frontglut' ), 'render' => function () { $h = Ui::form_open( 'password_change', array( 'after' => 'reset' ) ); $h .= Fields::render( array( 'key' => 'current', 'type' => 'password', 'label' => __( 'Current password', 'frontglut' ), 'required' => 1, 'width' => '100' ), '', 'current' ); $h .= Fields::render( array( 'key' => 'pass1', 'type' => 'password', 'label' => __( 'New password', 'frontglut' ), 'required' => 1, 'width' => '50' ), '', 'pass1' ); $h .= Fields::render( array( 'key' => 'pass2', 'type' => 'password', 'label' => __( 'Confirm new password', 'frontglut' ), 'required' => 1, 'width' => '50' ), '', 'pass2' ); return $h . Ui::form_close( __( 'Change password', 'frontglut' ) ); } ), 'privacy' => array( 'label' => __( 'Privacy', 'frontglut' ), 'render' => function () { if ( ! Settings::get( 'auth.allow_delete' ) ) { return '

' . esc_html__( 'Data requests are disabled.', 'frontglut' ) . '

'; } return '

' . esc_html__( 'Request a copy of your personal data, or ask us to erase it. You will receive a confirmation email first.', 'frontglut' ) . '

' . Ui::action_btn( __( 'Request data export', 'frontglut' ), 'privacy_request', array( 'type' => 'export_personal_data' ), '', false, 'none' ) . ' ' . Ui::action_btn( __( 'Request account deletion', 'frontglut' ), 'privacy_request', array( 'type' => 'remove_personal_data' ), 'frontglut-danger', true, 'none' ); } ), ) ); $h = '
'; $first = true; foreach ( $tabs as $id => $t ) { $h .= ''; $first = false; } $h .= '
'; $first = true; foreach ( $tabs as $id => $t ) { $h .= '
' . Ui::kses( call_user_func( $t['render'] ) ) . '
'; $first = false; } return Ui::header( __( 'My profile', 'frontglut' ) ) . '
' . $h . '
'; } public function ajax_profile( $req ) { $user = wp_get_current_user(); $form = Forms::default_form( 'profile' ); $post = isset( $req['frontglut_f'] ) && is_array( $req['frontglut_f'] ) ? $req['frontglut_f'] : array(); $fields = array_filter( $form['fields'], function ( $f ) use ( $user ) { return empty( $f['roles'] ) || array_intersect( (array) $f['roles'], (array) $user->roles ); } ); list( $v, $errors ) = Fields::process( array_values( $fields ), $post ); foreach ( $fields as $f ) { if ( 'user_email' === $f['storage'] && ! empty( $v[ $f['key'] ] ) ) { $other = email_exists( $v[ $f['key'] ] ); if ( $other && (int) $other !== $user->ID ) { $errors[ $f['key'] ] = __( 'This email is used by another account.', 'frontglut' ); } } } if ( $errors ) { Ajax::fail( __( 'Please fix the highlighted fields.', 'frontglut' ), 400, array( 'errors' => $errors ) ); } $upd = array( 'ID' => $user->ID ); foreach ( $fields as $f ) { $k = $f['key']; if ( ! array_key_exists( $k, $v ) ) { continue; } if ( in_array( $f['storage'], array( 'user_email', 'user_url', 'display_name' ), true ) ) { $upd[ $f['storage'] ] = $v[ $k ]; } elseif ( in_array( $f['storage'], array( 'first_name', 'last_name', 'description' ), true ) ) { update_user_meta( $user->ID, $f['storage'], $v[ $k ] ); } else { $this->save_extra( $user->ID, array( $f ), $v, array() ); } } $r = wp_update_user( $upd ); if ( is_wp_error( $r ) ) { Ajax::fail( $r->get_error_message() ); } Audit::log( 'profile_updated', (string) $user->ID ); return array( 'message' => __( 'Profile updated.', 'frontglut' ) ); } public function ajax_password( $req ) { $user = wp_get_current_user(); if ( ! wp_check_password( isset( $req['current'] ) ? (string) $req['current'] : '', $user->user_pass, $user->ID ) ) { Ajax::fail( __( 'Current password is wrong.', 'frontglut' ), 400, array( 'errors' => array( 'current' => __( 'Wrong password.', 'frontglut' ) ) ) ); } $p1 = isset( $req['pass1'] ) ? (string) $req['pass1'] : ''; if ( strlen( $p1 ) < 8 || $p1 !== ( isset( $req['pass2'] ) ? (string) $req['pass2'] : '' ) ) { Ajax::fail( __( 'New passwords must match and be at least 8 characters.', 'frontglut' ) ); } wp_set_password( $p1, $user->ID ); wp_set_auth_cookie( $user->ID, true ); Audit::log( 'password_changed', (string) $user->ID ); return array( 'message' => __( 'Password changed.', 'frontglut' ) ); } public function ajax_privacy( $req ) { if ( ! Settings::get( 'auth.allow_delete' ) ) { Ajax::fail( __( 'Disabled.', 'frontglut' ), 403 ); } $type = isset( $req['type'] ) && 'remove_personal_data' === $req['type'] ? 'remove_personal_data' : 'export_personal_data'; if ( 'remove_personal_data' === $type && user_can( get_current_user_id(), 'manage_options' ) ) { Ajax::fail( __( 'Administrators cannot request deletion here.', 'frontglut' ) ); } $id = wp_create_user_request( wp_get_current_user()->user_email, $type ); if ( is_wp_error( $id ) ) { Ajax::fail( $id->get_error_message() ); } wp_send_user_request( $id ); return array( 'message' => __( 'We emailed you a confirmation link.', 'frontglut' ) ); } public function avatar( $args, $id_or_email ) { $uid = 0; if ( is_numeric( $id_or_email ) ) { $uid = (int) $id_or_email; } elseif ( $id_or_email instanceof \WP_User ) { $uid = $id_or_email->ID; } elseif ( $id_or_email instanceof \WP_Post ) { $uid = (int) $id_or_email->post_author; } elseif ( is_string( $id_or_email ) && is_email( $id_or_email ) ) { $u = get_user_by( 'email', $id_or_email ); $uid = $u ? $u->ID : 0; } if ( $uid ) { $aid = (int) get_user_meta( $uid, 'frontglut_avatar', true ); if ( $aid ) { $url = wp_get_attachment_image_url( $aid, array( (int) ( $args['size'] ?? 96 ), (int) ( $args['size'] ?? 96 ) ) ); if ( $url ) { $args['url'] = $url; $args['found_avatar'] = true; } } } return $args; } /* ---------------- admin users list (frontend) ---------------- */ public function render_users() { $q = Request::filter( 's', 'text', '' ); $pg = Request::page(); $args = array( 'number' => 20, 'paged' => $pg, 'orderby' => 'registered', 'order' => 'DESC' ); if ( $q ) { $args['search'] = '*' . $q . '*'; } $uq = new \WP_User_Query( $args ); $rows = array(); foreach ( $uq->get_results() as $u ) { $pending = get_user_meta( $u->ID, 'frontglut_pending_approval', true ); $rows[] = array( 'name' => '' . esc_html( $u->display_name ) . '
' . esc_html( $u->user_email ) . '', 'role' => esc_html( implode( ', ', array_map( 'translate_user_role', array_map( function ( $r ) { return wp_roles()->roles[ $r ]['name'] ?? $r; }, $u->roles ) ) ) ), 'status' => $pending ? Ui::badge( __( 'Awaiting approval', 'frontglut' ), 'warn' ) : ( get_user_meta( $u->ID, 'frontglut_verify_token', true ) ? Ui::badge( __( 'Unverified', 'frontglut' ), 'warn' ) : Ui::badge( __( 'Active', 'frontglut' ), 'ok' ) ), 'since' => Ui::date( $u->user_registered ), 'act' => $pending ? Ui::action_btn( __( 'Approve', 'frontglut' ), 'user_approve', array( 'id' => $u->ID ), 'frontglut-primary' ) : '', ); } return Ui::header( __( 'Users', 'frontglut' ) ) . Ui::filters( array(), array( 's' => $q ) ) . Ui::table( array( 'name' => __( 'User', 'frontglut' ), 'role' => __( 'Role', 'frontglut' ), 'status' => __( 'Status', 'frontglut' ), 'since' => __( 'Registered', 'frontglut' ), 'act' => '' ), $rows ) . Ui::pager( $uq->get_total(), 20, $pg, array( 's' => $q ) ); } public function admin_user_fields( $user ) { if ( ! current_user_can( 'edit_users' ) ) { return; } $p = get_user_meta( $user->ID, 'frontglut_pending_approval', true ); echo '

FrontGlut

' . esc_html__( 'Awaiting approval', 'frontglut' ) . '' . ( $p ? esc_html__( 'Yes — use the FrontGlut Users screen or delete the meta key frontglut_pending_approval.', 'frontglut' ) : esc_html__( 'No', 'frontglut' ) ) . '
'; } /* ---------------- public member page ---------------- */ public function rewrites() { add_rewrite_rule( '^member/([^/]+)/?$', 'index.php?frontglut_member=$matches[1]', 'top' ); } public function sc_member( $atts ) { $a = shortcode_atts( array( 'id' => 0 ), $atts ); $u = $a['id'] ? get_userdata( (int) $a['id'] ) : null; if ( ! $u ) { return ''; } Assets::need(); return $this->member_html( $u ); } private function member_html( $u ) { $posts = get_posts( array( 'author' => $u->ID, 'posts_per_page' => 10, 'post_status' => 'publish' ) ); $h = '
' . get_avatar( $u->ID, 80 ) . '

' . esc_html( $u->display_name ) . '

' . esc_html( get_user_meta( $u->ID, 'description', true ) ) . '

'; if ( $posts ) { $h .= '

' . esc_html__( 'Recent posts', 'frontglut' ) . '

'; } // Filtered markup is sanitised before it is returned to the shortcode / page. return Ui::kses( apply_filters( 'frontglut_member_html', $h . '
', $u ) ); } private function render_member_page( $login ) { $u = get_user_by( 'login', sanitize_user( $login ) ); if ( ! $u ) { global $wp_query; $wp_query->set_404(); status_header( 404 ); return; } Assets::need(); get_header(); echo '
' . wp_kses( $this->member_html( $u ), Ui::allowed_html() ) . '
'; get_footer(); exit; } /* ---------------- social login ---------------- */ private function provider( $p ) { $cfg = array( 'google' => array( 'auth' => 'https://accounts.google.com/o/oauth2/v2/auth', 'token' => 'https://oauth2.googleapis.com/token', 'info' => 'https://openidconnect.googleapis.com/v1/userinfo', 'scope' => 'openid email profile' ), 'facebook' => array( 'auth' => 'https://www.facebook.com/v19.0/dialog/oauth', 'token' => 'https://graph.facebook.com/v19.0/oauth/access_token', 'info' => 'https://graph.facebook.com/me?fields=id,name,email', 'scope' => 'email,public_profile' ), ); $cfg = apply_filters( 'frontglut_oauth_providers', $cfg ); if ( ! isset( $cfg[ $p ] ) || ! Settings::get( 'auth.' . $p . '_id' ) || ! Settings::get( 'auth.' . $p . '_secret' ) ) { return null; } return $cfg[ $p ]; } private function social_start( $p ) { $c = $this->provider( $p ); if ( ! $c || is_user_logged_in() ) { return; } $state = wp_generate_password( 20, false ); // Bind the OAuth state to this browser so a callback link cannot be replayed in someone else's browser (login CSRF). setcookie( 'frontglut_oauth', wp_hash( $state ), time() + 600, COOKIEPATH ? COOKIEPATH : '/', COOKIE_DOMAIN, is_ssl(), true ); set_transient( 'frontglut_oauth_' . $state, isset( $_GET['to'] ) ? esc_url_raw( wp_unslash( $_GET['to'] ) ) : '', 600 ); // phpcs:ignore WordPress.Security.NonceVerification -- where to go after login; later passed through wp_validate_redirect(). $url = add_query_arg( array( 'client_id' => Settings::get( 'auth.' . $p . '_id' ), 'redirect_uri' => add_query_arg( 'frontglut_social_cb', $p, home_url( '/' ) ), 'response_type' => 'code', 'scope' => $c['scope'], 'state' => $state ), $c['auth'] ); wp_redirect( $url ); // phpcs:ignore WordPress.Security.SafeRedirect -- external OAuth provider. exit; } private function social_callback( $p ) { $c = $this->provider( $p ); $state = isset( $_GET['state'] ) ? sanitize_text_field( wp_unslash( $_GET['state'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification -- the OAuth state is the CSRF token; verified just below. $to = get_transient( 'frontglut_oauth_' . $state ); $bound = isset( $_COOKIE['frontglut_oauth'] ) ? sanitize_text_field( wp_unslash( $_COOKIE['frontglut_oauth'] ) ) : ''; if ( ! $c || false === $to || '' === $state || '' === $bound || ! hash_equals( wp_hash( $state ), $bound ) || empty( $_GET['code'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification -- authorised by the OAuth state bound to this browser. wp_safe_redirect( add_query_arg( 'frontglut_social_err', 1, self::page_url( 'login_page', home_url( '/' ) ) ) ); exit; } delete_transient( 'frontglut_oauth_' . $state ); setcookie( 'frontglut_oauth', '', time() - 3600, COOKIEPATH ? COOKIEPATH : '/', COOKIE_DOMAIN, is_ssl(), true ); $tok = wp_remote_post( $c['token'], array( 'timeout' => 15, 'headers' => array( 'Accept' => 'application/json' ), 'body' => array( 'code' => sanitize_text_field( wp_unslash( $_GET['code'] ) ), 'client_id' => Settings::get( 'auth.' . $p . '_id' ), 'client_secret' => Settings::get( 'auth.' . $p . '_secret' ), 'redirect_uri' => add_query_arg( 'frontglut_social_cb', $p, home_url( '/' ) ), 'grant_type' => 'authorization_code' ) ) ); // phpcs:ignore WordPress.Security.NonceVerification $tok = is_wp_error( $tok ) ? array() : json_decode( wp_remote_retrieve_body( $tok ), true ); if ( empty( $tok['access_token'] ) ) { wp_safe_redirect( add_query_arg( 'frontglut_social_err', 1, self::page_url( 'login_page', home_url( '/' ) ) ) ); exit; } $info = wp_remote_get( $c['info'], array( 'timeout' => 15, 'headers' => array( 'Authorization' => 'Bearer ' . $tok['access_token'] ) ) ); $info = is_wp_error( $info ) ? array() : json_decode( wp_remote_retrieve_body( $info ), true ); $info = apply_filters( 'frontglut_oauth_userinfo', is_array( $info ) ? $info : array(), $p, $tok['access_token'] ); $mail = isset( $info['email'] ) ? sanitize_email( $info['email'] ) : ''; // Google says whether it verified the address; Facebook only returns confirmed addresses. if ( 'google' === $p && empty( $info['email_verified'] ) ) { $mail = ''; } if ( ! $mail ) { wp_safe_redirect( add_query_arg( 'frontglut_social_err', 2, self::page_url( 'login_page', home_url( '/' ) ) ) ); exit; } $user = get_user_by( 'email', $mail ); if ( ! $user ) { if ( ! Settings::get( 'auth.registration_open' ) ) { wp_die( esc_html__( 'Registration is closed.', 'frontglut' ) ); } $login = sanitize_user( current( explode( '@', $mail ) ), true ); while ( username_exists( $login ) ) { $login .= wp_rand( 1, 99 ); } $role = Caps::registration_role( Settings::get( 'auth.default_role', 'subscriber' ) ); $id = wp_insert_user( array( 'user_login' => $login, 'user_email' => $mail, 'user_pass' => wp_generate_password( 24 ), 'display_name' => isset( $info['name'] ) ? sanitize_text_field( $info['name'] ) : $login, 'role' => $role ) ); if ( is_wp_error( $id ) ) { wp_die( esc_html( $id->get_error_message() ) ); } $user = get_userdata( $id ); do_action( 'frontglut_user_registered', $id, array() ); Mailer::send_template( 'admin_new_user', get_option( 'admin_email' ), array( 'username' => $login, 'email' => $mail, 'review_url' => admin_url( 'users.php' ) ) ); if ( Settings::get( 'auth.approval' ) ) { update_user_meta( $id, 'frontglut_pending_approval', 1 ); wp_safe_redirect( add_query_arg( 'frontglut_pending', 1, self::page_url( 'login_page', home_url( '/' ) ) ) ); exit; } } if ( get_user_meta( $user->ID, 'frontglut_pending_approval', true ) ) { wp_safe_redirect( add_query_arg( 'frontglut_pending', 1, self::page_url( 'login_page', home_url( '/' ) ) ) ); exit; } // Privileged accounts must use the normal WordPress login, so login-security and 2FA plugins always apply to them. foreach ( apply_filters( 'frontglut_social_login_blocked_caps', array( 'manage_options', 'edit_users', 'promote_users', 'edit_others_posts', 'manage_woocommerce' ) ) as $cap ) { if ( user_can( $user, $cap ) ) { wp_safe_redirect( add_query_arg( 'frontglut_social_err', 3, self::page_url( 'login_page', home_url( '/' ) ) ) ); exit; } } wp_set_current_user( $user->ID ); wp_set_auth_cookie( $user->ID, true ); do_action( 'wp_login', $user->user_login, $user ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core hook, fired so security/audit plugins see social logins. wp_safe_redirect( self::after_login_url( $user, (string) $to ) ); exit; } }