'POST', 'callback' => [ $this, 'handle' ], 'permission_callback' => '__return_true', 'args' => $this->get_args(), ], ] ); } public function handle( WP_REST_Request $request ): WP_REST_Response|WP_Error { if ( $this->is_rate_limited() ) { return new WP_Error( 'pulseviews_rate_limited', __( 'Too many requests. Please slow down.', 'pulseviews' ), [ 'status' => 429 ] ); } $post_id = (int) $request->get_param( 'post_id' ); if ( ! $post_id ) { return new WP_Error( 'pulseviews_invalid_post', 'Invalid post_id.', [ 'status' => 400 ] ); } $tracker = new ViewTracker(); $recorded = $tracker->record_view( $post_id, [ 'source' => 'js', 'country_code' => $this->get_country_from_cf(), ] ); return new WP_REST_Response( [ 'recorded' => $recorded, 'post_id' => $post_id, ], 200 ); } private function get_args(): array { return [ 'post_id' => [ 'required' => true, 'type' => 'integer', 'minimum' => 1, 'sanitize_callback' => 'absint', ], ]; } private function is_rate_limited(): bool { $ip_hash = hash( 'sha256', sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) ) ); $key = 'pulseviews_rl_' . $ip_hash; $hits = (int) get_transient( $key ); if ( $hits >= self::RATE_LIMIT ) { return true; } set_transient( $key, $hits + 1, MINUTE_IN_SECONDS ); return false; } private function get_country_from_cf(): string { return sanitize_text_field( wp_unslash( $_SERVER['HTTP_CF_IPCOUNTRY'] ?? '' ) ); } }