ID ); } } /** * Records that the member's current email address is verified. * * @param int $user_id User ID. * @return void */ public static function mark_email_verified( $user_id ) { $user = get_userdata( (int) $user_id ); if ( $user && '' !== $user->user_email ) { update_user_meta( $user->ID, self::EMAIL_VERIFIED, strtolower( $user->user_email ) ); } } /** * Whether the member's current email address is verified. Changing the * address makes it unverified again, because the stored address differs. * * @param WP_User $user Member. * @return bool */ public static function email_verified( WP_User $user ) { $verified = (string) get_user_meta( $user->ID, self::EMAIL_VERIFIED, true ); return '' !== $verified && strtolower( $user->user_email ) === $verified; } /** * Whether SpaceBay is completing a login right now (after every step * passed), so add-ons that watch wp_login do not ask for a step again. * * @return bool */ public static function is_completing() { return self::$completing; } /** * Checks the current password before a sensitive change. * * @param string $password Password typed by the member. * @return true|WP_Error */ public static function confirm_password( $password ) { $user = wp_get_current_user(); $limit = Throttle::check( 'reauth', 'user:' . $user->ID, 10 ); if ( is_wp_error( $limit ) ) { return $limit; } if ( '' === (string) $password || ! wp_check_password( (string) $password, $user->user_pass, $user->ID ) ) { Throttle::hit( 'reauth', 'user:' . $user->ID, 10, 15 * MINUTE_IN_SECONDS ); return new WP_Error( 'spacebay_wrong_password', __( 'That password is not correct.', 'spacebay' ), array( 'status' => 403, 'field' => 'password', ) ); } Throttle::clear( 'reauth', 'user:' . $user->ID ); return true; } /** * Whether the community shows its own login and signup pages. * * @return bool */ public static function has_pages() { return (bool) Settings::get( 'auth_pages', true ); } /** * Whether visitors can create an account on the signup page. * * @return bool */ public static function signup_open() { return self::has_pages() && (bool) Settings::get( 'signup_enabled', false ) && self::network_allows_signup(); } /** * On a network of sites, the network administrator decides whether people * may create accounts at all (Network Admin, Settings, Registration). * * @return bool */ public static function network_allows_signup() { return ! is_multisite() || in_array( get_site_option( 'registration', 'none' ), array( 'user', 'all' ), true ); } /** * Address of the login page: the community one, or the WordPress one. * * @return string */ public static function login_url() { return self::has_pages() ? Portal::link( 'login' ) : wp_login_url( Portal::url() ); } /** * Address of the signup page, empty when signing up is not possible. * * @return string */ public static function signup_url() { if ( self::has_pages() ) { return self::signup_open() ? Portal::link( 'signup' ) : ''; } return get_option( 'users_can_register' ) ? wp_registration_url() : ''; } /** * Returns a safe address on this site to open after logging in. * * @param string $redirect Requested address. * @return string */ public static function safe_redirect( $redirect ) { $redirect = trim( (string) $redirect ); if ( '' === $redirect ) { return Portal::url(); } $safe = wp_validate_redirect( esc_url_raw( $redirect ), Portal::url() ); // Only full addresses are used, so "javascript:" and other odd values end at the community home. return preg_match( '#^https?://#i', (string) $safe ) ? $safe : Portal::url(); } /** * Logs a member in after every step is passed. * * @param WP_User $user Member. * @param bool $remember Whether to keep them logged in for two weeks. * @return void */ public static function log_in( WP_User $user, $remember ) { self::$completing = true; wp_set_auth_cookie( $user->ID, (bool) $remember ); wp_set_current_user( $user->ID ); /** This action is documented in wp-includes/user.php */ do_action( 'wp_login', $user->user_login, $user ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Core hook, so other plugins see the login. self::$completing = false; } /** * Creates an account from a checked signup form. * * @param array $clean Values from SignupFields::validate(). * @return WP_User|WP_Error */ public static function create_account( array $clean ) { $username = '' !== $clean['username'] ? $clean['username'] : self::username_from_name( $clean['name'] ); $name = '' !== $clean['name'] ? $clean['name'] : $username; $parts = preg_split( '/\s+/', $clean['name'], 2 ); $user_id = wp_insert_user( array( 'user_login' => $username, 'user_nicename' => '' !== $clean['username'] ? '' : $username, 'user_email' => $clean['email'], 'user_pass' => $clean['password'], 'display_name' => $name, 'nickname' => $name, 'first_name' => (string) ( $parts[0] ?? '' ), 'last_name' => (string) ( $parts[1] ?? '' ), 'role' => get_option( 'default_role', 'subscriber' ), ) ); if ( is_wp_error( $user_id ) ) { return new WP_Error( 'spacebay_signup_failed', wp_strip_all_tags( $user_id->get_error_message() ), array( 'status' => 400 ) ); } Profiles::ensure( $user_id ); if ( $clean['profile'] ) { Profiles::update( $user_id, $clean['profile'] ); } /** * Fires after someone created an account on the community signup page. * * @since 1.0.0 * * @param int $user_id New user ID. * @param array $clean Checked form values, without the password. */ do_action( 'spacebay_member_signed_up', (int) $user_id, array_diff_key( $clean, array( 'password' => true ) ) ); // Tell the site administrator, like WordPress does for its own signup form. wp_send_new_user_notifications( $user_id, 'admin' ); $profile = Profiles::get( $user_id ); $pending = $profile && 'pending' === $profile['status']; Mail::send( $user_id, array( 'subject' => __( 'Welcome!', 'spacebay' ), 'text' => sprintf( /* translators: %s: Community name. */ __( 'Welcome to %s', 'spacebay' ), wp_specialchars_decode( (string) Settings::get( 'community_name' ), ENT_QUOTES ) ), 'excerpt' => $pending ? __( 'Your account is ready. A moderator will review it soon, and you will get an email when you can join spaces and post.', 'spacebay' ) : __( 'Your account is ready. Say hello, join a space and share what you are working on.', 'spacebay' ), 'button' => __( 'Open the community', 'spacebay' ), ) ); return get_userdata( $user_id ); } /** * Builds a free username from the member's name and a short random part, * for example "jane-doe-k3x9". It is also the member's address and @mention * name (WordPress copies it to user_nicename), which every member sees, so it * is never built from the email address. * * @param string $name Display name; may be empty. * @return string */ public static function username_from_name( $name ) { $name = (string) $name; // Someone who typed their email address as their name: do not use it. $base = false === strpos( $name, '@' ) ? sanitize_title( remove_accents( $name ) ) : ''; $base = trim( (string) preg_replace( '/[^a-z0-9]+/', '-', strtolower( $base ) ), '-' ); $base = '' !== $base ? trim( substr( $base, 0, 30 ), '-' ) : 'member'; for ( $try = 0; $try < 20; $try++ ) { $username = $base . '-' . strtolower( wp_generate_password( $try < 10 ? 4 : 8, false ) ); if ( ! username_exists( $username ) && validate_username( $username ) && ! get_user_by( 'slug', $username ) ) { return $username; } } return 'member-' . strtolower( wp_generate_password( 12, false ) ); } /* * --------------------------------------------------------------------- * Logins that do not go through the community login page * --------------------------------------------------------------------- */ }