*/ private static function legacy_prefixes(): array { return array( 'aivalve_tokens_', 'ai_valve_tokens_', ); } public function __construct( private readonly Settings $settings, ) {} /* ------------------------------------------------------------------ * Record usage * ----------------------------------------------------------------*/ /** * Increment both per-plugin and global counters. */ public function record( string $plugin_slug, int $tokens, string $provider_id = '' ): void { if ( $tokens <= 0 ) { return; } $today = UsageClock::current_date(); $month = UsageClock::current_month(); // Per-plugin daily. $this->increment( self::daily_key( $today, $plugin_slug ), $tokens ); // Per-plugin monthly. $this->increment( self::monthly_key( $month, $plugin_slug ), $tokens ); // Global daily. $this->increment( self::daily_key( $today, '*' ), $tokens ); // Global monthly. $this->increment( self::monthly_key( $month, '*' ), $tokens ); // Per-provider counters. if ( '' !== $provider_id ) { $this->increment( self::daily_key( $today, 'provider:' . $provider_id ), $tokens ); $this->increment( self::monthly_key( $month, 'provider:' . $provider_id ), $tokens ); } } /* ------------------------------------------------------------------ * Read counters * ----------------------------------------------------------------*/ public function plugin_tokens_today( string $slug ): int { return $this->read( self::daily_key( UsageClock::current_date(), $slug ) ); } public function plugin_tokens_this_month( string $slug ): int { return $this->read( self::monthly_key( UsageClock::current_month(), $slug ) ); } public function global_tokens_today(): int { return $this->read( self::daily_key( UsageClock::current_date(), '*' ) ); } public function global_tokens_this_month(): int { return $this->read( self::monthly_key( UsageClock::current_month(), '*' ) ); } public function provider_tokens_today( string $provider_id ): int { return $this->read( self::daily_key( UsageClock::current_date(), 'provider:' . $provider_id ) ); } public function provider_tokens_this_month( string $provider_id ): int { return $this->read( self::monthly_key( UsageClock::current_month(), 'provider:' . $provider_id ) ); } /* ------------------------------------------------------------------ * Budget helpers (for AlertManager) * ----------------------------------------------------------------*/ /** * Returns the percentage of the global daily budget consumed (0-100+). * Returns 0 if no limit is set. */ public function global_daily_pct(): float { $limit = (int) $this->settings->get( 'global_daily_limit', 0 ); if ( $limit <= 0 ) { return 0.0; } return ( $this->global_tokens_today() / $limit ) * 100; } public function global_monthly_pct(): float { $limit = (int) $this->settings->get( 'global_monthly_limit', 0 ); if ( $limit <= 0 ) { return 0.0; } return ( $this->global_tokens_this_month() / $limit ) * 100; } public function plugin_daily_pct( string $slug ): float { $limit = $this->settings->plugin_daily_budget( $slug ); if ( $limit <= 0 ) { return 0.0; } return ( $this->plugin_tokens_today( $slug ) / $limit ) * 100; } public function plugin_monthly_pct( string $slug ): float { $limit = $this->settings->plugin_monthly_budget( $slug ); if ( $limit <= 0 ) { return 0.0; } return ( $this->plugin_tokens_this_month( $slug ) / $limit ) * 100; } /* ------------------------------------------------------------------ * Internal helpers * ----------------------------------------------------------------*/ private static function daily_key( string $date, string $slug ): string { return self::PREFIX . 'daily_' . $date . '_' . $slug; } private static function monthly_key( string $month, string $slug ): string { return self::PREFIX . 'monthly_' . $month . '_' . $slug; } private function increment( string $key, int $amount ): void { $current = $this->read( $key ); update_option( $key, $current + $amount, false ); } private function read( string $key ): int { $value = get_option( $key, null ); if ( null !== $value ) { return (int) $value; } $suffix = substr( $key, strlen( self::PREFIX ) ); foreach ( self::legacy_prefixes() as $legacy_prefix ) { $legacy_value = get_option( $legacy_prefix . $suffix, null ); if ( null !== $legacy_value ) { return (int) $legacy_value; } } return 0; } /* ------------------------------------------------------------------ * Cleanup * ----------------------------------------------------------------*/ /** * Delete all rolling token counter options. Used during uninstall. */ public static function delete_all(): void { global $wpdb; foreach ( array_merge( array( self::PREFIX ), self::legacy_prefixes() ) as $prefix ) { $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name LIKE %s", $wpdb->esc_like( $prefix ) . '%' ) ); } } }