get_option( 'accessibility_audio_tts_api_key', '' ), 'voice_id' => get_option( 'accessibility_audio_tts_voice_id', '' ), 'voice_name' => get_option( 'accessibility_audio_tts_voice_name', '' ), 'model' => get_option( 'accessibility_audio_tts_model', 'eleven_multilingual_v2' ), 'post_types' => get_option( 'accessibility_audio_tts_post_types', array( 'post' ) ), 'processing_mode' => get_option( 'accessibility_audio_tts_processing_mode', 'wp_cron' ), 'voices_list' => get_option( 'accessibility_audio_tts_voices_list', array() ), 'voices_cache_timestamp' => get_option( 'accessibility_audio_tts_voices_cache_timestamp', 0 ), 'bulk_limit' => get_option( 'accessibility_audio_tts_bulk_limit', 32 ), 'performance_profile' => get_option( 'accessibility_audio_tts_performance_profile', 'slow' ), 'chatgpt_api_key' => get_option( 'accessibility_audio_tts_chatgpt_api_key', '' ), 'chatgpt_prompt' => get_option( 'accessibility_audio_tts_chatgpt_prompt', '' ), 'chatgpt_model' => get_option( 'accessibility_audio_tts_chatgpt_model', 'gpt-5' ), ); } /** * Save settings. * * @param array $settings Settings to save. * @return bool True on success. */ public function save_settings( $settings ) { $sanitized = $this->sanitize_settings( $settings ); if ( ! \Accessibility_Audio_TTS\Core\ElevenLabs_Models::is_supported( $sanitized['model'] ) ) { return false; } update_option( 'accessibility_audio_tts_api_key', $sanitized['api_key'] ); update_option( 'accessibility_audio_tts_voice_id', $sanitized['voice_id'] ); update_option( 'accessibility_audio_tts_voice_name', $sanitized['voice_name'] ); update_option( 'accessibility_audio_tts_model', $sanitized['model'] ); update_option( 'accessibility_audio_tts_post_types', $sanitized['post_types'] ); update_option( 'accessibility_audio_tts_processing_mode', $sanitized['processing_mode'] ); update_option( 'accessibility_audio_tts_bulk_limit', $sanitized['bulk_limit'] ); update_option( 'accessibility_audio_tts_chatgpt_model', $sanitized['chatgpt_model'] ); return true; } /** * Sanitize settings. * * @param array $settings Raw settings input. * @return array Sanitized settings. */ private function sanitize_settings( $settings ) { return array( 'api_key' => sanitize_text_field( $settings['api_key'] ?? '' ), 'voice_id' => sanitize_text_field( $settings['voice_id'] ?? '' ), 'voice_name' => sanitize_text_field( $settings['voice_name'] ?? '' ), 'model' => sanitize_text_field( $settings['model'] ?? 'eleven_multilingual_v2' ), 'post_types' => array_map( 'sanitize_text_field', $settings['post_types'] ?? array( 'post' ) ), 'processing_mode' => sanitize_text_field( $settings['processing_mode'] ?? 'wp_cron' ), 'bulk_limit' => ( 'all' === ( $settings['bulk_limit'] ?? '' ) ) ? 'all' : intval( $settings['bulk_limit'] ?? 32 ), 'chatgpt_model' => sanitize_text_field( $settings['chatgpt_model'] ?? 'gpt-5' ), ); } /** * Check if voice cache is expired. * * @return bool True if cache is expired or invalid. */ public function is_voice_cache_expired() { $cache_timestamp = get_option( 'accessibility_audio_tts_voices_cache_timestamp', 0 ); $cache_duration = 24 * 60 * 60; // 24 hours in seconds. if ( empty( $cache_timestamp ) ) { accessibility_audio_tts_debug_log( '[Settings_Manager] Voice cache: No timestamp found, cache expired', true ); return true; } $time_since_cache = time() - $cache_timestamp; $is_expired = ( $time_since_cache > $cache_duration ); if ( $is_expired ) { accessibility_audio_tts_debug_log( "[Settings_Manager] Voice cache expired: {$time_since_cache}s ago (limit: {$cache_duration}s)", true ); } else { $hours_left = ( $cache_duration - $time_since_cache ) / 3600; accessibility_audio_tts_debug_log( "[Settings_Manager] Voice cache valid: {$hours_left} hours remaining", true ); } return $is_expired; } /** * Update voice cache with timestamp. * * @param array $voices Array of voices. * @return bool Success status. */ public function update_voice_cache( $voices ) { if ( ! is_array( $voices ) ) { accessibility_audio_tts_debug_log( '[Settings_Manager] Voice cache update failed: Invalid voices data', true ); return false; } $timestamp = time(); $voices_updated = update_option( 'accessibility_audio_tts_voices_list', $voices ); $timestamp_updated = update_option( 'accessibility_audio_tts_voices_cache_timestamp', $timestamp ); if ( $voices_updated && $timestamp_updated ) { accessibility_audio_tts_debug_log( '[Settings_Manager] Voice cache updated successfully: ' . count( $voices ) . ' voices cached', true ); return true; } else { accessibility_audio_tts_debug_log( '[Settings_Manager] Voice cache update failed: Database error', true ); return false; } } /** * Invalidate voice cache (force refresh on next request). * * @param string $reason Reason for invalidation. * @return bool Success status. */ public function invalidate_voice_cache( $reason = 'manual' ) { $result = delete_option( 'accessibility_audio_tts_voices_cache_timestamp' ); if ( $result ) { accessibility_audio_tts_debug_log( "[Settings_Manager] Voice cache invalidated: {$reason}", true ); } else { accessibility_audio_tts_debug_log( "[Settings_Manager] Voice cache invalidation failed: {$reason}", true ); } return $result; } /** * Get cached voices if valid, null if expired. * * @return array|null Cached voices or null if expired. */ public function get_cached_voices() { if ( $this->is_voice_cache_expired() ) { return null; } $voices = get_option( 'accessibility_audio_tts_voices_list', array() ); if ( empty( $voices ) ) { accessibility_audio_tts_debug_log( '[Settings_Manager] Voice cache: No voices in cache', true ); return null; } accessibility_audio_tts_debug_log( '[Settings_Manager] Voice cache: Returning ' . count( $voices ) . ' cached voices', true ); return $voices; } } // Backward compatibility: old FQCN used in controllers. if ( ! class_exists( '\\Accessibility_Audio_TTS\\Admin\\Settings\\Settings_Manager' ) ) { class_alias( 'Accessibility_Audio_TTS\\Admin\\Settings\\Settings_Manager', '\\Accessibility_Audio_TTS\\Admin\\Settings\\Settings_Manager' ); }