validate_api_key( $settings['api_key'] ?? '' ); if ( ! $api_key_validation['valid'] ) { $errors['api_key'] = $api_key_validation['error']; } // Validate voice settings. $voice_validation = $this->validate_voice_settings( $settings ); if ( ! $voice_validation['valid'] ) { $errors = array_merge( $errors, $voice_validation['errors'] ); } // Validate model. $model_validation = $this->validate_model( $settings['model'] ?? '' ); if ( ! $model_validation['valid'] ) { $errors['model'] = $model_validation['error']; } // Validate processing mode. $mode_validation = $this->validate_processing_mode( $settings['mode'] ?? '' ); if ( ! $mode_validation['valid'] ) { $errors['mode'] = $mode_validation['error']; } // Validate post types. $post_types_validation = $this->validate_post_types( $settings['post_types'] ?? array() ); if ( ! $post_types_validation['valid'] ) { $errors['post_types'] = $post_types_validation['error']; } return array( 'valid' => empty( $errors ), 'errors' => $errors, ); } /** * Validate API key. * * @since 1.0.0 * @param string $api_key API key to validate. * @return array Validation result. */ public function validate_api_key( string $api_key ): array { if ( empty( $api_key ) ) { return array( 'valid' => false, 'error' => __( 'API key is required', 'accessibility-audio-tts-text-to-speech-for-articles' ), ); } if ( strlen( $api_key ) < 10 ) { return array( 'valid' => false, 'error' => __( 'API key appears to be too short', 'accessibility-audio-tts-text-to-speech-for-articles' ), ); } if ( ! preg_match( '/^[a-zA-Z0-9_-]+$/', $api_key ) ) { return array( 'valid' => false, 'error' => __( 'API key contains invalid characters', 'accessibility-audio-tts-text-to-speech-for-articles' ), ); } return array( 'valid' => true ); } /** * Validate voice settings. * * @since 1.0.0 * @param array $settings Settings containing voice data. * @return array Validation result. */ public function validate_voice_settings( array $settings ): array { $errors = array(); $voice_id = $settings['voice_id'] ?? ''; $voice_name = $settings['voice_name'] ?? ''; // Allow first-time save when voice cache is not yet populated. $voices = get_option( 'accessibility_audio_tts_voices_list', array() ); $voices_known = is_array( $voices ) && ! empty( $voices ); if ( empty( $voice_id ) && $voices_known ) { $errors['voice_id'] = __( 'Voice selection is required', 'accessibility-audio-tts-text-to-speech-for-articles' ); } if ( empty( $voice_name ) && $voices_known ) { $errors['voice_name'] = __( 'Voice name is required', 'accessibility-audio-tts-text-to-speech-for-articles' ); } // Validate voice ID format (ElevenLabs format). if ( ! empty( $voice_id ) && ! preg_match( '/^[a-zA-Z0-9]{20,}$/', $voice_id ) ) { $errors['voice_id'] = __( 'Invalid voice ID format', 'accessibility-audio-tts-text-to-speech-for-articles' ); } return array( 'valid' => empty( $errors ), 'errors' => $errors, ); } /** * Validate model selection. * * @since 1.0.0 * @param string $model Model to validate. * @return array Validation result. */ public function validate_model( string $model ): array { if ( empty( $model ) ) { return array( 'valid' => false, 'error' => __( 'Model selection is required', 'accessibility-audio-tts-text-to-speech-for-articles' ), ); } if ( ! \Accessibility_Audio_TTS\Core\ElevenLabs_Models::is_supported( $model ) ) { return array( 'valid' => false, 'error' => __( 'Invalid model selected', 'accessibility-audio-tts-text-to-speech-for-articles' ), ); } return array( 'valid' => true ); } /** * Validate processing mode. * * @since 1.0.0 * @param string $mode Processing mode to validate. * @return array Validation result. */ public function validate_processing_mode( string $mode ): array { if ( empty( $mode ) ) { return array( 'valid' => false, 'error' => __( 'Processing mode is required', 'accessibility-audio-tts-text-to-speech-for-articles' ), ); } if ( ! in_array( $mode, self::ALLOWED_MODES, true ) ) { return array( 'valid' => false, 'error' => __( 'Invalid processing mode selected', 'accessibility-audio-tts-text-to-speech-for-articles' ), ); } return array( 'valid' => true ); } /** * Validate post types selection. * * @since 1.0.0 * @param array $post_types Post types to validate. * @return array Validation result. */ public function validate_post_types( array $post_types ): array { if ( empty( $post_types ) ) { return array( 'valid' => false, 'error' => __( 'At least one content type must be selected', 'accessibility-audio-tts-text-to-speech-for-articles' ), ); } if ( ! is_array( $post_types ) ) { return array( 'valid' => false, 'error' => __( 'Invalid post types format', 'accessibility-audio-tts-text-to-speech-for-articles' ), ); } // Get available post types. $available_post_types = get_post_types( array( 'public' => true ) ); // Check if all selected post types are valid. foreach ( $post_types as $post_type ) { if ( ! in_array( $post_type, $available_post_types, true ) ) { return array( 'valid' => false, 'error' => sprintf( /* translators: %s: invalid post type */ __( 'Invalid post type: %1$s', 'accessibility-audio-tts-text-to-speech-for-articles' ), esc_html( $post_type ) ), ); } } return array( 'valid' => true ); } /** * Validate ChatGPT settings. * * @since 1.0.0 * @param array $settings ChatGPT settings. * @return array Validation result. */ public function validate_chatgpt_settings( array $settings ): array { $errors = array(); $api_key = $settings['chatgpt_api_key'] ?? ''; $prompt = $settings['chatgpt_prompt'] ?? ''; if ( ! empty( $api_key ) ) { if ( strlen( $api_key ) < 20 ) { $errors['chatgpt_api_key'] = __( 'ChatGPT API key appears to be too short', 'accessibility-audio-tts-text-to-speech-for-articles' ); } if ( ! str_starts_with( $api_key, 'sk-' ) ) { $errors['chatgpt_api_key'] = __( 'ChatGPT API key should start with "sk-"', 'accessibility-audio-tts-text-to-speech-for-articles' ); } } if ( ! empty( $prompt ) && strlen( $prompt ) > 2000 ) { $errors['chatgpt_prompt'] = __( 'ChatGPT prompt is too long (max 2000 characters)', 'accessibility-audio-tts-text-to-speech-for-articles' ); } return array( 'valid' => empty( $errors ), 'errors' => $errors, ); } /** * Sanitize setting value based on setting type. * * @since 1.0.0 * @param string $setting_key Setting key. * @param mixed $value Setting value. * @return mixed Sanitized value. */ public function sanitize_setting_value( string $setting_key, $value ) { switch ( $setting_key ) { case 'api_key': case 'voice_id': case 'voice_name': case 'chatgpt_api_key': return sanitize_text_field( $value ); case 'model': return sanitize_text_field( $value ); case 'mode': return in_array( $value, self::ALLOWED_MODES, true ) ? $value : 'wp_cron'; case 'post_types': return is_array( $value ) ? array_map( 'sanitize_text_field', $value ) : array(); case 'chatgpt_prompt': return sanitize_textarea_field( $value ); default: return sanitize_text_field( $value ); } } /** * Get allowed models. * * @since 1.0.0 * @return array Allowed models. */ public function get_allowed_models(): array { return \Accessibility_Audio_TTS\Core\ElevenLabs_Models::ids(); } /** * Get allowed processing modes. * * @since 1.0.0 * @return array Allowed processing modes. */ public function get_allowed_modes(): array { return self::ALLOWED_MODES; } }