= self::WINDOW ) { $entry = [ 'since' => $now, 'count' => 0 ]; } if ( (int) $entry['count'] >= $limit ) { return false; } $entry['count'] = (int) $entry['count'] + 1; set_transient( $key, $entry, self::WINDOW ); return true; } /** Booking tokens: bin2hex( random_bytes( 32 ) ). */ const TOKEN_RE = '/^[a-f0-9]{64}$/'; /** * The booking behind a public token, rate-limited: 404 not_found for an * unknown or malformed token (counted per address, not per token), 429 * rate_limited once either budget is spent. * * @param callable|null $lookup fn( string $token ): ?object, default Booking::getByToken * @return object|WP_Error the booking row */ public static function for_token( string $token, ?callable $lookup = null ) { $booking = preg_match( self::TOKEN_RE, $token ) ? ( $lookup ?? [ Booking::class, 'getByToken' ] )( $token ) : null; if ( ! $booking ) { return self::hit( 'miss:' . self::address() ) ? self::not_found() : self::error(); } return self::hit( $token ) ? $booking : self::error(); } public static function not_found(): WP_Error { return new WP_Error( 'not_found', __( 'Booking not found.', 'vizyt' ), [ 'status' => 404 ] ); } public static function error(): WP_Error { return new WP_Error( 'rate_limited', __( 'Too many requests. Please wait a minute and try again.', 'vizyt' ), [ 'status' => 429 ] ); } private static function address(): string { return isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : ''; } }