'gemini_key', 'openai' => 'openai_key', 'anthropic' => 'anthropic_key', 'grok' => 'grok_key', 'deepl' => 'deepl_key', 'google' => 'google_key', 'lecto' => 'lecto_key', 'microsoft' => 'microsoft_key', ); foreach ( $key_mappings as $engine => $key_field ) { if ( ! empty( $settings[ $key_field ] ) ) { return $engine; } } return 'none'; } /** * Resolves target path matrices inside your isolated sandbox environment. * Updated to provide clean separation for WordPress Core files. */ private function resolve_po_file_path( $slug, $type, $filename ) { $slug = preg_replace( '/[^A-Za-z0-9_\-\/.]/', '', $slug ); $slug = str_replace( array( '..', '\\' ), '', $slug ); $filename = sanitize_file_name( $filename ); // Dynamic System Core Context Whitelist Map $allowed_core_types = array( 'wordpress', 'core', 'default', 'admin', 'admin-network', 'continents-cities', 'network', 'continents', 'cities' ); $is_core_type = in_array( $type, $allowed_core_types, true ) || in_array( $slug, $allowed_core_types, true ); if ( $is_core_type ) { $sub_folder = 'core'; } else { $sub_folder = in_array( $type, array( 'theme', 'themes' ), true ) ? 'themes' : 'plugins'; } $upload_dir = wp_upload_dir(); $sandbox_dir = trailingslashit( $upload_dir['basedir'] ) . 'af-translate/' . $sub_folder; if ( ! file_exists( $sandbox_dir ) ) { wp_mkdir_p( $sandbox_dir ); file_put_contents( $sandbox_dir . '/index.php', '' ); } $sandbox_target_path = $sandbox_dir . '/' . $filename; // Path containment validation execution layer if ( class_exists( 'AF_Translate_Path_Validator' ) ) { if ( ! AF_Translate_Path_Validator::validate_containment( $sandbox_target_path, true ) ) { return ''; } } if ( file_exists( $sandbox_target_path ) ) { return $sandbox_target_path; } // Build precise target lookup fallbacks $fallback_structures = array(); if ( $is_core_type ) { $system_lang_dir = defined( 'WP_LANG_DIR' ) ? WP_LANG_DIR : WP_CONTENT_DIR . '/languages'; $fallback_structures[] = trailingslashit( $system_lang_dir ) . $filename; } elseif ( 'themes' === $sub_folder ) { $fallback_structures[] = WP_LANG_DIR . '/themes/' . $filename; $fallback_structures[] = get_theme_root() . '/' . $slug . '/languages/' . $filename; $fallback_structures[] = get_theme_root() . '/' . $slug . '/' . $filename; } else { $fallback_structures[] = WP_LANG_DIR . '/plugins/' . $filename; $fallback_structures[] = WP_PLUGIN_DIR . '/' . $slug . '/languages/' . $filename; $fallback_structures[] = WP_PLUGIN_DIR . '/' . $slug . '/' . $filename; } foreach ( $fallback_structures as $source_path ) { if ( file_exists( $source_path ) ) { if ( class_exists( 'AF_Translate_Path_Validator' ) ) { if ( ! AF_Translate_Path_Validator::validate_containment( $source_path, false ) ) { continue; } } if ( copy( $source_path, $sandbox_target_path ) ) { return $sandbox_target_path; } } } return $sandbox_target_path; } /** * Real Extraction Action: Connects directly to your actual PO Processor */ private function parse_po_file_to_array( $file_path ) { if ( ! empty( $file_path ) && class_exists( 'AF_Translate_PO_Processor' ) && file_exists( $file_path ) ) { return AF_Translate_PO_Processor::parse_file( $file_path ); } return array(); } /** * Real File Compilation Action: Commits updates and builds the runtime binary object */ private function write_array_to_po_file( $file_path, $catalog_data ) { if ( empty( $file_path ) || ! class_exists( 'AF_Translate_PO_Processor' ) ) { return false; } $raw_po_text = AF_Translate_PO_Processor::generate_po_string( $catalog_data ); $write_status = ( false !== file_put_contents( $file_path, $raw_po_text ) ); if ( $write_status && class_exists( 'AF_Translate_Compiler_Engine' ) ) { $mo_target_path = str_replace( '.po', '.mo', $file_path ); if ( AF_Translate_Path_Validator::validate_containment( $mo_target_path, true ) ) { AF_Translate_Compiler_Engine::compile_po_to_mo( $file_path, $mo_target_path ); } } return $write_status; } /** * 1. Action Handler: Bulk Processing Integration (Chunk-Supported Pagination Model) */ public function execute_bulk_ai_translation() { check_ajax_referer( 'af_translate_secure_action', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Security Exception: Unauthorized capability state.', 'af-translate' ) ) ); } $locale = isset( $_POST['locale'] ) ? sanitize_text_field( wp_unslash( $_POST['locale'] ) ) : ''; $slug = isset( $_POST['slug'] ) ? sanitize_text_field( wp_unslash( $_POST['slug'] ) ) : ''; $type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : ''; $filename = isset( $_POST['filename'] ) ? sanitize_text_field( wp_unslash( $_POST['filename'] ) ) : ''; $posted_provider = isset( $_POST['provider'] ) ? sanitize_text_field( wp_unslash( $_POST['provider'] ) ) : ''; $process_mode = isset( $_POST['process_mode'] ) ? sanitize_text_field( wp_unslash( $_POST['process_mode'] ) ) : 'untranslated_only'; // Capture pagination matrix parameters $offset = isset( $_POST['offset'] ) ? absint( $_POST['offset'] ) : 0; $batch_size = isset( $_POST['batch_size'] ) ? absint( $_POST['batch_size'] ) : 40; $allowed_core_types = array( 'wordpress', 'core', 'default', 'admin', 'admin-network', 'continents-cities', 'network', 'continents', 'cities' ); $is_core_type = in_array( $type, $allowed_core_types, true ) || in_array( $slug, $allowed_core_types, true ); if ( empty( $locale ) || empty( $type ) || empty( $filename ) || ( empty( $slug ) && ! $is_core_type ) ) { wp_send_json_error( array( 'message' => __( 'Missing contextual localization matrices keys.', 'af-translate' ) ) ); } $provider = $this->resolve_active_provider( $posted_provider ); $file_path = $this->resolve_po_file_path( $slug, $type, $filename ); if ( empty( $file_path ) || ! file_exists( $file_path ) ) { wp_send_json_error( array( 'message' => __( 'Target PO source file could not be mirrored or found.', 'af-translate' ) ) ); } $catalog_data = $this->parse_po_file_to_array( $file_path ); if ( empty( $catalog_data ) ) { wp_send_json_error( array( 'message' => __( 'Target translation file structure is unreadable or structurally empty.', 'af-translate' ) ) ); } $strings_to_process = array(); foreach ( $catalog_data as $index => $node ) { if ( empty( $node['msgid'] ) ) { continue; } $existing_translation = isset( $node['msgstr'][0] ) ? $node['msgstr'][0] : ''; if ( 'all' === $process_mode ) { $strings_to_process[ $index ] = $node['msgid']; } else { if ( empty( $existing_translation ) ) { $strings_to_process[ $index ] = $node['msgid']; } } } $total_eligible_strings = count( $strings_to_process ); if ( empty( $strings_to_process ) ) { wp_send_json_error( array( 'message' => __( 'No eligible strings detected matching your target visibility filter inside this file.', 'af-translate' ) ) ); } // Extract a subset array chunk slice while maintaining original associative map keys intact $sliced_strings_to_process = array_slice( $strings_to_process, $offset, $batch_size, true ); if ( class_exists( 'AF_Translate_AI_Engine' ) ) { $translated_results = AF_Translate_AI_Engine::translate_batch( $sliced_strings_to_process, $locale, $provider ); if ( is_wp_error( $translated_results ) ) { wp_send_json_error( array( 'message' => $translated_results->get_error_message() ) ); } // Calculation maps for activity logger insights boundaries $current_batch_end = min( $offset + $batch_size, $total_eligible_strings ); $log_context = ! empty( $slug ) ? $slug : $type; /* translators: 1: starting loop element, 2: ending index value boundary, 3: maximum elements catalog total, 4: dynamic target locale token string, 5: engine label slug */ $log_message = sprintf( __( 'Processed batch chunk AI translation tracking iteration (%1$d to %2$d of %3$d elements) for "%4$s" via %5$s engine.', 'af-translate' ), $offset + 1, $current_batch_end, $total_eligible_strings, strtoupper( $locale ), ucfirst( $provider ) ); if ( class_exists( 'AF_Translate_Admin' ) && method_exists( 'AF_Translate_Admin', 'log_activity' ) ) { AF_Translate_Admin::log_activity( $log_message, $log_context ); } wp_send_json_success( array( 'staged_translations' => $translated_results, 'is_complete' => ( $offset + $batch_size ) >= $total_eligible_strings, 'next_offset' => $offset + $batch_size, 'total_strings' => $total_eligible_strings, 'message' => __( 'Staging chunk iteration execution finished safely.', 'af-translate' ) ) ); } wp_send_json_error( array( 'message' => __( 'Core translation runtime framework not discovered.', 'af-translate' ) ) ); } /** * 2. Action Handler: Single Workbench Suggestion Engine */ public function execute_single_ai_suggestion() { check_ajax_referer( 'af_translate_secure_action', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Security Exception: Unauthorized capability state.', 'af-translate' ) ) ); } $source = isset( $_POST['source'] ) ? wp_kses_post( wp_unslash( $_POST['source'] ) ) : ''; $locale = isset( $_POST['locale'] ) ? sanitize_text_field( wp_unslash( $_POST['locale'] ) ) : ''; $posted_provider = isset( $_POST['provider'] ) ? sanitize_text_field( wp_unslash( $_POST['provider'] ) ) : ''; $provider = $this->resolve_active_provider( $posted_provider ); if ( empty( $source ) || empty( $locale ) ) { wp_send_json_error( array( 'message' => __( 'Required evaluation string missing data context vectors.', 'af-translate' ) ) ); } if ( class_exists( 'AF_Translate_AI_Engine' ) ) { $translation_output = AF_Translate_AI_Engine::translate_single( $source, $locale, $provider ); if ( is_wp_error( $translation_output ) ) { wp_send_json_error( array( 'message' => $translation_output->get_error_message() ) ); } /* translators: 1: Target locale code (e.g. ES_ES), 2: AI translation engine name */ $log_message = sprintf( __( 'Requested inline dynamic AI translation suggestion for "%1$s" via %2$s engine.', 'af-translate' ), strtoupper( $locale ), ucfirst( $provider ) ); if ( class_exists( 'AF_Translate_Admin' ) && method_exists( 'AF_Translate_Admin', 'log_activity' ) ) { AF_Translate_Admin::log_activity( $log_message, 'ai-engine' ); } wp_send_json_success( array( 'translation' => $translation_output ) ); } wp_send_json_error( array( 'message' => __( 'AI system runtime profile failed to resolve processing.', 'af-translate' ) ) ); } /** * 3. Action Handler: Commit Staged Configuration States Securely Back to Disk File */ public function save_workbench_matrix_to_disk() { check_ajax_referer( 'af_translate_secure_action', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( array( 'message' => __( 'Security Exception: Unauthorized capability state.', 'af-translate' ) ) ); } $slug = isset( $_POST['slug'] ) ? sanitize_text_field( wp_unslash( $_POST['slug'] ) ) : ''; $type = isset( $_POST['type'] ) ? sanitize_text_field( wp_unslash( $_POST['type'] ) ) : ''; $filename = isset( $_POST['filename'] ) ? sanitize_text_field( wp_unslash( $_POST['filename'] ) ) : ''; // Preserve safe HTML markup in translations by using wp_kses_post instead of sanitize_textarea_field $user_edits = isset( $_POST['user_edits'] ) && is_array( $_POST['user_edits'] ) ? map_deep( wp_unslash( $_POST['user_edits'] ), 'wp_kses_post' ) : array(); $allowed_core_types = array( 'wordpress', 'core', 'default', 'admin', 'admin-network', 'continents-cities', 'network', 'continents', 'cities' ); $is_core_type = in_array( $type, $allowed_core_types, true ) || in_array( $slug, $allowed_core_types, true ); if ( empty( $type ) || empty( $filename ) || empty( $user_edits ) || ( empty( $slug ) && ! $is_core_type ) ) { wp_send_json_error( array( 'message' => __( 'Missing core compilation parameters or update payloads.', 'af-translate' ) ) ); } $file_path = $this->resolve_po_file_path( $slug, $type, $filename ); $catalog_data = $this->parse_po_file_to_array( $file_path ); if ( empty( $catalog_data ) ) { wp_send_json_error( array( 'message' => __( 'Base tracking data tree unavailable for compiler reconstruction routines.', 'af-translate' ) ) ); } $merged_count = 0; foreach ( $user_edits as $index => $text ) { $index = sanitize_text_field( $index ); if ( isset( $catalog_data[ $index ] ) ) { if ( ! isset( $catalog_data[ $index ]['msgstr'] ) || ! is_array( $catalog_data[ $index ]['msgstr'] ) ) { $catalog_data[ $index ]['msgstr'] = array(); } $catalog_data[ $index ]['msgstr'][0] = $text; $merged_count++; } } $write_status = $this->write_array_to_po_file( $file_path, $catalog_data ); if ( ! $write_status ) { wp_send_json_error( array( 'message' => __( 'Disk compilation write operation failure occurred inside your sandbox.', 'af-translate' ) ) ); } $log_context = ! empty( $slug ) ? $slug : $type; /* translators: 1: Count of modified strings, 2: Target PO file name */ $log_message = sprintf( __( 'Committed %1$d workbench tracking entry changes into sandbox file catalog (%2$s).', 'af-translate' ), $merged_count, $filename ); if ( class_exists( 'AF_Translate_Admin' ) && method_exists( 'AF_Translate_Admin', 'log_activity' ) ) { AF_Translate_Admin::log_activity( $log_message, $log_context ); } /* translators: %d: Number of compiled strings */ $success_message = sprintf( __( 'Successfully updated and compiled %d strings to your sandbox target files.', 'af-translate' ), $merged_count ); wp_send_json_success( array( 'message' => $success_message ) ); } } new AF_Translate_AJAX_Handler();