get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT user_id) FROM {$wpdb->usermeta} WHERE meta_key = %s AND meta_value = %s", WPCEV_Tokens::META_VERIFIED, '1' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $unverified = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(DISTINCT user_id) FROM {$wpdb->usermeta} WHERE meta_key = %s AND meta_value = %s", WPCEV_Tokens::META_VERIFIED, '0' ) ); $total = count_users(); $total_users = isset( $total['total_users'] ) ? (int) $total['total_users'] : 0; $grandfathered = max( 0, $total_users - $verified - $unverified ); $out = array( 'verified' => $verified, 'unverified' => $unverified, 'grandfathered' => $grandfathered, 'total' => $total_users, ); set_transient( self::COUNT_TRANSIENT, $out, self::COUNT_TTL ); return $out; } public static function bust_count_cache() { delete_transient( self::COUNT_TRANSIENT ); } /** * Recent unverified users (most recently registered first), capped. * * @param int $limit * @return array */ public static function recent_unverified( $limit = 5 ) { // phpcs:ignore WordPress.DB.SlowDBQuery -- one indexed meta key on a single column; admin-only widget capped at 50 results. $query = new WP_User_Query( array( 'meta_key' => WPCEV_Tokens::META_VERIFIED, // phpcs:ignore WordPress.DB.SlowDBQuery 'meta_value' => '0', // phpcs:ignore WordPress.DB.SlowDBQuery 'orderby' => 'registered', 'order' => 'DESC', 'number' => max( 1, min( 50, (int) $limit ) ), 'count_total' => false, ) ); return $query->get_results(); } /** * Return ALL unverified user IDs — used by the CSV export. No paging * because (a) unverified counts are typically small and (b) the export * is a one-shot admin action; if a site really has tens of thousands * of unverified users, the export endpoint streams. * * @return array */ public static function all_unverified_ids() { // phpcs:ignore WordPress.DB.SlowDBQuery -- admin-only CSV export; same indexed key as the dashboard widget. $query = new WP_User_Query( array( 'meta_key' => WPCEV_Tokens::META_VERIFIED, // phpcs:ignore WordPress.DB.SlowDBQuery 'meta_value' => '0', // phpcs:ignore WordPress.DB.SlowDBQuery 'fields' => 'ID', 'number' => -1, 'count_total' => false, ) ); return array_map( 'intval', (array) $query->get_results() ); } /* ─── Failed-send ring buffer ────────────────────────────────────────── */ /** * Record a failed send. Trims the buffer to FAIL_LOG_MAX entries (oldest * dropped). Stored as autoload=no so it doesn't bloat the autoload pool. * * @param int $user_id * @param string $email * @param string $code 'wp_mail_failed' | 'invalid_user' | … * @param string $context 'register' | 'profile_update' | 'resend' | 'cli' | … */ public static function log_failure( $user_id, $email, $code, $context = '' ) { $log = self::get_failures(); $log[] = array( 'time' => time(), 'user_id' => (int) $user_id, 'email' => substr( (string) $email, 0, 254 ), 'code' => sanitize_key( $code ), 'context' => sanitize_key( $context ), ); // Drop oldest entries beyond the cap. if ( count( $log ) > self::FAIL_LOG_MAX ) { $log = array_slice( $log, -self::FAIL_LOG_MAX ); } update_option( self::FAIL_LOG_OPTION, $log, false ); } /** * @return array */ public static function get_failures() { $log = get_option( self::FAIL_LOG_OPTION, array() ); return is_array( $log ) ? $log : array(); } public static function clear_failures() { delete_option( self::FAIL_LOG_OPTION ); } }