get()['active_provider']; } public function set_active_provider( string $provider_id ): bool { $data = $this->get(); $data['active_provider'] = sanitize_key( $provider_id ); return update_option( self::OPTION_NAME, $data ); } public function get_model( string $provider_id ): string { $data = $this->get(); // Per-task override (M36) wins while a task context is set — this is // how the task→model map reaches providers without changing their // interface (they all read the model through this method). if ( null !== $this->task_context ) { $task_model = (string) ( $data['task_models'][ $this->task_context ] ?? '' ); if ( '' !== $task_model ) { return $task_model; } } return (string) ( $data['providers'][ $provider_id ]['model'] ?? '' ); } public function set_task_context( ?string $task ): void { $this->task_context = $task; } /** * @return array */ public function get_task_models(): array { return array_map( 'strval', (array) $this->get()['task_models'] ); } /** * Merge-safe: only the tasks present in $map change; every other task * keeps its stored model (a form that doesn't render a task must not * blank it). Keys outside AiGenerationService::TASKS are dropped. * * @param array $map */ public function update_task_models( array $map ): bool { $data = $this->get(); $stored = (array) ( $data['task_models'] ?? array() ); $sanitized = array(); foreach ( \AcezSeo\Ai\AiGenerationService::TASKS as $task ) { $value = array_key_exists( $task, $map ) ? $map[ $task ] : ( $stored[ $task ] ?? '' ); $sanitized[ $task ] = sanitize_text_field( (string) $value ); } $data['task_models'] = $sanitized; return update_option( self::OPTION_NAME, $data ); } public function is_tested( string $provider_id ): bool { $data = $this->get(); return (bool) ( $data['providers'][ $provider_id ]['tested'] ?? false ); } public function mark_tested( string $provider_id, bool $tested ): bool { $provider_id = sanitize_key( $provider_id ); $data = $this->get(); if ( ! isset( $data['providers'][ $provider_id ] ) ) { return false; } $data['providers'][ $provider_id ]['tested'] = $tested; return update_option( self::OPTION_NAME, $data ); } public function is_ready(): bool { $active = $this->get_active_provider(); return '' !== $active && $this->has_api_key( $active ) && $this->is_tested( $active ); } public function get_monthly_limit(): int { return (int) $this->get()['monthly_limit']; } public function set_monthly_limit( int $limit ): bool { $data = $this->get(); $data['monthly_limit'] = max( 0, $limit ); return update_option( self::OPTION_NAME, $data ); } public function get_api_key( string $provider_id ): string { $data = $this->get(); $encrypted = (string) ( $data['providers'][ $provider_id ]['api_key_encrypted'] ?? '' ); if ( '' === $encrypted ) { return ''; } return ApiKeyEncryption::decrypt( $encrypted ); } public function has_api_key( string $provider_id ): bool { return '' !== $this->get_api_key( $provider_id ); } /** * @param array{api_key?: string, model?: string} $settings */ public function update_provider( string $provider_id, array $settings ): bool { $provider_id = sanitize_key( $provider_id ); $data = $this->get(); if ( ! isset( $data['providers'][ $provider_id ] ) ) { $data['providers'][ $provider_id ] = array( 'api_key_encrypted' => '', 'model' => '', ); } if ( isset( $settings['model'] ) ) { $data['providers'][ $provider_id ]['model'] = sanitize_text_field( $settings['model'] ); } // Leave the stored key untouched when the field was submitted empty — // re-saving the model shouldn't force re-entering the API key every time. // Trimmed: surrounding whitespace is never part of a real API key (it // sneaks in when copying) and a whitespace-only value must not silently // overwrite a working stored key. $api_key = isset( $settings['api_key'] ) ? trim( (string) $settings['api_key'] ) : ''; if ( '' !== $api_key ) { $data['providers'][ $provider_id ]['api_key_encrypted'] = ApiKeyEncryption::encrypt( $api_key ); } return update_option( self::OPTION_NAME, $data ); } public function disconnect_provider( string $provider_id ): bool { $provider_id = sanitize_key( $provider_id ); $data = $this->get(); unset( $data['providers'][ $provider_id ] ); if ( $data['active_provider'] === $provider_id ) { $data['active_provider'] = ''; } return update_option( self::OPTION_NAME, $data ); } /** * @return array{active_provider: string, providers: array, task_models: array, monthly_limit: int} */ private function get(): array { $stored = get_option( self::OPTION_NAME, array() ); return wp_parse_args( is_array( $stored ) ? $stored : array(), array( 'active_provider' => '', 'providers' => array(), 'task_models' => array(), 'monthly_limit' => 0, ) ); } }