settings_manager = new Settings_Manager(); } /** * Save Accessibility Audio TTS settings. * * @return void */ public function save_accessibility_audio_tts_settings() { if ( ! $this->validate_ajax_request() ) { return; } try { $fields = $this->sanitize_common_fields(); $post_types = $this->sanitize_post_types(); $chatgpt_fields = $this->sanitize_chatgpt_fields(); $settings_to_save = array_merge( $fields, $chatgpt_fields, array( 'post_types' => $post_types, 'bulk_limit' => ( 'all' === $fields['bulk_limit'] ) ? 'all' : intval( $fields['bulk_limit'] ), ) ); // Use Settings_Manager to save settings. $result = $this->settings_manager->save_settings( $settings_to_save ); // Save additional settings. $this->save_additional_settings( $settings_to_save ); // Handle ChatGPT API key with special validation. $this->handle_chatgpt_api_key( $chatgpt_fields['chatgpt_api_key'] ); // Save ChatGPT prompt and model. update_option( 'accessibility_audio_tts_chatgpt_prompt', $chatgpt_fields['chatgpt_prompt'] ); update_option( 'accessibility_audio_tts_chatgpt_model', $chatgpt_fields['chatgpt_model'] ); if ( $result ) { wp_send_json_success( array( 'message' => __( 'Settings have been saved successfully', 'accessibility-audio-tts-text-to-speech-for-articles' ), 'settings' => $settings_to_save, ) ); } else { wp_send_json_error( array( 'message' => __( 'An error occurred while saving settings', 'accessibility-audio-tts-text-to-speech-for-articles' ), ) ); } } catch ( \Exception $e ) { $this->handle_ajax_exception( $e, 'Settings save' ); } } /** * Get voices from ElevenLabs API. * * @return void */ public function get_voices() { if ( ! $this->validate_ajax_request() ) { return; } try { $fields = $this->sanitize_common_fields(); if ( ! $this->validate_api_key_format( $fields['api_key'] ) ) { wp_send_json_error( array( 'message' => __( 'Invalid API key format', 'accessibility-audio-tts-text-to-speech-for-articles' ) ) ); return; } // Initialize ElevenLabs client. $client = new \Accessibility_Audio_TTS\API\ElevenLabs_Client( $fields['api_key'], '', '' ); $voices = $client->get_voices(); if ( $voices['success'] ) { wp_send_json_success( array( 'voices' => $voices['voices'], 'message' => __( 'Voices have been fetched successfully', 'accessibility-audio-tts-text-to-speech-for-articles' ), ) ); } else { wp_send_json_error( array( 'message' => __( 'Error fetching voices: ', 'accessibility-audio-tts-text-to-speech-for-articles' ) . $voices['error'], ) ); } } catch ( \Exception $e ) { $this->handle_ajax_exception( $e, 'Get voices' ); } } /** * Save voices list to cache. * * @return void */ public function save_voices_list() { if ( ! $this->validate_ajax_request() ) { return; } try { // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Called from validate_ajax_request context, complex array structure sanitized below $voices_data = isset( $_POST['voices'] ) ? wp_unslash( $_POST['voices'] ) : array(); if ( ! is_array( $voices_data ) ) { wp_send_json_error( array( 'message' => __( 'Invalid voices data', 'accessibility-audio-tts-text-to-speech-for-articles' ) ) ); return; } // Sanitize voices data. $sanitized_voices = array_map( function ( $voice ) { return array( 'voice_id' => sanitize_text_field( $voice['voice_id'] ?? '' ), 'name' => sanitize_text_field( $voice['name'] ?? '' ), 'category' => sanitize_text_field( $voice['category'] ?? '' ), 'labels' => array_map( 'sanitize_text_field', $voice['labels'] ?? array() ), ); }, $voices_data ); // Save to option. $saved = update_option( 'accessibility_audio_tts_voices_list', $sanitized_voices ); if ( $saved ) { wp_send_json_success( array( 'message' => __( 'Voices list has been saved', 'accessibility-audio-tts-text-to-speech-for-articles' ), 'count' => count( $sanitized_voices ), ) ); } else { wp_send_json_error( array( 'message' => __( 'Error saving voices', 'accessibility-audio-tts-text-to-speech-for-articles' ) ) ); } } catch ( \Exception $e ) { $this->handle_ajax_exception( $e, 'Save voices list' ); } } /** * Get cached voices. * * @return void */ public function get_cached_voices() { if ( ! $this->validate_ajax_request() ) { return; } try { $cached_voices = get_option( 'accessibility_audio_tts_voices_list', array() ); wp_send_json_success( array( 'voices' => $cached_voices, 'message' => __( 'Voices fetched from cache', 'accessibility-audio-tts-text-to-speech-for-articles' ), 'count' => count( $cached_voices ), ) ); } catch ( \Exception $e ) { $this->handle_ajax_exception( $e, 'Get cached voices' ); } } /** * Update voice cache. * * @return void */ public function update_voice_cache() { if ( ! $this->validate_ajax_request() ) { return; } try { $fields = $this->sanitize_common_fields(); if ( ! $this->validate_api_key_format( $fields['api_key'] ) ) { wp_send_json_error( array( 'message' => __( 'Invalid API key format', 'accessibility-audio-tts-text-to-speech-for-articles' ) ) ); return; } // Get fresh voices from API. $client = new \Accessibility_Audio_TTS\API\ElevenLabs_Client( $fields['api_key'], '', '' ); $voices = $client->get_voices(); if ( $voices['success'] ) { // Update cache. update_option( 'accessibility_audio_tts_voices_list', $voices['voices'] ); wp_send_json_success( array( 'voices' => $voices['voices'], 'message' => __( 'Voice cache updated', 'accessibility-audio-tts-text-to-speech-for-articles' ), 'count' => count( $voices['voices'] ), ) ); } else { wp_send_json_error( array( 'message' => __( 'Error updating cache: ', 'accessibility-audio-tts-text-to-speech-for-articles' ) . $voices['error'], ) ); } } catch ( \Exception $e ) { $this->handle_ajax_exception( $e, 'Update voice cache' ); } } /** * Save additional settings not handled by Settings_Manager. * * @param array $settings Settings to persist. * @return void */ private function save_additional_settings( $settings ) { if ( ! empty( $settings['voice_name'] ) ) { update_option( 'accessibility_audio_tts_voice_name', $settings['voice_name'] ); } update_option( 'accessibility_audio_tts_performance_profile', $settings['performance_profile'] ); update_option( 'accessibility_audio_tts_hide_branding', $settings['hide_branding'] ?? false ); // Save toggle options from POST directly. // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Called after validate_ajax_request. $post = wp_unslash( $_POST ); if ( isset( $post['background_enabled'] ) ) { update_option( 'accessibility_audio_tts_background_enabled', sanitize_text_field( $post['background_enabled'] ) ); } if ( isset( $post['chatgpt_enabled'] ) ) { update_option( 'accessibility_audio_tts_chatgpt_enabled', sanitize_text_field( $post['chatgpt_enabled'] ) ); } } /** * Handle ChatGPT API key with special validation. * * @param string $chatgpt_key API key value. * @return void */ private function handle_chatgpt_api_key( $chatgpt_key ) { $chatgpt_key = trim( $chatgpt_key ); accessibility_audio_tts_debug_log( '[Settings_Controller] Saving ChatGPT key, length: ' . strlen( $chatgpt_key ), true ); if ( empty( $chatgpt_key ) ) { accessibility_audio_tts_debug_log( '[Settings_Controller] User cleared ChatGPT API key - removing from database', true ); delete_option( 'accessibility_audio_tts_chatgpt_api_key' ); } else { // Prevent overwriting with masked/placeholder values from UI. $looks_masked = ( false !== strpos( $chatgpt_key, '*' ) ) || 'sk-' === $chatgpt_key || 'sk-proj-' === $chatgpt_key || strlen( $chatgpt_key ) < 20; if ( $looks_masked ) { accessibility_audio_tts_debug_log( '[Settings_Controller] Masked/placeholder ChatGPT key detected - keeping existing stored key', true ); } else { update_option( 'accessibility_audio_tts_chatgpt_api_key', $chatgpt_key ); } } // Verify what was saved. $saved_key = get_option( 'accessibility_audio_tts_chatgpt_api_key', '' ); accessibility_audio_tts_debug_log( '[Settings_Controller] Verified saved key length: ' . strlen( $saved_key ), true ); } /** * Validate API key format. * * @param string $api_key API key value. * @return bool Whether key format is valid. */ private function validate_api_key_format( $api_key ) { return ! empty( $api_key ) && strlen( $api_key ) > 10; } }