, so nothing about the requester is stored in readable form. */ final class RateLimit { /** * Count one attempt. Returns false when this requester has already used $max attempts in the * current $window seconds (the attempt is refused and not counted). */ public static function hit( string $bucket, string $who, int $max, int $window ): bool { $key = self::key( $bucket, $who ); $n = (int) get_transient( $key ); if ( $n >= $max ) { return false; } set_transient( $key, $n + 1, $window ); return true; } /** Has this requester used up the bucket, without counting an attempt? */ public static function blocked( string $bucket, string $who, int $max ): bool { return (int) get_transient( self::key( $bucket, $who ) ) >= $max; } public static function clear( string $bucket, string $who ): void { delete_transient( self::key( $bucket, $who ) ); } /** The requesting address, as the web server reports it. */ public static function ip(): string { $ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : ''; return (string) filter_var( $ip, FILTER_VALIDATE_IP ) ?: 'unknown'; } private static function key( string $bucket, string $who ): string { return 'dashbread_rl_' . md5( $bucket . '|' . $who ); } }