capabilities ) ); return ! array_diff( $caps, array( 'read', 'level_0' ) ); } /** Роль новых пользователей: из настроек, если она безопасна, иначе подписчик. */ public static function new_user_role() { foreach ( array( (string) PRPS_Settings::get( 'role' ), (string) get_option( 'default_role', 'subscriber' ) ) as $role ) { if ( '' !== $role && self::is_safe_role( $role ) ) { return $role; } } return 'subscriber'; } /** Пользователь по значению мета-поля. */ public static function find( $provider, $id ) { if ( '' === (string) $id ) { return null; } $users = get_users( array( 'meta_key' => self::meta_key( $provider ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_value' => (string) $id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value 'number' => 1, 'count_total' => false, ) ); return $users ? $users[0] : null; } /** Подписи способов входа. */ public static function label( $provider ) { $labels = array( 'phone' => __( 'Телефон', 'propusk' ), 'vk' => 'VK ID', 'yandex' => __( 'Яндекс ID', 'propusk' ), 'max' => 'MAX', ); return $labels[ $provider ] ?? $provider; } /** * Вход или привязка по подтверждённой личности. * * @param array $who provider, id, phone?, email?, first_name?, last_name?. * @param string $redirect Куда вернуть после входа. * @return string|WP_Error Адрес для перехода. */ public static function sign_in( array $who, $redirect = '' ) { $provider = (string) $who['provider']; $id = (string) $who['id']; $phone = isset( $who['phone'] ) ? PRPS_Phone::normalize( $who['phone'] ) : ''; $email = isset( $who['email'] ) ? sanitize_email( $who['email'] ) : ''; // Пользователь уже вошёл — привязываем способ входа к его аккаунту. if ( is_user_logged_in() ) { $me = wp_get_current_user(); $owner = self::find( $provider, $id ); if ( $owner && (int) $owner->ID !== (int) $me->ID ) { /* translators: %s: способ входа */ return new WP_Error( 'taken', sprintf( __( 'Этот %s уже привязан к другому аккаунту.', 'propusk' ), self::label( $provider ) ) ); } update_user_meta( $me->ID, self::meta_key( $provider ), $id ); if ( $phone && ! self::find( 'phone', $phone ) ) { update_user_meta( $me->ID, self::PHONE, $phone ); } /** Способ входа привязан к аккаунту. */ do_action( 'prps/linked', $me->ID, $provider ); return add_query_arg( 'prps_linked', $provider, admin_url( 'profile.php' ) ) . '#prps-profile'; } $user = self::find( $provider, $id ); if ( ! $user && $phone ) { $user = self::find( 'phone', $phone ); } if ( ! $user && $email && PRPS_Settings::get( 'link_email' ) && 'phone' !== $provider ) { $by_email = get_user_by( 'email', $email ); if ( $by_email ) { // Аккаунты с правами администратора автоматически не связываются: только из профиля, после входа. if ( user_can( $by_email, 'edit_others_posts' ) ) { /* translators: %s: способ входа */ return new WP_Error( 'admin', sprintf( __( 'Аккаунт с этой почтой нельзя связать автоматически. Войдите как обычно и привяжите %s в профиле.', 'propusk' ), self::label( $provider ) ) ); } $user = $by_email; } } if ( ! $user ) { if ( ! self::can_register() ) { return new WP_Error( 'no_user', __( 'Аккаунт не найден. Регистрация новых пользователей на сайте закрыта.', 'propusk' ) ); } $user = self::create( $who, $phone, $email ); if ( is_wp_error( $user ) ) { return $user; } } if ( 'phone' !== $provider ) { update_user_meta( $user->ID, self::meta_key( $provider ), $id ); } if ( $phone && ! get_user_meta( $user->ID, self::PHONE, true ) && ! self::find( 'phone', $phone ) ) { update_user_meta( $user->ID, self::PHONE, $phone ); } /** * Перед входом: Pro может потребовать второй шаг (например, для администраторов). * * @param true|WP_Error|string $allow true — впустить; строка — адрес второго шага. * @param WP_User $user Пользователь. * @param string $provider Способ входа. */ $allow = apply_filters( 'prps/before_login', true, $user, $provider ); if ( is_wp_error( $allow ) || is_string( $allow ) ) { return $allow; } wp_set_current_user( $user->ID ); wp_set_auth_cookie( $user->ID, true, is_ssl() ); do_action( 'wp_login', $user->user_login, $user ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- стандартный хук WordPress. /** * Пользователь вошёл. * * @param WP_User $user Пользователь. * @param string $provider Способ входа. */ do_action( 'prps/signed_in', $user, $provider ); return self::redirect_for( $user, $redirect ); } /** Куда вести после входа. */ public static function redirect_for( WP_User $user, $requested = '' ) { $default = (string) PRPS_Settings::get( 'redirect' ); if ( '' === $default ) { $default = user_can( $user, 'edit_posts' ) ? admin_url() : home_url( '/' ); } $to = '' !== $requested ? wp_validate_redirect( $requested, $default ) : $default; return (string) apply_filters( 'login_redirect', $to, $requested, $user ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- стандартный фильтр WordPress. } /** Новый пользователь. */ private static function create( array $who, $phone, $email ) { $base = ''; if ( $email ) { $base = sanitize_user( strstr( $email, '@', true ), true ); } if ( strlen( $base ) < 3 ) { $base = $phone ? 'user' . substr( $phone, -4 ) : 'user'; } $login = $base; for ( $i = 2; username_exists( $login ); $i++ ) { $login = $base . $i; } if ( $email && email_exists( $email ) ) { $email = ''; } $first = sanitize_text_field( (string) ( $who['first_name'] ?? '' ) ); $last = sanitize_text_field( (string) ( $who['last_name'] ?? '' ) ); $id = wp_insert_user( array( 'user_login' => $login, 'user_pass' => wp_generate_password( 32, true, true ), 'user_email' => $email, 'first_name' => $first, 'last_name' => $last, 'display_name' => trim( $first . ' ' . $last ) ? trim( $first . ' ' . $last ) : ( $phone ? PRPS_Phone::masked( $phone ) : $login ), 'role' => self::new_user_role(), ) ); if ( is_wp_error( $id ) ) { return new WP_Error( 'create', __( 'Не удалось создать аккаунт. Обратитесь к администратору сайта.', 'propusk' ) ); } /** * Создан пользователь при первом входе. * * @param int $id ID пользователя. * @param array $who Данные способа входа. */ do_action( 'prps/registered', $id, $who ); return get_user_by( 'id', $id ); } /** Отвязать способ входа. */ public static function unlink( $user_id, $provider ) { delete_user_meta( $user_id, self::meta_key( $provider ) ); } /** Счётчики для обзора. */ public static function stats() { global $wpdb; $out = array( 'total' => (int) count_users()['total_users'] ); foreach ( array( 'phone', 'vk', 'yandex', 'max' ) as $p ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery $out[ $p ] = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT user_id) FROM {$wpdb->usermeta} WHERE meta_key = %s AND meta_value <> ''", self::meta_key( $p ) ) ); } // phpcs:ignore WordPress.DB.DirectDatabaseQuery $out['none'] = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->users} u WHERE NOT EXISTS (SELECT 1 FROM {$wpdb->usermeta} m WHERE m.user_id = u.ID AND m.meta_key IN (%s, %s, %s, %s) AND m.meta_value <> '')", self::PHONE, 'prps_vk', 'prps_yandex', 'prps_max' ) ); return $out; } }